算法分析与设计CH8:线性时间的排序——计数排序、基数排序、桶排序

2023-10-31 14:30

本文主要是介绍算法分析与设计CH8:线性时间的排序——计数排序、基数排序、桶排序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

CH8:Sorting in linear time

Comparision Sort 比较排序:在排序的最终结果中,各元素的次序依赖于他们之间的比较

任何的***比较排序**最好的最坏情况下经过 Ω ( n l g n ) \Omega(nlgn) Ω(nlgn)*次比较,归并,堆排都是最优的。

8.1 排序算法的下界

S o r t < a 1 , a 2 , a 3 , . . . , a n > Sort<a_1,a_2,a_3,...,a_n> Sort<a1,a2,a3,...,an>

判定树:Decision Tree

内部节点: i : j i:j ij标识,满足 1 ≤ i , j ≤ n 1\leq i,j\leq n 1i,jn

叶子结点为一个排序序列: < Π ( 1 ) , Π ( 2 ) , . . . , Π ( n ) > <\Pi(1),\Pi(2),...,\Pi(n)> <Π(1),Π(2),...,Π(n)>

算法的执行对应一条从根节点到叶子结点的路径,每个内部节点标识一次比较 a i ≤ a j a_i\leq a_j aiaj

image-20220608142727278

树包含所有可能出现的结果。

上树表示的是直接插入排序的结果。

从根节点到达任意一个可达叶子结点直接的最简单路径的长度:最坏情况下的比较次数

所有情况:叶子数量为 n n n的全排列,即 n ! n! n!
h ≥ l g ( n ! ) ≥ l g ( n / e ) n = n l g n − n l g e = Ω ( n l g n ) h\geq lg(n!) \geq lg(n/e)^n\\ =nlgn-nlge=\Omega(nlgn) hlg(n!)lg(n/e)n=nlgnnlge=Ω(nlgn)
归并排序和堆排序是渐进分析框架下的最优比较排序算法

不可能比 Ω ( n l g n ) \Omega(nlgn) Ω(nlgn)更好

------------------------------------------>不靠比大小排序,数据具有特殊性

8.2 Counting Sort 计数排序

8.1.1 算法思路

输入:小范围内整数,重复值较多。数据范围为 [ 1 … … n ] [1……n] [1……n]

输出:另一个顺序数组 [ 1... n ] [1...n] [1...n]已排序

辅助数组:C[1…k]

image-20220608142810187

算法思想:

  • For x in A, if there are 17 elements less than x in A, then x belongs in output position 18.
  • How if several elements in A have the same value? – Put the 1st in position 18, 2nd in position 19,3rd in position 20,…
  • How if there are 17 elements not greater than x in A? – Put the last one in position 17, the penultimate one in position 16,…

利用数据特殊性,使用k个计数器。

vector<int> countingSort(vector<int>& vec, int k) {// 计数器初始化为全0 vector<int> C(k+1, 0);					// 初始化计数器 vector<int> result = vec;				// 初始化结果数组 // 统计等于该数的数据数量for (int i = 1; i < vec.size(); i++) {C[vec[i]]++;							// 记录等于该值的元素 } // 统计小于等于该数的数据量 for (int i = 2; i <= k; i++) {C[i] += C[i-1];						// 记录小于等于该值的元素有多少个 }// 倒着扫描填表 for (int i = vec.size()-1; i >= 1; i--) {		// 从原数组的最后一个开始向前扫描:保证稳定排序 result[C[vec[i]]] = vec[i];					// 三重嵌套,注意 C[vec[i]]--; 								// 使用一次减一回 }return result;
}

8.1.2 算法分析

(1)时间复杂度分析

该算法如果输入的数据是小范围的,k不超过n,那么时间复杂度为 Θ ( n ) \Theta(n) Θ(n),最坏情况下也是 Θ ( n ) \Theta(n) Θ(n)

image-20220608142842599
(2)偏移考虑

数组范围:

  • 如果范围是 0 … … k 0……k 0……k,那么计数器C[0]也使用即可
  • 如果范围是 [ 2 … … k ] [2……k] [2……k],那么计数器 C [ 1 ] C[1] C[1]不用
  • 如果范围是 [ 5000 … … 5100 ] [5000……5100] [5000……5100],那么计数器所有的 C [ v e c [ i ] − 偏移量 ] C[vec[i]-偏移量] C[vec[i]偏移量]
// 给定某个范围,比如5000~5015
void counting_sort2(vector<int>& vec, vector<int>& result, int start, int k) {	 vector<int> counter(k - start + 1, 0);		// 初始化为待排序数组的范围个0:0~k个0for (int i = 0; i < vec.size(); i++) {counter[vec[i]-start]++;				// 等于vec[i]的个数 } for (int i = 1; i <= k - start; i++) {counter[i] += counter[i-1]; 	// 小于等于vec[i]的个数 }print(counter); for (int i = vec.size()-1; i>=0; i--) {	result[counter[vec[i] - start]-1] = vec[i];counter[vec[i] - start]--;}print(counter); 
}
(3)扫描顺序

最后一步填入结果数组时,从原数组的最后向前扫描,保证稳定性。

image-20220608142904243

8.3 Radix Sort 基数排序

8.3.1 算法思路

Digit by Digit Sort:按位排序

基数排序:使用辅助数组的稳定排序,首先对最低有效位进行排序

image-20220608142927092

对每一位进行排序时使用的都是稳定排序,最后结果稳定。

RADIX-SORT(A, d)for (int i = 1; i <= d; i++) {do use a stable sort to sort array A on digit i;	// 每一轮对某一位进行排序}

若位数不固定:采用最大的位数。

counting sort 合适。

8.2.2 时间复杂度分析

时间复杂度为 Θ ( d O ( n ) ) \Theta(dO(n)) Θ(dO(n)),其中 d d d是一个常数,时间复杂度为 Θ ( n ) \Theta(n) Θ(n)

8.4 BucketSort

8.4.1 算法思路

数据特殊性表现在:数据均匀分布。每个区间的数出现的概率相等。

(1)步骤
  • 为每一个值分配一个桶

    A [ i ] A[i] A[i]插入到桶 B [ ⌊ n A [ i ] ⌋ ] B[\lfloor nA[i]\rfloor] B[⌊nA[i]⌋]中,桶范围为 0 n − 1 0~n-1 0 n1

    映射关系保证,桶n-k内的数一定比桶n-k-r内的数大

  • 对于每一个桶,内部使用直接插入排序

    满足均匀分布,数字较少,插入排序可以认为为常数时间

  • 将各个桶合并

    常数时间

image-20220608142952537
(2)算法实现

实际实现时桶的数据结构需要考虑用链表数组来实现。首先给出伪代码:

image-20220608100627515
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>using namespace std;class LinkNode{
public:double val;LinkNode *next;LinkNode() : val(0), next(nullptr) {};LinkNode(double x): val(x), next(nullptr) {}LinkNode(int x, LinkNode *next) : val(x), next(next) {}
};void printVec(vector<double>& vec) {for(int i = 0; i<vec.size(); i++) {cout << vec[i] << " ";if((i+1)%5 == 0) {	// 控制打印,一行输出5个 cout << endl;}}cout<<endl;
}void random_generation(vector<double>& vec, int a, int b, int n) {	// 产生a到b之间均匀分布的n个随机数 srand((unsigned int)time(NULL));for(int i = 0; i < n; i++) {double num = rand()*1.0/RAND_MAX;	// 映射到0到1 num = num*(b-a) + a;vec.push_back(num);}} void bucket_sort(vector<double>& vec, vector<double>& res, int start, int end) {vector<LinkNode> linklist_vec;int n = vec.size();// 处理链表数组的头结点,头结点不放置数据for(int i = 0; i < vec.size(); i++) {linklist_vec.push_back(LinkNode(0));	} // 根据数值得到放置桶的下标 for (int i = 0; i<vec.size(); i++) {int index = floor(n*(vec[i] - start)/(end - start));LinkNode *head = &linklist_vec[index];while(head->next != nullptr and head->next->val<vec[i]) {head = head->next;}// 找到要插入的位置LinkNode* next_Pointer = head->next;head->next =new LinkNode(vec[i]);head->next->next = next_Pointer; }// 将非空桶中的数据取出,得到最终的顺序// 扫描并连接链表for(int i = 0; i < vec.size(); i++) {LinkNode *head = &linklist_vec[i];while(head->next != nullptr) {head = head->next;res.push_back(head->val);}} 
}int main() {vector<double> vec;int start = 2, end = 5, n = 30;// 准备随机数 random_generation(vec, start, end, n);// 桶排序vector<double> res;bucket_sort(vec, res, start, end);printVec(res);
}

8.4.2 算法分析

算法的时间复杂度为 O ( n ) O(n) O(n),插入的时间和内部排序的时间都视作常数。

image-20220608101238422

若区间不在0~1之间,那么要进行修改:

  • [0~2]: B [ ⌊ n A [ i ] / 2 ⌋ ] B[\lfloor nA[i]/2\rfloor] B[⌊nA[i]/2⌋]
  • [a, b]: B [ ⌊ n ( A [ i ] − a ) / ( b − a ) ⌋ ] B[\lfloor n(A[i]-a)/(b-a)\rfloor] B[⌊n(A[i]a)/(ba)⌋]

想一下怎么把这个数变到0~1就行了。

8.5 总结

在这里插入图片描述

这篇关于算法分析与设计CH8:线性时间的排序——计数排序、基数排序、桶排序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/316011

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费