经典排序(7+3)

定义:排序是将一组具有相同数据类型的数据元素调整为按关键字从小到大(从大到小)排列的过程。

概念:关键字是数据元素或记录中某个数据项的值,它可以标记一个数据元素。若关键字可以唯一标识一个数据元素,则称此关键字为主关键字;若其可以标识若干数据元素,则称为次关键字。

稳定排序和不稳定分排序:在排序过程中,若按次关键字排序,且具有相同关键字的数据元素之间的相对次序或位置不变,则称这种排序方法为稳定排序;反之,为不稳定排序。(所有跨关键字比较的排序方法都是不稳定的)

增排序和减排序:若排序结果是按照关键字从小到大的顺序排列,则称其为增排序;若排序结果是按照关键字按从大到小排序,则称其为减排序。

内部排序和外部排序:若在排序过程中,整个文件或数据表都是放在内存中处理,排序是不涉及数据的内外存交换,这种排序称为内部排序;若排序过程中需要进行数据的内外交换,则称这种排序叫外部排序。

非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。

线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。

时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。

空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。


排序方法时间复杂度(最好)时间复杂度(最坏)时间复杂度(平均)空间复杂度稳定性
插入排序O(n)O(n^2)O(n^2)O(1)稳定
希尔排序O(n)O(n^2)O(n^1.3)O(1)不稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
堆排序O(n*logn)O(n*logn)O(n*logn)O(1)不稳定
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
快速排序O(n*logn)O(n^2)O(n*logn)O(logn)~O(n)不稳定
归并排序O(n*logn)O(n*logn)O(n*logn)O(n)稳定
计数排序O(n+k)O(n+k)O(n+k)O(n+k)稳定
桶排序O(n)O(n^2)O(n+k)O(n+k)稳定
基数排序O(n*k)O(n*k)O(n*k)O(n+k)稳定
#define maxsize 20
typedef struct{
	KeyType key;//关键字 
	OtherType otherdata;
}RecordType;
typedef struct{
	RecordType r[maxsize+1];//r[0]为工作单元或闲置,对1~n中的单元进行排序 
	int length;//顺序表长度 
}SeqList; 


插入排序(Insertion sort):通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

算法描述:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。
void insertion(SeqList *L){
	int i,j;
	for(i=2;i<L->length;i++){
		L->r[0]=L->r[i];//设置监视哨:免去查找过程每一步都要检测查找表查找是否完毕,提高查找效率 
		j=i-1;
		while(L->r[0].key<L->r[j].key){
			L->r[j+1]=L->r[j];
			j=j-1;
		}
		L->r[j+1]=L->r[0];
	}
} 

希尔排序(Shell sort):简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。

算法描述:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
void shellinsert(SeqList *L,int delta){
	//对顺序表做一趟希尔插入排序,delta为该趟排序增量
	int i,j,k;
	for(i=1;i<=delta;i++){
		for(j=i+delta;j<=L->length;j=j+delta){
			L->r[0].key=L->r[j].key;//备份L->r[j],不做监视哨
			k=j-delta;
			while(L->r[0].key<L->r[k].key && k>0){
				L->r[k+delta]=L->r[k];
				k=k-delta;
			} 
			L->r[k+delta]=L->r[0];
		}
	} 
} 
void shell(SeqList* &L,int di[],int n){
	//对顺序表L按照增量序列di[0]--di[n-1]进行希尔排序,其中d[n-1]为1;
	for(int i=0;i<=n-1;i++){
		shellinsert(L,di[i]);
	} 
}

选择排序(Selection sort):首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

算法描述:n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  1. 初始状态:无序区为R[1..n],有序区为空;
  2. 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  3. n-1趟结束,数组有序化了。
void selection(SeqList *L){
	int k;//选择当前待排序列中关键字最小的下标为k元素,与下标为i元素互换,避免每次比较成功后就互换所带来的开销; 
	RecordType x;
	for(int i=1;i<L->length;i++){
		k=i;
		for(int j=i+1;j<=n;j++){
			if(L->r[j].key<L->r[k].key){
				k=j;
			}
		} 
		if(k!=j){
			x=L->r[i];
			L->r[i]=L->r[k];
			L->r[k]=x;
		}
	}
}

堆排序(Heap sort):利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

算法描述:

  1. 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

具体实现请点击--


冒泡排序(Bubble sort):重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

算法描述:

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  3. 针对所有的元素重复以上的步骤,除了最后一个;
  4. 重复步骤1~3,直到排序完成。
void Bubble(SeqList *L){
	//每轮比较的结果是确定最后一个元素位置 
	int change=1;//使得顺序表有序时,可以结束比较,提前退出
	RecordType x;
	for(int i=1;i<L->length && change;i++){
		change=0;
		for(int j=1;j<=L->length-i;j++){
			if(L->r[j].key>L->r[j+1].key){
				x=L->r[j];
				L->r[j]=L->r[j+1];
				L->r[j+1]=x;
				change=1;
			}
		}
	} 
}

快速排序(Quick sort):通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

算法描述:快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
int partition(SeqList *H,int left,int right){
	//对顺序表H中的H->r[left]到H->[right]部分进行快速排序一次划分,并返回中间数
	RecordType x=H->r[left];
	int low,high;
	low=left;
	high=right;
	while(low<high){
		//首先从右到左扫描,查找第一个关键字小于x.key的元素 
		while(H->r[high].key>=x.key && low<high) high--;
		if(low<high){
			H->r[low]=H->r[high];
			low++;
		}
		//然后从右到左扫描,查找第一个关键字不小于x.key的元素 
		while(H->r[low].key<x.key && low<high) low++;
		if(low<high){
			H->r[high]=H->r[low];
			high--;
		}
	} 
	H->r[low]=x;
	return low;
} 
void quick(SeqList *L,int low,int high){
	//对顺序表L用快速排序算法进行排序
	int mid;
	if(low<high){
		mid=partition(L,low,high);
		quick(L,low,mid-1);
		quick(L,mid+1,high);
	} 
}

归并排序(Merge sort):采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

算法描述:归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。它将数组平均分成两部分,当数组分得足够小时,即数组中只有一个元素时,只有一个元素的数组自然而然地就可以视为是有序的,此时就可以进行合并操作了。因此,合并两个有序的子数组,是从 只有一个元素 的两个子数组开始合并的。

void mergex(RecordType R[],RecordType R1[],int low,int mid,int high){
	//数组R[]中R[low]~R[mid]和R[mid+1]~R[high] 分别按照关键字有序排列
	//将它们合并成一个有序序列,存放在数组R1的R1[low]~R1[high]中
	int i,j,k;
	i=low;
	j=mid+1;
	k=low;
	while(i<=mid && j<=high){
		if(R[i].key<=R[j].key){
			R1[k]=R[i];
			i++;
		}else{
			R1[k]=R[j];
			j++;
		}
		k++;
	} 
	while(i<=mid) R1[k++]=R[i++];
	while(j<=high) R1[k++]=R[j++];
}
void mergey(RecordType R[],RecordType R1[],int len){
	//对R进行一趟归并,结果放在R1中
	int j,i=0;
	while(i+len*2<=n){//n为待排元素个数 
		mergex(R,R1,i,i+len-1,i+len*2-1);
		i=i+len*2;
	} 
	if(i+len-1<n-1) mergex(R,R1,i,i+len-1,n-1);
	else
		for(j=i;j<n;j++) R1[j]=R[j];
}
void merge(SeqList L){
	int len,n;
	RecordType *R1;
	n=L.length;
	len=1;
	R1=(RecordType*)malloc(sizeof(RecordType)*n);
	while(len<=n/2+1){
		mergey(L.r,R1,len);
		len=len*2;
		mergey(R1,L.r,len);
		len=len*2;
	}
	free(R1);
}

计数排序(Counting sort):计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

算法描述:

  1. 找出待排序的数组中最大和最小的元素;
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。


桶排序(Bucket sort):桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

算法描述:

  1. 设置一个定量的数组当作空桶;
  2. 遍历输入数据,并且把数据一个一个放到对应的桶里去;
  3. 对每个不是空的桶进行排序;
  4. 从不是空的桶里把排好序的数据拼接起来。 

桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。


基数排序(Radix sort):基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

算法描述:

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);

基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值