按排序过程中依据的不同原则可分为:
1 插入排序
2 选择排序
3 交换排序
4 归并排序
5 计数排序
按照时间复杂度可分为:
1 简单的排序方法 O(n^2), 包括直接插入排序、冒泡排序、选择排序
2 先进的排序方法O(nlogn), 包括归并排序、快速排序、堆排序
3 基数排序 O(d.n)
各种算法的实现默认一般都是按升序排序。
按稳定性分
稳定排序,所有复杂度为O(n^2)的简单排序都是稳定的,基数排序也是稳定的。
不稳定排序,所有性能较好的排序方法都是不稳定的。
插入排序:依次插入一个未排序元素到已排序的序列中。插入排序是稳定排序,因它是每次考虑连续的两个元素。
说一种方法是稳定的,是说为实现排序,它可以是稳定的(当然我们可以以非稳定的方法实现),那么该方法就是稳定的,即稳定优先。
最好O(n), 最坏O(n^2)
插入排序与STL sort的比较,单位ms
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
/* 稳定排序 要注意保持稳定 */ template<typename T> void InsertionSort(T *begin, T *end) { T *p, *q; for (p=begin+1; p!=end; p++) { q=p-1; T t = *p; //排序的稳定是由t<*q保证的,换成其它形式也可以排序,但是不能保证稳定 while (q >= begin && t<*q) { *(q+1)=*q; q--; } *(q+1) = t; } }
希尔排序:是对直接插入排序的改进
当序列基本正序时,直接插入排序的效率接近O(n)。另一方面直接插入排序算法简单,因此在n很小时,效率很高。, 希尔排序正是基于这两点做改进。
主要策略:把序列分成很多小的子序列,分别进行直接插入排序。不断扩大子序列的长度直到长度为n,即完成排列。
这里的子序列并不是连续元素构成的,而是间隔相同的一系列元素构成的。这样随着子系列长度的增大(对应于增量的减小,子系列数的减小),整个系列就会基本有序。
希尔排序的性能还和使用的增量系列有关。一个比较好的增量系列是:dlta[k] = 2^(t-k+1)-1。t为总的排序趟数,k为趟序。t = floor(log2(n+1))。
假设n=100, 那么t=6, dlta = {63, 31, 15, 7, 3, 1}
Shell排序与STL sort的比较,单位ms。可见Shell 排序的性能确实不错。不过实现会稍有些复杂。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
template<typename T> void ShellSort(T *begin, T *end) { T *p, *q; //先求增量系列 int t=0, sum=1; //t总的排序趟数 int n=end-begin; while (sum<n) { sum *= 2; t++; } //多于一个元素 if (sum > 1) { sum /= 2; t--; } int *dlta = new int[t]; int i; for (i=0; i<t; i++) { dlta[i] = sum-1; sum /= 2; } i=0; while (i<t) { for (p=begin+dlta[i]; p!=end; p++) { if (*p<*(p-dlta[i])) { q = p-dlta[i]; T temp = *p; while (q>=begin && temp<*q) { *(q+dlta[i]) = *q; q -= dlta[i]; } *(q+dlta[i]) = temp; } } i++; } delete [] dlta; }
交换排序
冒泡排序是最简单的一种交换排序。交换:如果两个元素不符合某个顺序,则交换它们的值。
冒泡排序的性能始终是O(n^2)。最坏交换次数是O(n^2)
冒泡排序与STL sort的比较。可见冒泡排序的性能比直接插入排序的性能要差不少。基本不会用到,主要作为理解交换排序这个概念。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
template<typename T> void BubbleSort(T *begin, T *end) { T *p, *q; for (p=end-1; p>begin; p--) { for (q=begin; q<p; q++) { if (*(q+1)<*q) Swap(*(q+1), *q); } } }
快速排序:最常用到的排序。其平均性能是最好的。
是对冒泡排序的一种改进。它的基本思想是,通过一趟排序根据中轴值将待排记录分成两个系列,一个系列中的所有元素均大于中轴值,另一个系列中的元素均小于中轴值。然后分别利用同样的方法排序两个系列直到整个系列有序。
快速排序与STL sort的比较。两者性能基本一样,实现应该也差不多。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
template<typename T> void _QuickSort(T *begin, T *end) { if (begin < end) { T *p=begin, *q=end; int n = end - begin+1; T pivotKey = *(begin+n/2); //中轴值 int flg=1; while (p < q) { //从前面找一个大于中轴值的值,从后面找一个小于中轴值的值,交换两者的值 while (p<=q && *p<pivotKey) p++; while (p<=q && *q>pivotKey) q--; if (*p == *q) { if (p<q) //如果待排序记录都一样,这里的flg就是必须的 { if (flg==1) {p++; flg=0;} else { q--; flg=1; } } } else Swap(*p, *q); } _QuickSort(begin, p-1); _QuickSort(p+1, end); } } //这个函数是为了使QuickSort的接口和其它方法保持一致 template<typename T> void QuickSort(T *begin, T *end) { _QuickSort(begin, end-1); }
选择排序
简单选择排序:每次从未排序的记录中选择一个最小的加入到已排序记录中。
选择排序的性能还是不错的,因为交换次数少。在几个简单排序中是性能最好的一个。性能也很稳定,都是O(n^2)
选择排序与STL sort比较
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
template<typename T> void SelectionSort(T *begin, T *end) { T *p, *q; for (p=begin; p<end-1; p++) { T *t = p; for (q=p+1; q<end; q++) { if (*q < *t) t = q; } Swap(*t, *p); } }
显然,选择排序的主要操作是关键字的比较。那么要改进的突破口就在于减少关键字的比较。
堆排序:利用“堆”来减少选择“最小关键字”时所做的比较操作。利用堆可以在O(log2n)内找出一个集合中的最小关键字。从而使堆排序的时间复杂度将为O(nlog2n)。
堆排序的主要操作是建堆、调整堆、交换。
建堆:把一个集合调整为一个堆。升序排序要用到大根堆。
堆的主要应用不在排序,而在于选出一个集合中的最大或最小值。
堆排序与STL sort比较,性能上和快速排序差不多。
感觉这个比快速排序容易实现。而且只需要一个单位的辅助空间,也没有递归的开销。不过,可能对于线性结构需要知道位置信息,对于链式结构必须组织成树形结构。所以应用场景不如快排广泛。下面的代码中有一处错误: T orgin = *p 应改为 T orgin = p[k];
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
/* 下标从0开始 应尽量减少元素的交换次数 */ template<typename T> void AdjustHeap(T *p, int k, int n) { T orgin = *p; int t; while (k<n) { t=k; if (2*(k+1)-1<n) t = 2*(k+1)-1; if (2*(k+1)<n && p[t]<p[2*(k+1)]) t = 2*(k+1); if (t==k || p[t]<orgin ) //最大值小于根 { break; } p[k] = p[t]; k = t; } p[k] = orgin; } template<typename T> void CreateHeap(T *p, int n) { for (int i=n/2; i>=0; i--) AdjustHeap(p, i, n); } template<typename T> void HeapSort(T *begin, T *end) { int n=end-begin; CreateHeap(begin, n); for (int i=n-1; i>0; i--) { Swap(begin[i], begin[0]); AdjustHeap(begin, 0, i); } }
归并排序
把待排序记录分成两个子系列,分别排序两个子系列,然后再把这两个子系列归并。对子系列的排序一应用归并排序。
可知 f(n) = 2f(n/2) + n ,即f(n)=O( nlog2n )
归并排序最大的特点是它是一种稳定的排序。
与STL sort的比较,相比快速排序和堆排序,性能会稍微慢一点。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
template<typename T> void MergeSort(T *begin, T *end) { if (begin >= end-1) return; if (begin+1 == end-1) { if (*(end-1) < *begin) Swap(*begin, *(end-1)); } else { T *mid=begin+(end-begin)/2; MergeSort(begin, mid+1); //not include mid+1 MergeSort(mid+1, end); T *tarry = new T[end-begin]; T *pstart = tarry; T *p = begin; T *q = mid+1; int i=0; while (p<=mid && q<end) { if (*q<*p) { *tarry++ = *q; q++; } else { *tarry++ = *p; p++; } } while (p<=mid) { *tarry++ = *p++; } while (q<end) { *tarry++ = *q++; } tarry = pstart; while (begin < end) { *begin++ = *tarry++; } delete [] pstart; } }