剛開始學習算法,參考大佬博客仍是有不少不明白的,因而一步步解析,寫下筆記記錄。ios
大佬博客地址:算法
https://blog.csdn.net/fuzekun/article/details/85220468數組
問題描述
n我的參加某項特殊考試。
爲了公平,要求任何兩個認識的人不能分在同一個考場。
求是少須要分幾個考場才能知足條件。
輸入格式
第一行,一個整數n(1<n<100),表示參加考試的人數。
第二行,一個整數m,表示接下來有m行數據
如下m行每行的格式爲:兩個整數a,b,用空格分開 (1<=a,b<=n) 表示第a我的與第b我的認識。
輸出格式
一行一個整數,表示最少分幾個考場。
樣例輸入
5
8
1 2
1 3
1 4
2 3
2 4
2 5
3 4
4 5
樣例輸出
4
樣例輸入
5
10
1 2
1 3
1 4
1 5
2 3
2 4
2 5
3 4
3 5
4 5
樣例輸出
5
由於數據量很小可使用回溯算法。
應用兩層回溯:
第一層回溯是將考生放在不一樣考場裏面產生的效果,好比學生3號能夠放在教室1和教室2中那麼放在那一個教室會產生更好的效果這是一層回溯。
第二層回溯是考生放入之前的考場仍是考生本身從新用一個考場。好比考生3號能夠放進教室1和教室2,也能夠放進教室3。
應用簡單的剪枝技巧:
如今考場的數量已經大於之前最少的考場數量了就不用在展開了。
由於題目中沒有時間限制,因此能夠不使用vector,使用二維數組存放邊,使用二維數組存放教室中學生。
另外使用vecot中的find()老是報錯因此就不使用了
使用二維數組的時間複雜度約爲O(nn)由於沒一個學生須要遍歷以前的考場就須要遍歷每一個人一次1 + 2…+n - 1;
使用vector最好的狀況是NlogN,這種狀況對應只有一個考場,每一個學生耗費logN的複雜度,NlogN,最壞的狀況就會退成O(NN),對應着N個考場,每一個以前的學生都須要遍歷一次。
#include<stdio.h>
#include<iostream>
#include<vector>
#include<string.h>
using namespace std;
const int maxn = 100 + 5;
int room[maxn][maxn];//保存教室i中第j個學生的id
int gra[maxn][maxn];//存圖
int m,n;
int res = maxn + 500;//保存答案
void solve(int id,int num)
{
//printf("%d %d\n",id,num);
if(num >= res){//剪枝
return ;
}
if(id > n){//學生的編號從1開始防止room[][] = 0不是沒學生的標誌
res = min(res,num);
return ;
}
for(int i = 1;i <= num;i++){//找到id是否有符合的教室
int k = 0,flag = 1;
while(room[i][k]){//查找學生教室中是否有認識的人
// printf("%d %d %d\n",id,room[i][k],gra[id][room[i][k]]);
if(gra[id][room[i][k]]){//教室裏面有認識的人
//printf("衝突\n");
flag = 0;
break;
}
k++;//不要寫在數組裏面防止執行順序的不對而出錯
}
if(flag){ //回溯必定對應着判斷
room[i][k] = id;
solve(id+1,num);
room[i][k] = 0; //第一層回溯
}
}
//從新開啓一個教室
room[num+1][0] = id;
solve(id+1,num+1);
room[num+1][0] = 0;//第二層回溯
}
void init()
{
memset(room,0,sizeof(room));
}
int main()
{
init();
scanf("%d%d",&n,&m);
for(int i = 0;i < m;i++){
int a,b;
scanf("%d%d",&a,&b);
gra[a][b] = gra[b][a] = 1;
}
solve(1,0);
printf("%d\n",res);
return 0;
}函數
分析:
假設5我的,1,2,3,4,5 1,3,5互相認識
程序開始
輸入 n=5 5我的 m=?命令數
初始 room[i][j] =0 i爲房間,j爲學生
輸入
1 3
1 5
3 5
gra[1][3] = gra[3][1] =1
gra[1][5] = gra[5][1] =1
gra[3][5] = gra[5][3] =1
solve(id=1,num=0){ id爲學生,num爲房間數
room[1][0] =1
solve(id=2,num=1){
room[1][1]=2
solve(id=3,num=1){
room[2][0]=3
solve(id=4,num=2){
room[1][2]=4
solve(id=5,num=2){
room[3][0]=5
solve(id=6,num=3){
res=num=3
return
}
room[3][0]=0
}
room[1][2]=0
room[2][1]=4
solve(id=5,num=2){
room[3][0]=5
solve(id=6,num=3){
res=num=3
return
}
room[3][0]=0
}
room[2][1]=0
room[3][0]=4
solve(id=5,num=3){
3 >= 3 剪枝
return
}
room[3][0]=0
}
room[2][0]=0
}
room[1][1]=0
room[2][0]=2
solve(id=3,num=2){
room[2][1]=3
solve(id=4,num=2){
room[1][1]=4
solve(id=5,num=2){
room[3][0]=5
solve(id=6,num=3){
res =3
return
}
room[3][0]=0
}
room[1][1]=0
room[2][2]=4
solve(id=5,num=2){
room[3][0]=5
solve(id=6,num=3){
res =3
return
}
room[3][0]=0
}
room[3][0]=4
solve(id=5,num=3){
剪枝
return
}
}
room[2][1]=0
room[3][0]=3
solve(id=4,num=3){
剪枝
return
}
}
room[2][0]=0
}
room[2][0]=2
solve(id=3,num=2)
……
}
遞歸邏輯步驟
首先進入函數,
1判斷當前的num屋子數是否大於等於已記錄的屋子數res,若是符合則剪枝return
2判斷當前的人id是否超過總人數,若是符合則記錄當前屋子數res且return
3循環判斷如今的人id在已存在的num屋子數是否有認識人,若是沒有則放在相應的後面
4建立一個新屋子,將如今的人放入,並將下一我的進行判斷
假設過程:
id=1的人沒有房子,開設新房子num=1並放入,
遞歸id=2,發現num=1能夠放入,
遞歸id=3,發現num=1有認識的人,開設新房間num=2放入
遞歸id=4,發現num=1能夠放入
遞歸id=5,發現num=1,num=2有認識的人,開設新房間num=3放入
遞歸id=6,發現超過人數,記錄下第一次獲得的房間數res=num=3
回溯id=4,不放進num=1,放入num=2
遞歸id=5,發現num=1,num=2有認識的人,開設新房間num=3放入
遞歸id=6,發現房間數num=3與res相等,表示不會出現更優解,進行剪枝
回溯id=4,不放進num=1,num=2,開設新房間num=3
遞歸id=5,發現房間數num=3與res相等,表示不會出現更優解,進行剪枝
id=4中止
id=3中止
回溯id=2,開設新房間num=2放入
遞歸id=3,發現num=1有認識的人,放入num=2中
遞歸id=4,發現num=1能夠放入
遞歸id=5,發現num=1,num=2有認識的人,開設新房間num=3放入
遞歸id=6,發現超過人數,記錄下第一次獲得的房間數res=num=3
回溯id=4,不放進num=1,放入num=2
遞歸id=5,發現num=1,num=2有認識的人,開設新房間num=3放入
遞歸id=6,發現房間數num=3與res相等,表示不會出現更優解,進行剪枝
回溯id=4,不放進num=1,num=2,開設新房間num=3
遞歸id=5,發現房間數num=3與res相等,表示不會出現更優解,進行剪枝
id=4中止
遞歸id=3,開設新房間num=3,發現房間數num=3與res相等,表示不會出現更優解,進行剪枝
id=3中止
id=2中止
id=1中止
細節:首先進行剪枝判斷來優化程序,當id數超過總人數表示一條支路到盡頭,到盡頭後往前回溯上一級的另一種可能性,一樣到盡頭後再回溯,直到頂級結束
全部的回溯以開設新房間爲最差方式結束。
思想核心:首先選擇一個起點,先沿着一條路走到盡頭,而後回溯上一步走另一種可能,全部可能性走完後繼續回溯上級再走上級的另外可能
學習
整體的步驟圖(差很少能看。。):優化
①spa
②.net
③blog
④遞歸
⑤
⑥
⑦
(回到起點結束)