每个算法都接收一个含有元素的数组和一个包含元素个数的整数,我们假设N是传递到排序函数中的元素个数:
一、插入排序
1、 直接插入排序:
最简单的排序算法之一。插入排序有N-1趟(PASS)排序组成,对于P = 1趟到P=N-1趟,插入排序保证从位置0到位置P-1上的元素已经是有序状态(数据结构与算法分析是如此描述的);通俗讲就是把数组分成有序区和无序区,然后通过嵌套的2个循环不断的将无序区的元素调到有序区的适当位置。
C语言代码实现如下:
void insertsort(int a[],int n) { int p,j; int temp; for(p = 1;p < n;p++) { temp = a[p]; for(j = p;j > 0 && a[j-1] > temp;j-- ) a[j] = a[j-1;] a[j] = temp; } }
算法分析:
每个嵌套循环的都花费N次迭代,因此插入排序的最坏时间复杂度为O(N2),但是如果输入的数据是已经排好序的,那么其时间复杂度为O(N),因为内层循环总是立即判定不成立而停止。
2、 希尔排序:
又称为缩小增量排序,希尔排序使用的h1,h2,h3……ht,叫做增量序列,只要h1=1任何增量序列都是可行的,在使用增量hk的一趟排序之后对于每一个i我们都有a[i] <= a[i + hk];所有相隔hk的元素都将被排序(数据结构与算法分析是如此描述的);算法将无序序列增量的划分为元素个数相同的若干组,使用直接插入排序法进行排序,然后不断的缩小增量至为1,。
C语言代码实现如下:
void shellsort(int a[],int n) { int i,j,increment; int temp; for(increment = n/2;increment > 0;increment /= 2) for(i = increment;increment < n;i++) { temp = a[i]; for(j = i;j > increment && a[j - increment] > temp;j -= increment) a[j] = a[j - increment]; a[j] = temp; } }
算法分析:
使用希尔增量时(即increment /= 2)希尔排序的运行时间为O(N2)此数学证明相当复杂0.0,使用Hibbard增量(这是什么增量?自己GOOGLE)的希尔排序算法的最坏运行时间复杂度为O(N3/2),希尔排序的性能在实践中是完全可以接受的,即使是对于数以万计的N也是如此,编程的简单特点使得他成为对适度的大量输入数据经常选用的算法。
二、选择排序
1、 直接选择排序:
将数组分成有序区和无序区,选择无序序区的的最大元素与无序区的首元素交换位置,有序区扩大无序区缩小直到无序区元素个数为1。
C语言代码实现如下:
1 void selectsort(int a[],int n) 2 3 { 4 5 int i,j; 6 7 int temp; 8 9 for(i = 0;i < n – 1;i++ ) 10 11 for(j = i+1;j < n;j++) 12 13 { 14 15 if(a[j] < a[i]) 16 17 { 18 19 temp = a[i]; 20 21 a[i] = a[j]; 22 23 a[j] = temp; 24 25 } 26 27 } 28 29 }
算法分析:
快速选择排序算法最简单的排序算法,时间复杂度为O(N2),无论输入的数据是否是已经排序,性能较差。
2、 堆排序:
1堆的定义是N个元素的序列{k1,k2,k3,…,kn},当且仅当满足ki<=k2i并且ki<=k2i+1或者是>=,堆的含义表明完全二叉树中所有非终端结点的值均不大于或不小于其左右孩子结点的值
2二叉树的有一条这样的性质:如果对一颗有N个结点的完全二叉树的结点按从上往下从左往右编号,对任意结点i有:如果i=1则结点i是二叉树的根节点,无双亲,如果i>1则其双亲结点PARENT(i)是结点└i/2┘(打不出来)。
从一个无序序列建堆的过程就是一个反复筛选的过程,若将此序列看成是一个完全二叉树,则最后一个非终端结点是第└n/2┘个元素,因此筛选只需从第└n/2┘个元素开始。建立堆之后执行swap操作取出根元素,重新建立堆,如此反复达到排序的目的。
C语言代码实现如下:
1 #define leftchile(i) (2 * (i) + 1) 2 3 void perdown(int a[],int i,int n) 4 5 { 6 7 int child; 8 9 int temp; 10 11 for(temp = a[i];leftchild(i) < n;i = child) 12 13 { 14 15 child = leftchild(i); 16 17 if(child < n - 1 && a[child] < a[child + 1]) 18 19 child++; 20 21 if(temp < a[child]) 22 23 a[i] = a[child]; 24 25 else 26 27 break; 28 29 } 30 31 a[i] = temp; 32 33 } 34 35 36 37 void heapsort(int a[],int n) 38 39 { 40 41 int i; 42 43 for(i = n/2;i >= 0;i--) 44 45 perdown(a,i,n); 46 47 for(i = n-1;i > 0;i--) 48 49 { 50 51 swap(&a[0],&a[i]); 52 53 perdown(a,0,i); 54 55 } 56 57 } 58 59
算法分析:
堆排序算法对记录数较少的文件并不值得提倡,但是对N较大的文件还是很有效的。因为其运行时间主要消耗在建初始堆和调整新堆时的反复筛选上,堆排序在最坏的情况下其时间复杂度为O(NlogN),相对于快速排序来说这是 堆排序的最大优点,此外堆排序仅需要一个记录大小供交换用的辅助存储空间。
三、交换排序
1、 冒泡排序
交换排序中的冒泡排序是最为大家所熟知的排序,原理就是进行N-1趟排序,每趟将数组中最大或者最小的元素通过交换排到最后,判断冒泡排序结束的标志是其中一趟排序下来没有发生值的交换。
C语言代码实现如下:
1 void bubblesort(int a[],int n) 2 3 { 4 5 int i,j = 0; 6 7 int temp; 8 9 int flag; 10 11 for(i = n-1;i >= 1;i--) 12 13 { 14 15 flag = 0; 16 17 for(j = 0;j < i;j++) 18 19 { 20 21 if(a[j] > a[j+1]) 22 23 { 24 25 temp = a[j+1]; 26 27 a[j+1] = a[j]; 28 29 a[j] = temp; 30 31 flag = 1; 32 33 } 34 35 } 36 37 if(flag == 0) 38 39 break; 40 41 } 42 43 }
算法分析:
根据算法很容易看出,若初始序列为正序,只需要进行一趟排序,在排序过程中进行n-1次的关键字的比较,若为反序则进行n-1趟排序,其时间复杂度为O(N2)。
2、 快速排序
快速排序是对冒泡排序的一种改进,他的基本思想是通过一趟排序将待排数据分割成独立的两部分,其中一部分数据均比另一部分数据小或者大,然后在分别对两部分数据进行排序,以达到整个数据有序,其实用到了递归和分治的思想。
C语言代码实现如下:
1 int partition(int a[],int low,int high) 2 3 { 4 5 int pivotkey; 6 7 pivotkey = a[low]; 8 9 while(low < high) 10 11 { 12 13 while(low < high && a[high] >= pivotkey) 14 15 high--; 16 17 a[low] = a[high]; 18 19 while(low < high && a[low] <= pivotkey) 20 21 low--; 22 23 a[high] = a[low]; 24 25 } 26 27 a[low] = pivotkey; 28 29 } 30 31 32 33 void qsort(int a[],int low,int high) 34 35 { 36 37 int pivotloc; 38 39 if(low < high) 40 41 { 42 43 pivotloc = partition(a,low,high); 44 45 qsort(a,low,pivotkey - 1); 46 47 qsort(a,pivotket + 1,high); 48 49 } 50 51 } 52 53 54 55 void quicksort(int a[],int n) 56 57 { 58 59 qsort(a,0,n-1); 60 61 }
算法分析:
快速排序是实践中最快的已知排序算法,他的平均时间复杂度是O(NlogN),最坏的情形的时间复杂度是O(N2)。