一、插入排序
【直接插入排序】插入排序就是将一个待排数据按其大小插入到一个有序表的适当位置,并插入后仍有序。工作原理是构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
分为三类:直接插入排序;折半插入排序;希尔插入排序。
【折半插入排序】
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
1、从第一个元素开始,该元素可以认为已经被排序
2、取出下一个元素,在已经排序的元素序列中从后向前扫描
3、如果该元素(已排序)大于新元素,将该元素移到下一位置
4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5、将新元素插入到该位置后
6、重复步骤2~5
例如,把{4,3,1,2}进行排序,算法过程见图:
观看flash:
点击打开链接
通常,有序的序表是r[1]、r[2].....,而r[0]是哨兵,用于存放需要排序的数据,属于赋值空间。
如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。
(1)最好情况是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。
(2)最坏情况是,序列是降序排列,此时需要进行的比较共有n(n-1)/2{1+2+...+n-1}次。插入排序的赋值操作是比较操作的次数减去(n-1)次。
平均来说插入排序算法复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数
据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort
算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。所以,算法时间
复杂度是:o(n^2);空间复杂度o(1)。是外部排序法,是稳定的。
代码如下:
void insertion_sort(int array[], int first, int last) { int i,j; int temp; for (i = first+1; i<=last;i++) { temp = array[i]; j=i-1; //与已排序的数逐一比较,大于temp时,该数后移 while((j>=first) && (array[j] > temp)) //当first=0,j循环到-1时,由于[[短路求值]],不会运算array[-1] { array[j+1] = array[j]; j--; } array[j+1] = temp; //被排序数放到正确的位置 } }
折半(二分)插入(Binary insert sort)排序是一种在直接插入排序算法上进行小改动的排序算法。其与直接排序算法最大的区别在于查找插入位置时使用的是二分查找的方式,在速度上有一定提升。
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
1、从第一个元素开始,该元素可以认为已经被排序
2、取出下一个元素,在已经排序的元素序列中二分查找到第一个比它大的数的位置
3、将新元素插入到该位置后
4、重复上述两步
二分插入排序是一种稳定的排序。当n较大时,总排序码比较次数比直接插入排序的最差情况好得多,但比最好情况要差,所元素初始序列已经按排序码接近有序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。
1)稳定
2)空间代价:O(1)
3)时间代价:插入每个记录需要O(log i)比较,最多移动i+1次,最少2次。最佳情况O(n log n),最差和平均情况O(n^2)。
代码如下:
void BinInsertSort(int a[], int n) { int key, left, right, middle; for (int i=1; i<n; i++) { key = a[i]; left = 0; right = i-1; while (left<=right) { middle = (left+right)/2; if (a[middle]>key) right = middle-1; else left = middle+1; } for(int j=i-1; j>=left; j--) { a[j+1] = a[j]; } a[left] = key; } }
【希尔排序】
插入排序法针对记录较少或表接近有序时,直接插入排序效率比较高。希尔排序是基于插入排序的这两点性质而提出改进方法的:
(1)开始排序时,由于选取的间隔较大,所以分组内记录个数较少,排序较快
(2)在以后的排序中,虽然分组中记录个数增多,但分组内的记录已经基本有序
算法描述
1、先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。
2、所有距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序。
3、取第二个增量d2<d1重复上述的分组和排序,
4、直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
算法过程如下例子:
假设待排序文件有10个记录,其关键字分别是:49,38,65,97,76,13,27,49,55,04。增量序列的取值依次
为:5,2,1。
不稳定的;空间复杂度是o(1)。
代码如下:
#include <stdio.h> int main() { const int n = 5; int i, j, temp; int gap = 0; int a[] = {5, 4, 3, 2, 1}; while (gap<=n) { gap = gap * 3 + 1; } while (gap > 0) { for ( i = gap; i < n; i++ ) { j = i - gap; temp = a[i]; while (( j >= 0 ) && ( a[j] > temp )) { a[j + gap] = a[j]; j = j - gap; } a[j + gap] = temp; } gap = ( gap - 1 ) / 3; } }
二、交换排序
关键点:两两比较带排序记录的关键字,如果逆序,则交换位置。
【冒泡排序】
简单的说就是:它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来,从后向前比较 。例如,无序记录r[1....n],通过无序区中相邻关键字的比较,如果逆序,则交换位置。通常从最后开始比较,最多需要n-1
趟冒泡排序。
算法描述
1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3、针对所有的元素重复以上的步骤,除了最后一个。
4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
如下图所示:
flsh如下:
点击打开链接
最差时间复杂度O(n^2)
最优时间复杂度O(n)
平均时间复杂度O(n^2)
最差空间复杂度总共O(n),需要辅助空间O(1)
代码如下:
#include <stdlib.h> #include <stdio.h> typedef int T; //从前或向后冒泡 void sort(T *a, int len) { int i; int j; T t; for (i = 0; i<len; i++) { for (j = 0; j<len-i-1; j++) { if (a[j]>a[j + 1]) { t = a[j]; a[j] = a[j + 1]; a[j + 1] = t; } } } } T *array(int num) { int i; T *a = (T *)malloc(num*sizeof(T)); for (i = 0; i < num; i++) { scanf("%d", &a[i]); } return a; } int main(int argc, char *argv[]) { int num; printf("input length:"); scanf("%d", &num); printf("input array:"); T *a = array(num); int i = 0; sort(a, num); for (i = 0; i<num; i++) { printf("%d ", a[i]); } printf("\n"); free(a); system("pause"); return 0; }
【快速排序】
快速排序是由东尼·霍尔所发展的一种排序算法。其基本思想是基本思想是,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
快速排序使用分治法来把一个串(list)分为两个子串行(sub-lists)。
步骤为:
1、从数列中挑出一个元素,称为 "基准"(pivot),
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
一趟快速排序的算法是:
1)设置两个变量i、j, 排序开始的时候:i=0,j=N-1;2)以第一个数组元素作为关键数据,赋值给 key,即 key=A[0];3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于 key的值A[j],将A[j]和A[i]互换;4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于 key的A[i],将A[i]和A[j]互换;5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于 key,4中A[i]不大于 key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。例如:
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。