到目前为止,关于排序的问题,前面已经介绍了很多,从插入排序、合并排序、堆排序以及快速排序,每一种都有其适用的情况,在时间和空间复杂度上各有优势。它们都有一个相同的特点,以上所有排序的结果序列,各个元素的次序都是基于输入元素之间的比较,因此,把这类排序成为比较排序。

对一个含有n个元素的输入序列,任何比较排序在最坏情况下都要用(nlogn)次比较来进行排序,由此也可以知道合并排序和堆排序是渐进最优的。

本章介绍了三种线性时间排序算法,计数排序、基数排序和桶排序,这些算法都是用非比较的操作来确定排序顺序。

下面将详细介绍这三种排序算法的实现。

GitHub 算法导论 第八章程序代码

计数排序

计数排序是基于对输入数据作某种假设条件下进行的排序算法。其作出,输入是由一个小范围内整数构成,即n个输入元素中的每一个都是介于0~k之间的整数。当k = O(n)时,计数排序的运行时间为O(n)。

其基本思想是,对每一个输入元素x,确定出小于x的元素个数,即要得出这个元素x是第几个位置,有了这样的信息,就可以把x直接放在最终的输出数组当中。

下面给出计数排序的算法实现,输入数据为data[] , 输出结果存在result[]中,输入数据个数为N = 10 , 每个元素都是位于 0~101 之间的整数,MAX = 101;

  1. #include <iostream>
  2. #include <ctime>
  3. #include <cstdlib>
  4. #define N 10
  5. #define MAX 101
  6. using namespace std;
  7. //计数排序函数声明
  8. void CountingSort(int *data, int *result, int k);
  9. int main()
  10. {
  11. //声明一个待排序数组
  12. int array[N];
  13. //声明排序后数组
  14. int result[N];
  15. //设置随机化种子,避免每次产生相同的随机数
  16. srand(time(0));
  17. for (int i = 0; i<N; i++)
  18. {
  19. array[i] = rand() % MAX;//数组赋值使用随机函数产生1-100之间的随机数
  20. }
  21. cout << "排序前:" << endl;
  22. for (int j = 0; j<N; j++)
  23. {
  24. cout << array[j] << " ";
  25. }
  26. cout << endl << "排序后:" << endl;
  27. //调用快速排序函数对该数组进行排序
  28. CountingSort(array, result , MAX);
  29. for (int k = 0; k<N; k++)
  30. {
  31. cout << result[k] << " ";
  32. }
  33. cout << endl;
  34. system("pause");
  35. return 0;
  36. }//main
  37. //计数排序算法实现
  38. void CountingSort(int *data, int *result, int k)
  39. {
  40. int C[MAX] = { 0 };
  41. //(1) 费时 O(k)
  42. for (int i = 0; i < k; i++)
  43. C[i] = 0;
  44. //(2) 费时O(n)
  45. for (int j = 0; j < N; j++)
  46. C[data[j]] = C[data[j]] + 1;
  47. //(3) 费时O(k)
  48. for (int i = 1; i < k; i++)
  49. {
  50. C[i] = C[i] + C[i - 1];
  51. }
  52. //测试当前源数据的目标位置
  53. /*for (int k = 0; k < N; k++)
  54. {
  55. cout << C[data[k]] << "\t";
  56. }*/
  57. //得到排序后的目标序列
  58. //(4)费时 O(n)
  59. for (int j = 0; j < N ; j++ )
  60. {
  61. //保证数据下标不会越界 需-1
  62. result[C[data[j]]-1] = data[j];
  63. C[data[j]] -= 1;
  64. }
  65. }

计数排序是一种稳定的排序算法,所谓稳定性,即是指具有相同值的元素在输出数组中的相对次序与它们在输入数组中的次序相同。

对于计数排序的性能,它由于前面介绍的比较排序时间下界(nlogn),从以上代码可以看出,计数排序算法步骤(1)~(4)所需的时间复杂度为O(n+k) , 当k=O(n)时,运行时间则为O(n)是一个线性时间排序算法。

基数排序

基数排序是一种按位排序算法,对输入待排序序列,求出其最大位数,从低有效位到最高有效位,分别对改组数据进行排序。

代码实现如下:

  1. #include <iostream>
  2. #include <ctime>
  3. #include <cstdlib>
  4. #define N 10
  5. #define MAX 1000
  6. using namespace std;
  7. //基数排序函数声明
  8. void RadixSort(int *data, int n);
  9. //计算待排数组中最长位数
  10. int ComputeDigits(int *data , int n);
  11. //按照d位数字对数组排序算法
  12. void digitSort(int *data, int n, int d);
  13. int main()
  14. {
  15. //声明一个待排序数组
  16. int array[N];
  17. //设置随机化种子,避免每次产生相同的随机数
  18. srand(time(0));
  19. for (int i = 0; i<N; i++)
  20. {
  21. array[i] = rand() % MAX;//数组赋值使用随机函数产生1-100之间的随机数
  22. }
  23. cout << "排序前:" << endl;
  24. for (int j = 0; j<N; j++)
  25. {
  26. cout << array[j] << " ";
  27. }
  28. cout << endl << "排序后:" << endl;
  29. //调用快速排序函数对该数组进行排序
  30. RadixSort(array, N);
  31. for (int k = 0; k<N; k++)
  32. {
  33. cout << array[k] << " ";
  34. }
  35. cout << endl;
  36. system("pause");
  37. return 0;
  38. }//main
  39. //基数排序算法实现
  40. void RadixSort(int *data, int n)
  41. {
  42. int digits = ComputeDigits(data, n);
  43. //选用一个稳定排序以各位数字对输入序列排序
  44. for (int i = 0; i < digits; i++)
  45. {
  46. digitSort(data, n, i);
  47. }
  48. }
  49. //计算待排数组中最长位数
  50. int ComputeDigits(int *data , int n)
  51. {
  52. int max = data[0];
  53. for (int i = 1; i < n; i++)
  54. {
  55. if (data[i] > max)
  56. max = data[i];
  57. }
  58. //临时计数变量
  59. int count = 0;
  60. while (max)
  61. {
  62. count++;
  63. max /= 10;
  64. }
  65. return count;
  66. }
  67. //按照d位数字对数组排序算法
  68. void digitSort(int *data, int n, int d)
  69. {
  70. int digitArray[10][N];
  71. for (int i = 0; i < 10; i++)
  72. for (int j = 0; j < N; j++)
  73. digitArray[i][j] = -1;
  74. //当前输入序列中有n个数字待排
  75. for (int i = 0; i < n; i++)
  76. {
  77. //得到当前位对应的数字
  78. int index = data[i] / (int)pow(10, d) % 10;
  79. for (int j = 0; j < n; j++)
  80. {
  81. if (digitArray[index][j] == -1)
  82. {
  83. digitArray[index][j] = data[i];
  84. break;
  85. }
  86. }
  87. }
  88. int k = 0;
  89. //将按位排序后的数组更新到源序列
  90. for (int i = 0; i < 10; i++)
  91. {
  92. for (int j = 0; j < N; j++)
  93. {
  94. if (digitArray[i][j] != -1)
  95. {
  96. data[k++] = digitArray[i][j];
  97. digitArray[i][j] = -1;
  98. }
  99. }
  100. }
  101. }

基数排序是一种稳定排序,以上代码中,对各位分别排序采用的是接下来介绍的桶排序。

桶排序

对于桶排序与计数排序类似也是对输入做了某种假设,因而运行很快。假设输入待排序列是由一个随机过程产生,该过程将元素均匀而独立的分布在[0 , 1) 上,桶排序的思想就是将该区间均匀的分成n个大小相同的桶,分别对各个桶中的元素按照直接插入排序,然后再把各个桶列出来即是排序结果。

对于桶排序的程序实现,输入采用[0 , 1000)的一组数据,道理同上,按照元素的最高位,建立下标为0~9的十个桶,将相应元素加入到相应桶中,加入过程采用直接插入排序,然后将桶中元素按照下标递增的方式罗列,即是最终排序结果。

  1. #include <iostream>
  2. #include <ctime>
  3. #include <cstdlib>
  4. #define N 10
  5. #define MAX 1000
  6. using namespace std;
  7. //桶排序函数声明
  8. void BucketSort(int *data, int n);
  9. //计算待排数组中最长位数
  10. int ComputeDigits(int *data, int n);
  11. int main()
  12. {
  13. //声明一个待排序数组
  14. int array[N];
  15. //设置随机化种子,避免每次产生相同的随机数
  16. srand(time(0));
  17. for (int i = 0; i<N; i++)
  18. {
  19. array[i] = (rand() % MAX);//数组赋值使用随机函数产生1-1000之间的随机数
  20. }
  21. cout << "排序前:" << endl;
  22. for (int j = 0; j<N; j++)
  23. {
  24. cout << array[j] << " ";
  25. }
  26. cout << endl << "排序后:" << endl;
  27. //调用快速排序函数对该数组进行排序
  28. BucketSort(array , N);
  29. for (int k = 0; k<N; k++)
  30. {
  31. cout << array[k] << " ";
  32. }
  33. cout << endl;
  34. system("pause");
  35. return 0;
  36. }//main
  37. //计算待排数组中最长位数
  38. int ComputeDigits(int *data, int n)
  39. {
  40. int max = data[0];
  41. for (int i = 1; i < n; i++)
  42. {
  43. if (data[i] > max)
  44. max = data[i];
  45. }
  46. int count = 0;
  47. while (max)
  48. {
  49. count++;
  50. max /= 10;
  51. }
  52. return count;
  53. }
  54. void BucketSort(int *data, int n)
  55. {
  56. //计算输入序列中最大元素的位数
  57. int digits = ComputeDigits(data, n);
  58. //按照最高位0~9 创建10个桶
  59. int bucket[10][N+1];
  60. for (int i = 0; i < 10; i++)
  61. {
  62. //该桶的第一个元素设置为存储桶中元素个数
  63. bucket[i][0] = 0;
  64. //其余元素初始化为-1
  65. for (int j = 1; j < N + 1; j++)
  66. bucket[i][j] = -1;
  67. }
  68. //对每个输入元素按照插入排序放入相应的桶中
  69. for (int i = 0; i < n; i++)
  70. {
  71. //得到目标桶的序号
  72. int index = data[i] / (int)pow(10, digits-1);
  73. //得到当前桶中元素个数
  74. int count = bucket[index][0];
  75. int j = count;
  76. //按照直接插入排序将元素插入桶中
  77. while (j >0 && bucket[index][j] > data[i])
  78. {
  79. bucket[index][j + 1] = bucket[index][j];
  80. j--;
  81. }
  82. bucket[index][j + 1] = data[i];
  83. bucket[index][0]++;
  84. }
  85. //将每个桶中的元素合并到data中
  86. int k = 0;
  87. for (int i = 0; i < 10; i++)
  88. {
  89. for (int j = 1; j <= bucket[i][0] ; j++)
  90. {
  91. data[k++] = bucket[i][j];
  92. }
  93. }
  94. }

桶排序是一种线性时间排序算法,运行时间可以达到 O(n)

《算法导论》 — Chapter 8 线性时间排序的更多相关文章

  1. 算法导论 第八章 线性时间排序(python)

    比较排序:各元素的次序依赖于它们之间的比较{插入排序O(n**2) 归并排序O(nlgn) 堆排序O(nlgn)快速排序O(n**2)平均O(nlgn)} 本章主要介绍几个线性时间排序:(运算排序非比 ...

  2. 算法导论学习之线性时间求第k小元素+堆思想求前k大元素

    对于曾经,假设要我求第k小元素.或者是求前k大元素,我可能会将元素先排序,然后就直接求出来了,可是如今有了更好的思路. 一.线性时间内求第k小元素 这个算法又是一个基于分治思想的算法. 其详细的分治思 ...

  3. "《算法导论》之‘排序’":线性时间排序

    本文参考自一博文与<算法导论>. <算法导论>之前介绍了合并排序.堆排序和快速排序的特点及运行时间.合并排序和堆排序在最坏情况下达到O(nlgn),而快速排序最坏情况下达到O( ...

  4. Python线性时间排序——桶排序、基数排序与计数排序

    1. 桶排序 1.1 范围为1-M的桶排序 如果有一个数组A,包含N个整数,值从1到M,我们可以得到一种非常快速的排序,桶排序(bucket sort).留置一个数组S,里面含有M个桶,初始化为0.然 ...

  5. 《算法导论》读书笔记之排序算法—Merge Sort 归并排序算法

    自从打ACM以来也算是用归并排序了好久,现在就写一篇博客来介绍一下这个算法吧 :) 图片来自维基百科,显示了完整的归并排序过程.例如数组{38, 27, 43, 3, 9, 82, 10}. 在算法导 ...

  6. 排序算法的C语言实现(下 线性时间排序:计数排序与基数排序)

    计数排序 计数排序是一种高效的线性排序. 它通过计算一个集合中元素出现的次数来确定集合如何排序.不同于插入排序.快速排序等基于元素比较的排序,计数排序是不需要进行元素比较的,而且它的运行效率要比效率为 ...

  7. "《算法导论》之‘线性表’":基于静态分配的数组的顺序表

    首先,我们来搞明白几个概念吧(参考自网站数据结构及百度百科). 线性表 线性表是最基本.最简单.也是最常用的一种数据结构.线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外, ...

  8. "《算法导论》之‘线性表’":双向循环链表

    本文双链表介绍部分参考自博文数组.单链表和双链表介绍 以及 双向链表的C/C++/Java实现. 1 双链表介绍 双向链表(双链表)是链表的一种.和单链表一样,双链表也是由节点组成,它的每个数据结点中 ...

  9. "《算法导论》之‘线性表’":基于数组实现的单链表

    对于单链表,我们大多时候会用指针来实现(可参考基于指针实现的单链表).现在我们就来看看怎么用数组来实现单链表. 1. 定义单链表中结点的数据结构 typedef int ElementType; cl ...

随机推荐

  1. 2018 年度码云热门项目排行榜 TOP 10

    2016 年度码云热门项目排行榜 TOP 10 是通过开源项目2016年在码云上的 Watch.Star.Fork 数量来评定的榜单.码云平台发展至今,涌现了越来越多优秀的开源项目,越来越多的开源作者 ...

  2. PMD - Avoid autogenerated methods to access private fields and methods of inner / outer classes

    PMD错误 Avoid autogenerated methods to access private fields and methods of inner / outer classes 样例 p ...

  3. Rsync 实现远程同步

    介绍 rsync命令是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件.rsync使用所谓的“rsync算法”来使本地和远程两个主机之间的文件达到同步,这个算法只传送两个文件的不同部 ...

  4. 洛谷1083(差分+二分 or 线段树)

    第一种方法:可以二分最大天数订单的答案然后通过差分求一下是否可行. ; int n, m, a[maxn], ans; struct section { int cnt, l, r; }b[maxn] ...

  5. Vue-cli构建项目, 组件中js代码引入图片路径问题

    问题描述 .vue的组件分成三个部分, template结构部分, script路径代码, style页面样式 首先, 我们可以在template可以正确引入, 无论是dev, 还是build都没有问 ...

  6. 用css来修饰页面文本

    <html> <head> <title>修饰文本字体</title> <style type="text/css"> ...

  7. c库函数-字符串

    一 strok:从字符串中按照分隔符提取所有字串 char s[] = "水发产品,47.6,不合格,mg/kg,17-05-21 15:04;";  char *delim = ...

  8. Oracle、MySQL和SqlServe分页查询的语句区别

    ★先来定义分页语句将要用到的几个参数: int currentPage ; //当前页 int pageRecord ; //每页显示记录数 以之前的ADDRESSBOOK数据表为例(每页显示10条记 ...

  9. hihocoder1067 最近公共祖先·二

    思路: 使用tarjan算法,这是一种离线算法. 实现: #include <bits/stdc++.h> using namespace std; typedef pair<int ...

  10. Android studio 时间选择器

    相当简单加载 gradle文件然后做一个textview即可. 1.首先我们要在build.gradle中写上这一行代码: compile 'com.feezu.liuli:timeselector: ...