当前位置: 代码迷 >> C语言 >> 选择排序[分而治之算法应用]
  详细解决方案

选择排序[分而治之算法应用]

热度:404   发布时间:2004-11-20 11:47:00.0
选择排序[分而治之算法应用]

选择排序[分而治之算法应用]

对于给定的n 个元素的数组a [ 0 : n - 1 ],要求从中找出第k小的元素。当a [ 0 : n - 1 ]被排序时,该元素就是a [ k - 1 ]。假设n = 8,每个元素有两个域k e y和I D,其中k e y是一个整数,I D是一个字符。假设这8个元素为[ ( 1 2 ,a),( 4 ,b),( 5 ,c),( 4 ,d),( 5 ,e),( 1 0 ,f),( 2 ,g),( 2 0 ,h)], 排序后得到数组[ ( 2 ,g),( 4 ,d),( 4 ,b),( 5 ,c),( 5 ,e),( 1 0 ,f),( 1 2 ,a),( 2 0 ,h) ]。如果k = 1,返回I D为g 的元素;如果k = 8,返回I D为h 的元素;如果k = 6,返回是I D为f 的元素;如果k = 2,返回I D为d 的元素。实际上,对最后一种情况,所得到的结果可能不唯一,因为排序过程中既可能将I D为d 的元素排在a [ 1 ],也可能将I D为b 的元素排在a [ 1 ],原因是它们具有相同大小的k e y,因而两个元素中的任何一个都有可能被返回。但是无论如何,如果一个元素在k = 2时被返回,另一个就必须在k = 3时被返回。 选择问题的一个应用就是寻找中值元素,此时k = [n / 2 ]。中值是一个很有用的统计量,例如中间工资,中间年龄,中间重量。其他k值也是有用的。例如,通过寻找第n / 4 , n / 2和3 n / 4这三个元素,可将人口划分为4份。 选择问题可在O ( n l o g n )时间内解决,方法是首先对这n个元素进行排序(如使用堆排序式或归并排序),然后取出a [ k - 1 ]中的元素。若使用快速排序(如图1 4 - 11所示),可以获得更好的平均性能,尽管该算法有一个比较差的渐近复杂性O( n2 )。 可以通过修写程序1 4 - 6来解决选择问题。如果在执行两个w h i l e循环后支点元素a [ l ]被交换到a [ j ] ,那么a [ l ]是a [ l : j ]中的第j - l + 1个元素。如果要寻找的第k 个元素在a [ l : r ]中,并且j - l + 1等于k,则答案就是a [ l ];如果j - l + 1 < k,那么寻找的元素是r i g h t中的第k - j + l - 1个元素,否则要寻找的元素是left 中的第k个元素。因此,只需进行0次或1次递归调用。新代码见程序1 4 - 7。S e l e c t中的递归调用可用f o r或w h i l e循环来替代(练习2 5)。 程序14-7 寻找第k 个元素 template<class T> T Select(T a[], int n, int k) {// 返回a [ 0 : n - 1 ]中第k小的元素 // 假定a[n] 是一个伪最大元素 if (k < 1 || k > n) throw OutOfBounds(); return select(a, 0, n-1, k); } template<class T> T select(T a[], int l, int r, int k) {// 在a [ l : r ]中选择第k小的元素 if (l >= r) return a[l]; int i = l, // 从左至右的游标 j = r + 1; // 从右到左的游标 T pivot = a[l]; // 把左侧>= pivot的元素与右侧<= pivot 的元素进行交换 while (true) { do {// 在左侧寻找>= pivot 的元素 i = i + 1; } while (a[i] < pivot); do {// 在右侧寻找<= pivot 的元素 j = j - 1; } while (a[j] > pivot); if (i >= j) break; // 未发现交换对象 Swap(a[i], a[j]); } if (j - l + 1 == k) return pivot; // 设置p i v o t a[l] = a[j]; a[j] = pivot; // 对一个段进行递归调用 if (j - l + 1 < k) return select(a, j+1, r, k-j+l-1); else return select(a, l, j-1, k); }

搜索更多相关的解决方案: 分而治之  算法  应用  选择  

----------------解决方案--------------------------------------------------------
程序1 4 - 7在最坏情况下的复杂性是( n2 ),此时left 总是为空,而且第k个元素总是位于r i g h t.

如果假定n 是2的幂,则可以取消公式(2 - 1 0)中的向下取整操作符。通过使用迭代方法,可以得到t (n) = (n)。若仔细地选择支点元素,则最坏情况下的时间开销也可以变成(n)。一种选择支点元素的方法是使用“中间的中间( m e d i a n - o f - m e d i a n)”规则,该规则首先将数组a中的n 个元素分成n/r 组,r 为某一整常数,除了最后一组外,每组都有r 个元素。然后通过在每组中对r 个元素进行排序来寻找每组中位于中间位置的元素。最后根据所得到的n/r 个中间元素,递归使用选择算法,求得所需要的支点元素。

例2-6 [中间的中间] 考察如下情形:r=5, n=27, 并且a= [ 2,6,8,1,4,1 0,2 0,6,2 2,11,9,8,4,3,7,8,1 6,11,1 0,8,2,1 4,1 5,1,1 2,5,4 ]。这2 7个元素可以被分为6组[ 2 , 6 , 8 , 1 , 4 ],[ 1 0 , 2 0 , 6 , 2 2 , 11 ],[ 9 , 8 , 4 , 3 , 7 ],[ 8 , 1 6 , 11 , 1 0 , 8 ],[ 2 , 1 4 , 1 5 , 1 , 1 2 ]和[ 5 , 4 ],每组的中间元素分别为4 , 11 , 7 , 1 0 , 1 2和4。[ 4 , 11 , 7 , 1 0 , 1 2 , 4 ]的中间元素为7。这个中间元素7被取为支点元素。由此可以得到l e ft= [ 2 , 6 , 1 , 4 , 6 , 4 , 3 , 2 , 1 , 5 , 4 ],m i d d l e= [ 7 ] ,r i g h t= [ 8 , 1 0 , 2 0 , 2 2 , 11 , 9 , 8 , 8 , 1 6 , 11 , 1 0 , 8 , 1 4 , 1 5 , 1 2 ]。

如果要寻找第k个元素且k&lt; 1 2,则仅仅需要在l e f t中寻找;如果k= 1 2,则要找的元素就是支点元素;如果k&gt; 1 2,则需要检查r i g h t中的1 5个元素。在最后一种情况下,需在r i g h t中寻找第(k- 1 2 )个元素。

定理2-2 当按“中间的中间”规则选取支点元素时,以下结论为真:

1) 若r=9, 那么当n≥9 0时,有m a x { |l e f e|, |r i g h t| }≤7n / 8。

2) 若r= 5,且a 中所有元素都不同,那么当n≥2 4时,有max{| left |, | right | }≤3n/ 4。

证明这个定理的证明留作练习2 3。

根据定理2 - 2和程序1 4 - 7可知,如果采用“中间的中间”规则并取r= 9,则用于寻找第k个元素的时间t (n)可按如下递归公式来计算:

在上述递归公式中,假设当n<9 0时使用复杂性为nl o gn的求解算法,当n≥9 0时,采用“中间的中间”规则进行分而治之求解。利用归纳法可以证明,当n≥1时有t (n)≤7 2cn (练习2 4 )。

当元素互不相同时,可以使用r= 5来得到线性时间性能。

----------------解决方案--------------------------------------------------------
分而治之方法还可以用于实现另一种完全不同的排序方法,这种排序法称为快速排序(quick sort)。在这种方法中, n 个元素被分成三段(组):左段l e f t,右段r i g h t和中段m i d d l e。中段仅包含一个元素。左段中各元素都小于等于中段元素,右段中各元素都大于等于中段元素。因此l e f t和r i g h t中的元素可以独立排序,并且不必对l e f t和r i g h t的排序结果进行合并。m i d d l e中的元素被称为支点( p i v o t )。图1 4 - 9中给出了快速排序的伪代码。


/ /使用快速排序方法对a[ 0 :n- 1 ]排序

从a[ 0 :n- 1 ]中选择一个元素作为m i d d l e,该元素为支点

把余下的元素分割为两段left 和r i g h t,使得l e f t中的元素都小于等于支点,而right 中的元素都大于等于支点

递归地使用快速排序方法对left 进行排序

递归地使用快速排序方法对right 进行排序

所得结果为l e f t + m i d d l e + r i g h t

图14-9 快速排序的伪代码


考察元素序列[ 4 , 8 , 3 , 7 , 1 , 5 , 6 , 2 ]。假设选择元素6作为支点,则6位于m i d d l e;4,3,1,5,2位于l e f t;8,7位于r i g h t。当left 排好序后,所得结果为1,2,3,4,5;当r i g h t排好序后,所得结果为7,8。把right 中的元素放在支点元素之后, l e f t中的元素放在支点元素之前,即可得到最终的结果[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]。

把元素序列划分为l e f t、m i d d l e和r i g h t可以就地进行(见程序1 4 - 6)。在程序1 4 - 6中,支点总是取位置1中的元素。也可以采用其他选择方式来提高排序性能,本章稍后部分将给出这样一种选择。

程序14-6 快速排序

template&lt;class T&gt;

void QuickSort(T*a, int n)

{// 对a[0:n-1] 进行快速排序

{// 要求a[n] 必需有最大关键值

quickSort(a, 0, n-1);

template&lt;class T&gt;

void quickSort(T a[], int l, int r)

{// 排序a [ l : r ], a[r+1] 有大值

if (l &gt;= r) return;

int i = l, // 从左至右的游标

j = r + 1; // 从右到左的游标

T pivot = a[l];

// 把左侧&gt;= pivot的元素与右侧&lt;= pivot 的元素进行交换

while (true) {

do {// 在左侧寻找&gt;= pivot 的元素

i = i + 1;

} while (a[i] &lt; pivot);

do {// 在右侧寻找&lt;= pivot 的元素

j = j - 1;

} while (a[j] &gt; pivot);

if (i &gt;= j) break; // 未发现交换对象

Swap(a[i], a[j]);

}

// 设置p i v o t

a[l] = a[j];

a[j] = pivot;

quickSort(a, l, j-1); // 对左段排序

quickSort(a, j+1, r); // 对右段排序

}

----------------解决方案--------------------------------------------------------
若把程序1 4 - 6中d o - w h i l e条件内的&lt;号和&gt;号分别修改为&lt; =和&gt; =,程序1 4 - 6仍然正确。实验结果表明使用程序1 4 - 6的快速排序代码可以得到比较好的平均性能。为了消除程序中的递归,必须引入堆栈。不过,消除最后一个递归调用不须使用堆栈。消除递归调用的工作留作练习(练习1 3)。程序1 4 - 6所需要的递归栈空间为O (n)。若使用堆栈来模拟递归,则可以把这个空间减少为O ( l o gn)。在模拟过程中,首先对left 和right 中较小者进行排序,把较大者的边界放入堆栈中。在最坏情况下l e f t总是为空,快速排序所需的计算时间为(n2 )。在最好情况下, l e f t和r i g h t中的元素数目大致相同,快速排序的复杂性为(nl o gn)。令人吃惊的是,快速排序的平均复杂性也是(nl o gn)。

定理2-1 快速排序的平均复杂性为(nl o gn)。

证明用t (n) 代表对含有n 个元素的数组进行排序的平均时间。当n≤1时,t (n)≤d,d为某一常数。当n &lt;1时,用s 表示左段所含元素的个数。由于在中段中有一个支点元素,因此右段中元素的个数为n-s- 1。所以左段和右段的平均排序时间分别为t (s), t (n-s- 1 )。分割数组中元素所需要的时间用cn 表示,其中c 是一个常数。因为s 有同等机会取0 ~n- 1中的任何一个值.

如对(2 - 8)式中的n 使用归纳法,可得到t (n)≤kn l o ge n,其中n&gt; 1且k=2(c+d),e~2 . 7 1 8为自然对数的基底。在归纳开始时首先验证n= 2时公式的正确性。根据公式( 1 4 - 8),可以得到t( 2 )≤2c+ 2d≤k nl o ge 2。在归纳假设部分,假定t(n)≤kn l o ge n(当2≤n<m 时,m 是任意一个比2大的整数=.

图1 4 - 1 0对本书中所讨论的算法在平均条件下和最坏条件下的复杂性进行了比较。


方法最坏复杂性平均复杂性

冒泡排序n2 n2

计数排序n2 n2

插入排序n2 n2

选择排序n2 n2

堆排序nl o gn nl o gn

归并排序nl o gn nl o gn

快速排序n2 nl o gn

图14-10 各种排序算法的比较


中值快速排序( median-of-three quick sort)是程序1 4 - 6的一种变化,这种算法有更好的平均性能。注意到在程序1 4 - 6中总是选择a [ 1 ]做为支点,而在这种快速排序算法中,可以不必使用a [ 1 ]做为支点,而是取{a[1],a[(1+r)/2],a[r]} 中大小居中的那个元素作为支点。例如,假如有三个元素,大小分别为5,9,7,那么取7为支点。为了实现中值快速排序算法,一种最简单的方式就是首先选出中值元素并与a[1] 进行交换,然后利用程序1 4 - 6完成排序。如果a [ r ]是被选出的中值元素,那么将a[1] 与a[r] 进行交换,然后将a [ 1 ](即原来的a [ r ])赋值给程序1 4 - 6中的变量p i v o t,之后继续执行程序1 4 - 6中的其余代码。

图2 - 11中分别给出了根据实验所得到的归并排序、堆排序、插入排序、快速排序的平均时间。对于每一个不同的n, 都随机产生了至少1 0 0组整数。随机整数的产生是通过反复调用s t d l i b . h库中的r a n d o m函数来实现的。如果对一组整数进行排序的时间少于1 0个时钟滴答,则继续对其他组整数进行排序,直到所用的时间不低于1 0个时钟滴答。在图2 - 11中的数据包含产生随机整数的时间。对于每一个n,在各种排序法中用于产生随机整数及其他开销的时间是相同的。因此,图2 - 11中的数据对于比较各种排序算法是很有用的。

对于足够大的n,快速排序算法要比其他算法效率更高。从图中可以看到快速排序曲线与插入排序曲线的交点横坐标比2 0略小,可通过实验来确定这个交点横坐标的精确值。可以分别用n = 1 5 , 1 6 , 1 7 , 1 8 , 1 9进行实验,以寻找精确的交点。令精确的交点横坐标为nBr e a k。当n≤nBreak 时,插入排序的平均性能最佳。当n>nBreak 时,快速排序性能最佳。当n>nBreak 时,把插入排序与快速排序组合为一个排序函数,可以提高快速排序的性能,实现方法是把程序1 4 - 6中的以下语句:

if(l &gt;= r)r e t u r n ;

替换为

if (r-1&lt;nBreak) {InsertionSort(a,l,r); return;}

这里I n s e r t i o n S o r t ( a , l , r )用来对a [ 1 : r ]进行插入排序。测量修改后的快速排序算法的性能留作练习(练习2 0)。用更小的值替换n B r e a k有可能使性能进一步提高(见练习2 0)。

大多数实验表明,当n>c时(c为某一常数),在最坏情况下归并排序的性能也是最佳的。而当n≤c时,在最坏情况下插入排序的性能最佳。通过将插入排序与归并排序混合使用,可以提高归并排序的性能(练习2 1)。
----------------解决方案--------------------------------------------------------
可以运用分而治之方法来解决排序问题,该问题是将n 个元素排成非递减顺序。分而治之方法通常用以下的步骤来进行排序算法:若n 为1,算法终止;否则,将这一元素集合分割成两个或更多个子集合,对每一个子集合分别排序,然后将排好序的子集合归并为一个集合。

假设仅将n 个元素的集合分成两个子集合。现在需要确定如何进行子集合的划分。一种可能性就是把前面n- 1个元素放到第一个子集中(称为A),最后一个元素放到第二个子集里(称为B)。按照这种方式对A递归地进行排序。由于B仅含一个元素,所以它已经排序完毕,在A排完序后,只需要用程序2 - 1 0中的函数i n s e r t将A和B合并起来。把这种排序算法与I n s e r t i o n S o r t(见程序2 - 1 5)进行比较,可以发现这种排序算法实际上就是插入排序的递归算法。该算法的复杂性为O (n 2 )。把n 个元素划分成两个子集合的另一种方法是将含有最大值的元素放入B,剩下的放入A中。然后A被递归排序。为了合并排序后的A和B,只需要将B添加到A中即可。假如用函数M a x(见程序1 - 3 1)来找出最大元素,这种排序算法实际上就是S e l e c t i o n S o r t(见程序2 - 7)的递归算法。

假如用冒泡过程(见程序2 - 8)来寻找最大元素并把它移到最右边的位置,这种排序算法就是B u b b l e S o r t(见程序2 - 9)的递归算法。这两种递归排序算法的复杂性均为(n2 )。若一旦发现A已经被排好序就终止对A进行递归分割,则算法的复杂性为O(n2 )(见例2 - 1 6和2 - 1 7)。

上述分割方案将n 个元素分成两个极不平衡的集合A和B。A有n- 1个元素,而B仅含一个元素。下面来看一看采用平衡分割法会发生什么情况: A集合中含有n/k 个元素,B中包含其余的元素。递归地使用分而治之方法对A和B进行排序。然后采用一个被称之为归并( m e rg e)的过程,将已排好序的A和B合并成一个集合。

例2-5 考虑8个元素,值分别为[ 1 0,4,6,3,8,2,5,7 ]。如果选定k = 2,则[ 1 0 , 4 , 6 , 3 ]和[ 8 , 2 , 5 , 7 ]将被分别独立地排序。结果分别为[ 3 , 4 , 6 , 1 0 ]和[ 2 , 5 , 7 , 8 ]。从两个序列的头部开始归并这两个已排序的序列。元素2比3更小,被移到结果序列;3与5进行比较,3被移入结果序列;4与5比较,4被放入结果序列;5和6比较,.。如果选择k= 4,则序列[ 1 0 , 4 ]和[ 6 , 3 , 8 , 2 , 5 , 7 ]将被排序。排序结果分别为[ 4 , 1 0 ]和[ 2 , 3 , 5 , 6 , 7 , 8 ]。当这两个排好序的序列被归并后,即可得所需要的排序序列。

图2 - 6给出了分而治之排序算法的伪代码。算法中子集合的数目为2,A中含有n/k个元素。

template&lt;class T&gt;

void sort( T E, int n)

{ / /对E中的n 个元素进行排序, k为全局变量

if (n &gt;= k) {

i = n/k;

j = n-i;

令A 包含E中的前i 个元素

令B 包含E中余下的j 个元素

s o r t ( A , i ) ;

s o r t ( B , j ) ;

m e rge(A,B,E,i,j,); //把A 和B 合并到E

}

else 使用插入排序算法对E 进行排序

}

图14-6 分而治之排序算法的伪代码



从对归并过程的简略描述中,可以明显地看出归并n个元素所需要的时间为O (n)。设t (n)为分而治之排序算法(如图1 4 - 6所示)在最坏情况下所需花费的时间,则有以下递推公式:

其中c 和d 为常数。当n / k≈n-n / k 时,t (n) 的值最小。因此当k= 2时,也就是说,当两个子集合所包含的元素个数近似相等时, t (n) 最小,即当所划分的子集合大小接近时,分而治之算法通常具有最佳性能。

可以用迭代方法来计算这一递推方式,结果为t(n)= (nl o gn)。虽然这个结果是在n为2的幂时得到的,但对于所有的n,这一结果也是有效的,因为t(n) 是n 的非递减函数。t(n) =(nl o gn) 给出了归并排序的最好和最坏情况下的复杂性。由于最好和最坏情况下的复杂性是一样的,因此归并排序的平均复杂性为t (n)= (nl o gn)。

图2 - 6中k= 2的排序方法被称为归并排序( m e rge sort ),或更精确地说是二路归并排序(two-way merge sort)。下面根据图1 4 - 6中k= 2的情况(归并排序)来编写对n 个元素进行排序的C + +函数。一种最简单的方法就是将元素存储在链表中(即作为类c h a i n的成员(程序3 -8))。在这种情况下,通过移到第n/ 2个节点并打断此链,可将E分成两个大致相等的链表。

归并过程应能将两个已排序的链表归并在一起。如果希望把所得到C + +程序与堆排序和插入排序进行性能比较,那么就不能使用链表来实现归并排序,因为后两种排序方法中都没有使用链表。为了能与前面讨论过的排序函数作比较,归并排序函数必须用一个数组a来存储元素集合E,并在a 中返回排序后的元素序列。为此按照下述过程来对图1 4 - 6的伪代码进行细化:当集合E被化分成两个子集合时,可以不必把两个子集合的元素分别复制到A和B中,只需简单地在集合E中保持两个子集合的左右边界即可。接下来对a 中的初始序列进行排序,并将所得到的排序序列归并到一个新数组b中,最后将它们复制到a 中。图1 4 - 6的改进版见图1 4 - 7。



template&lt;class T&gt;

M e rgeSort( T a[], int left, int right)

{ / /对a [ l e f t : r i g h t ]中的元素进行排序

if (left &lt; right) {//至少两个元素

int i = (left + right)/2; //中心位置

M e rgeSort(a, left, i);

M e rgeSort(a, i+1, right);

M e rge(a, b, left, i, right); //从a 合并到b

Copy(b, a, left, right); //结果放回a

}

}

----------------解决方案--------------------------------------------------------
图14-7 分而治之排序算法的改进



可以从很多方面来改进图1 4 - 7的性能,例如,可以容易地消除递归。如果仔细地检查图1 4 - 7中的程序,就会发现其中的递归只是简单地重复分割元素序列,直到序列的长度变成1为止。当序列的长度变为1时即可进行归并操作,这个过程可以用n 为2的幂来很好地描述。长度为1的序列被归并为长度为2的有序序列;长度为2的序列接着被归并为长度为4的有序序列;这个过程不断地重复直到归并为长度为n 的序列。图1 4 - 8给出n= 8时的归并(和复制)过程,方括号表示一个已排序序列的首和尾。



初始序列[8] [4] [5] [6] [2] [1] [7] [3]

归并到b [4 8] [5 6] [1 2] [3 7]

复制到a [4 8] [5 6] [1 2] [3 7]

归并到b [4 5 6 8] [1 2 3 7]

复制到a [4 5 6 8] [1 2 3 7]

归并到b [1 2 3 4 5 6 7 8]

复制到a [1 2 3 4 5 6 7 8]

图14-8 归并排序的例子



另一种二路归并排序算法是这样的:首先将每两个相邻的大小为1的子序列归并,然后对上一次归并所得到的大小为2的子序列进行相邻归并,如此反复,直至最后归并到一个序列,归并过程完成。通过轮流地将元素从a 归并到b 并从b 归并到a,可以虚拟地消除复制过程。二路归并排序算法见程序1 4 - 3。

程序14-3 二路归并排序

template&lt;class T&gt;

void MergeSort(T a[], int n)

{// 使用归并排序算法对a[0:n-1] 进行排序

T *b = new T [n];

int s = 1; // 段的大小

while (s &lt; n) {

MergePass(a, b, s, n); // 从a归并到b

s += s;

MergePass(b, a, s, n); // 从b 归并到a

s += s;

}

}

为了完成排序代码,首先需要完成函数M e rg e P a s s。函数M e rg e P a s s(见程序1 4 - 4)仅用来确定欲归并子序列的左端和右端,实际的归并工作由函数M e rg e (见程序1 4 - 5 )来完成。函数M e rg e要求针对类型T定义一个操作符&lt; =。如果需要排序的数据类型是用户自定义类型,则必须重载操作符&lt; =。这种设计方法允许我们按元素的任一个域进行排序。重载操作符&lt; =的目的是用来比较需要排序的域。

程序14-4 MergePass函数

template&lt;class T&gt;

void MergePass(T x[], T y[], int s, int n)

{// 归并大小为s的相邻段

int i = 0;

while (i &lt;= n - 2 * s) {

// 归并两个大小为s的相邻段

Merge(x, y, i, i+s-1, i+2*s-1);

i = i + 2 * s;

}

// 剩下不足2个元素

if (i + s &lt; n) Merge(x, y, i, i+s-1, n-1);

else for (int j = i; j &lt;= n-1; j++)

// 把最后一段复制到y

y[j] = x[j];

}

程序14-5 Merge函数

template&lt;class T&gt;

void Merge(T c[], T d[], int l, int m, int r)

{// 把c[l:m]] 和c[m:r] 归并到d [ l : r ] .

int i = l, // 第一段的游标

j = m+1, // 第二段的游标

k = l; // 结果的游标

/ /只要在段中存在i和j,则不断进行归并

while ((i &lt;= m) &amp;&amp; (j &lt;= r))

if (c[i] &lt;= c[j]) d[k++] = c[i++];

else d[k++] = c[j++];

// 考虑余下的部分

if (i &gt; m) for (int q = j; q &lt;= r; q++)

d[k++] = c[q];

else for (int q = i; q &lt;= m; q++)

d[k++] = c[q];

}

自然归并排序(natural merge sort)是基本归并排序(见程序1 4 - 3)的一种变化。它首先对输入序列中已经存在的有序子序列进行归并。例如,元素序列[ 4,8,3,7,1,5,6,2 ]中包含有序的子序列[ 4,8 ],[ 3,7 ],[ 1,5,6 ]和[ 2 ],这些子序列是按从左至右的顺序对元素表进行扫描而产生的,若位置i 的元素比位置i+ 1的元素大,则从位置i 进行分割。对于上面这个元素序列,可找到四个子序列,子序列1和子序列2归并可得[ 3 , 4 , 7 , 8 ],子序列3和子序列4归并可得[ 1 , 2 , 5 , 6 ],最后,归并这两个子序列得到[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]。因此,对于上述元素序列,仅仅使用了两趟归并,而程序1 4 - 3从大小为1的子序列开始,需使用三趟归并。作为一个极端的例子,假设输入的元素序列已经排好序并有n个元素,自然归并排序法将准确地识别该序列不必进行归并排序,但程序1 4 - 3仍需要进行[ l o g2 n] 趟归并。因此自然归并排序将在(n) 的时间内完成排序。而程序1 4 - 3将花费(n l o gn) 的时间。

----------------解决方案--------------------------------------------------------