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

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

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

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

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

计数排序

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

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

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

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
#define MAX 101 using namespace std; //计数排序函数声明
void CountingSort(int *data, int *result, int k); int main()
{
//声明一个待排序数组
int array[N];
//声明排序后数组
int result[N];
//设置随机化种子,避免每次产生相同的随机数
srand(time(0));
for (int i = 0; i<N; i++)
{
array[i] = rand() % MAX;//数组赋值使用随机函数产生1-100之间的随机数
}
cout << "排序前:" << endl;
for (int j = 0; j<N; j++)
{
cout << array[j] << " ";
}
cout << endl << "排序后:" << endl;
//调用快速排序函数对该数组进行排序
CountingSort(array, result , MAX);
for (int k = 0; k<N; k++)
{
cout << result[k] << " ";
}
cout << endl; system("pause");
return 0;
}//main //计数排序算法实现
void CountingSort(int *data, int *result, int k)
{
int C[MAX] = { 0 };
//(1) 费时 O(k)
for (int i = 0; i < k; i++)
C[i] = 0; //(2) 费时O(n)
for (int j = 0; j < N; j++)
C[data[j]] = C[data[j]] + 1; //(3) 费时O(k)
for (int i = 1; i < k; i++)
{
C[i] = C[i] + C[i - 1];
} //测试当前源数据的目标位置
/*for (int k = 0; k < N; k++)
{
cout << C[data[k]] << "\t"; }*/ //得到排序后的目标序列
//(4)费时 O(n)
for (int j = 0; j < N ; j++ )
{
//保证数据下标不会越界 需-1
result[C[data[j]]-1] = data[j];
C[data[j]] -= 1;
}
}

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

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

基数排序

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

代码实现如下:

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
#define MAX 1000 using namespace std; //基数排序函数声明
void RadixSort(int *data, int n); //计算待排数组中最长位数
int ComputeDigits(int *data , int n); //按照d位数字对数组排序算法
void digitSort(int *data, int n, int d); int main()
{
//声明一个待排序数组
int array[N]; //设置随机化种子,避免每次产生相同的随机数
srand(time(0));
for (int i = 0; i<N; i++)
{
array[i] = rand() % MAX;//数组赋值使用随机函数产生1-100之间的随机数
}
cout << "排序前:" << endl;
for (int j = 0; j<N; j++)
{
cout << array[j] << " ";
}
cout << endl << "排序后:" << endl;
//调用快速排序函数对该数组进行排序
RadixSort(array, N); for (int k = 0; k<N; k++)
{
cout << array[k] << " ";
}
cout << endl; system("pause");
return 0;
}//main //基数排序算法实现
void RadixSort(int *data, int n)
{
int digits = ComputeDigits(data, n);
//选用一个稳定排序以各位数字对输入序列排序
for (int i = 0; i < digits; i++)
{
digitSort(data, n, i);
} } //计算待排数组中最长位数
int ComputeDigits(int *data , int n)
{
int max = data[0];
for (int i = 1; i < n; i++)
{
if (data[i] > max)
max = data[i];
} //临时计数变量
int count = 0; while (max)
{
count++;
max /= 10;
} return count;
} //按照d位数字对数组排序算法
void digitSort(int *data, int n, int d)
{
int digitArray[10][N];
for (int i = 0; i < 10; i++)
for (int j = 0; j < N; j++)
digitArray[i][j] = -1; //当前输入序列中有n个数字待排
for (int i = 0; i < n; i++)
{
//得到当前位对应的数字
int index = data[i] / (int)pow(10, d) % 10;
for (int j = 0; j < n; j++)
{
if (digitArray[index][j] == -1)
{
digitArray[index][j] = data[i];
break;
}
}
} int k = 0;
//将按位排序后的数组更新到源序列
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < N; j++)
{
if (digitArray[i][j] != -1)
{
data[k++] = digitArray[i][j];
digitArray[i][j] = -1;
}
}
}
}

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

桶排序

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

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

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
#define MAX 1000 using namespace std; //桶排序函数声明
void BucketSort(int *data, int n); //计算待排数组中最长位数
int ComputeDigits(int *data, int n); int main()
{
//声明一个待排序数组
int array[N]; //设置随机化种子,避免每次产生相同的随机数
srand(time(0));
for (int i = 0; i<N; i++)
{
array[i] = (rand() % MAX);//数组赋值使用随机函数产生1-1000之间的随机数
}
cout << "排序前:" << endl;
for (int j = 0; j<N; j++)
{
cout << array[j] << " ";
}
cout << endl << "排序后:" << endl;
//调用快速排序函数对该数组进行排序
BucketSort(array , N); for (int k = 0; k<N; k++)
{
cout << array[k] << " ";
}
cout << endl; system("pause");
return 0;
}//main //计算待排数组中最长位数
int ComputeDigits(int *data, int n)
{
int max = data[0];
for (int i = 1; i < n; i++)
{
if (data[i] > max)
max = data[i];
} int count = 0; while (max)
{
count++;
max /= 10;
} return count;
} void BucketSort(int *data, int n)
{
//计算输入序列中最大元素的位数
int digits = ComputeDigits(data, n); //按照最高位0~9 创建10个桶
int bucket[10][N+1];
for (int i = 0; i < 10; i++)
{
//该桶的第一个元素设置为存储桶中元素个数
bucket[i][0] = 0;
//其余元素初始化为-1
for (int j = 1; j < N + 1; j++)
bucket[i][j] = -1;
} //对每个输入元素按照插入排序放入相应的桶中
for (int i = 0; i < n; i++)
{
//得到目标桶的序号
int index = data[i] / (int)pow(10, digits-1); //得到当前桶中元素个数
int count = bucket[index][0]; int j = count;
//按照直接插入排序将元素插入桶中
while (j >0 && bucket[index][j] > data[i])
{
bucket[index][j + 1] = bucket[index][j];
j--;
}
bucket[index][j + 1] = data[i];
bucket[index][0]++;
} //将每个桶中的元素合并到data中
int k = 0;
for (int i = 0; i < 10; i++)
{
for (int j = 1; j <= bucket[i][0] ; j++)
{
data[k++] = bucket[i][j];
}
}
}

桶排序是一种线性时间排序算法,运行时间可以达到 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. 为什么wait,notify和notifyAll要与synchronized一起使用?

    https://blog.csdn.net/qq_39907763/article/details/79301813 Object.wait(),Object.notify(),Object.noti ...

  2. 线段树(成段更新) POJ 3468 A Simple Problem with Integers

    题目传送门 /* 线段树-成段更新:裸题,成段增减,区间求和 注意:开long long:) */ #include <cstdio> #include <iostream> ...

  3. JavaScript中简单排序总结

    JavaScript中简单排序总结 冒泡排序 经典排序算法, 双重for循环 在第二个for循环的时候, j < arr.len -1 -i , 这一步的优化很重要 function bullS ...

  4. h5-23-百度地图api

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name ...

  5. hadoop-0.20.2伪分布式安装简记

    1.准备环境 虚拟机(redhat enterprise linux 6.5) jdk-8u92-linux-x64.tar.gz hadoop-0.20.2.tar.gz 2.关闭虚拟机的防火墙,s ...

  6. Android获取本地相册图片、拍照获取图片

    需求:从本地相册找图片,或通过调用系统相机拍照得到图片. 容易出错的地方: 1,当我们指定了照片的uri路径,我们就不能通过data.getData();来获取uri,而应该直接拿到uri(用全局变量 ...

  7. javaee 第八周作业

    hashcode()和equals()的作用.区别.联系 先来试想一个场景,如果你想查找一个集合中是否包含某个对象,那么程序应该怎么写呢?通常的做法是逐一取出每个元素与要查找的对象一一比较,当发现两者 ...

  8. TCP和流

    http://www.cnblogs.com/lwzz/archive/2011/07/03/2096963.html TCP是一种流协议(stream protocol).这就意味着数据是以字节流的 ...

  9. JavaSE-27 JDBC

    学习要点 JDBC 查询数据 添加数据 修改数据 删除数据 JDBC 1  JDBC的定义 JDBC是Java数据库连接技术的简称,提供连接和操作各种常用数据库的能力. 2  JDBC工作原理 3  ...

  10. 项目中常用的js方法(持续更新)

    <script> var utils = { //时间戳转日期(timestamp:时间戳 默认当前时间) dateFormat: function(timestamp = new Dat ...