《算法导论》 — 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 ...
随机推荐
- PHP fgets 函数
<?php $handle=fopen("../good/html/1.txt","r"); ; //打开一个远程文件 $content="&q ...
- RAID基础
磁盘类型 类型 IDE Integrated Drive Electronics SATA Srial ATA SCSI Small Computer System Interface FC Fibe ...
- apache http server2.2 + tomcat5.5 性能调优
httpd加tomcat做负载均衡,采用session复制方式共享session,采用http-proxy连接方式,打开status mod 一.没有做httpd和tomcat的启动参数修改,包括jv ...
- html/css实现聊天布局
效果图 项目结构 html代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8" / ...
- AJPFX总结集合的概念
//java 中集合的概述========================================================== 集合的概念: 为 ...
- MySQL 当记录不存在时insert,当记录存在时更新
网上基本有三种解决方法. 第一种: 示例一:insert多条记录 假设有一个主键为 client_id 的 clients 表,可以使用下面的语句: INSERT INTO clients (clie ...
- re匹配语法-match、search和findall
1.re.match() 匹配第一个值 列表里的值可以有多个范围,有一个符合就可以. match只匹配第一个值,所以列表里的范围是第一个值得取值范围.如果第一个值被设定好且存在,那么列表的取值范围变为 ...
- Web框架_MVC vs MVT
MVC 大部分开发语言中都有MVC框架 MVC框架的核心思想是:解耦 降低各功能模块之间的耦合性,方便变更,更容易重构代码,最大程度上实现代码的重用 M表示model,主要用于对数据库层的封装 V表示 ...
- C++静态全局变量和全局变量的区别
静态全局变量 非静态全局变量 存储方式 静态存储 静态存储 作用域 定义该变量的源文件内 所有源文件 解释: 共同点:全局变量(外部变量)的说明之前再冠以static 就构 成了静态的全局变量.全 ...
- Spring JDBC 例子
http://www.yiibai.com/spring/spring_jdbc_example.html 要了解有关Spring JDBC框架与JdbcTemplate类的概念,让我们写这将实现所有 ...