算法期末备考-第1练
考虑到 大家针对备考 算法无从下手。
同时算法是最后一门考试科目,可能复习比较匆忙就考试了。
从今天开始每天进行一练,希望大家每天花上至少一个小时来复习,只要大家重视起这门课,就不会挂科。
算法是以理解为基础。
“理解是最好的记忆”
不要背代码,不要背代码,不要背代码。
等你理解算法核心后,做题和复习起来才会胸有成竹
内容主要分为
1、掌握算法基本思路
2、历年真题
3、课后习题
如果发现代码有错误或者有疑问请及时联系我。
放在博客上,主要是因为比较容易修改。
不用光顾着看,大脑非常容易欺骗自己,大家必须动手实践一下。
道理就好比记单词。
建议:
通过热身环节后,大家自己试着写一下后面的题目。
如果遇到不会才看参考代码,千万不要先入为主直接背代码,这样效率低而且容易忘。
热身环节
子集树问题
题目描述:
给定3个元素的一个集合,输出对应的所有子集的情况。
3个元素构成的子集为2^3=8种情况。
分别为“111,110,101,100,011,010,001,000”
题解:
面对子集的问题有很多种解法,其中一种方法为BFS,对于这颗搜索树来说是层次遍历。
顺带借助子集树问题来介绍一下BFS算法。
介绍
BFS 是 Breadth first Search 的首字母组合。
中文翻译回来是“宽度优先搜索”
在这个题目中就相当于"层次遍历",一层一层地遍历,其字母顺序为:“A,BC,DFGH,IJKLMNO”。
算法过程
过程中利用到了数据结构“队列”利用其先进先出的特性。
请大家拿出草稿纸模拟其中过程。
1、把根节点A放入队列。
2、弹出队首结点,同时将 队首结点的左右两个结点依次放入队列。
3、重复第二步就能实现层次遍历。
1、A
2、“A” | 'BC'
3、“B” | C'DE'
4、“C” | DE'FG'
……
图示:
弹出队首用“”表示。
新加入队列的元素用''表示。
" | " 后面的是当前队列中的元素。
前置知识:
过程中需要用到队列,在此之前必须要知道如何定义队列中元素的属性。
“属性”,其实根据题目来说的,
通常根据题目进行加属性。
如果是搜索树,以树为结构的必定要有一个变量作为层数 我常用Step来表示。
如迷宫类问题,必须有用于定位的坐标"int x,y"。
如果是背包问题,每个结点表示都是一种状态,每个背包都有自身的价值和重量。“int 价值:val,重量:w”
如果是整数变换问题,每个结点都有各自的值。“int x”
如果题目需要记录对应的路径,则定义“ char path[N]”
typedef struct Node{ int step ; char path[N]; }Node;
然后是C++库中提供的STL库中的queue
#include<queue> //对应的头文件 using namespace std; //利用STL库一定记住要写上"命名空间" <queue> //定义 queue<Node>Q; //定义存放Node类型队列,其名为"Q" //库函数 Q.push( (Node)*** ); //把Node类型变量,插入到Q的队尾 Node t = Q.front(); //将队首元素进行赋值给Node类型的变量t Q.pop(); //弹出队首元素 Q.empty(); //判断当前的队列中是否为空? 返回bool类型 , 若是真的为空,返回true,否则返回 false
算法实现的大体思路:
将队列中队首结点拿出来,判断其结点是否为叶子结点。
1、如果为叶子结点,输出叶子结点中的路径
2、否则,把自己与其相连的两个孩子结点给插入到队列后面。
然后具体套路模版上课已经讲过,如果忘记了的同学可以看看下面的代码复习一下具体过程。

1 //子集树 2 3 #include<queue> 4 #include<cstdio> 5 using namespace std; 6 7 //定义结点记录信息 8 typedef struct Node{ 9 char path[6]; 10 int step ; 11 }Node; 12 13 // BFS套路 14 void BFS(){ 15 16 //第一步:定义根节点和队列,并把根节点压入队列 17 queue<Node> Q ; 18 Node first ; 19 first.step = 0 ; 20 Q.push(first) ; 21 22 //第二步:保持队列非空 23 while( !Q.empty() ){ 24 25 //第三步:取出队首元素,别忘了同时要弹出队首元素 26 Node cur = Q.front() ; 27 Q.pop() ; 28 29 //第四步:设计 最终状态 进行的操作 30 if( cur.step == 3 ){ 31 for( int i = 1 ; i <= 3 ; i ++ ){ 32 printf("%c",cur.path[i]); 33 } 34 putchar(' '); 35 continue ; 36 } 37 38 //第五步:设计 下一步怎么走,用"Next"来命名下一步结点,压入队尾 39 40 Node Next = cur ; 41 Next.step += 1 ; 42 43 //走左边 44 Next.path[ cur.step + 1 ] = 'L' ; 45 Q.push( Next ); 46 47 //走右边 48 Next.path[ cur.step + 1 ] = 'R' ; 49 Q.push( Next ); 50 } 51 } 52 53 int main() 54 { 55 BFS(); 56 return 0; 57 }
历年真题
01背包问题
题目描述:
有一个背包容量为30;同时有三个物品<value,weight>为<45,16>,<25,15>,<25,15>
请问,对于背包能承受的最大重量来说,背包的最大价值为多少?
题解:
以子集树为基础,每个结点只是多了价值和重量。
但是非叶子结点在放入物品时必须必须要加上限制条件,必须是背包能放入的情况下才插入队尾。

1 //01背包问题 2 3 #include<queue> 4 #include<cstdio> 5 #include<algorithm> 6 using namespace std; 7 8 //定义结点记录信息 9 typedef struct Node{ 10 int val , w ; 11 int step ; 12 }Node; 13 14 int weight[3] = { 16 , 15 , 15 }; 15 int value[3] = { 45 , 25 , 25 }; 16 int V = 30 ; 17 18 // BFS套路 19 void BFS(){ 20 21 int n = 3 ; // 物品个数 22 int ans = 0 ; // 答案 23 24 //第一步:定义根节点和队列,并把根节点压入队列 25 queue<Node> Q ; 26 Node first ; 27 first.step = first.val = first.w = 0 ; 28 Q.push(first) ; 29 30 //第二步:保持队列非空 31 while( !Q.empty() ){ 32 33 //第三步:取出队首元素,别忘了同时要弹出队首元素 34 Node cur = Q.front() , Next ; 35 Q.pop() ; 36 37 //第四步:设计 最终状态 进行的操作 <=> 到达叶子结点. 38 if( cur.step == n ){ 39 ans = max( ans , cur.val ); 40 continue ; 41 } 42 43 //第五步:设计 下一步怎么走,用"Next"来命名下一步结点,压入队尾 44 Next = cur ; 45 Next.step += 1 ; 46 47 //该操作为:物品不放进背包 <=> 子集树往左边走 48 Q.push( Next ); 49 50 //该操作为:物品放入背包 <=> 子集树往右边走 51 //前提是:保证物品能放入背包 52 if( Next.w + weight[ cur.step ] <= V ){ 53 Next.w += weight[ cur.step ]; 54 Next.val += value[ cur.step ]; 55 Q.push( Next ) ; 56 } 57 } 58 printf("%d ",ans); 59 } 60 61 int main() 62 { 63 BFS(); 64 return 0; 65 }
布线问题
题目描述:
给定一个迷宫,其中白色为路,灰色为墙,每移动一格就说明距离加一,给定起点和终点。
请问 a->b的最短距离为多少?
题解:
对于这类迷宫题目,记住
定义结点是{x,y,step}的三元组,maze[][]迷宫路和墙的情况,vis[][]迷宫是否被访问,dir[4][2]方向数组
向四个方向遍历时必须也要做到三个判断:
1、不在边界,即(x,y)要合法
2、maze[x][y]必须为路,如果时墙就走不过去。
3、vis[x][y]看看x,y是否访问过。
注意细节,然后到达终点提前结束即可。

1 //布线问题 2 3 #include<queue> 4 #include<cstdio> 5 #include<algorithm> 6 using namespace std; 7 8 //定义结点记录信息 9 typedef struct Node{ 10 int x , y ; 11 int step ; 12 }Node; 13 14 int dir[4][2] = { 15 {-1,0}, 16 {0,-1} , {0,1}, 17 {1,0} 18 }; 19 20 int maze[9][9] ; 21 int vis[9][9] ; 22 int n = 9 , m = 9 ; 23 int Sx,Sy , Ex,Ey ; //Start( x , y ) , End( x ,y ) 24 25 void Init(){ 26 maze[1][3] = maze[2][3] = maze[2][4] = 27 maze[3][5] = maze[4][4] = maze[4][5] = 28 maze[5][5] = maze[6][1] = maze[7][1] = 29 maze[7][2] = maze[7][3] = maze[8][1] = 30 maze[8][2] = maze[8][3] = 1; 31 32 vis[1][3] = vis[2][3] = vis[2][4] = 33 vis[3][5] = vis[4][4] = vis[4][5] = 34 vis[5][5] = vis[6][1] = vis[7][1] = 35 vis[7][2] = vis[7][3] = vis[8][1] = 36 vis[8][2] = vis[8][3] = -1; 37 Sx = 3 , Sy = 2 ; 38 Ex = 4 , Ey = 7 ; 39 40 //起点特定标记 41 vis[Sx][Sy] = -1 ; 42 } 43 44 // BFS套路 45 void BFS(){ 46 47 //是否能到达终点 的标记位 48 bool flag = false ; 49 50 //第一步:定义根节点和队列,并把根节点压入队列 51 queue<Node> Q ; 52 Node first = Node{ Sx , Sy , 0 } ; 53 Q.push(first) ; 54 55 //第二步:保持队列非空 56 while( !Q.empty() ){ 57 58 //第三步:取出队首元素,别忘了同时要弹出队首元素 59 Node cur = Q.front() , Next ; 60 Q.pop() ; 61 62 //第四步:设计 最终状态 进行的操作 63 if( cur.x == Ex && cur.y == Ey ){ 64 printf("The Shortest Path : %d ",cur.step ) ; 65 flag = true ; 66 break ; 67 } 68 69 //第五步:设计 下一步怎么走,用"Next"来命名下一步结点,压入队尾 70 71 for( int i = 0 ; i < 4 ; i++ ){ 72 int tx = cur.x + dir[i][0] ; 73 int ty = cur.y + dir[i][1] ; 74 if( ( 1 <= tx && tx <= n && 1 <= ty && ty <= m ) && maze[tx][ty] == 0 ){ 75 if( vis[tx][ty] == 0 ){ 76 vis[tx][ty] = cur.step + 1 ; 77 Next = Node{ tx , ty , cur.step + 1 } ; 78 Q.push( Next ); 79 } 80 } 81 } 82 } 83 // 如果没有到达终点输出"Impossible" 84 if( !flag ){ 85 printf("Impossible "); 86 } 87 // 输出BFS遍历路径 88 else{ 89 for( int i = 1 ; i <= n; i ++ ){ 90 for( int j = 1 ; j <= m ; j++ ){ 91 printf("%4d",vis[i][j]); 92 } 93 putchar(' '); 94 } 95 putchar(' '); 96 } 97 } 98 99 int main() 100 { 101 Init(); 102 BFS(); 103 return 0; 104 }
课后习题
整数变换问题
题目描述:
关于整数i的变换f和g定义如下:f(i)=3i,g(i)=i/2。
试设计一个算法,对于给定的2个整数n和m,用最少的变换次数将n变成m。
输入: 15 4 输出: 4 ggfg
题解:
其实这个问题跟子集树类似,问题虽然不知道答案落在哪一层。
但是通过二叉树层次遍历即可,左边是:f()变换,右边是:g()变换
只要遇到答案即可提前结束程序,注意该题目还需要加上路径记录。

1 //整数变换问题 2 //关于整数i的变换f和g定义如下:f(i)=3*i,g(i)= i/2. 3 //试设计一个算法,对于给定的2个整数n和m,用最少的变换次数将n变成m。 4 5 #include<queue> 6 #include<cstdio> 7 #include<algorithm> 8 using namespace std; 9 10 //定义结点记录信息 11 typedef struct Node{ 12 int x , step ; 13 char path[30]; 14 }Node; 15 16 int S = 15 , E = 4 ; 17 void BFS( int S ){ // Start 18 19 queue<Node> Q; 20 Node first ; 21 first.x = S ; 22 first.step = 0 ; 23 Q.push(first) ; 24 25 while( !Q.empty() ){ 26 Node cur = Q.front() , Next ; 27 Q.pop() ; 28 29 if( cur.x == E ){ 30 printf("最少变换的次数: %d ",cur.step ); 31 32 //输出变化的过程 33 for( int i = 0 ; i < cur.step ; i ++ ) 34 printf("%c",cur.path[i]); 35 putchar(' '); 36 break ; 37 } 38 39 Next = cur ; 40 Next.x = cur.x * 3 ; 41 Next.step += 1 ; 42 Next.path[cur.step] = 'f' ; 43 Q.push( Next ); 44 45 Next = cur ; 46 Next.x = cur.x / 2 ; 47 Next.step += 1 ; 48 Next.path[cur.step] = 'g' ; 49 Q.push( Next ); 50 } 51 } 52 53 int main() 54 { 55 BFS(S); 56 return 0; 57 } 58