C++线性时间的排序算法分析

前面的文章已经介绍了几种排序算法,如插入排序(直接插入排序,折半插入排序,希尔排序)、交换排序(冒泡排序,快速排序)、选择排序(简单选择排序,堆排序)、2-路归并排序(可以参考前一篇文章:各种内部排序算法的实现)等,这些排序算法都有一个共同的特点,就是基于比较。

本文将介绍三种非比较的排序算法:计数排序,基数排序,桶排序。它们将突破比较排序的Ω(nlgn)下界,以线性时间运行。

一、比较排序算法的时间下界

所谓的比较排序是指通过比较来决定元素间的相对次序。

“定理:对于含n个元素的一个输入序列,任何比较排序算法在最坏情况下,都需要做Ω(nlgn)次比较。”
也就是说,比较排序算法的运行速度不会快于nlgn,这就是基于比较的排序算法的时间下界。

通过决策树(Decision-Tree)可以证明这个定理,关于决策树的定义以及证明过程在这里就不赘述了。读者可以自己去查找资料,这里推荐大家看一看麻省理工学院公开课:算法导论的《MIT公开课:线性时间排序》

根据上面的定理,我们知道任何比较排序算法的运行时间不会快于nlgn。那么我们是否可以突破这个限制呢?当然可以,接下来我们将介绍三种线性时间的排序算法,它们都不是通过比较来排序的,因此,下界Ω(nlgn)对它们不适用。

二、计数排序(Counting Sort)

计数排序的基本思想就是对每一个输入元素x,确定小于x的元素的个数,这样就可以把x直接放在它在最终输出数组的位置上,例如:

算法的步骤大致如下:

①.找出待排序的数组中最大和最小的元素

②.统计数组中每个值为i的元素出现的次数,存入数组C的第i项

③.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)

④.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

C++代码如下:

/*************************************************************************
  > File Name: CountingSort.cpp
  > Author: SongLee
 ************************************************************************/
#include<iostream>
using namespace std; 

/*
 *计数排序:A和B为待排和目标数组,k为数组中最大值,len为数组长度
 */
void CountingSort(int A[], int B[], int k, int len)
{
  int C[k+1];
  for(int i=0; i<k+1; ++i)
    C[i] = 0;
  for(int i=0; i<len; ++i)
    C[A[i]] += 1;
  for(int i=1; i<k+1; ++i)
    C[i] = C[i] + C[i-1];
  for(int i=len-1; i>=0; --i)
  {
    B[C[A[i]]-1] = A[i];
    C[A[i]] -= 1;
  }
} 

/* 输出数组 */
void print(int arr[], int len)
{
  for(int i=0; i<len; ++i)
    cout << arr[i] << " ";
  cout << endl;
} 

/* 测试 */
int main()
{
  int origin[8] = {4,5,3,0,2,1,15,6};
  int result[8];
  print(origin, 8);
  CountingSort(origin, result, 15, 8);
  print(result, 8);
  return 0;
}

当输入的元素是0到k之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k)。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。计数排序是一个稳定的排序算法。

可能你会发现,计数排序似乎饶了点弯子,比如当我们刚刚统计出C,C[i]可以表示A中值为i的元素的个数,此时我们直接顺序地扫描C,就可以求出排序后的结果。的确是这样,不过这种方法不再是计数排序,而是桶排序,确切地说,是桶排序的一种特殊情况。

三、桶排序(Bucket Sort)

桶排序(Bucket Sort)的思想是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法)。当要被排序的数组内的数值是均匀分配的时候,桶排序可以以线性时间运行。桶排序过程动画演示:Bucket Sort,桶排序原理图如下:

C++代码如下:

/*************************************************************************
  > File Name: BucketSort.cpp
  > Author: SongLee
 ************************************************************************/
#include<iostream>
using namespace std; 

/* 节点 */
struct node
{
  int value;
  node* next;
}; 

/* 桶排序 */
void BucketSort(int A[], int max, int len)
{
  node bucket[len];
  int count=0;
  for(int i=0; i<len; ++i)
  {
    bucket[i].value = 0;
    bucket[i].next = NULL;
  } 

  for(int i=0; i<len; ++i)
  {
    node *ist = new node();
    ist->value = A[i];
    ist->next = NULL;
    int idx = A[i]*len/(max+1); // 计算索引
    if(bucket[idx].next == NULL)
    {
      bucket[idx].next = ist;
    }
    else /* 按大小顺序插入链表相应位置 */
    {
      node *p = &bucket[idx];
      node *q = p->next;
      while(q!=NULL && q->value <= A[i])
      {
        p = q;
        q = p->next;
      }
      ist->next = q;
      p->next = ist;
    }
  } 

  for(int i=0; i<len; ++i)
  {
    node *p = bucket[i].next;
    if(p == NULL)
      continue;
    while(p!= NULL)
    {
      A[count++] = p->value;
      p = p->next;
    }
  }
} 

/* 输出数组 */
void print(int A[], int len)
{
  for(int i=0; i<len; ++i)
    cout << A[i] << " ";
  cout << endl;
} 

/* 测试 */
int main()
{
  int row[11] = {24,37,44,12,89,93,77,61,58,3,100};
  print(row, 11);
  BucketSort(row, 235, 11);
  print(row, 11);
  return 0;
}

四、基数排序(Radix Sort)

基数排序(Radix Sort)是一种非比较型排序算法,它将整数按位数切割成不同的数字,然后按每个位分别进行排序。基数排序的方式可以采用MSD(Most significant digital)或LSD(Least significant digital),MSD是从最高有效位开始排序,而LSD是从最低有效位开始排序。

当然我们可以采用MSD方式排序,按最高有效位进行排序,将最高有效位相同的放到一堆,然后再按下一个有效位对每个堆中的数递归地排序,最后再将结果合并起来。但是,这样会产生很多中间堆。所以,通常基数排序采用的是LSD方式。

LSD基数排序实现的基本思路是将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。需要注意的是,对每一个数位进行排序的算法必须是稳定的,否则就会取消前一次排序的结果。通常我们使用计数排序或者桶排序作为基数排序的辅助算法。基数排序过程动画演示:Radix Sort

C++实现(使用计数排序)如下:

/*************************************************************************
  > File Name: RadixSort.cpp
  > Author: SongLee
 ************************************************************************/
#include<iostream>
using namespace std; 

// 找出整数num第n位的数字
int findIt(int num, int n)
{
  int power = 1;
  for (int i = 0; i < n; i++)
  {
    power *= 10;
  }
  return (num % power) * 10 / power;
} 

// 基数排序(使用计数排序作为辅助)
void RadixSort(int A[], int len, int k)
{
  for(int i=1; i<=k; ++i)
  {
    int C[10] = {0};  // 计数数组
    int B[len];    // 结果数组 

    for(int j=0; j<len; ++j)
    {
      int d = findIt(A[j], i);
      C[d] += 1;
    } 

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

    for(int j=len-1; j>=0; --j)
    {
      int d = findIt(A[j], i);
      C[d] -= 1;
      B[C[d]] = A[j];
    } 

    // 将B中排好序的拷贝到A中
    for(int j=0; j<len; ++j)
      A[j] = B[j];
  }
} 

// 输出数组
void print(int A[], int len)
{
  for(int i=0; i<len; ++i)
    cout << A[i] << " ";
  cout << endl;
} 

// 测试
int main()
{
  int A[8] = {332, 653, 632, 5, 755, 433, 722, 48};
  print(A, 8);
  RadixSort(A, 8, 3);
  print(A, 8);
  return 0;
}

基数排序的时间复杂度是 O(k·n),其中n是排序元素个数,k是数字位数。注意这不是说这个时间复杂度一定优于O(nlgn),因为n可能具有比较大的系数k。

另外,基数排序不仅可以对整数排序,也可以对有多个关键字域的记录进行排序。例如,根据三个关键字年、月、日来对日期进行排序。

(0)

相关推荐

  • 使用C++实现全排列算法的方法详解

    复制代码 代码如下: <P>不论是哪种全排列生成算法,都遵循着"原排列"→"原中介数"→"新中介数"→"新排列"的过程.</P><P>其中中介数依据算法的不同会的到递增进位制数和递减进位制数.</P><P>关于排列和中介数的一一对应性的证明我们不做讨论,这里仅仅给出了排列和中介数的详细映射方法.</P> · 递增进位制和递减进位制数  所谓递增进位制和递减

  • 利用C++的基本算法实现十个数排序

    冒泡排序法原理:它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成. 冒泡排序算法的运作如下:1.比较相邻的元素.如果第一个比第二个大,就交换他们两个. 2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数. 3.针对所有的元素重复以上的步骤,除了最后一个. 4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较. 示例代码:

  • C++实现各种排序算法类汇总

    C++可实现各种排序算法类,比如直接插入排序.折半插入排序.Shell排序.归并排序.简单选择排序.基数排序.对data数组中的元素进行希尔排序.冒泡排序.递归实现.堆排序.用数组实现的基数排序等. 具体代码如下: #ifndef SORT_H #define SORT_H #include <iostream> #include <queue> using namespace std; // 1.直接插入排序 template<class ElemType> void

  • C++归并排序算法实例

    归并排序 归并排序算法是采用分治法的一个非常典型的应用.归并排序的思想是将一个数组中的数都分成单个的:对于单独的一个数,它肯定是有序的,然后,我们将这些有序的单个数在合并起来,组成一个有序的数列.这就是归并排序的思想.它的时间复杂度为O(N*logN). 代码实现 复制代码 代码如下: #include <iostream> using namespace std;   //将有二个有序数列a[first...mid]和a[mid...last]合并. void mergearray(int

  • C++冒泡排序算法实例

    冒泡排序 大学学习数据结构与算法最开始的时候,就讲了冒泡排序:可见这个排序算法是多么的经典.冒泡排序是一种非常简单的排序算法,它重复地走访过要排序的数列,每一次比较两个数,按照升序或降序的规则,对比较的两个数进行交换.比如现在我要对以下数据进行排序: 10 3 8 0 6 9 2 当使用冒泡排序进行升序排序时,排序的步骤是这样的: 3 10 8 0 6 9 2  // 10和3进行对比,10>3,交换位置 3 8 10 0 6 9 2  // 10再和8进行对比,10>8,交换位置 3 8 0

  • C++实现八个常用的排序算法:插入排序、冒泡排序、选择排序、希尔排序等

    本文实现了八个常用的排序算法:插入排序.冒泡排序.选择排序.希尔排序 .快速排序.归并排序.堆排序和LST基数排序 首先是算法实现文件Sort.h,代码如下: /* * 实现了八个常用的排序算法:插入排序.冒泡排序.选择排序.希尔排序 * 以及快速排序.归并排序.堆排序和LST基数排序 * @author gkh178 */ #include <iostream> template<class T> void swap_value(T &a, T &b) { T t

  • C++插入排序算法实例

    插入排序 没事喜欢看看数据结构和算法,增加自己对数据结构和算法的认识,同时也增加自己的编程基本功.插入排序是排序中比较常见的一种,理解起来非常简单.现在比如有以下数据需要进行排序: 10 3 8 0 6 9 2 当使用插入排序进行升序排序时,排序的步骤是这样的: 10 3 8 0 6 9 2 // 取元素3,去和10进行对比 3 10 8 0 6 9 2 // 由于10比3大,将10向后移动,将3放置在原来10的位置:再取8与前一个元素10进行对比 3 8 10 0 6 9 2 // 同理移动1

  • 基于C++实现的各种内部排序算法汇总

    提起排序算法相信大家都不陌生,或许很多人已经把它们记得滚瓜烂熟,甚至随时可以写出来.是的,这些都是最基本的算法.这里就把各种内部排序算法总结归纳了一下,包括插入排序(直接插入排序,折半插入排序,希尔排序).交换排序(冒泡排序,快速排序).选择排序(简单选择排序,堆排序).2-路归并排序.(另:至于堆排序算法,前面已经有一篇文章针对堆排序的算法实现做了详细的描述) C++实现代码如下: /*******************************************************

  • C++堆排序算法的实现方法

    本文实例讲述了C++实现堆排序算法的方法,相信对于大家学习数据结构与算法会起到一定的帮助作用.具体内容如下: 首先,由于堆排序算法说起来比较长,所以在这里单独讲一下.堆排序是一种树形选择排序方法,它的特点是:在排序过程中,将L[n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素. 一.堆的定义 堆的定义如下:n个关键字序列L[n]成为堆,当且仅当该序列满足: ①L(i) <= L(2i)且L(i) <= L(2

  • C++选择排序算法实例

    选择排序 选择排序是一种简单直观的排序算法,它的工作原理如下.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾.以此类推,直到所有元素均排序完毕. 选择排序的主要优点与数据移动有关.如果某个元素位于正确的最终位置上,则它不会被移动.选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换.在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常

  • C++简单实现的全排列算法示例

    本文实例讲述了C++简单实现的全排列算法.分享给大家供大家参考,具体如下: #include "stdafx.h" #include <string> #include <algorithm> #include <iostream> void func(const char *str_in) { std::string str(str_in); std::sort(str.begin(),str.end()); do { std::cout<&

  • 全排列算法的非递归实现与递归实现的方法(C++)

    (一)非递归全排列算法基本思想是:    1.找到所有排列中最小的一个排列P.    2.找到刚刚好比P大比其它都小的排列Q,    3.循环执行第二步,直到找到一个最大的排列,算法结束.下面用数学的方法描述:给定已知序列 P =  A1A2A3An ( Ai!=Aj , (1<=i<=n  , 1<=j<=n, i != j  ) )找到P的一个最小排列Pmin = P1P2P3Pn  有  Pi > P(i-1) (1 < i <= n)从Pmin开始,总是目

随机推荐