C++线性时间的排序算法分析_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。

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

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索c++
, 算法
, 排序
, 时间
线性
线性排序算法、线性时间排序算法、c语言排序算法、c语言冒泡排序算法、c语言快速排序算法,以便于您获取更多的相关知识。

时间: 2024-09-22 03:13:35

C++线性时间的排序算法分析_C 语言的相关文章

c++中八大排序算法_C 语言

概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短:  1.插入排序-直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入

C++实现位图排序实例_C 语言

在<编程珠玑>一书里提到了一种算法导论里没有提到过的位图排序方法,这种排序方法是通过牺牲空间效率来追求时间效率(线性时间)以达到时间-空间折中与双赢的目的.本文以实例形式简单讲一下位图排序思想. 一.问题描述      1.输入:一个至多包含1千万个非负整数的文件      2.特征:①每个数都是小于10000000的非负整数:②没有重复的数字:③数据之间不存在关联关系.      3.约束:①最多1MB的内存空间可用:②磁盘空间充足:③运行时间最多几分钟,最好是线性时间.          

详细总结C++的排序算法_C 语言

排序算法经过了很长时间的演变,产生了很多种不同的方法.对于初学者来说,对它们进行整理便于理解记忆显得很重要.每种算法都有它特定的使用场合,很难通用.因此,我们很有必要对所有常见的排序算法进行归纳. 我不喜欢死记硬背,我更偏向于弄清来龙去脉,理解性地记忆.比如下面这张时间复杂度图,我们将围绕这张图来分析. 上面的这张图来自一个PPT.它概括了数据结构中的所有常见的排序算法,给大家总结如下. 区分稳定与不稳定:快速.希尔.堆.选择不稳定,其他排序算法均稳定. 平均时间复杂度:冒泡,选择,插入是O(n

简单了解C语言中直接插入排序与直接选择排序实现_C 语言

直接插入排序基本思路: 1. 从a[0]开始,也就是从1个元素开始是有序的,a[1]~a[n-1]是无序的. 2. 从a[1]开始并入前面有序的数组,直到n-1. #include <stdio.h> #define N 5 void insertsort(int a[], int n); void swap(int *x, int *y); void insertsort(int a[], int n){ int i,j; for(i=1; i<n; i++){ for(j=i; j&

C++获取当前系统时间的方法总结_C 语言

本文实例讲述了C++获取当前系统时间的方法.分享给大家供大家参考.具体如下: 方案- 优点:仅使用C标准库:缺点:只能精确到秒级 #include <time.h> #include <stdio.h> int main( void ) { time_t t = time(0); char tmp[64]; strftime(tmp,sizeof(tmp),"%Y/%m/%d %X %A 本年第%j天 %z",localtime(&t)); puts(

C语言求解最长公共子字符串问题及相关的算法分析_C 语言

题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串.注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中.请编写一个函数,输入两个字符串,求它们的最长公共子序列,并打印出最长公共子序列. 例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子序列,则输出它们的长度4,并打印任意一个子序列.分析:求最长公共子序列(Longest Common Subsequence, LCS)是一道非常经典的动

算法学习入门之使用C语言实现各大基本的排序算法_C 语言

首先来看一下排序算法的一些相关概念:1.稳定排序和非稳定排序 简单地说就是所有相等的数经过某种排序方法后,仍能保持它们在排序之前的相对次序,我们就说这种排序方法是稳定的.反之,就是非稳定的. 比如:一组数排序前是a1,a2,a3,a4,a5,其中a2=a4,经过某种排序后为a1,a2,a4,a3,a5,则我们说这种排序是稳定的,因为a2排序前在a4的前面,排序后它还是在a4的前面.假如变成a1,a4,a2,a3,a5就不是稳定的了. 2.内排序和外排序 在排序过程中,所有需要排序的数都在内存,并

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

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

C++实现当前时间动态显示的方法_C 语言

本文实例讲述了C++实现当前时间动态显示的方法.分享给大家供大家参考.具体如下: /* 24-06-10 10:44 动态显示时间 但不是最优的 功能很单一 本程序关键是对时钟函数的使用 **tm结构定义了 年.月.日.时.分.秒.星期.当年中的某一天.夏令时 **用localtime获取当前系统时间,该函数将一个time_t时间转换成tm结构表示的时间,函数原型: struct tm * localtime(const time_t *) **使用gmtime函数获取格林尼治时间,函数原型: