【原创】经典排序回顾
部分摘抄自:https://www.cnblogs.com/skywang12345/p/3603935.html
一、冒泡排序
思路:冒泡,顾名思义,每一次都将最重的元素往数列的末端沉。外层:一层循环,每一次都将一个最大的元素移动到末端,故边界i: 0..n; 里层:负责一次具体的移动,通过不断的比较相邻的两个元素,移动最大的元素。
平均时间复杂度:O(n^2)
空间复杂度:(定义:一个算法在运行过程中临时占用存储空间的大小。)由于此算法的临时存储空间不随输入数据大小n变化,只存在在相邻两个元素交换过程中的一次临时存储(tmp),故为O(1)。
稳定性:(定义:如在数列中有a[j]=a[j](i<j),若在排序后,a[i]与a[j]的先后顺序没有发生改变,则算法是稳定的。)因为在两两交换时的判断条件不包括两元素相等,故冒泡排序是稳定的。
1 void bubble_sort(int a[], int n){ 2 for(int i=0;i<n;i++){ 3 for(int j=0;j<n-i-1;j++){ 4 if(a[j]>a[j+1]){ 5 swap(a[j],a[j+1]); 6 } 7 } 8 } 9 }
二、快速排序
快速排序是基于分治策略。首先,选中一个基准值,根据比较,将数列分为两个独立的部分,其中一部分的数都要比另一部分的小。之后再独立地将两个子序列分别照此方法排序。
思路:
1.前提:由于是根据分治法,递归地对序列进行排序,所以,首先,方法的参数应该有3部分:quick_sort(数列对象,排序起始地址,排序结束地址),也就是确定边界值。
2.对排序的数列确定一个基准值(pivot), (习惯性地从起始下标处取值)。
3.从对立方向(也就是终点下标处)j 向左扫描,直到碰见小于pivot的值,将此值移动到左边。
4.再跳转到对立方向,从更新后位置的右边 i 开始向右继续扫描,直到碰见大于pivot的值,将此值移动到右边。
5.重复3,4。直到i=j,将此处a[i]赋值为pivot值。第一次排序完成。即pivot左边的值都不大于它,右边的都不小于它。
6.递归地对pivot左边与右边的子序列进行排序。
平均时间复杂度:O( nlog(n) )。因为快排采用的分治法,所以可以想象成一个二叉树。
补充: (时间复杂度证明)——其中 T(n)=aT(n/b)+f(n) 是用得很多的递归方程。
1 T(n)=2T(n/2)+f(n)----f(n)-O(n),因为实际上我们对每一层递归树进行划分的时候,都是将整个数组都遍历了一遍 2 T(n)=4T(n/4)+2f(n) 3 ...log2N=k,共进行k次 4 T(n)=nT(1)+kf(n)=O(n)+kO(n)==kO(n)=O(n*logn)
空间复杂度:O(logn)。主要空间消耗是在递归调用上,每次递归调用都会需要临时存储一些数据。
稳定性:不稳定
1 void quick_sort(int a[], int left, int right){ 2 if(left<right){ 3 int i=left; 4 int j=right; 5 int tmp=a[i]; 6 while(i<j){ 7 while(i<j && a[j]>tmp) j--; 8 9 if(a[j]<tmp){ 10 a[i]=a[j]; 11 i++; 12 13 } 14 while(i<j && a[i]<tmp) i++; 15 if(a[i]>tmp){ 16 a[j]=a[i]; 17 j--; 18 } 19 } 20 a[i]=tmp; 21 quick_sort(a, left, j--); 22 quick_sort(a,i++,right); 23 } 24 }
三、直接插入排序
思路:将一个待排序列表看作两部分,一部分是已经排序好的,另一部分有待排序的。从有待排序的部分依次选取值拿到已经排序的部分进行比较,插入到合适的位置,直到所有的都已经排序完成。
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
1 void insert_sort(int a[], int n){ 2 int tmp; 3 int i,j,k; 4 for(i=1; i<n;i++){ 5 for(j=i-1;j>=0;j--){ 6 if(a[j]<=a[i]){//在已经排序的数列里往回找,直到找到一个不大于(所以插入排序是稳定的)当前数的下标位置 7 break; 8 } 9 if(j!=i-1){//后移元素 10 tmp=a[i]; 11 for(k=i;k>j+1;k--){ 12 a[k]=a[k-1]; 13 14 } 15 a[j+1]=tmp; 16 } 17 } 18 } 19 }
四、选择排序
思路:将一个待排序序列看作两部分,一部分是已经排序好的,另一部分有待排序的。从待排序的部分依次选择最小值,依次排在已排序的序列的末尾(即与末尾元素交换)。
(与插入排序的区别:1.插入排序是按下标依次从待排序序列取值,而选择排序是从待排序序列中依次选择最小值 2.插入排序将拿出的数拿到已排序序列中对比插入到合适位置,而选择排序直接放到已排序序列的末尾。)
平均时间复杂度:O(n*2)
空间复杂度:O(1)
稳定性:不稳定
1 void select_sort(int a[], int n){ 2 int i,j,min; 3 fot(i=0; i<n; i++){ 4 min=i; 5 for(j=i+1; j<n;j++){//从未排序的i+1及后面的数中选取最小值 6 if(a[j]<a[min]) min=j; 7 } 8 if(min !=i){ 9 swap(a[i],a[min]);//将最小值交换到i位 10 } 11 } 12 }
五、堆排序
思路:(一种特殊的选择排序)将未排序序列转换为大/小顶堆,取最大/小值,放入未排序序列末尾。对剩余未排序序列重复操作,直到排序完成。
平均时间复杂度:O(n*logn)
空间复杂度:O(1)
稳定性:不稳定
1 void maxheap_down(int a[], int start, int end) 2 { 3 int c = start; // 当前(current)节点的位置 4 int l = 2*c + 1; // 左(left)孩子的位置 5 int tmp = a[c]; // 当前(current)节点的大小 6 for (; l <= end; c=l,l=2*l+1) 7 { 8 // "l"是左孩子,"l+1"是右孩子 9 if ( l < end && a[l] < a[l+1]) 10 l++; // 左右两孩子中选择较大者,即m_heap[l+1] 11 if (tmp >= a[l]) 12 break; // 调整结束 13 else // 交换值 14 { 15 a[c] = a[l]; 16 a[l]= tmp; 17 } 18 } 19 } 20 21 void heap_sort_asc(int a[], int n) 22 { 23 int i; 24 25 // 从(n/2-1) --> 0逐次遍历。遍历之后,得到的数组实际上是一个(最大)二叉堆。 26 for (i = n / 2 - 1; i >= 0; i--) 27 maxheap_down(a, i, n-1); 28 29 // 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素 30 for (i = n - 1; i > 0; i--) 31 { 32 // 交换a[0]和a[i]。交换后,a[i]是a[0...i]中最大的。 33 swap(a[0], a[i]); 34 // 调整a[0...i-1],使得a[0...i-1]仍然是一个最大堆。 35 // 即,保证a[i-1]是a[0...i-1]中的最大值。 36 maxheap_down(a, 0, i-1); 37 } 38 }
补充:在C++中,可以使用priority_queue优先队列,是基于堆排序实现的。
六、归并排序
思路:它与"从下往上"在排序上是反方向的。它基本包括3步:
① 分解 -- 将当前区间一分为二,即求分裂点 mid = (low + high)/2;
② 求解 -- 递归地对两个子区间a[low...mid] 和 a[mid+1...high]进行归并排序。递归的终结条件是子区间长度为1。
③ 合并 -- 将已排序的两个子区间a[low...mid]和 a[mid+1...high]归并为一个有序的区间a[low...high]。
平均时间复杂度:O(n*logn)
空间复杂度:O(n)
稳定性:稳定
1 /* 2 * 将一个数组中的两个相邻有序区间合并成一个 3 * 4 * 参数说明: 5 * a -- 包含两个有序区间的数组 6 * start -- 第1个有序区间的起始地址。 7 * mid -- 第1个有序区间的结束地址。也是第2个有序区间的起始地址。 8 * end -- 第2个有序区间的结束地址。 9 */ 10 void merge(int a[], int start, int mid, int end) 11 { 12 int *tmp = (int *)malloc((end-start+1)*sizeof(int)); // tmp是汇总2个有序区的临时区域 13 int i = start; // 第1个有序区的索引 14 int j = mid + 1; // 第2个有序区的索引 15 int k = 0; // 临时区域的索引 16 17 while(i <= mid && j <= end) 18 { 19 if (a[i] <= a[j]) 20 tmp[k++] = a[i++]; 21 else 22 tmp[k++] = a[j++]; 23 } 24 25 while(i <= mid) 26 tmp[k++] = a[i++]; 27 28 while(j <= end) 29 tmp[k++] = a[j++]; 30 31 // 将排序后的元素,全部都整合到数组a中。 32 for (i = 0; i < k; i++) 33 a[start + i] = tmp[i]; 34 35 free(tmp); 36 } 37 38 /* 39 * 归并排序(从上往下) 40 * 41 * 参数说明: 42 * a -- 待排序的数组 43 * start -- 数组的起始地址 44 * endi -- 数组的结束地址 45 */ 46 void merge_sort_up2down(int a[], int start, int end) 47 { 48 if(a==NULL || start >= end) 49 return ; 50 51 int mid = (end + start)/2; 52 merge_sort_up2down(a, start, mid); // 递归排序a[start...mid] 53 merge_sort_up2down(a, mid+1, end); // 递归排序a[mid+1...end] 54 55 // a[start...mid] 和 a[mid...end]是两个有序空间, 56 // 将它们排序成一个有序空间a[start...end] 57 merge(a, start, mid, end); 58 }