算法导论 — 思考题8-3 变长数据项的排序

变长数据项的排序
  a. 给定一个整数数组,其中不同的整数所包含的数字的位数可能不同,但该数组中,所有整数中包含的总数字位数为 n n n。设计一个算法,使其可以在 O ( n ) O(n) O(n)时间内对该数组进行排序。
  b. 给定一个字符串数组,其中不同的字符串所包含的字符数可能不同,但所有字符串中的总字符个数为 n n n。设计一个算法,使其可以在 O ( n ) O(n) O(n)时间内对该数组进行排序。(注意:此处的顺度是指标准的字典序,例如 a < a b < b a < ab < b a<ab<b。)
  
  
  a.
  假设数组一共有 m m m个元素,各元素的位数分别为 d 1 , d 2 , … , d m d_1, d_2, …, d_m d1,d2,,dm,显然有 ∑ i = 1 m d i = n \sum_{i=1}^m{d_i} =n i=1mdi=n。如果采用基数排序,运行时间为 O ( d m a x m ) O(d_{max}m) O(dmaxm),其中 d m a x = max ⁡ 1 ≤ i ≤ m d i d_{max}=\max_{1≤i≤m}{d_i} dmax=max1imdi,这与题目要求的 O ( n ) O(n) O(n)运行时间不符。例如,假设元素个数 m = n / 2 + 1 m = n/2 + 1 m=n/2+1,有一个元素的位数为 n / 2 n/2 n/2,其他 n / 2 n/2 n/2个元素的位数都为 1 1 1,这种情况下基数排序的运行时间为 O ( n 2 ) O(n^2) O(n2)
  为了可以在 O ( n ) O(n) O(n)时间内完成排序,考虑先按照元素的位数对元素进行分组,相同位数的元素分为一组,然后采用基数排序分别对每组元素进行排序,然后按照位数从小到大依次输出每组元素即可完成排序。
  算法导论 — 思考题8-3 变长数据项的排序_第1张图片
  该算法的时间复杂度取决于两部分,一部分是分组的时间,一部分是对各组元素进行排序的时间。分组的时间取决于确定各元素的位数所花费的时间,对于一个有 d i d_i di位的元素,需要 Θ ( d i ) Θ(d_i) Θ(di)时间来确定它的位数。于是,确定所有元素的位数所花费的总时间为 ∑ i = 1 m Θ ( d i ) = Θ ( n ) \sum_{i=1}^m{Θ(d_i)}=Θ(n) i=1mΘ(di)=Θ(n)。对各组元素的排序采用基数排序。对于第 j j j组来说,假设其中有 m j m_j mj个元素,该组元素都包含 j j j个数位,基数排序的时间为 Θ ( j • m j ) Θ(j•m_j) Θ(jmj)。于是,对各组元素进行基数排序的总时间为 ∑ j = 1 n Θ ( j ∙ m j ) = Θ ( n ) \sum_{j=1}^n{Θ(j∙m_j)}=Θ(n) j=1nΘ(jmj)=Θ(n)。根据以上分析,该算法的时间复杂度为 Θ ( n ) Θ(n) Θ(n)
  
  b.
  同样考虑借用基数排序的思想。在对数字的基数排序中,排序从低位开始,再到高位。与此不同,在对字符串的基数排序中,排序先从最左边的字符开始,再到右边的字符。下面给出一个例子。
  算法导论 — 思考题8-3 变长数据项的排序_第2张图片
  注意,第一轮排序对所有字符串的最左边的字符进行排序。在第一轮排序过后,所有字符串按照最左边的字符进行分组(如上图所示,第一轮排序过后,at和a为一组,box和bat为一组,fix、fox和fit为一组,I为一组)。因为要求按字典序来排序,所以可以断言,在最终排好序的序列中,第一组字符串肯定排在第二组字符串之前,第二组字符串也肯定排在第三组字符串之前……。于是在第二轮排序中,分别对第一轮分组后的各组元素进行组内排序,而不改变组与组之间的顺序。第二轮排序之后,又可以按各字符串的第二个字符进行分组,第三轮排序又针对第二轮的分组进行组内排序,如此递归下去,最后可以完成整个字符串序列的排序。
  假如所有字符串都是C风格字符串(即字符串以’\0’结尾),假设第 i i i个字符串的长度为 l i l_i li,那么在第 l i + 1 l_i+1 li+1轮排序中,该字符串以’\0’参与排序,它一定会排在所在分组的前列,因为字符’\0’的值小于其他任何字符的值,而所有长度大于 l i l_i li的字符串都排在这个长度为 l i l_i li字符串的后面,这符合字典序。所以在第 l i + 2 l_i+2 li+2轮排序中,这个长度为 l i l_i li字符串字符串可以不用参与排序,只需要让所有长度大于 l i l_i li的字符串参与排序。
  根据以上分析,一个长度为 l i l_i li的字符串会参与 l i + 1 l_i+1 li+1轮排序。如果每一轮排序都采用线性时间的计数排序,那么一个长度为 l i l_i li的字符串在每一轮排序中贡献 O ( 1 ) O(1) O(1)时间,在所有 l i + 1 l_i+1 li+1轮排序中贡献 O ( l i + 1 ) O(l_i+1) O(li+1)时间。于是,对所有字符串进行排序的总时间为
   ∑ i = 1 m O ( l i + 1 ) = O ( ∑ i = 1 m ( l i + 1 ) ) = O ( n + m ) = O ( n ) \sum\limits_{i=1}^m{O(l_i+1)} =O(\sum\limits_{i=1}^m{(l_i+1)})=O(n+m)=O(n) i=1mO(li+1)=O(i=1m(li+1))=O(n+m)=O(n)
  在上式中, m m m为字符串的个数。假如没有空字符串,即所有字符串的长度至少为 1 1 1,则肯定有 m ≤ n m ≤ n mn,所以在上式中 O ( n + m ) = O ( n ) O(n+m) = O(n) O(n+m)=O(n)是成立的。
  下面组出该算法的伪代码。
  算法导论 — 思考题8-3 变长数据项的排序_第3张图片
  要对整个字符串数组 A A A进行排序,只需要调用 S O R T − S T R I N G ( A , 1 , A . l e n g t h , 1 ) {\rm SORT-STRING}(A, 1, A.length, 1) SORTSTRING(A,1,A.length,1)即可。

代码链接
  https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter08/Problem_8-3

你可能感兴趣的:(算法导论)