算法期末備考-第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('\n'); 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\n",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\n",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\n"); 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('\n'); 94 } 95 putchar('\n'); 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\n",cur.step ); 31 32 //輸出變化的過程 33 for( int i = 0 ; i < cur.step ; i ++ ) 34 printf("%c",cur.path[i]); 35 putchar('\n'); 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