- 计数排序_COUNTINGSORT
- 计数排序_COUNTINGSORT分析
- 代码分析
计数排序_COUNTINGSORT
我们之前讲过四种基本的排序方法:
《快速排序_QUICKSORT》:http://blog.csdn.net/ii1245712564/article/details/45749061
《堆排序_HEAPOSRT》http://blog.csdn.net/ii1245712564/article/details/45534625
《归并排序_MERGESORT》:http://blog.csdn.net/ii1245712564/article/details/42173549
《插入排序_INSERTSORT》:http://blog.csdn.net/ii1245712564/article/details/42079225
上面的四种排序方法都有一个共同点,就是他们的排序都是基于决策树
的。也就是说,他们排序的一句就是通过在元素与元素之间的比较进行的。这种排序的最坏情况下运行时间下界至少为Ω(nlogn)。上面的排序算法中,堆排序
和归并排序
的最坏情况y运行时间为Ω(nlogn),而快速排序
和插入排序
的最坏运行时间为O(n2)
引理:任何排序算法都需要做Ω(nlogn)次比较
证明:因为决策树是一棵
完全二叉树
,即每一个非叶子节点都有两个孩子节点。我们在这里假设我们输入n个元素的数组,决策树的深度为h,于是我们可以得到下面的不等式n!h≤2h于是两边同时取对数≥log(n!)=Ω(nlogn)
所以任何一种基于决策树的排序算法最坏情况(深度最深)运行时间的下界都为Ω(nlogn)
计数排序_COUNTINGSORT分析
上面我们讲过,任何基于决策树的排序算法最坏情况下的运行时间为Ω(nlogn),决策树的核心操作就是比较数组中的两个元素。那有没有一种方法在不比较两个任何两个元素的情况下对数组进行排序呢?
假设现在输入一个规模为n的输入A={a1,a2,...,an},数组中的每一个元素都满足0≤ai≤k(1≤i≤n), 其中k=O(n)。相信你也看到了,上面的数组有一个限制,就是数组里面的每一个元素都在一个同一个区间范围之内.说明数组的里面的元素am大小是有限的,于是我们创建一个数组countArray[1...k]来来保存数组A里面每一个元素出现的次数countArray[am]表示元素am出现的次数,新建一个数组outArray[1...n]来保存排序结果。
下面是C++代码实现:
/*************************************************
* @Filename: countingSort.cc
* @Author: qeesung
* @Email: [email protected]
* @DateTime: 2015-05-16 13:26:21
* @Version: 1.0
* @Description: 计数排序
**************************************************/
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
/**
* 计数排序
* @param inArray 输入的数组
* @param outArray 输出的数组
* @param minEle 数组里面的最小元素
* @param maxEle 数组里面的最大元素
* @param arraySize 数组元素个数
*/
void countingSort(int inArray[] ,int outArray[], int minEle ,
int maxEle , int arraySize )
{
if(inArray == NULL || outArray == NULL || maxEle < minEle || arraySize < 0)
return;
// 新建一个缓存数组来保存比一个inArray[i]元素大的个数
const int & countArraySize = maxEle - minEle+1;
int countArray[countArraySize];
// 初始化count数组
for (int i = 0; i < countArraySize; ++i)
{
countArray[i] = 0;
}
// 遍历一遍inArray,统计各个元素的数量
for (int i = 0; i < arraySize; ++i)
{
countArray[inArray[i]-minEle]++;
}
// 现在使得计算比inArray[i]小的元素的个数
for (int i = 1; i < countArraySize; ++i)
{
countArray[i] = countArray[i] + countArray[i-1];
}
// 再次遍历一遍inArray,将元素放入outArray合适的位置
for (int i = arraySize-1; i >= 0; --i)
{
outArray[countArray[inArray[i]-minEle]-1] = inArray[i];
countArray[inArray[i]-minEle]--;
}
}
void printArray(int array[] , int arraySize)
{
if(array == NULL)
return;
for (int i = 0; i < arraySize; ++i)
{
cout<<array[i]<<"\t";
}
cout<<endl;
}
int main(int argc, char const *argv[])
{
const int & arraySize = 10;
int inArray[arraySize];
srand((int)(time(NULL)));
for (int i = 0; i < arraySize; ++i)
{
inArray[i] = rand()%9;
}
cout<<"before sort array :"<<endl;
printArray(inArray , arraySize);
int outArray[arraySize];
countingSort(inArray , outArray , 0 ,9 , arraySize);
cout<<"after sort array:"<<endl;
printArray(outArray , arraySize);
while(1);
return 0;
}
运行结果:
before sort array :
4 7 4 5 2 4 6 0 0 2
after sort array :
0 0 2 2 4 4 4 5 6 7
代码分析
代码分析:
countingSort
函数里面有四个for循环for循环1
// 初始化count数组 for (int i = 0; i < countArraySize; ++i) { countArray[i] = 0; }
这里的运行时间为O(countArraySize),也就是输入区间的宽度
for循环2
// 遍历一遍inArray,统计各个元素的数量 for (int i = 0; i < arraySize; ++i) { countArray[inArray[i]-minEle]++; }
这里遍历一边输入数组,统计输入数组里面的每一个元素的个数,运行时间为O(n)
for循环3
// 现在使得计算比inArray[i]小的元素的个数 for (int i = 1; i < countArraySize; ++i) { countArray[i] = countArray[i] + countArray[i-1]; }
这个循环做了一个非常精妙的事情,上一个循环里面我们统计出每一个元素的个数,这个循环我们统计数组里面比某个元素小的元素的个数,所以运行时间为O(countArraySize)
for循环4
// 再次遍历一遍inArray,将元素放入outArray合适的位置 for (int i = arraySize-1; i >= 0; --i) { outArray[countArray[inArray[i]-minEle]-1] = inArray[i]; countArray[inArray[i]-minEle]--; }
这个循环里面有一个需要注意的地方,那就是循环变量
i
是从0
到arrySize
,还是从arraySize
到0
,如果我们从arraySize
到0
,那么排序算法是稳定的,如果从0
到arrySize
,那么排序算法是不稳定的。这里一定要注意。在这个循环里面做了两件事:- 将
inArray[i]
元素放入到outArray
合适的位置,我们知道比inArray[i]
小的元素个数为countArray[inArray[i]-minEle]
,说明我们的元素应该放在outArray
的countArray[inArray[i]-minEle]-1
(数组应该从0开始,所以需要减去1)上.因为countArray[inArray[i]-minEle]-1
位置之间的那些位置需要放比inArray[i]
小的那些元素啊- 将
countArray[inArray[i]-minEle]--
,这样是为了避免存在相同的元素,因为我们再统计比自己小的元素的个数的时候是这样做的countArray[i] = countArray[i] + countArray[i-1]
,我们加上自己的countArray[i]
的大小,说明在区间[ countArray[i-1] , countArray[i] ]
之间存放的是所有相同的元素inArray[i]
这个循环的运行时间为O(n)
通过上面的分析,我们知道计数排序的运行时间为:
O(countArraySize)+O(n)+O(countArraySize)+O(n)=O(countArraySize+n)
因为我们之前设定为k=O(n),这个设定是必须的,如果我们两个元素,a1=0,a2=10000,如果再按照上面的做法来做,那就得不偿失了。
所以计数排序的运行时间为O(n)