上两篇文章,《排序算法总结(1)——插入排序》、《排序算法总结(2)——选择排序》。
排序主要分为插入排序、选择排序、交换排序、归并排序、计数(也有叫分配)排序。现在总结一下交换排序
下面开始第三部分:交换排序
三、交换排序
交换排序的基本思想是两两比较待排序对象的关键码,如果发生逆序(即排列顺序与期望的相反)就交换,直到所有对象都排序完毕。本节将介绍3种常见的交换排序算法,即冒泡排序、shaker排序和快速排序。
1)冒泡排序
冒泡排序的基本思想是模拟气泡的上浮过程。可以把数组想象成一个竖立的容器,有很多的泡泡(数据元素),大泡泡对应关键码较小的数据元素,小泡泡则对应关键码较大的数据元素,由生活常识可知,显然大泡泡会首先浮上去。冒泡排序是稳定的算法。
源代码:
void BubbleSort(int *a, int len) { for (int i = len-1; i > 0; i --) { int j = len-1; while (j > len - i -1 ) { if (a[j] < a[j-1]) { int temp = a[j]; a[j] = a[j-1]; a[j-1] = temp; } j--; } } }
也有的教材上的源代码如下所示:
void BubbleSort(int *a, int len) { int temp; for (int i = 0;i < len; i++) for (int j = i+1; j < len; j++) if (a[i] > a[j]) { temp = a[i]; a[i] = a[j]; a[j] = temp; } }
但是我认为上面的代码虽然功能上实现了排序,但不是冒泡的那个意思。
2)shaker排序
Shaker排序又叫鸡尾酒排序或双向冒泡排序,它是冒泡排序的一种轻微改进。与冒泡排序相同,Shaker排序也是一种稳定排序算法。不同的是,普通的冒泡排序算法仅是从低到高比较序列里的每个元素,或者说普通的冒泡排序算法只能每次从前向后按一个次序进行遍历,而sbaker排序方法每次遍历却包括两个方向,先从前向后再从后向前,在从前往后遍历后能记录最后发生交换的两个元素位置,从后往前遍历时就从这个位置开始。这种双向交替比较不仅可以使小的浮上水面,同时也会使大的沉入水底,因而较普通的冒泡算法在效率上有所改进。
源代码:
void ShakerSort(int *a, int len) { int i,j,k,flag = 1; int temp; for ( i = 1; i < len && flag == 1; i ++) { flag = 0; //从左到右 for (j = i-1; j < len - i; j++) { if (a[j] > a[j+1]) { flag = 1; temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; } } //从右到左 for ( k = j-1; k > i; k --) { if (a[k] < a[k-1]) { flag = 1; temp = a[k]; a[k] = a[k-1]; a[k-1] = temp; } } } }
注意:上面算法还有优化的地方,比如从左向右比较时记录最后一次发生交换的位置,然后从右向左交换位置时从该位置开始比较。从右到左时同理要记录最后一次交换时的位置。
源代码如下:
void ShakerSort(int *a, int len) { int i,j,k,flag = 1; int temp, left = 0, right = len -1; for ( i = 1; i < len && flag == 1; i ++) { flag = 0; //从左到右 for (j = left; j < len - i; j++) { if (a[j] > a[j+1]) { flag = 1; temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; right = j;//记录最后一次交换的位置 } } //从右到左 for ( k = right ; k > i; k --) { if (a[k] < a[k-1]) { flag = 1; temp = a[k]; a[k] = a[k-1]; a[k-1] = temp; left = k;//记录最后一次交换的位置 } } } }
3) 快速排序
快速排序也称为分区排序,它是一种平均性能非常好的排序方法。它采用了一种分治的策略,其基本思想是取待排序对象序列中的某个对象为基准(比如第一个对象),按照该关键码的大小,将整个对象序列划分为左右两个子序列,其中左侧子序列中所有对象的关键码都小于或等于基准对象的关键码,右侧子序列中所有对象的关键码都大于基准对象的关键码,基准对象排在这两个子序列中间,然后分别对这两个子序列重复施行上述方法,直到排序完成为止。
快速排序算法仅需要选择基准项并对包含超过一个元素的子数组进行分区。
源代码:
void QuickSort(int *a, int low, int high) { int i = low, j = high; int temp = a[low]; while (i<j) { while (i<j && temp <= a[j]) j--; if (i < j) { a[i] = a[j]; i++; } while (i<j && temp > a[i]) i++; if (i < j) { a[j] = a[i]; j--; } } a[i] = temp; if (low < i) QuickSort(a, low,i-1); if (j < high) QuickSort(a, j+1, high); }
通过理论证明得到快速排序算法的平均排序时间是O(n*logn)。快速排序之所以被称为“快速排序”, 也是因为就平均效率而言,它是所有内排序方法中最好的一个,这二点也已经得到了实验的证明。