6.1.3 汉诺塔问题
【问题】 汉诺塔问题(Hanio tower problem)来自一个古老的传说:有一座宝塔(塔A),其上有64个金碟,所有碟子按从大到小由塔底堆放至塔顶。紧挨着这座宝塔有另外两座宝塔(塔B和塔C),要求把塔A上的碟子移动到塔C上去,其间可以借助于塔 B。每次只能移动一个碟子,任何时候都不能把一个碟子放在比它小的碟子上面。
【想法】对于n个碟子的汉诺塔问题,可以通过以下三个步骤实现:
(1)将塔A上的n一1个碟子借助塔C先移到塔B上;
(2)将塔A上剩下的一个碟子移到塔C上;
(3)将n-1个碟子从塔B借助于塔 A 移到塔C上。
当n=3时的求解过程如图6-2所示,显然这是一个递归求解的过程
【算法分析】 汉诺塔问题的递归算法存在如下递推关系式:
使用扩展递归技术对递推式进行推导,得到该递推式的解O (2^n)。
【算法实现】 设丽数 Hanio 实现将n个碟子从塔A 借助塔B移到塔C上,字符型形参A、B和C表示塔A、塔B和塔C, 程序如下。
#include <iostream>
using namespace std;
void Hanio(int n, char A, char B, char C)
{
if (n == 1)
cout<<A<<"-->"<<C<<"\t";
else
{
Hanio(n-1, A, C, B);
cout<<A<<"-->"<<C<<"\t";
Hanio(n-1, B, A, C);
}
}
int main( )
{
char A = 'A', B = 'B', C = 'C';
int n;
cout<<"请输入碟子的个数:";
cin>>n;
Hanio(n, A, B, C);
return 0;
}
6.2.1 归并排序
【问题】 归并排序(merge sort)的分治策略如下(图6-3)。
(1)划分:将待排序序列从中间位置划分为两个长度相等的子序列
(2) 求解子问题:分别对这两个子序列进行排序,得到两个有序子序列。
(3) 合并:将这两个有序子序列合并成一个有序序列
【想法】归并排序首先执行划分过程,将序列划分为两个子序列,如果子序列的长度
为1,则划分结束,否则继续执行划分,结果将具有n个记录的待排序序列划分为n个长度为1的有序子序列;然后进行两两合并,得到[n/2]个长度为2(最后一个有序序列的长度可能是1)的有序子序列,再进行两两合并,得到[n/4]个长度为4的有序序列(最后-个有序序列的长度可能小于4),以此类推,直至得到一个长度为n的有序序列。图6-4给出了归并排序的例子。
【算法】 设对数组r[n]进行升序排列,算法如下。
算法:归并排序 MergeSort
输人:待排序数组r[n],待排序区间[s,t]
输出:升序序列r[s]~r[t]
1.如果s等于t,则待排序区间只有一个记录,算法结束;
2.计算划分的位置:m=(s+t)/2;
3.对前半个子序列比r[s]~r[m]进行升序排列;
4.对后半个子序列 r[m+1]~r[t]进行升序排列;
5.合并两个升序序列r[s]~r[m]和r[m+1]~r[t];
【算法分析】 设待排序记录个数为n,则执行一趟合并算法的时间复杂度为O(n).所以,归并排序算法存在如下递推式:
使用扩展递归技术对递推式进行推导,归并排序的时间复杂度是O(nlog以2为底对数n)。
【算法实现】 归并排序的合并操作需要将两个相邻的有序子序列合并为一个有序序列,在合并过程中可能会破坏原来的有序序列,所以,合并不能就地进行。设将有序子序列r[s]~r[m]和r[m+1]~r[t]合并为有序序列r1[s]~rl[t], 再将合并结果传回数组r[s]~r[t],设函数 Merge实现合并操作,函数 MergeSort 实现归并排序,程序如下。
#include <iostream>
using namespace std;
void Merge(int r[ ], int s, int m, int t);
void MergeSort(int r[ ], int s, int t);
int main( )
{
int i, n = 8, r[8] = {8, 3,2, 6, 7, 1, 5, 4};
for (i = 0; i < n; i++) {
cout << r[i] << " ";
}
cout << endl;
MergeSort(r, 0, n - 1);
for (i = 0; i < n; i++) {
cout << r[i] << " ";
}
cout << endl;
return 0;
}
void Merge(int r[ ], int s, int m, int t)
{
int r1[t];
int i = s, j = m + 1, k = s;
while (i <= m && j <= t)
{
if (r[i] <= r[j]) r1[k++] = r[i++]; //较小者放入r1[k]
else r1[k++] = r[j++];
}
while (i <= m) //处理第一个子序列剩余记录
r1[k++] = r[i++];
while (j <= t) //处理第二个子序列剩余记录
r1[k++] = r[j++];
for (i = s; i <= t; i++) //将合并结果传回数组r
r[i] = r1[i];
}
void MergeSort(int r[ ], int s, int t) //对序列r[s]~r[t]进行归并排序
{
if (s == t) return; //只有一个记录,已经有序
else
{
int m = (s + t)/2; //划分
MergeSort(r, s, m); //归并排序前半个子序列
MergeSort(r, m+1, t); //归并排序后半个子序列
Merge(r, s, m, t); //合并两个有序子序列
}
}