码迷,mamicode.com
首页 > 编程语言 > 详细

线性排序算法---- 计数排序, 基数排序, 桶排序

时间:2015-08-10 17:40:48      阅读:230      评论:0      收藏:0      [点我收藏+]

标签:

技术分享

 

排序算法里,除了比较排序算法(堆排序,归并排序,快速排序),还有一类经典的排序算法-------线性时间排序算法。听名字就让人兴奋! 线性时间排序,顾名思义,算法复杂度为线性时间O(n) , 非常快,比快速排序还要快的存在,简直逆天。下面我们来仔细看看三种逆天的线性排序算法, 计数排序,基数排序和桶排序。

 

1计数排序  counting Sort
 计数排序 假设 n个 输入元素中的每一个都是在 0 到 k 区间内的一个整数,当 k= O(N) 时,排序运行的时间为 O(N).
 
计数排序的基本思想: 对每一个输入元素 x,确定小于x 的元素个数,利用这点可以直接把x 放到它在输出数组中的位置上。例如如果有17个元素小于x,则 x 应该在第 18个输出位置上。当有几个元素相同时,这一方案要略做修改,因为不能把它们放在相同的输出位置上。
 
在计数排序算法中,A[0....n-1 ] 长度为 len =n; 还需要 B[0..n-1]存放排序输出, C[0....K] 提供临时存储空间。0-K  为 数组A的范围.
给出代码:
#include <iostream>

using namespace std;


void countingSort(int A[], int B[], int len,int k)
{
   int C[k+1], i ,value,pos;
   for(i=0;i<=k;i++)
        C[i]=0;
   for(i=0; i<len;i++)
    C[A[i]]++;// C[i] now contains the number of elements equal to i;
   
    for(i =1; i<=k;i++)
         C[i] =C[i]+C[i-1];//C[i] now contains the number of elements less or equal to i;
  for(i=len-1;i>=0;i--)
  {
       B[C[A[i]]-1] = A[i];
       C[A[i]]--; // remove duplicate element
  }
    
}

int main()
{
    int A[]={0,2,5,6,3,4,7,8,1,2};
    int len = sizeof(A)/sizeof(int);
    int B[len];
    countingSort(A,B,len,8);

    for(int i =0; i< len;++i)
         cout<<B[i]<<" ";

    cout<<endl;

    return 0;



}
对于每一个A[i]值来说,C[A[i]] 就是A[j] 在输出数组中的最终正确位置,这是因为共有C[A[i]] 个元素小于或等于 A[j] . 因为所有的元素可能并不是互异的,所以,我们每将一个值A[j] 放入数组B 中以后,都要将C[A[i]] 减一,这样当遇到下一个值等于 A[i]的输入元素时,该元素就直接被放到输出数组中的A[i]的前一个位置了。
 
计数排序的一个重要性质就是它是稳定的:具有相同值得元素在输出数组中的相对次序与它们在输入数组中的相对次序相同。也就是说,对于两个相同的数来说,在输入数组中先出现的数,在输出数组中也位于前面。
 
 
 
2 .基数排序 (radix sort)

 基数排序是另外一种比较有特色的排序方式,它是怎么排序的呢?我们可以按照下面的一组数字做出说明:12、 104、 13、 7、 9

    (1)按个位数排序是12、13、104、7、9

    (2)再根据十位排序104、7、9、12、13

    (3)再根据百位排序7、9、12、13、104

    这里注意,如果在某一位的数字相同,那么排序结果要根据上一轮的数组确定,举个例子来说:07和09在十分位都是0,但是上一轮排序的时候09是排在07后面的;同样举一个例子,12和13在十分位都是1,但是由于上一轮12是排在13前面,所以在十分位排序的时候,12也要排在13前面。

    所以,一般来说,10基数排序的算法应该是这样的?

    (1)判断数据在各位的大小,排列数据;

    (2)根据1的结果,判断数据在十分位的大小,排列数据。如果数据在这个位置的余数相同,那么数据之间的顺序根据上一轮的排列顺序确定;

    (3)依次类推,继续判断数据在百分位、千分位......上面的数据重新排序,直到所有的数据在某一分位上数据都为0。
 
 
我们有时候会用基数排序来对具有多关键字域的记录进行排序。
基数排序的代码是非常直观的。在下面的代码中,我们假设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
当然,如果有负数的情况,可以先把正数和负数分开成两个数组,将负数部分全部先变成正数由大到小排序,输出的时候加上负号正序输出;正数数组就直接由小到大,然后正序输出。
 
 
给出基数排序的代码 (其中排序方法为 countingSort(计数排序) 
 
#include <iostream>
#include <algorithm>


using namespace std;

int numofDigits(int a[],int len)
{
   int largest = a[0];
//  cout<<"len "<<len<<endl;
 
   for(int i=1;i<len;++i)
  {
     if(a[i]>largest)
        largest =a[i];

  }

//     cout<<"largest "<<largest<<endl;   
    int digits =0;
    while(largest)
    {
         digits++;
         largest/=10;
    }
//   cout<<"digits "<<digits<<endl;
    return digits;
}


void radixSort(int A[], int radix , int len,int digits)
{

// cout<<"sort len "<<len<<endl;
  int* B= new int[len];
  int* C= new int[radix];  // radix 为10
  int divide =1; // 每次把数字缩小10倍
 
for(int i =0; i<digits ;++i)
{
   // 复制A 数组
     for(int j =0;j<len;++j)
          B[j] =A[j];
     for(int j =0; j<radix;++j)
         C[j] =0;  //初始化    

     for(int index =0; index<len;++index)
     {
          int tmpValue = (B[index]/divide)%radix ;
          C[tmpValue]++;
     }

    for(int  j =1; j<radix;j++)
         C[j] =C[j]+C[j-1];

    for(int j =len-1 ;j>=0;j--)
    {
         int tmpValue = (B[j]/divide)%radix ;
         A[C[tmpValue]-1] = B[j] ;
         C[tmpValue]--;
    }
   
    divide = divide *radix ;
     for(int k =0;k<len;k++)
        cout<<A[k]<<" ";
       cout<<endl;
}
    delete[] C;
    delete[] B;         
               
}

int main()
{
int a[]= {3,4,3,2,4,556,7874,3232,435,12,345,3235,78};
int len = sizeof(a)/sizeof(a[0]);
int digits = numofDigits(a,len);
radixSort(a,10,len,digits);
  
  cout<<"Final sort list is"<<endl;
for(int i = 0; i<len;i++)
     cout<<a[i]<<" ";
cout<<endl;
return 0;
}

基数排序结果如图:

技术分享

 
 
3 .桶排序 (bucketSort)
桶排序 假设 输入数据服从均匀分布,平均情况下它的时间代价为 O(n) 
计数排序 假设 数据都属于一区间内的整数。 而桶排序 则假设 输入是由一个随机过程产生,该过程将元素均匀,独立地分布在[0,1) 区间上。
 
桶排序将[0,1) 区间划分为n 个相同大小的子区间,或称为桶。然后将n个输入数分别放到各个桶中。因为输入数据是均匀,独立地分布在[0,1)区间上,所以一般不会出现多数落在同一个桶中的情况。为了得到输出结果,我们先对每个桶中的数进行排序,然后遍历每个桶,按照次序把各个桶中的元素列出来即可。
 

补充说明三点

1,桶排序是稳定的

2,桶排序是常见排序里最快的一种,比快排还要快…大多数情况下

3,桶排序非常快,但是同时也非常耗空间,基本上是最耗空间的一种排序算法

 
先给出一个简单的利用桶排序的例子(全是整数)。我感觉跟和计数排序比较类似。
 

无序数组有个要求,就是成员隶属于固定(有限的)的区间,如范围为[0-9](考试分数为1-100等)

例如待排数字[6 2 4 1 5 9]

准备10个空桶,最大数个空桶

[6 2 4 1 5 9]           待排数组

[0 0 0 0 0 0 0 0 0 0]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

1,顺序从待排数组中取出数字,首先6被取出,然后把6入6号桶,这个过程类似这样:空桶[ 待排数组[ 0 ] ] = 待排数组[ 0 ]

[6 2 4 1 5 9]           待排数组

[0 0 0 0 0 0 6 0 0 0]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

2,顺序从待排数组中取出下一个数字,此时2被取出,将其放入2号桶,是几就放几号桶

[6 2 4 1 5 9]           待排数组

[0 0 2 0 0 0 6 0 0 0]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

3,4,5,6省略,过程一样,全部入桶后变成下边这样

[6 2 4 1 5 9]           待排数组

[0 1 2 0 4 5 6 0 0 9]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

0表示空桶,跳过,顺序取出即可:1 2 4 5 6 9
 
技术分享
 
#include <iostream>

using namespace std;

void bucketSort(int A[], int len,int max)
{
   int count[max]={0};// max-1 为 数组中最大的值

   for(int i=0;i<len;++i)
      count[A[i]]++;
 
  

  for(int i=0,index=0;i<max;++i) // i 为 桶的标号,index 为数组A的下标
    for(int j =0; j<count[i];++j)// j 为 一个桶中相同的数的序号
         A[index++] = i ;

}

int main()
{
  int a[] = {6,2,4,1,5,9};
  int len = sizeof(a)/sizeof(a[0]);
//cout<<"len "<<len<<endl;
  bucketSort(a,len,10);

  for(int i =0; i<len;++i)
       cout<<a[i]<<" ";
  cout<<endl;
   return 0;
}   
 
简单桶排序参考链接:http://www.cnblogs.com/kkun/archive/2011/11/23/2260267.html
 
接下来看看更普遍的一种用法
 
  例如要对大小为[1..1000]范围内的n个整数A[1..n]排序,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储(10..20]的整数,……集合B[i]存储((i-1)*10, i*10]的整数,i = 1,2,..100。总共有100个桶。然后对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 然后再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任何排序法都可以。最后依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这样就得到所有数字排好序的一个序列了。

        下图表示出了桶排序作用于有10个数的输入数组上的操作过程。
技术分享
 
利用的数据结构为数组和链表.
 
// practice1.cpp : 
//

//#include "stdafx.h"

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

//
void bucketSort(double* a,int n)
{

    typedef struct Node{
        double key;
        struct Node * next; 
    }Node;

    typedef struct{
         Node * next;
    }Head;
    int i,j;
    Head head[10]={NULL}; // 十个空桶 , 0-0.09 为 0号桶, 0.1-0.19 为1 号桶,依次类推
    Node * p;
    Node * q;
    Node * node;
    for(i=0;i<=n;i++){   // 将数组a中的所有元素一次分入桶中
        node=(Node*)malloc(sizeof(Node));
        node->key=a[i];
        node->next=NULL;
        p =head[(int)(a[i]*10)].next;     
        q =head[(int)(a[i]*10)].next;
        if(p == NULL){                        // 如果桶中没有元素,则第一个入桶的元素为桶中的头节点
            head[(int)(a[i]*10)].next=node;
            continue;
        }
        else                               //如果桶中本身就有元素,则需要将该元素插入链表适当的节点上。使得桶类链表有序
        {
            if(node->key < p->key)        // 如果桶中新入的元素 比头节点值还小,则新元素为头节点。
            {
                head[(int)(a[i]*10)].next=node;
                node->next=p;
                continue;
            }                            // 将新元素按照大小顺序插入链表。
            while(p){
            if(node->key < p->key)
                break;
            q=p;
            p=p->next;
        }
        if(p == NULL){
            q->next=node;
        }else{
            node->next=p;
            q->next=node;
        }
    }    
    }
    j=0;
    for(i=0;i<10;i++){
        p=head[i].next;
        while(p){
            a[j++]=p->key;
            p=p->next;
        }
    }
}

int main()
{
    int i;
    double a[13]={0.14,0.13,0.25,0.23,0.29,0.81,0.52,0.51,0.83,0.50,0.69,0.12,0.16};
    bucketSort(a,12);
    for(i=0;i<=12;i++)
        printf("%-6.2f",a[i]);
    printf("\n");
    //system("pause");
    return 0;
}
 博客中的程序,均在 ubuntu 下,gcc 4.9.2 下编译,运行通过。可以直接运行。
 

线性排序算法---- 计数排序, 基数排序, 桶排序

标签:

原文地址:http://www.cnblogs.com/deanlan/p/4718302.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!