比较排序和非比较排序:
常见的排序算法都是比较排序,非比较排序包括计数排序、桶排序和基数排序,非比较排序对数据有要求,因为数据本身包含了定位特征,所有才能不通过比较来确定元素的位置。
比较排序的时间复杂度通常为O(n^2)或者O(nlogn),比较排序的时间复杂度下界就是O(nlogn),而非比较排序的时间复杂度可以达到O(n),但是都需要额外的空间开销。
冒泡排序通过重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来,直到没有再需要交换的元素为止(对n个项目需要O(n^2)的比较次数)。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
最差时间复杂度O(n^2)
最优时间复杂度O(n)
平均时间复杂度O(n^2)
最差空间复杂度:总共O(n),需要辅助空间O(1)
常用的选择排序方法有简单选择排序和堆排序,这里只说简单选择排序,堆排序后面再说。
假设所排序序列的记录个数为n,i 取 1,2,…,n-1。
从所有n-i+1个记录(Ri,Ri+1,…,Rn)中找出排序码最小(或最大)的记录,与第i个记录交换。执行n-1趟 后就完成了记录序列的排序。
通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj,Ai原来在位置前,排序后Ai还是要在Aj位置前。
排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。
不稳定排序算法口诀:快希选堆(快些选堆)
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。
将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,是稳定的排序方法。
空间复杂度O(1)。
平均时间复杂度O(n^2)。
最差情况:反序,需要移动n*(n-1)/2个元素 ,运行时间为O(n^2)。
最好情况:正序,不需要移动元素,运行时间为O(n)。
直接插入排序中要把插入元素与已有序序列元素依次进行比较,效率非常低。
折半插入排序,使用使用折半查找的方式寻找插入点的位置, 可以减少比较的次数,但移动的次数不变, 时间复杂度和空间复杂度和直接插入排序一样,在元素较多的情况下能提高查找性能。
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值。
由于堆中每次都只能删除第0个数据,通过 取出第0个数据再执行堆的删除操作、重建堆(实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。),然后再取,如此重复实现排序。
空间复杂度O(1)。
平均时间复杂度O(nlogn)。
最差情况:运行时间为O(n^2)。
最好情况:运行时间为O(n)。
不稳定。
归并排序,是创建在归并操作上的一种有效的排序算法该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。
即先使每个子序列有序,再将两个已经排序的序列合并成一个序列的操作。若将两个有序表合并成一个有序表,称为二路归并。
归并排序速度仅次于快速排序,为稳定排序算法(即相等的元素的顺序不会改变),一般用于对总体无序,但是各子项相对有序的数列。
时间复杂度为O(nlogn)
空间复杂度为 O(n)
归并排序比较占用内存,但却是一种效率高且稳定的算法。
快速排序(Quicksort)是对冒泡排序的一种改进,又称划分交换排序。快速排序使用分治法策略来把一个序列分为两个子序列。
事实上,快速排序通常明显比其他Ο(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。
最差时间复杂度 Ο(n^2)
最优时间复杂度 Ο(n log n)
平均时间复杂度Ο(n log n)
最差空间复杂度 根据实现的方式不同而不同
希尔排序法(缩小增量法)属于插入类排序,是将整个无序列分割成若干小的子序列分别进行插入排序的方法。
1. 把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
2. 随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
2. 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
希尔排序是一个不稳定的排序,其时间复杂度受步长(增量)的影响。
空间复杂度: O(1)
时间复杂度: 平均 O(n^1.3),最好 O(n) ,最坏 O(n^2)
桶排序(Bucket sort)或所谓的箱排序,是一个排序算法(属于分而治之)。
假设有一组长度为N的待排关键字序列K[1….n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就作为B[i]中的元素。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]….B[M]中的全部内容即是一个有序序列。
桶排序与归并排序、快速排序看起来好像很类似,都用到了分而治之的方法,而桶排序的重点在于通过关键函数将数据放到有序排列的桶中。
比如求0~1间的小数排序,可以分成10个桶,分别存入0~0.1, 0.1~0.2….,之后先在0.1的桶中排序,再归并的时间也是常数了,所以说桶数越多,所花的时间也越少。
最差时间复杂度 O(n^2)
平均时间复杂度 O(n+k)
最差空间复杂度 O(n*k)
平均情况下桶排序以线性时间运行,桶排序是稳定的,排序非常快,但是同时也非常耗空间,基本上是最耗空间的一种排序算法。
对N个关键字进行桶排序的时间复杂度分为两个部分:
很显然,第2部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。因此,我们需要尽量做到下面两点:
基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
我们可以按照下面的一组数字做出说明:12、 104、 13、 7、 9
(1)按个位数排序是12、13、104、7、9
(2)再根据十位排序104、7、9、12、13
(3)再根据百位排序7、9、12、13、104
(4)如果数据在这个位置的余数相同,那么数据之间的顺序根据上一轮的排列顺序确定;
基数排序的时间复杂度是O(k·n),其中n是排序元素个数,k是数字位数。注意这不是说这个时间复杂度一定优于O(n·log(n)),k的大小取决于数字位的选择和待排序数据所属数据类型的全集的大小;k决定了进行多少轮处理,而n是每轮处理的操作数目。
基数排序基本操作的代价较小,k一般不大于logn,所以基数排序一般要快过基于比较的排序,比如快速排序。
最差空间复杂度是O(k·n)
计数排序是一类基于非比较的排序算法,主要用于对一定范围内的整数排序(特别是出现大量重复的情况)时,它的复杂度为Ο(n+k)(其中k是整数的范围)。
假设输入的线性表L的长度为n,表中元素为L1,L2,..,Ln,线性表的元素属于有限偏序集S={S1,S2,..Sk}。则计数排序可以描述如下:
#include
using namespace std;
const int MAXN = 100000; // 待排序线性表长度
const int k = 1000; // 数据范围
int a[MAXN], c[MAXN], ranked[MAXN];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> a[i]; //输入数据
++c[a[i]]; // 记录重复的数据
}
for (int i = 1; i < k; ++i)
c[i] += c[i-1]; // 得到计数表
for (int i = n-1; i >= 0; --i)
ranked[--c[a[i]]] = a[i]; // 得到排序后的表
for (int i = 0; i < n; ++i)
cout << ranked[i] << endl;
return 0;
}
这篇博文是个人的学习笔记,内容许多来源于网络(包括CSDN、博客园及百度百科等),博主主要做了微不足道的整理工作。由于在做笔记的时候没有注明来源,所以如果有作者看到上述文字中有自己的原创内容,请私信本人修改或注明来源,非常感谢>_<