分治问题
Table of Contents
1 分治算法
分治(divide-and-conquer)算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互 独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。。这个技巧是很多高效算法的基础,如排序算 法(快速排序,归并排序),傅立叶变换(快速傅立叶变换),二叉树搜索等。
2 分治法求最大值
为了更好的理解分治算法,在进入正题前我们先去一个简单的例子。下面以求数组里N个项中最大的项为例进行说明。
- 我们可以通过普通的数组遍历求出
for(t=a[0],i=1;i<N;i++) if(a[i]>t) t=a[i];
- 通过分治法
Item max(Item a[],int l,int r) { Item u,v; int m=(l+r)/2; if(l==r) return a[l]; u=max(a,l,m); v=max(a,m,r); if(u>v) return u; else return v; }
3 汉诺塔(tower of Hanoi)问题
对于递归,对于分治问题,如果不讨论古老的汉诺塔问题,好像有点不完整。
3.1 汉诺塔问题的递归解
它指定每一步应该移动那个圆盘,以及移动的方向("+"表示向右移动装上的盘,但到达最右端的时候循环到最左 端;"-" 表示向左移动装上的盘,但到达最左端的时候循环到最右端。)
3.1.1 思路
要想从某根移动N个盘到后面的柱子上:
- 首先移动N-1个小盘到最左边的柱子上(内部递归性)
- 其次把第N个圆盘移动到右边的柱子上
- 接着,(递归性的)将N-1个圆盘放到第N个盘的上面就完成了。
3.1.2 解决方案
void hanoi(int N,int d) { if(N==0) return; hanoi(N-1,-d); //对应1 shift(N,d); //对应2 hanoi(N-1,d); //对应3 }
3.1.3 具体实现
#include <stdio.h> static int count; void shift(int N,int d) { if(d>0) printf("+"); printf("%d\n",N*d); ++count; } void hanoi(int N,int d) { if(N==0) return ; hanoi(N-1,-d); shift(N,d); hanoi(N-1,-d); } int main(int argc, char *argv[]) { int N; printf("请输入要移动的汉诺塔层数:\n"); scanf("%d",&N); hanoi(N,1); // 1表示右移,-1表示左移 printf("总的步数为:%d\n",count); return 0; }
3.1.4 性质
汉诺塔问题的递归分治算法得到的解需要2N -1步移动。
3.2 分治法绘制标尺
这个问题和汉诺塔有什么关系呢?让我们来看看:
3.2.1 问题描述
在一个尺子上,每隔1英寸就在1/2英寸处画上一个标记,以1/4英寸为间隔画上稍短的标志,一次类推。我们设计 一个程序以任意分辨率画出这些标志。
我们重新设置比例让任务变成在0和2n 之间每个点出画一个标记。以n=3为例,如图:
3.2.2 递归实现
void rule(int l,int r,int n) { int m=(r+l)/2; if(h>0) { rule(l,m,h-1); mark(m,h); rule(m,r,h-1); } }
3.2.3 分析
当函数中n=3时,长度为8的标尺得到的标尺长度分别为:1、2、1、3、1、2、1。通过结果可以看出,长度序列与 汉诺塔问题中移动圆盘的序列完全一样。实际上递归程序是相同的缘故。这两个程序都是基本的分治方法的演化。
3.3 二进制与汉诺塔的关系
注意看图中,偶数行中尾部0的个数。是不是也一样,是不是很神奇,哈哈。
3.4 结论
对于汉诺塔问题,n位数字所蕴含的就是该问题的一个简单的算法。我们如果在实际中怎么来移动上面的圆盘呢:
- 如果我们想把整个圆盘移动到右边的柱子上,则
- 若圆盘的个数N为奇数,将最小盘想同方向移动。
- 若圆盘的个数N为偶数,将最小盘想反方向移动。
- 不包括小盘,进行唯一一步合法的操作。(就是我们在移动了最小的盘之后,另外两个柱子上唯一合法的操作 就是把较小的盘移动的较大的盘上)。
- 每隔一步移动一下最小盘,原则同1。(就如同标尺上没隔一个刻度都是最短的一样)。