《算法导论》 — Chapter 8 线性时间排序
序
到目前为止,关于排序的问题,前面已经介绍了很多,从插入排序、合并排序、堆排序以及快速排序,每一种都有其适用的情况,在时间和空间复杂度上各有优势。它们都有一个相同的特点,以上所有排序的结果序列,各个元素的次序都是基于输入元素之间的比较,因此,把这类排序成为比较排序。
对一个含有n个元素的输入序列,任何比较排序在最坏情况下都要用(nlogn)次比较来进行排序,由此也可以知道合并排序和堆排序是渐进最优的。
本章介绍了三种线性时间排序算法,计数排序、基数排序和桶排序,这些算法都是用非比较的操作来确定排序顺序。
下面将详细介绍这三种排序算法的实现。
计数排序
计数排序是基于对输入数据作某种假设条件下进行的排序算法。其作出,输入是由一个小范围内整数构成,即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 线性时间排序的更多相关文章
- 算法导论 第八章 线性时间排序(python)
比较排序:各元素的次序依赖于它们之间的比较{插入排序O(n**2) 归并排序O(nlgn) 堆排序O(nlgn)快速排序O(n**2)平均O(nlgn)} 本章主要介绍几个线性时间排序:(运算排序非比 ...
- 算法导论学习之线性时间求第k小元素+堆思想求前k大元素
对于曾经,假设要我求第k小元素.或者是求前k大元素,我可能会将元素先排序,然后就直接求出来了,可是如今有了更好的思路. 一.线性时间内求第k小元素 这个算法又是一个基于分治思想的算法. 其详细的分治思 ...
- "《算法导论》之‘排序’":线性时间排序
本文参考自一博文与<算法导论>. <算法导论>之前介绍了合并排序.堆排序和快速排序的特点及运行时间.合并排序和堆排序在最坏情况下达到O(nlgn),而快速排序最坏情况下达到O( ...
- Python线性时间排序——桶排序、基数排序与计数排序
1. 桶排序 1.1 范围为1-M的桶排序 如果有一个数组A,包含N个整数,值从1到M,我们可以得到一种非常快速的排序,桶排序(bucket sort).留置一个数组S,里面含有M个桶,初始化为0.然 ...
- 《算法导论》读书笔记之排序算法—Merge Sort 归并排序算法
自从打ACM以来也算是用归并排序了好久,现在就写一篇博客来介绍一下这个算法吧 :) 图片来自维基百科,显示了完整的归并排序过程.例如数组{38, 27, 43, 3, 9, 82, 10}. 在算法导 ...
- 排序算法的C语言实现(下 线性时间排序:计数排序与基数排序)
计数排序 计数排序是一种高效的线性排序. 它通过计算一个集合中元素出现的次数来确定集合如何排序.不同于插入排序.快速排序等基于元素比较的排序,计数排序是不需要进行元素比较的,而且它的运行效率要比效率为 ...
- "《算法导论》之‘线性表’":基于静态分配的数组的顺序表
首先,我们来搞明白几个概念吧(参考自网站数据结构及百度百科). 线性表 线性表是最基本.最简单.也是最常用的一种数据结构.线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外, ...
- "《算法导论》之‘线性表’":双向循环链表
本文双链表介绍部分参考自博文数组.单链表和双链表介绍 以及 双向链表的C/C++/Java实现. 1 双链表介绍 双向链表(双链表)是链表的一种.和单链表一样,双链表也是由节点组成,它的每个数据结点中 ...
- "《算法导论》之‘线性表’":基于数组实现的单链表
对于单链表,我们大多时候会用指针来实现(可参考基于指针实现的单链表).现在我们就来看看怎么用数组来实现单链表. 1. 定义单链表中结点的数据结构 typedef int ElementType; cl ...
随机推荐
- Lucky Array Codeforces - 121E && Bear and Bad Powers of 42 Codeforces - 679E
http://codeforces.com/contest/121/problem/E 话说这题貌似暴力可A啊... 正解是想出来了,结果重构代码,调了不知道多久才A 错误记录: 1.线段树搞混num ...
- 因磁盘空间不足导致HDFS的NameNode进入安全模式问题记录
因磁盘空间不足导致HDFS的NameNode进入安全模式问题记录,调用API上传及下载文件时报如下错误信息: org.apache.hadoop.ipc.RemoteException(org.apa ...
- DataTable数据检索的性能分析[转]
原文链接 作者写得非常好,我学到了许多东西,这里只是转载! 我们知道在.NET平台上有很多种数据存储,检索解决方案-ADO.NET Entity Framework,ASP.NET Dynamic D ...
- Cocos工作两周感受
我是一个专注搞Unity开发的程序猿哈哈,但是最近的项目要采用Cocos引擎开发.在迷茫和学习成长中已经不知不觉过了两周.我就简单谈谈我这两周学习Cocos的一个感受. 具体说公司是采用js语言来开发 ...
- sql server 2012 从删库到跑路
问题: 向sql server 2012单个数据库中导入1500万+条数据的时候,报错: 错误 0xc0202009: 数据流任务 1: SSIS 错误代码 DTS_E_OLEDBERROR.出现 O ...
- 在Java8的foreach()中使用break、continue
java8的lambda中,foreach()处理集合时不能使用break和continue这两个方法,可以使用return实现 参考:https://blog.csdn.net/lmy86263/a ...
- Verilog设计中的锁存器
问题: 什么是锁存器? 什么时候出现锁存器? 锁存器对电路有什么影响? 如何在FPGA设计中避免锁存器? 在FPGA设计中应该避免锁存器.实际上,锁存器与D触发器实现的逻辑功能基本相同,都有暂存数据的 ...
- IOS之UIStepper控件详解
在iOS5中新增了一个数字输入控件UIStepper,它可以递进式输入数量.UIStepper继承自UIControl,它主要的事件是UIControlEventValueChanged,每当它的值改 ...
- HDU 4465 Candy (数学期望)
题意:有两个盒子各有n个糖(n<=2*105),每天随机选1个(概率分别为p,1-p),然后吃掉一颗糖.直到有一天打开盒子一看,这个盒子没有糖了.输入n,p,求此时另一个盒子里糖的个数的数学期望 ...
- 分类IP地址(A、B、C类)的指派范围、一般不使用的特殊IP地址
分类IP地址(A.B.C类)的指派范围.一般不使用的特殊IP地址 A类地址:0开头,8位网络号 B类地址:10开头,16位网络号 C类地址:110开头,24位网络号 D类地址:1110开头,多播地址 ...