排序算法
对于排序是我们日常中最常用的算法之一,那么我们来总结一下排序算法,我们先不分析线性时间排序
①冒泡排序:
(1)算法思想:冒泡排序一般是第一个接触的排序算法,他的思想是两两进行比较,先将较小的数冒出,进而再冒出大些的数。比较好理解
(2)时间复杂度:O(n^2)
(3)算法描述:
void bubble(int a[], int n){ int i,j,t; bool flag = true; for(i = 1; i < n - 1 ; i ++){ if (flag == false) break; flag = false; for(j = n - 1; j >= i + 1; j--){ if(a[j] < a[j - 1]) { int t= a[j]; a[j] = a[j -1]; a[j - 1] = t; flag = true; } } } }
有的人可能会说这个和原先自己学的有些许不一样,因为这个是冒泡排序的改进版,所谓改进也只是增加了一个标志来判断是否已经排序完毕,如果剩下的数已经排序完毕,那么将不排序。这样可以优化效率。
②选择排序
(1)算法思想:选择排序是冒泡排序的延伸,他的思想是拿出第一个数,从这个数之后找到除了这个数以外最小的数,然后将这两个数进行交换。然后循环对第二个数进行处理,以此类推。
(2)时间复杂度:O(n^2)
(3)算法描述:
void seletion_sort(int a[], int n){ int i, j; for(i = 0; i < n - 1; i ++){ int min_state = i; int min_num = a[i]; for(j = i + 1; j < n ; j ++) { if(a[j] < min_num){ min_state = j; min_num = a[j]; } } int t = a[i]; a[i] = a[min_state]; a[min_state] = t; } }
③插入排序
(1)算法思想:插入排序的思想是先将第一个数视为一个有序的数列,然后将第二个数跟第一个数排序。进而将前两个数看做一个有序数列,将第三个数插入。以此类推
(2)时间复杂度:O(n^2)
(3)算法描述:
void Insert_sort(int a[], int n){ int i, j; for(i = 2; i < n; i++){ int t = a[i]; j = i - 1; while(j >= 0 && a[j] > t){ a[j + 1] = a[j]; j --; } a[j + 1] = t; } }
④归并排序
(1)算法思想:归并排序的算法就是分治法,简而言之就是将序列分为两个部分,进而再分成更小的几个部分,对小部分进行排序,然后再用递归将小部分组成大的有序数列。当然,归并排序分为从顶向下和从下向顶两种,下面分别进行介绍。
(2)时间复杂度:O(nlogn)
(3)算法描述
1.对于从顶往下的归并排序如算法思想中所讲:
void MERGE(int a[], int temp[], int begin, int middle, int end){ int i = 0; int j = begin; int k = middle; int len = end - begin; while(j < middle && k <end){ if(a[j] < a[k]){ temp[ i++ ] = a[j]; j++; } else{ temp[ i++ ] = a[k]; k++; } } if(j == middle) while ( k < end) temp[i++] = a[k ++]; else while(j < middle) temp[i ++] = a[j ++]; for(i = 0; i < len; i++){ a[begin + i] = temp[i]; } } void recuMergesort(int a[], int temp[], int begin, int end){ if(begin < end){ int middle = (begin + end) / 2; recuMergesort(a, temp, begin, middle); recuMergesort(a, temp, middle+1, end); MERGE(a, temp, begin, middle + 1, end + 1); } } void fromtop_Merge_sort(int a[], int n){ int *temp = (int *)malloc(sizeof(int) * n); recuMergesort(a, temp, 0 , n - 1); free(temp); }
2.对于从下往上的归并排序,我们要对下面的数进行进行归并排序,类似于已经从上往下已经分好的情况,借用一下百度百科的图片来解释一下
MERGE函数部分相同,只是在对数的处理上有一定区别。
void frombase_Merge_sort(int a[], int n){ int len = 1; int i = 0; int *temp = (int *)malloc(n * sizeof(int)); while(len < n){ for(i = 0;i + 2*len < n; i += 2*len) MERGE(a, temp, i, i + len, i + 2*len); if(i + len < n) MERGE(a, temp, i, i+len, n); len *= 2; } free(temp); }
⑤快速排序
(1)算法思想:快排是最经常使用的排序算法,原因就是其排序的速度快,最基本的快排的原理是什么呢?就是取最左面的一个数,然后将其作为一个标志,从第二个数和最后一个数开始同时向右向左寻找左面第一个比标志大的数和右面第一个比标志小的数。将其进行交换,继续循环到左右两个查找到同一个元素。然后将这个元素和最左面那个元素进行交换。此时此刻标志所在的地方左面所有的数比标志小,右面所有的数比标志大。我们再对标志左面和右面分别进行快排。最后得到排好序的数列。
(2)时间复杂度O(nlogn)
(3)算法描述:
void quicksort(int a[], int left, int right, int n){ int i, j, s ; if(left < right){ s = a[left]; i = left; j = right; while(1){ while(i + 1 < n && a[++ i] < s); while(j - 1 > -1 && a[-- j] > s); if(i >= j) break; swap(a[i],a[j]); } a[left] = a[j]; a[j] = s; quicksort(a, left, j, j - left); quicksort(a, j + 1, right, right - j - 1); } }
当然我们很容易会发现,如果这个序列已经排好序了,那么快排会重新打乱顺序,再进行排列,就会退化成为一个冒泡排序的速度。那么快排重要的一点就是取那个轴。针对轴的取得问题,快排还有下面两个版本。分别是随机取轴和取右轴
1.随机取轴:
为了克服如上提出的问题导致的效率下降的问题,所以采取随机取轴是非常好的。
void Rondom_quicksort(int a[], int left, int right){ if(left < right){ int index = rand() % (right - left + 1) + left; int data = a[index]; int i = left - 1; int j = right + 1; while(1){ while( a[ ++i] < data); while( a[ --j] > data); if(i >= j) break; int t= a[i]; a[i] = a[j]; a[j] = t; } Rondom_quicksort(a, left, i - 1); Rondom_quicksort(a, j +1 , right); } }
2.取右轴:
取右轴的算法思想是从左面第一个数开始遍历,如果这个数小于右轴,那么将这个数依次放在左面,最后遍历到右轴左面那个数的时候,整个数列已经分成三部分:比右轴小的部分,比右轴大的部分,和右轴。然后将右轴放到这两个区域中间,进而对两边进行递归。
void quicksort(int a[], int left, int right){ int i, j, s, t; if(left < right){ s = a[right]; i = left - 1; for(j = left; j < right; j ++){ if(a[j] <= s){ t = a[++i]; a[i] = a[j]; a[j] = t; } } t = a[right]; a[right] = a[i + 1]; a[i + 1] = t; quicksort(a, left, i); quicksort(a, i + 2, right); } }
⑥希尔排序
(1)算法思想:希尔排序是升级版的插入排序,它的思想是有一个增量,对第一个数和第1加增量个数进行比较,按大小关系进行排列交换,然后对第二个数和第2加增量个数进行比较,直到进行完毕一轮以后,对增量取一半,重复操作。
(2)时间复杂度:希尔排序的时间复杂度是涉及到增量的选择,而又涉及数学中难以解决的问题,所以不可知,但是针对不同的增量选择有以下时间复杂度:
(图为转载)
(3)算法描述:
void shell_sort(int a[], int n){ int i, j, t, d; d = n / 2; while(d >= 1){ for(i = d - 1; i < n; i ++){ t = a[i]; j = i - d; while(j >= 0 && a[j] > t){ a[j + d] = a[j]; j = j - d; } a[ j + d] = t; } d /= 2; } }
常用的除了线性时间排序之外的排序算法有这些,其中还有最后一个小问题,那就是,对于都是时间复杂度为O(n^2)的冒泡,选择和插入排序,哪个更快,如果我们拿大数据测试的话,显然插入排序更快,为什么呢,因为插入排序时间就为逆序对数,但是冒泡排序时间至少为逆序对数,而选择排序每一轮只交换一次,所以效率比冒泡排序要更快。具体的线性时间排序我们下一篇再讨论。