第8章 线性时间排序

最后更新于:2022-04-01 07:35:17

# 一、概念 ### 1.比较排序 比较排序是指通过输入元素间的比较来确定各元素次序的排序算法。 任何比较排序在最坏情况下都要用O(nlgn)次比较来进行排序 合并排序和堆排序是渐近最优的 ### 2.非比较排序 非比较排序指使用一些非比较的操作来确定排序顺序的排序算法 对于非比较排序,下界O(nlgn)不适用 计数排序是稳定排序,若n个数据的取值范围是[0..k],则运行时间为O(n+k),运行空间是O(n+k) 基数排序也是稳定排序,需要另一个稳定排序作为基础,若n个d位数,每一位有k种取值可能,所用的稳定排序运行时间为O(n+k),则基数排序的时间是O(d(n+k)) 桶排序也是稳定排序,当输入数据符合均匀分布时,桶排序可以以线性时间运行。所设所有元素均匀分布在区间[0,1)上,把区间[0,1)划分成n个相同大小的子区间(桶),对各个桶中的数进行排序,把依次把各桶中的元素列出来。 # 二、代码 ~~~ #include <iostream> #include <cmath> using namespace std; int length_A, digit; void Print(int *A, int start, int end) { int i; for(i = start; i <= end; i++) { if(i == start)cout<<'{'; else cout<<' '; cout<<A[i]; } cout<<'}'<<endl; } //计数排序 void Counting_Sort(int *A, int *B, int k) { int i, j; //将C数组初始化为0,用于计数 int *C = new int[k+1]; for(i = 0; i <= k; i++) C[i] = 0; //C[j]表示数字j在数组A中出现的次数 for(j = 1; j <= length_A; j++) C[A[j]]++; //C[i]表示所以<=i的数字出现过的次数 for(i = 1; i <= k; i++) C[i] = C[i] + C[i-1]; //初始化B为0,B用于输出排序结果 for(i = 1; i <= length_A; i++) B[i] = 0; for(j = length_A; j >= 1; j--) { //如果<=A[j]的数字的个数是x,那么排序后A[j]应该出现在第x个位置,即B[x]=A[j] B[C[A[j]]] = A[j]; C[A[j]]--; } delete C; } //基数排序调用的稳定排序 void Stable_Sort(int *A, int *B, int k, int d) { int i, j; //将C数组初始化为0,用于计数 int *C = new int[k+1]; for(i = 0; i <= k; i++) C[i] = 0; int *D = new int[length_A+1]; for(j = 1; j <= length_A; j++) { //D[j]表示第[j]个元素的第i位数字 D[j] = A[j] % (int)pow(10.0, d) / (int)pow(10.0, d-1); //C[j]表示数字D[j]在数组A中出现的次数 C[D[j]]++; } //C[i]表示所以<=i的数字出现过的次数 for(i = 1; i <= k; i++) C[i] = C[i] + C[i-1]; //初始化B为0,B用于输出排序结果 for(i = 1; i <= length_A; i++) B[i] = 0; for(j = length_A; j >= 1; j--) { //如果<=D[j]的数字的个数是x,那么排序后A[j]应该出现在第x个位置,即B[x]=A[j] B[C[D[j]]] = A[j]; C[D[j]]--; } delete []C; delete []D; } //基数排序 void Radix_Sort(int *A, int *B) { int i, j; //依次对每一位进行排序,从低位到高位 for(i = 1; i <= digit; i++) { Stable_Sort(A, B, 9, i); //输入的是A,输出的是B,再次排序时要把输出数据放入输出数据中 for(j = 1; j <= length_A; j++) A[j] = B[j]; } } int main() { cin>>length_A>>digit; int *A = new int[length_A+1]; int *B = new int[length_A+1]; int i; //随机产生length_A个digit位的数据 for(i = 1; i <= length_A; i++) { A[i] = 0; while(A[i] < (int)pow(10.0, digit-1)) A[i] = rand() % (int)pow(10.0, digit); } Print(A, 1, length_A); // Counting_Sort(A, B, 9); Radix_Sort(A, B); Print(A, 1, length_A); delete []A; delete []B; return 0; } ~~~ # 三、练习 ### 8.1 排序算法时间的下界 ~~~ 8.1-1 <span style="line-height: 20px; ">n-1,因为一个有序数组的排序中最少会进行n-1次比较</span> 8.1-2 数学题目不要看我 8.1-3 幸好有答案,不然题目的意思都看不懂。先解释一下题目的意思,高手跳过。 对于一个组包含n个元素的数据,可以有n!种不同的排列,也就是有n!种不同的输入。 对于一个排序算法,可以用一个决策树来表示。 对于任意一种输入排列,从树顶出发,根据该内结点的提示作对应的比较和调整,并决定进入其左子树还是右子树。当这个排列成为一个有序序列时到达叶子结点。这个叶结点就是这个输入排列对应的叶结点。从根结点到叶结点的长度就是这个排列的比较次数。 具有线性时间的输入是指n!种输入排列中满足以下条件的排列:排列的运行时间小于或等于O(n) 假设对于一组包含n个元素的数据,有n!种不同的输入排列,其中具有线性时间的输入排列有m种,求k = m/(n!) 若k<=1/2,那么所证明的命题成立。 后面一问是指k和1/n、1/(2^n)的比较 证明: 这m种输入排列分别对应决策树中的m个叶结点。一棵高度为h的树最多有2^h个叶结点。 2^h >= m =====> h >= lg(m) 若要一棵决策树包含m个叶结点,这棵决策树的高度最少为lg(m) 根据《算法导论》P98中定理8.1的公式,h>=O(nlgn) 只需要证明lg(m) <= O(nlgn),那么k就是可以取到的值。 分别令k等于1/2、1/n、1/(2^n),代入m = k * (n!)得: 计算结果略,这几个值都不满足 8.1-4 不能简单地将子序列的下界合起来,这样做不准确。因为有可能存在一种比“独立解决各个子序列的排序”更快的算法。 这种计算比较次数的一般思路是: (1)统计有多少种不同的输入排列 (2)每一种输入排列对应决策树的一个叶子结点。 (3)根据叶结点的数量与树的高度的关系,计算决策树的高度 (4)从树根到叶结点的长度就是这个叶结点对应的输入排列的比较次数 对于这道题,可以这样计算: (1)每个子序列有k个元素,因此有k!种不同的输入排列。 共有n/k个子序列,因此共有(k!)^(n/k)种输入排列 (2)决策树至少有(k!)^(n/k)个叶子 (3)一棵高度为h的决策树,最多有2^h个叶子结点 2^h >= (k!)^(n/k) =====> h >= (n/2)lg(k/2) (4)对于任意一树决策树,至少有一条叶子路径长度超过(n/2)lg(k/2),因此比较次数下界是O(nlgk) ~~~ ### 8.2 计数排序 ~~~ 8.2-1因为假设待排序数据的范围中[0,k],所以B被初始化为-1 A = {6 0 2 0 1 3 4 6 1 3 2} ==> C = {2 2 2 2 1 0 2} ==> C = {2 4 6 8 9 9 11} ==> B = {-1 -1 -1 -1 -1 2 -1 -1 -1 -1 -1} C = {2 4 5 8 9 9 11} ==> B = {-1 -1 -1 -1 -1 2 -1 3 -1 0 -1 -1} C = {2 4 5 7 9 9 11} ==> B = {-1 -1 -1 1 -1 2 -1 3 -1 -1 -1} C = {2 3 5 7 9 9 11} ==> B = {-1 -1 -1 1 -1 2 -1 3 -1 -1 6} C = {2 3 5 7 9 9 10} ==> B = {-1 -1 -1 1 -1 2 -1 3 4 -1 6} C = {2 3 5 7 8 9 10} ==> B = {-1 -1 -1 1 -1 2 3 3 4 -1 6} C = {2 3 5 6 8 9 10} ==> B = {-1 -1 1 1 -1 2 3 3 4 -1 6} C = {2 2 5 6 8 9 10} ==> B = {-1 0 1 1 -1 2 3 3 4 -1 6} C = {1 2 5 6 8 9 10} ==> B = {-1 0 1 1 2 2 3 3 4 -1 6} C = {1 2 4 6 8 9 10} ==> B = {0 0 1 1 2 2 3 3 4 -1 6} C = {0 2 4 6 8 9 10} ==> B = {0 0 1 1 2 2 3 3 4 6 6} C = {0 2 4 6 8 9 9} 8.2-2 辅助数组C[j]记录的是小于或等于i的元素的个数。若几个元素的大小相等,则把这些元素依次从后往前填入排序数组中。因为处理元素的顺序是从后往前的(L9),所以晚出现的元素会填到后面。因此是稳定排序 8.2-3 不稳定 8.2-4 COUNTING-SORT(A, B, k)中步骤L1-L4求出C,ans(a, b) = C[b] - C[a-1], C[-1]=0 ~~~ ### 8.3 基数排序 8.3-1 ~~~ A = {COW, DOG, SEA, RUG, ROW, MOB, BOX, TAB, BAR, EAR, TAR, DIG, BIG, TEA, NOW, FOX} ==> A = {SEA, TEA, MOB, TAB, DOG, RUG, DIG, BIG, BAR, EAR, TAR, COW, ROW, NOW, BOX, FOX} ==> A = {TAB, BAR, EAR, TAR, SEA, TEA, DIG, BIG, MOB, DOG, COW, ROW, NOW, BOX, FOX, RUB} ==> A = {BAR, BIG, BOX, COW, DIG, DOG, EAR, FOX, MOB, NOW, ROW, TAB, TAR, TEA, SEA, RUB} ~~~ 8.3-2 稳定排序有:插入排序,合并排序 方法:为每一个元素加入一个最初位置的属性,但两个元素的value一样大时,比较position,这样不会有相同的两个元素 额外空间:O(4n) 8.3-3 (1)当d=1时,元素只有一位,对这一位做排序就相当于对整个数组做排序了。 (2)证明当1到d-1位排序是正确的时,对d位的排序也是正确的。 (3)对于数组中的任意两个数a和b,假设它们的第d位分别是ad和bd 若ad<bd,则a会排到b的前面 若ad>bd,则a会排到b的后面 若ad=bd,则a和b的相对位置不变,因为是稳定排序,这一点可以保证。 8.3-4 把整数转换为n进制再排序,见[算法导论8.3-4](http://blog.csdn.net/mishifangxiangdefeng/article/details/7685839) 8.3-5 最坏情况下需要d遍 ### 8.4 桶排序 ~~~ 8.4-1 A = {0.79, 0.13, 0.16, 0.64, 0.39, 0.20, 0.89, 0.53, 0.71, 0.42} ==> A = {0.13, 0.16, 0.20, 0.39, 0.42, 0.53, 0.64, 0.79, 0.71, 0.89} ==> A = {0.13, 0.16, 0.20, 0.39, 0.42, 0.53, 0.64, 0.71, 0.79, 0.89} 8.4-2 最坏情况是O(n^2) 修改:使用最坏情况下时间为O(nlgn)的算法来处理桶的插入操作 8.4-3 E(X^2)=1/4 * 0 + 1/2 * 1 + 1/4 * 4 = 3/2 E^2(X)=[1/4 * 0 + 1/2 * 1 + 1/4 * 2]^2 = 1^2 = 1 8.4-4 假设分为n个桶 BUCKET-SORT(A) 1 n <- length[A] 2 for i <- 1 to n 3 do insert A[i] to list B[n*(A[i].x^2 + A[i].y^2)] 4 for i <- 0 to n-1 5 do sort list B[i] with insertion sort 6 concatenate the lists B[0], B[1], ……,B[n-1] together in order 8.4-5 ~~~ 不会,网上找了份答案,不懂 X合适分布P,不必然是均匀分布,且不知局限。但P(x)值属于[0,1],且对于X严格单调递增,排序P(x)即排序X。将P(x)均匀分为n个项目组,因为X随机拔取,所以落入每个局限的概率雷同。 若是(i-1)/n <= p(xi) < i/n,则将它放入第i个桶中。 [http://www.mysjtu.com/page/M0/S696/696126.html](http://www.mysjtu.com/page/M0/S696/696126.html) # 四、思考题 ### 8-1 比较排序的平均情况下界 ~~~ a)n个不同的元素对应n!个不同的输入,因此只有这n!个输入所对应的叶结点是有概率的,其余概率均为0。 由于这n!种输入的出现是等概率的,每一种的都是1/n1,因此其对应的叶结点的概率也是1/n! b)设T(n)是一个叶子结点n的在决策树T上的深度,RT(n)、LT(n)分别表示n在T的右、左子树上的深度。 那么T(n)=RT(n)+1或T(n)=LT(n)+1 因此D(T)=D(RT)+D(LT)+k c)后面这几题,以我有限的智商和数学能力理解不了,也看不懂网上的答案,不写了 ~~~ ### 8-2 以线性时间原地转换排序 ~~~ a)计数排序 b)快速排序 c)稳定排序 d)基数排序要求所使用的排序方法是满足的(条件2),如果要在O(bn)时间内完成,所使用算法的时间应该是O(n)(条件1),所以只有a可以 e)不稳定 COUNTING-SORT(A, k) 1 for i <- 0 to k 2 do C[i] <- 0 3 for j <-1 to length[A] 4 C[A[j]] <- C[A[j]] + 1 5 cnt <- 1 6 for i <- 1 to k 7 while C[i] > 0 8 A[cnt] <- i 9 C[i] <- C[j] - 1 10 cnt <- cnt + 1 ~~~ ### 8-3 排序不同长的数据项 见[算法导论-8-3-排序不同长度的数据项](http://blog.csdn.net/mishifangxiangdefeng/article/details/7686099) ~~~ a)先用计数排序算法法按数字位数排序O(n),再用基数排序的方法分别对每个桶中的元素排序O(n) b)递归使用计数排序,先依据第一个字母进行排序,首字相同的放在同一组,再对每一组分别使用计数排序的方法比较第二个字母 见到有人用字典树,也是可以的 ~~~ ### 8-4 水壶 ~~~ a)最原始的比较方法,所有的红水壶与所有的蓝水壶依次比较 c)算法类似于快排,由于不是同种颜色的水壶之间比较,所以采用交叉比较 step1:从红色水壶中随机选择一个 step2:以红色水壶为主元对蓝色水壶进行划分 step3:划分的结果是两个数组,并得到与红色水壶相同大小的蓝色水壶 step4:以这个蓝色水壶为主元,对红色水壶进行划分 step5:用step1到step4的过程不断迭代 ~~~ ### 8-5 平均排序 见[算法导论6.5-8堆排序-K路合并](http://blog.csdn.net/mishifangxiangdefeng/article/details/7668486) ~~~ a)1排序是完全排序 b)1,3,2,4,5,7,6,8,9,10 c)分工展开化简即可 d) step1:分别把1, 1+k, 1+2k, 1+3k,……;2, 2+k, 2+2k, 2+3k,……;……当作是单独的集合 step2:对每个集合进行排序时间为O(nlg(n/k)) e) step1:同d)step1 step2:用堆排序进行k路合并 ~~~ ### 8-6 合并已排序列表的下界 ~~~ a)2n个数,随机选n个数,可选的方法有C(2n,n)种 b)2^h >= C(2n,n) =====> h >= lg((2n)!) - 2lg(n!) =====> h >= 2nlg2n - 2nlgn =====> h >= 2nlg2 只能推到这了,最后怎么算,还希望高手指点 c)d)看上去很显然的事情,不知道怎么证 ~~~
';