MIT:算法导论——5.线性时间排序:计数排序、基数排序以及桶排序

注:教材《算法导论》第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 
#include 
#include 
#include 
#include 
#include 

using namespace std;

template
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( a, b, 10, 22 );
	
	for( int i = 0; i < 10; ++i )
		cout << b[i] << "\t";
	cout << endl;


	delete [] b;
	return 0;
}

template
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;
}






你可能感兴趣的:(数据结构与算法)