25
程程程程程程程程程程程程程 程程

《 程序设计实践 》

  • Upload
    tom

  • View
    79

  • Download
    7

Embed Size (px)

DESCRIPTION

《 程序设计实践 》. 程序设计方法之 搜索. 搜索的本质. 一句话的解释:在 所有的情况 中寻找 符合要求 的情况 类比的说法:搜索即一种 特殊 枚举 辨别:搜索是 有选择的、有一定效率的 ,枚举是 无选择的、低效率的. 题目引入: 傻大木买军火. 要求: - PowerPoint PPT Presentation

Citation preview

Page 1: 《 程序设计实践 》

《程序设计实践》

程序设计方法之搜索

Page 2: 《 程序设计实践 》

搜索的本质 一句话的解释:在所有的情况中寻找符合要求的情况 类比的说法:搜索即一种特殊枚举 辨别:搜索是有选择的、有一定效率的,枚举是无选择的、低效率的

Page 3: 《 程序设计实践 》

题目引入:傻大木买军火 要求: 1. 傻大木共购买了三种武器: 2 万美元一个的木瓜手雷,

6 万美元一支的啊卡卡 47 型冲锋枪和 1 万美元一个的大杀器。 2. 三种武器的数量各不相同。 3. 傻大木购买的木瓜手雷的个数在大杀器个数和冲锋枪支数之间。 4. 木瓜手雷必须成对购买。 5. 为了图吉利,傻大木购买的大杀器个数的尾数是 8(如 8 , 48 等)。 6. 如果冲锋枪的支数是一位数,那么木瓜手雷的个数一定是两位数。(但如果冲锋枪的支数不是一位数,那么木瓜手雷的个数可能是任何数。) 7. 傻大木的 n 万元可能恰好花完了,也可能没花完,但一定花了九成以上。

Page 4: 《 程序设计实践 》

题目的简单思路 for size_bomb = min_possible to max_possible

for size_kaka = min_possible to max_possible

for size_bigkiller = min_possible to max_possible

If Accept( size_bomb , size_kaka , size_bigkiller)

{

Output( size_bomb , size_kaka , size_bigkiller )

}

Page 5: 《 程序设计实践 》

总结基本思路 枚举所有可能的情况,再在所有可能的情况中寻找符合要求的情况 同时我们可以发现,这道题目的变量数目

是 3 (炸弹、咔咔、大杀器),是一个常量,也就是说,三层循环就可以枚举出所有可能的情况,那么对于变量数目为一个变量 n 的情况呢? 不妨看下一道题目

Page 6: 《 程序设计实践 》

深入:计算正行列式项 由键盘输入 n ,和一个 n*n 的矩阵,输出所有计算行列式时要用到的正项之和。 For example : Input:

21 23 4

Output:4

Page 7: 《 程序设计实践 》

解题思路 生成所有排列,即确定 n 个变量:

j1 、 j2……jn 的值 对于每一组 j1……jn ,计算对应逆序数 用求得的逆序数结合输入的矩阵计算出该项的值 t 若 t>0 加到和 sum 中

若 t<0 不做任何处理 最后输出 sum

Page 8: 《 程序设计实践 》

总结大概的代码: for j1 = 1 to n do

for j2 = 1 to n do……for jn = 1 to n doif 任意两个 ji , jk 互不相等{

t = calc( j1 , j2 …… jn)// 计算逆序数t = a[1][j1] * a[2][j2] * … * a[n][jn]if ( t > 0 ) sum = sum + t ;

}cout << sum ;

Page 9: 《 程序设计实践 》

我们发现: 要使用 n 层循环,也就是说,循环的个数是动态的 如何解决这个问题?

使用递归的方法, n 层循环转化成 n 层递归 具体方法,看如下的程序

Page 10: 《 程序设计实践 》

bool used[MAXN] ; // 用来实现 j1...jn 互不相同int j[MAXN] ;int t ;int sum ;

void Search( int k ){

if ( k > n ){

t = calc(); // 对于储存在数组 j 中的 n 个变量计算逆序数int i ;for ( i = 1 ; i <= n ; i++ )

t = t * j[i] ; // t = t * j1 * j2 * ... * jnif (t > 0) sum += t ; // sum = sum + t

return ;}for ( j[k] = 1 ; j[k] <= n ; j[k] ++ )if ( !used[j[k]] ) // 判断 j[k] 是否能取{

used[j[k]] = true; Search( k + 1 );used[j[k]] = false;

}}

Page 11: 《 程序设计实践 》

解决了这个问题我们就可以给出搜索问题的模版程序

Page 12: 《 程序设计实践 》

int j[MAXN] // MAXN 是可能的参数个数void Search( int k ){

if ( k > n ) // k 个参数的值已经确定了,验证是否符合条件{

if ( Accpet() ) // 如果满足条件{ ... ... } // 做相应操作return // 退出

}// 对于第 k 个变量 jk, 枚举所有可能的值for j[k] = each possible case if ( available( j[k] ) ) // 如果 jk 可以取这个值{

... // 完成相应操作,譬如 used[j[k]] = true;Search( k + 1 ); // 搜索下一个变量 j[k+1]... // 完成相应操作,譬如 used[j[k]] = false;

}}

Page 13: 《 程序设计实践 》

解决实际问题,案例 1 : travel 有一个 n*m 的棋盘,如图所示,骑士 X 最开始站在方格 (1,1) 中,目的地是方格 (n,m) 。他的每次都只能移动到上、左、右相邻的任意一个方格。每个方格中都有一定数量的宝物 k (可能为负),对于任意方格,骑士 X 能且只能经过最多 1 次(因此从 (1,1) 点出发后就不能再回到该点了)。

你的任务是,帮助骑士 X 从 (1,1) 点移动到 (n,m)点,且使得他获得的宝物数最多。

Page 14: 《 程序设计实践 》

按照一般的思路进行分析 枚举所有的情况:即枚举所有可能的路径 Search 的参数由两个变量构成: int x , y Search 函数枚举在 (x,y) 点处可能的走法,即:上、左、右 边界条件为 (x==n)&&(y==m) 当达到边界是,如果这条路径上的值大于已经发现的最大值,则更新最大宝藏值

Page 15: 《 程序设计实践 》

int d[3][2] = {{1,0},{0,-1},{0,1}}; // 存储 3 种行走方式对应的坐标变化值int data[MAXN+1][MAXM+1]; // 储存 (x,y) 中的宝藏价值int max;bool used[MAXN+1][MAXM+1]; // 用来辅助判断 (x,y) 是否走到过void Search( int x , int y , int sum)// 三个参数表示状态{

if (( x == n ) &&   ( y == m )) // 是否达到边界条件{

if ( sum > max ) // 如果大于已有最大值max = sum ; // 更新

return ; // 返回}int i ;for ( i = 0 ; i <= 2 ; i ++ ) // 枚举每种可能的走法if ( !used[x+d[i][0]][y+d[i][1]] ) // 判断这样走是否走重{

Search( x + d[i][0] , y + d[i][1] , data[x][y]+ data[x+d[i][0]][y+d[i][1]] ); // 搜索这个格子

}}

Page 16: 《 程序设计实践 》

案例 2 :滑雪 小袁非常喜欢滑雪, 因为滑雪很刺激。为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。小袁想知道在某个区域中最长的一个滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。如下: 一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为

24-17-16-1 。当然 25-24-23-...-3-2-1 更长。事实上,这是最长的一条。 你的任务就是找到最长的一条滑坡,并且将滑坡的长度输出。 滑坡的长度定义为经过点的个数,例如滑坡 24-17-16-1 的长度是 4 。

Page 17: 《 程序设计实践 》

继续按一般的思路分析 由于这个题目中间起点有多种可能: (1,1)

到 (n,m) 都有可能,故要考虑从多个起点开始进行搜索 对每个点枚举可能的情况:上下左右四个方向 如果从某个起点 (x0,y0) 走到某个点 (x,y)时,发现经过的路径长度 length > max ,则更新 max 最后输出 max 的值

Page 18: 《 程序设计实践 》

bool avail( int x1 , int y1 , int y1 , int y2 ); //avail 函数判断是否 (x1,y1) 的高度大于 (x2,y2) 的高度int max ; // 记录最大值void Search( int x , int y , int length ){

if ( length > max ) max = length ; // 如果到达 (x,y) 点时走过的路径已经大于 max, 更新 max 的值if ( avail( x , y , x - 1 , y ) ) Search( x - 1 , y , length + 1 ) ;if ( avail( x , y , x + 1 , y ) ) Search( x + 1 , y , length + 1 ) ;if ( avail( x , y , x , y - 1 ) ) Search( x , y - 1 , length + 1 ) ;if ( avail( x , y , x , y + 1 ) ) Search( x , y + 1 , length + 1 ) ;// 分别判断 (x,y) 的四个方向是否可走,如果可走,搜索这个方向

}

Page 19: 《 程序设计实践 》

main 函数的处理:int main(){

...int i , j ;max = 1; for ( i = 1 ; i <= n ; i ++ )

for ( j = 1 ; j <= n ; j ++ )Search( i , j , 1 );

// 分别把每个点作为起点展开搜索cout << max ;...

}

Page 20: 《 程序设计实践 》

最后,我们总结搜索题的一般特性 题目涉及的情况多 规律不明显,无法推到出题目的规律或者找到一个绝对可行的数学公式 一般要求是求出符合条件的解,求最大、最小值,求某种路径 符合以上特征的题目是多的,大家相信已经有一些感受:搜索是一种相当重要的算法

Page 21: 《 程序设计实践 》

解决搜索题目的一般方法 #1 分析题目中的所有可能情况,即搜索的对象(如傻大木为所有军火组合、行列式那道题目为所有的 1…n 组合、 travel 是所有

的 (1,1) 到 (n,m) 的路径、滑雪是所有的递减型滑道) 分析题目搜索的目标(即傻大木中符合条件的军火搭配、行列式中的正项、藏宝中的最大价值路径、滑雪中的最长滑道)

Page 22: 《 程序设计实践 》

解决搜索题目的一般方法 #2 分析为了实现搜索目的,而要用的表示状态的方法

具体而言,即对应的函数的头部:行列式: void search( int k )travel : void search( int x , int y , int sum )滑雪: void search( int x , int y , int length )要保证参数变量准确、完备的表示出每个状态的情况

Page 23: 《 程序设计实践 》

解决搜索题目的一般方法 #3 确定每个状态中状态转移的方式即搜索中的主程序部分

void Search( int x , int y , int length ){

if ( length > max ) max = length ; if ( avail( x , y , x - 1 , y ) ) Search( x - 1 , y , length +

1 ) ;if ( avail( x , y , x + 1 , y ) ) Search( x + 1 , y , length +

1 ) ;if ( avail( x , y , x , y - 1 ) ) Search( x , y - 1 , length +

1 ) ;if ( avail( x , y , x , y + 1 ) ) Search( x , y + 1 , length +

1 ) ;}

Page 24: 《 程序设计实践 》

解决搜索题目的一般方法 #4 根据确定的搜索目标确定边界条件,如:

if (( x == n ) &&   ( y == m )) // 是否达到边界条件{

if ( sum > max ) // 如果大于已有最大值max = sum ; // 更新

return ; // 返回}

Page 25: 《 程序设计实践 》

解决搜索题目的一般方法 #5 合成所有代码,即得到搜索的程序

P.S: 搜索是大量高级算法的思想基础和源头,好好掌握搜索对大家的解题能力有极大的帮助,希望大家多实践,达到融会贯通的程度 !