原文:click
- Stable & Not Stable
稳定排序算法会将相等的元素值维持其相对次序。如果一个排序算法是稳定的,当有两个有相等元素值 R 和 S ,且在原本的列表中R出现在 S 之前,那么在排序过的列表中 R 也将会是在 S 之前。
- O(n2) 与 O(n*logn) 的比较
- 冒泡排序(Bubble Sort)
重复比较要排序的数列,一次比较两个元素,如果后者较小则与前者交换元素:
1、比较相邻的元素,如果后者比较大,则交换两个元素
2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。
3、针对所有元素重复以上的步骤,除了最后一个。
冒泡排序对 n 个元素需要 O(n2) 的比较次数,且可以原地排序。冒泡排序仅适用于对于含有较少元素的数列进行排序。
最差时间复杂度 O(n^2)
平均时间复杂度 O(n^2)
最优时间复杂度 O(n)
最差空间复杂度 O(n),辅助空间 O(1) - 鸡尾酒排序(Cocktail Sort)
鸡尾酒排序,也就是双向冒泡排序(Bidirectional Bubble Sort) ,是冒泡排序的一种变形。此算法与冒泡排序的不同处在于排序是以双向在序列中进行的排序。如果序列中大部分元素已经排好序时,可以得到比冒泡排序更好的性能。
我们需要先将最大数据移动到最后,再把最小的移动到最前,这是一轮,直到所有的都排好。因为双向排序的两头都排序好了,我们只需要处理数组的中间部分即可,而单向即传统的额冒泡排序只有尾部的元素是排好序的,这时每轮处理都需要从头一直处理到已经排好序的元素的前面一个元素。虽然它在效率上有了点改进,但它也不能大幅度提高其排序的效率,这是由冒泡排序的基本过程所决定的。
本质上,算法没啥优化,只是双向进行调整,利用上浮和下沉
算法复杂度
最差时间复杂度 O(n^2)
平均时间复杂度 O(n^2)
最优时间复杂度 O(n)
最差空间复杂度 О(1)
//改进版的冒泡排序(双向冒泡)
void BidBubbleSort(int array[], int n)
{
int low, high, flag, i;
low = 0;
high = n - 1;
while(low < high)
{
flag=0;
for(i=low; i<high; i++) //正向冒泡
{
if(array[i] > array[i+1]) //找到剩下中最大的
{
Swap(&array[i], &array[i+1]);
flag = 1; //标志, 有数据交换
}
}
if( !flag )
break;
high--;
for( i=high; i>low; i-- ) //反向冒泡
{
if(array[i] < array[i-1]) //找到剩下中最小的
Swap(&array[i], &array[i-1]);
}
low++;
}
-
奇偶排序(Odd-Even Sort)
奇偶排序通过比较数组中个相邻的(奇-偶)位置元素,如果该奇偶元素对是错误的顺序(前者大于后者),则交换元素。然后再战队所有的(偶-奇)位置元素进行比较。如此交替进行下去。
算法复杂度:
最差时间复杂度 O(n^2)
平均时间复杂度 O(n^2)
最优时间复杂度 O(n)
最差空间复杂度 О(1) -
快速排序 ( Quick Sort )
快速排序使用 分治法 ( Divide-and-Conquer ) 策略将一个数列分成两个子数列并使用递归来处理。
算法描述:
–1、从数列中挑出一个元素,称为“主元”( pivot )。
–2、重新排序数列,所有元素比主元小的摆放在主元前面,所有元素比主元值大的摆在主元的后面(相同的数可以放到任何一边)。这个称为分区(partition)操作。在分区退出之后,该主元就处于数列的中间位置。
–3、递归地 ( recursively )把小于主元值元素的子数列和大于主元值元素的子数列排序。
递归的最底部清醒,是数列的大小是0或1,也就是总是被排序好的状况。这样一直递归下去,直到算法退出
递归的最后情况,就是数列的大小是0或者1,也就是说总是被排序好的状况。这样一直递归下去,直到算法退出。
QUICKSORT(A, p, r)
if p < r
then q <- PARTITION(A, p, r)
QUICKSORT(A, p, q - 1)
QUICKSORT(A, q + 1, r)
PARTITION(A, p, r)
x <- A[r]
i <- p - 1
for j <- p to r - 1
do if A[j] <= x
then i <- i + 1
exchange A[i] <-> A[j]
exchange A[i + 1] <-> A[r]
return i + 1
最差时间复杂度 O(n^2)
平均时间复杂度 O(nlog n)
最优时间复杂度 O(nlog n)
最差空间复杂度 根据实现的方式不同而不同 O(n) 辅助空间 O(log n)
快速排序的运行时间与划分是否对称有关,而后者又与选择了哪一个元素来进行划分有关,如果划分是对称的,那么快速排序从渐进意义上讲,就与合并算法一样快;如果划分不是对称的,那么从渐进意义上来讲,就与插入排序一样慢。
快速排序的平均运行时间与其最佳情况运行时间很接近,而不是非常接近与最差情况运行时间。
QUICKSORT的运行时间是由花在过程PARTITION上的时间所决定的。每当PARTITION过程被调用时,就要选出一个Pivot元素。后续对QUICKSORT和PARTITION的各次递归调用中,都不会包含该元素。于是,在快速排序算法的整个执行过程中,至多只可能调用PARTITION过程n次。
快速排序的随机化版本
快速排序的随机化版本是对足够大的输入的理想选择。
RANDOMIZED-QUICKSORT 的平均运行情况是O(nlogn),如果在递归的每一层上,RANDOMIZED-PARTITION 所做出的的划分使任意固定量的元素向划分的某一边,则算法的递归树深度为 O(lgn),且在每一层上所做的工作量都为O(n)。
1 RANDOMIZED-PARTITION(A, p, r)
2 i <- RANDOM(p, r)
3 exchange A[r] <-> A[i]
4 return PARTITION(A, p, r)
快速排序是二叉查找树的一个空间优化版本。但其不是循序地把数据插入到一个显式的树种,而是由快速排序组织这些数据项到一个由递归调用所隐含的树中。这两个算法完全地产生相同的比较次数,但是顺序不同。
快速排序的最直接竞争对手是 (Heap Sort) 。堆排序通常会慢于原地排序的快速排序,其最坏情况的运行时间总是O(nlogn) 。快速排序通常情况下更快,但仍然有最坏情况发生的机会。
快速排序也会与合并排序(Merge Sort)竞争。合并排序的特点是最坏情况有着O(nlogn)运行时间的优势。不像快速排序或堆排序,合并排序是一个问你的个排序算法,而且非常灵活,其设计可以用于操作链表,或大型链式存储等,例如磁盘存储或网络附加存储等。尽管快速排序也可以被重写使用在链表晌,但对于基准的选择总是个问题。合并排序的主要缺点实在最佳情况下需要O(n)额外的空间,而快速排序的原地分区额尾部递归仅使用O(logn)的空间。
- 选择排序(Selection Sort)
算法原理:
首先在未排序序列中找到最大或最小元素,存放到排序序列的起始位置,然后再从排序元素中继续寻找最大或最小元素,然后放到已排序序列的头部。以此类推,直到所有元素均排序完毕。
算法复杂度:
最差时间复杂度 О(n²)
平均时间复杂度 О(n²)
最优时间复杂度 О(n²)
最差空间复杂度 О(n),辅助空间 O(1) - 插入排序(Insertion Sort)
算法原理:
对于未排序数据,在已排序序列中从后向前扫描,找到相应位置,将位置后的已排序数据逐步向后挪位,将新元素插入到该位置。
算法描述:
1、从第一个元素开始,该元素可以认为已经被排序
2、取出下一个元素,在已经排序的元素序列中从后向前扫描
3、如果该元素(已排序)大于新元素,将钙元素移到下一位置
4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5、将新元素插入到该位置后
6、重复步骤2~5
算法复杂度:
In-Place 原地排序(即只需要用到 O(1) 的额外空间)
最差时间复杂度 O(n2)
平均时间复杂度 O(n2)
最优时间复杂度 O(n)
最差空间复杂度 O(n),辅助空间 O(1)
由于插入排序的算法内循环是紧密的,对小规模来说是一个快速的原地排序算法。
- 希尔排序(Shell Sort)
希尔排序是插入排序的一种更高效的改进版本,其基于插入排序的以下两个特点提出改进方法:
1、插入排序在对几乎已经排序的数据操作时,效率高,可以达到线性时间。
2、插入排序一般来说是低效的,其每次只能将数据移动一位。
算法描述:
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能,这样可以让一个元素一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎已排好,此时插入排序较快。
假设有一个很小的数据在一个以按圣墟排好序的数组的末端。如果用复杂度为O(n^2)的排序(冒泡排序或插入排序),可能会进行n次的比较和交换才能将该数据转移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少量比较和交换即可移到正确位置。
步长序列(Gap Sequences)
步长的选择是希尔排序的重要部分。只要最终步长为 1 人恶化步长串行都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定的步长排序,最终算法以步长为1进行排序。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序。
已知的最好步长串行是由 Sedgewick 提出的 (1, 5, 19, 41, 109,…),该步长的项来自 9 * 4^i - 9 * 2^i + 1 和 4^i - 3 * 2^i + 1 这两个算式。这项研究也表明 “比较在希尔排序中是最主要的操作,而不是交换。” 用这样步长串行的希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。
算法复杂度:
最差时间复杂度 O(nlog2 n)
平均时间复杂度 依赖于步长间隔 O(nlog2 n)
最优时间复杂度 O(nlogn)
最差空间复杂度 O(n),辅助空间 O(1)