各种排序算法的比较
排序方法 | 平均时间 | 最坏情况 | 辅助存储 |
简单排序 | O(n2) | O(n2) | O(1) |
快速排序 | O(nlogn) | O(n2) | O(logn) |
堆排序 | O(nlogn) | O(nlogn) | O(1) |
归并排序 | O(nlogn) | O(nlogn) | O(n) |
基数排序 | O(d(n+rd)) | O(d(n+rd)) | O(rd) |
快速排序
排序方法有很多种,但是最常用的是快速排序。
1> 使用方法
所在位置 stdlib.h
函数原型 void qsort(void*base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*))
参数意义
base 待排序数组首地址
num 数组中待排序元素数量
width 各元素的占用空间大小
compare 指向函数的指针
compare函数举例
从小到大排序
int compare(const void*a,const void*b) { return *(int*)a - *(int*)b; }
从大到小排序
int compare(const void*a,const void*b) { return *(int*)b - *(int*)a; }
2> 实现源码
实现思路:
整体思路是这样的:取一个数字作为基准,将待排序的数组以此分成两部分。左边都是小于基准的,右边都是大于基准的。然后再分别对划分后的两部分进行递归,重复二分划分。最后,就能得到从小到大排序的数组了。
具体说每一次的二分划分过程是这样的:取所需排序数列的第一个数字作为基准,再定义两个变量i、j作为两个哨兵,分别从左右两端向中间靠拢。先是j向左走,如果发现有对应数字比基准大,就继续向左走,否则就停止移动。类似的,i向右走,如果发现有对应数字比基准小,就继续向右走,否则就停止移动。到此,可以说找到了两个需要交换的靶子,于是将它们进行交换。交换之后,两个哨兵继续向前走,找到需要交换的靶子,并将它们交换。当两个哨兵碰头的时候,这次二分划分算是结束了。
void qsort(int * array, int left, int right) { int i, j, k; if (left > right){ return; } i = left; j = right; k = array[left]; while (i != j){ while(i < j && array[j] >= k) j--; while (i < j && array[i] <= k) i++; if (i < j){ int t; t = array[i]; array[i] = array[j]; array[j] = t; } } array[left] = array[i]; array[i] = k; qsort(array, left, i - 1); qsort(array, i + 1, right); }
3> 总结
快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。其实快速排序是基于一种叫做“二分”的思想。 摘自《坐在马桶上看算法:快速排序》
冒泡排序
实现思路
冒泡排序,顾名思义,大的沉下去,小的浮上来。
先比较相邻的两个数字,大的排在后边,小的排在前边;然后,后移一个位置,再次比较相邻的数字,重复上述操作;直到数组结尾。如此,则数组中最大的数字便排到了数组的最后位置上,而小的数字便排到了数组的前边。
重复以上操作,因为最后的一个位置已经排过,所以只需要排序到倒数第二个位置即可。
重复以上操作,直到剩下一个一个数字时停止,则排序结束。
实现源码
void BubbleSort(int a[], int length) { int i, j; for (i = 0; i < length - 1; i++){ for (j = 0; j < length - 1 - i; j++){ if (a[j] > a[j + 1]){ int t; t = a[j]; a[j] = a[j + 1]; a[j + 1] = t; } } } }
选择排序
实现思路
首先确定第一个位置的数字,拿第一个数字依次的与其后的数字进行比较,如果发现有比第一个数字小的,就进行交换。当进行到数组尾部的时候,数组中最小的数字就排在了第一个位置上。
然后,确定第二个位置的数字,重复以上的操作。
当倒数第二个位置也确定下来之后,排序工作就结束了。
实现代码
void SelcetSort(int a[], int length) { int i, j; for (i = 0; i < length - 1; i++){ for (j = i + 1; j < length; j++){ if (a[i] > a[j]){ int t; t = a[i]; a[i] = a[j]; a[j] = t; } } } }
插入排序
实现思路
插入排序的整理思路是这样的:首先以第一个数字为基准,然后拿第二个与第一个比较。如果第二个比第一个小,就交换它们,否则不交换。如此就算是第二个插入到数列中了。然后以前两个数字为基准,拿第三个数字与前两个数字进行比较,插入到合适的位置。
具体说一次插入过程是这样的:由插入规律可知,待插入的数字前边的数列都是排好序的。拿待插入的数字与其前边第一个数字比较,如果比这个数字大,就不用移动了。倘若比这个数字小,就将这个数字往后边移动一位。然后,再拿待插入的数字与其前边第二个数字比较,如果比这个数字大,那么其前边第一个数字的位置就是要插入的位置,否则继续移动。重复以上操作,找到合适的位置,然后将待插入的数字插入即完成了一次插入动作。
实现代码
void InsertSort(int a[], int len) { int i; for (i = 1; i < len; i++){ if (a[i] < a[i - 1]){ int k, j; k = a[i]; j = i; while (j > 0 && a[j - 1] > k){ a[j] = a[j - 1]; j--; } a[j] = k; } } }
参考资料: