计数排序_COUNTINGSORT

30 篇文章 1 订阅
30 篇文章 6 订阅

计数排序_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!h2hlog(n!)=Ω(nlogn)

所以任何一种基于决策树的排序算法最坏情况(深度最深)运行时间的下界都为 Ω(nlogn)

计数排序_COUNTINGSORT分析

上面我们讲过,任何基于决策树的排序算法最坏情况下的运行时间为 Ω(nlogn) ,决策树的核心操作就是比较数组中的两个元素。那有没有一种方法在不比较两个任何两个元素的情况下对数组进行排序呢?

假设现在输入一个规模为 n 的输入A={a1,a2,...,an},数组中的每一个元素都满足 0aik(1in) , 其中 k=O(n) 。相信你也看到了,上面的数组有一个限制,就是数组里面的每一个元素都在一个同一个区间范围之内.说明数组的里面的元素 am 大小是有限的,于是我们创建一个数组 countArray[1...k] 来来保存数组 A 里面每一个元素出现的次数countArray[am]表示元素 am 出现的次数,新建一个数组 outArray[1...n] 来保存排序结果。

下面是C++代码实现:

/*************************************************
* @Filename:    countingSort.cc
* @Author:      qeesung
* @Email:       qeesung@qq.com
* @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是从0arrySize,还是从arraySize0,如果我们从arraySize0,那么排序算法是稳定的,如果从0arrySize,那么排序算法是不稳定的。这里一定要注意。在这个循环里面做了两件事:
- 将inArray[i]元素放入到outArray合适的位置,我们知道比inArray[i]小的元素个数为countArray[inArray[i]-minEle],说明我们的元素应该放在outArraycountArray[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)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值