您的位置:首页 > 其它

算法导论第八章线性时间排序

2015-09-29 12:41 309 查看
一、线性时间排序算法历史概览

计数排序首先是由 Harold H. Seward 于1954年提出,而且他还提出将计数排序和基数排序进行结合的思想;基数排序是L.J.Comrie于1929年首次在一篇描述卡片穿孔机文档中提出的一种方法,它是从最低有效位开始,对一个有多位数组成的数进行排序的方法;而桶排序的基本思想则由E.J.Isaac和R.C.Singleton于1956年提出的,之后很多研究人员在这三种算法的基础上针对不同的应用场景又进一步改进,到了今天一个很成熟、很通用的地步。

二、O(nlgn)到O(n)的排序转变

从最初的O(n^2)到O(nlgn),再到O(n),排序算法的时间复杂度从非线性的时间要求到线性时间要求,这里面汇集了多少算法大牛的心血和智慧,从另外一个侧面也说明了算法的世界充满了多少奇思妙想的可能,只要稍微改动一下语句的表达,或许就能对算法的性能有一定的提升。本书这一部分都是着眼于排序算法,经历了从O(n^2)到O(n)的转变,让我惊叹算法的神奇之余,也让我有很大的动力去探索这些算法是怎么样一步步地进行优化改进,它们的主要的思想体现在什么地方,为什么稍微加点东西就能对算法性能有了质的提升。这些问题迫使我不由自主的思考,从而加快阅读的速度,也让自己的理解、记忆能力进一步得到了提升。虽然之前这些算法都有接触,也都写过,但是当时只是学着怎么应用,根本没有懂本质,所以,再一次看的时候,感觉仍然在看新东西一样,想不起来这个算法大概的一个思路。所以,在看书,尤其是在看专业书的时候,带着问题和好奇去阅读,会让自己阅读的质量有很大的提高,而且能够加深理解和记忆,达到事半功倍的效果。

这一章介绍的几种排序:计数排序、基数排序、桶排序都是线性时间排序算法,即这几个算法的时间复杂度都可以达到O(n);而之前几章介绍的几种算法:快速排序、堆排序、归并排序,插入排序等算法最好情况下的时间复杂度都为O(nlgn)。再进一步对这些算法分析可以发现一个有趣的性质:在排序的最终结果中,各元素的次序依赖于它们之间的比较,也就是说任何比较排序在最好情况下都要经过Ω(nlgn),即比较排序的下界为Ω(nlgn)。而本章所介绍的三种算法之所以时间复杂度降为线性的,就是因为没有出现比较,而是通过运算的方式。

1、决策树模型

决策树模型是对比较算法的时间复杂度为什么至少为O(nlgn)的理论性证明。如下图所示,显示了插入排序算法作用于包含3个元素的输入序列的决策树情况:

#include <iostream>

using namespace std;

typedef struct BucketNode{
double        nValue;
BucketNode    *pNext;
}Node;

void BucketSort_Link(double arrA[], int nLen)
{
Node *pBucket = new Node[10];

//initial the bucket
for (int i = 0; i < 10; i ++) {
pBucket[i].nValue = 0.0;
pBucket[i].pNext = NULL;
}

for (int i = 0; i < nLen; i ++) {
double nTemp = arrA[i];
Node *pNode = new Node();
pNode->nValue = nTemp;
pNode->pNext = NULL;

int nKey = int(arrA[i]*10);

if (pBucket[nKey].pNext == NULL) {
pBucket[nKey].pNext = pNode; //每个桶的第一个位置不存数据
}
else {
Node *p = &pBucket[nKey]; //p-->q
Node *q = pBucket[nKey].pNext;

while (q && q->nValue <= nTemp) {
p = q;
q = q->pNext;
}

pNode->pNext = q;
p->pNext = pNode;
}
}

int k = 0;
for (int i = 0; i < 10; i ++) {
Node *pTemp = pBucket[i].pNext;
while (pTemp) {
arrA[k ++] = pTemp->nValue;
pTemp = pTemp->pNext;
}
}
}

int main()
{
double arr[10] = {0.78, 0.17, 0.39,0.26,0.72,0.94,0.21,0.12,0.23,0.68};
BucketSort_Link(arr, 10);
for(int i = 0; i < 10; i ++)
cout << arr[i] << " ";

cout << endl;
cout << rand()/RAND_MAX;
return 0;
}


View Code
六:总结

1、本章介绍的几种算法都是线性时间的排序算法,通过学习,或许大家也注意到了,之所以能达到这样的复杂度要求,原因就是空间换时间:计数排序申请了O(n+k)的临时空间,基数排序申请了O(n+k)(如果采用计数排序),桶排序申请了O(k*n)。

2、时间复杂度上,计数排序为Θ(n+k)(当k=O(n), 为Θ(n),在实际中常用); 基数排序为Θ(d(n+k))(当采用的稳定排序的复杂度为Θ(n+k)时);桶排序为Θ(n)(严格推导见书本)。

3、其实,总的来说,三种排序算法在输入元素都做了一定的假设,基数排序归在计数排序中(因为我们平常应用中,都将计数排序作为基数排序的稳定排序来实现)。计数排序假设所有数据都属于一个小区间内的整数(0~k),而桶排序则假设输入是由一个随机过程产生,该过程将元素均匀、独立地分布在[0,1)区间上,即桶。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: