注:教材《算法导论》第8章 线性时间排序
一、决策树模型分析 排序算法下界 O(nlgn)
定理:在坏情况下,任何比较排序算法都需要做Ω(nlgn)次比较。
证明:对于一棵每个排列都是可达叶节点(l个)的决策树来说,树的高度(h)完全可以被确定。
因为输入数据的n!种可能的排列都是叶节点,所以有n! <= l <=2^h。
因为lg函数单调,所以h >= lg(n!) = Ω(nlgn)。
二、计数排序(使用计数器 数组)O(n+k)
// A[1...n],A.length = n,输入数组;
// B[1...n],存放排序的输出为计数器;
// 每个数都是在[0...k]区间内的整数。
COUNTING-SORT( A, B, k )
let C[0...k] be a new array// C提供临时存储空间,为计数器,对A每个数进行统计
for i = 0 to k// 初始化O(k)
C[i] = 0;
for i = i to n// 统计O(n)
C[A[i]] += 1;
for i = 1 to k// 累加O(k)
C[i] += C[i-1];// 这样A[i]与其目标位置c[A[i]]形成映射
for i = n to 1// 从高到底,保证stable sort
B[C[A[i]]] = A[i];//O(n)
C[A[i]] -= 1;
时间复杂度O(n+k),当k=O(n)时,排序的运行时间为O(n)。
三、基数排序O(d(n+k))
// 假设n个d位的元素存放在数组A中,其中第1位是最低位,第d位是最高位。
RADIX-SORT( A, d )
for i = 1 to d// ① 从低权重开始排序,② 采用稳定排序算法。
use a stable sort to sort array A on digit i
(1)基数排序的时间复杂度O(d(n+k)) -->O(n)。
(2)基数排序所需的辅助存储空间为O(n+rd)。
(3)基数排序是稳定的。
证明:数学归纳法。
t-1位有序,==>第t位仍有序:
① 第t-1位与第t位相等:stablity。
② 第t-1位与第t位不等:sorted。
四、算法比较
(1)直接插入排序
时间复杂度,平均:O(n^2), 最坏:O(n^2)。
算法所需的辅助空间是一个监视哨,辅助空间复杂度S(n)=O(1)。是一个就地排序。
直接插入排序是稳定的排序方法。
(2)冒泡排序
时间复杂度,平均:O(n^2)。
冒泡排序是就地排序,且它是稳定的。
(3)直接选择排序
时间复杂度,平均:O(n^2), 最坏:O(n^2)。
直接选择排序是一个就地排序,直接选择排序是不稳定的。
【反例】[2,2,1],每次选最小值放到数组头,因此首个的2与最小值1交换,导致不稳定。
(4)快速排序
时间复杂度,平均:O(nlgn), 最坏:O(n^2)。
快速排序在系统内部需要一个栈来实现递归。
若每次划分较为均匀,则其递归树的高度为O(lgn),故递归后需栈空间为O(lgn)。
最坏情况下,递归树的高度为O(n),所需的栈空间为O(n)。
快速排序是非稳定的。
(5)堆排序
时间复杂度,平均:O(nlgn), 最坏:O(nlgn)。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1)。
它是不稳定的排序方法。
(6)归并排序
时间复杂度,平均:O(nlgn), 最坏:O(nlgn)。
需要一个辅助向量来暂存两有序子文件归并的结果,
故其辅助空间复杂度为O(n),显然它不是就地排序。
若用单链表做存储结构,很容易给出就地的归并排序。
【推论】堆排序和归并排序是渐近最优的比较排序算法。因为其时间上界为O(nlgn)。
=========排序分类==================
插入排序
直接插入排序
希尔排序
交换排序
冒泡排序
快速排序
选择排序
直接选择排序
堆排序
归并排序
归并排序
分配排序
箱排序O(n)
基数排序O(d(n+k))
计数排序源码——
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <set>
using namespace std;
template<typename T>
void counting_sort( T a[], T b[], int len, int k );
int main( void )
{
int a[] = { 0, 20, 18, 20, 12, 5, 22, 15, 4, 7 };
int *b = new int[10];
counting_sort<int>( a, b, 10, 22 );
for( int i = 0; i < 10; ++i )
cout << b[i] << "\t";
cout << endl;
delete [] b;
return 0;
}
template<typename T>
void counting_sort( T a[], T b[], int len, int k )
{
T *c = new T[k+1];
for( int i = 0; i <= k; ++i )// 初始化计数器
c[i] = 0;
for( int i = 0; i < len; ++i )// 统计
++c[a[i]];
for( int i = 1; i <= k; ++i )// 累加
c[i] += c[i-1];// 这样A[i]与其目标位置c[A[i]]形成映射
for( int i = len - 1; i >= 0; --i )
b[--c[a[i]]] = a[i];// 从0开始计数,所以--放前面即可
delete [] c;
}