您的位置:首页 > 其它

线性时间排序——计数排序,基数排序,桶排序

2014-01-16 20:58 726 查看
线性时间排序即时间复杂度为Θ(n)的排序,主要有计数排序、基数排序和桶排序

以前的排序都涉及到元素比较,称为比较排序,渐近最优的为 随机快速排序(快速排序在平均情况下),merge sort归并排序和heap sort,时间复杂度为Θ(nlogn)

而这种排序都不是用比较的操作来排序,所以下届Θ(nlogn)对它们不适用

一、计数排序

时间复杂度为Θ(n+k),k为常数

计数排序适合所需排序的数组元素取值范围不大的情况,要求取值范围小于元素个数

#include <stdio.h>
#include <stdlib.h>

//计数排序
int CountSort(int* pData, int nLen)
{
int* pCout = NULL;         //保存记数数据的指针
pCout = (int*)malloc(sizeof(int) * nLen);  //申请空间
//初始化记数为0
for (int i = 0; i < nLen; ++i)
{
pCout[i] = 0;
}
//记录排序记数。在排序的值相应加一
for(int i = 0; i < nLen; ++i)
{
++pCout[pData[i]];        //增
}
//确定不比该位置大的数据个数
for (int i = 1; i < nLen; ++i)
{
pCout[i] += pCout[i - 1];
//不比他大的数据个数为他的个数加上前一个的记数。
}
int* pSort = NULL;         //保存排序结果的指针pSort=(int*)malloc(sizeof(int) * nLen);    //申请空间
for (int i = 0; i < nLen; ++i)
{
//把数据放在指定位置。因为pCout[pData[i]]的值就是不比他大数据的个数
//为什么要先减一,因为pCout[pData[i]]保存的是不比他大数据的个数中包括了
//他自己,我的下标是从零开始的!所以要先减一。
--pCout[pData[i]];//因为有相同数据的可能,所以要把该位置数据个数减一。
pSort[pCout[pData[i]]] = pData[i];
}
//排序结束,复制到原来数组中
for (int i = 0; i < nLen; ++i)
{
pData[i] = pSort[i];
}
//最后要注意释放申请的空间。
free(pCout);
free(pSort);

return 1;
}

int main() {
int nData[10] = {8,6,3,6,5,8,3,5,1,0};
CountSort(nData, 10);
for (int i = 0; i < 10; ++i)
{
printf("%d ", nData[i]);
}
printf("\n");
system("pause");
return 0;
}


二、基数排序

1,假设数组a所有元素i为十进制整数且位数不超过d位

2,递归对数组a的个位、十位、...、d位排序

当没位数字都界于1到k之间时,对n个d位数的每一位处理的时间为Θ(n+k),共d次处理,所以时间复杂度为Θ(dn+dk),d为常数

基数排序分LSD(Least significant digital)和MSD(Most significant digital),前者从最低位开始排,后者从最高位开始排

LSD需要保持较低位的位置,而MSD则不需要

#include <stdio.h>
#include <stdlib.h>
//计数排序,npRadix为对应的关键字序列,nMax是关键字的范围。npData是具体要
//排的数据,nLen是数据的范围,这里必须注意npIndex和npData对应的下标要一致
//也就是说npIndex[1] 所对应的值为npData[1]

int RadixCountSort(int* npIndex, int nMax, int* npData, int nLen)
{
//这里就不用说了,计数的排序。不过这里为了是排序稳定
//在标准的方法上做了小修改。
int* pnCount  = (int*)malloc(sizeof(int)* nMax);        //保存计数的个数
for (int i = 0; i < nMax; ++i)
{
pnCount[i] = 0;
}
for (int i = 0; i < nLen; ++i)    //初始化计数个数
{
++pnCount[npIndex[i]];
}
for (int i = 1; i < 10; ++i)  //确定不大于该位置的个数。
{
pnCount[i] += pnCount[i - 1];
}
int * pnSort  = (int*)malloc(sizeof(int) * nLen);    //存放零时的排序结果。
//注意:这里i是从nLen-1到0的顺序排序的,是为了使排序稳定。
for (int i = nLen - 1; i >= 0; --i)
{
--pnCount[npIndex[i]];
pnSort[pnCount[npIndex[i]]] = npData[i];
}
for (int i = 0; i < nLen; ++i)        //把排序结构输入到返回的数据中。
{
npData[i] = pnSort[i];
}
free(pnSort);                        //记得释放资源。
free(pnCount);
return 1;
}

//基数排序
int RadixSort(int* nPData, int nLen)
{
//申请存放基数的空间
int* nDataRadix    = (int*)malloc(sizeof(int) * nLen);
int nRadixBase = 1;    //初始化倍数基数为1
bool nIsOk = false; //设置完成排序为false
//循环,知道排序完成
while (!nIsOk)
{
nIsOk = true;
nRadixBase *= 10;
for (int i = 0; i < nLen; ++i)
{
nDataRadix[i] = nPData[i] % nRadixBase;
nDataRadix[i] /= nRadixBase / 10;
if (nDataRadix[i] > 0)
{
nIsOk = false;
}
}
if (nIsOk)        //如果所有的基数都为0,认为排序完成,就是已经判断到最高位了。
{
break;
}
RadixCountSort(nDataRadix, 10, nPData, nLen);
}
free(nDataRadix);
return 1;
}

int main()
{
//测试基数排序。
int nData[10] = {123,5264,9513,854,9639,1985,159,3654,8521,8888};
RadixSort(nData, 10);
for (int i = 0; i < 10; ++i)
{
printf("%d ", nData[i]);
}
printf("\n");
system("pause");
return 0;
}


三、桶排序

桶排序是另外一种以O(n)或者接近O(n)的复杂度排序的算法. 它假设输入的待排序元素是等可能的落在等间隔的值区间内.一个长度为N的数组使用桶排序, 需要长度为N的辅助数组. 等间隔的区间称为桶, 每个桶内落在该区间的元素. 桶排序是基数排序的一种归纳结果。

算法的主要思想: 待排序数组A[1...n]内的元素是随机分布在[0,1)区间内的的浮点数.辅助排序数组B[0....n-1]的每一个元素都连接一个链表.将A内每个元素乘以N(数组规模)取底,并以此为索引插入(插入排序)数组B的对应位置的连表中. 最后将所有的链表依次连接起来就是排序结果.

这个过程可以简单的分步如下:

设置一个定量的数组当作空桶子。
寻访序列,并且把项目一个一个放到对应的桶子去。
对每个不是空的桶子进行排序。
从不是空的桶子里把项目再放回原来的序列中。

note: 待排序元素越均匀, 桶排序的效率越高. 均匀意味着每个桶在中间过程中容纳的元素个数都差不多,不会出现特别少或者特别多的情况, 这样在排序子程序进行桶内排序的过程中会达到最优效率.(否则效率并不理想)

note: 将元素通过恰当的映射关系将元素尽量等数量的分到各个桶(值区间)里面, 这个映射关系就是桶排序算法的关键.桶的标记(数组索引Index)的大小也要和值区间有对应关系
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐