搜尋與圖論總結
1.深度優先搜素
2.寬度優先搜尋
3.樹與圖的儲存
4.樹與圖的DFS
5.樹與圖的BFS
6.拓撲排序
從使用資料結構來看
DFS stack
BFS queue
從使用空間來看
DFS O(H); “不具有最短性”
BFS O(2^h) “最短路”(第一次搜到的距離為最短距離)
1.深度優先搜尋
(1)回溯 : 注意恢復現場
(2)剪枝 : 最優型剪枝 可行性剪枝
一個DFS一定對應一個搜尋樹
最重要的想清楚順序
例1.數字排列
#include<bits/stdc++.h> using namespace std; const int N = 20; int path[N], vis[N]; int n; void dfs(int x) { if (x > n) { for (int i = 1; i <= n; i++) cout << path[i] << ' ';//輸出路徑 cout << endl; return; } for (int i = 1; i <= n; i++) { if (!vis[i]) { vis[i] = 1;//標記用過 path[x] = i;//加入路徑 dfs(x + 1);//列舉下一個位置 vis[i] = 0;//恢復現場 } } } int main() { cin >> n; dfs(1); return 0; }
例2:n皇后問題
順序1:列舉每一行的皇后放在什麼位置 / / / 正對角線上的元素座標和相同 dg[x + y]; \\ 反對角線上的元素座標差相同 由於y - x可能是負數所以加上n dg[y - x + n]; #include<bits/stdc++.h> using namespace std; int n; const int N = 20; bool pal[N],pg[N],upg[N]; char g[10][10]; void dfs(int x) { if (x > n) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) cout << g[i][j]; cout << endl; } cout << endl; return; } for (int i = 1; i <= n; i++) { if (!pal[i] && !pg[x + i] && !upg[n - i + x]) { pal[i] = pg[x + i] = upg[n - i + x] = true; g[x][i] = 'Q'; dfs(x + 1); pal[i] = pg[x + i] = upg[n - i + x] = false; g[x][i] = '.'; } } } int main() { cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) g[i][j] = '.'; dfs(1); system("PAUSE"); return 0; } 第二種 這種方式是更無腦的搜尋 #include<bits/stdc++.h> using namespace std; int n; const int N = 20; char g[N][N]; // 依次表示 行 列 正對角線 負對角線 bool row[N], col[N], pg[N], upg[N]; void dfs(int x, int y, int s) { //列舉行列和已經放皇后的個數 if (y > n) {//注意! 這個是本題的重點 x++; //當y列舉到超過邊界時 要讓他指向下一行的第一個元素 y = 1; } if (s >= n) { if (s == n) { //如果放皇后數目正好為n則輸出方案 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { cout << g[i][j]; } cout << endl; } cout << endl; } return; } if (x > n) //如果x超過邊界則return return; dfs(x, y + 1, s);//當前位置不放皇后 //判斷 行 列 正對角線 負對角線 if (!row[x] && !col[y] && !pg[x + y] && !upg[n - x + y]) { row[x] = col[y] = pg[x + y] = upg[n - x + y] = true; g[x][y] = 'Q'; //標記 dfs(x, y + 1, s + 1);//這個位置放皇后 列舉下一個位置 row[x] = col[y] = pg[x + y] = upg[n - x + y] = false; g[x][y] = '.';//恢復現場 } } int main() { cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) g[i][j] = '.'; dfs(1, 1, 0); return 0; }
2.BFS
第一次搜到的一定是最小的(圖中邊的權重相同)
只有邊的權重都為1時求最短路才可以使用BFS
DP是一種特殊的最短路 內部沒有環的最短路
基本步驟
初始-> queue
while queue 不空
{
t->隊頭
拓展t
}
BFS在拓展時也要用vis 進行判斷
因為只有BFS第一次搜到的點才是最短距離
例1
#include<bits/stdc++.h> using namespace std; const int N = 105; int a[N][N]; int vis[N][N]; int n, m; int ans = 0x3f3f3f3f; int dx[4] = { 0,0,1,-1 }; int dy[4] = { 1,-1,0,0 }; int k = 1; bool check(int x, int y) { return x >= 1 && x <= n && y >= 1 && y <= m; } struct node { int x; int y; int sum; }dia[100]; void bfs(int x,int y,int sum) { queue<node> q; node dian; dian.x = x; dian.y = y; dian.sum = sum; q.push(dian); while (!q.empty()) { int xx = q.front().x; int yy = q.front().y; int ss = q.front().sum; q.pop(); if (xx == n && yy == m) { cout << ss << endl; return; } for (int i = 0; i < 4; i++) { int nowx = xx + dx[i]; int nowy = yy + dy[i]; if (check(nowx, nowy) && !vis[nowx][nowy] && a[nowx][nowy] == 0) { node dian; dian.x = nowx; dian.y = nowy; dian.sum = ss + 1; vis[nowx][nowy] = 1; q.push(dian); } } } } int main() { cin >> n >> m; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { cin >> a[i][j]; } } bfs(1,1,0); system("PAUSE"); return 0; }
例2
#include<bits/stdc++.h>
using namespace std;
#include<unordered_map>
int bfs(string start)
{
queue<string > q;
vector<pair<string, int> > v;
q.push(start);
v
}
int main()
{
string start;
cin >> start;
cout << bfs(start) << endl;
system("PAUSE");
return 0;
}
#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
int bfs(string state) {
queue<string> q;
unordered_map<string, int> d;
q.push(state);
d[state] = 0;
int dx[4] = { -1,0,1,0 };
int dy[4] = { 0,1,0,-1 };
string end = "12345678x";
while (q.size())
{
string t = q.front();
q.pop();
if (t == end)return d[t];
int distance = d[t];
int k = t.find('x');
int x = k / 3, y = k % 3;
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < 3 && b >= 0 && b < 3) {
swap(t[a * 3 + b], t[k]);
if (!d.count(t))
{
d[t] = distance + 1;
q.push(t);
}
swap(t[a * 3 + b], t[k]);
}
}
}
return -1;
}
int main()
{
char s[2];
string state;
for (int i = 0; i < 9; i++)
{
cin >> s;
state+= *s;
}
cout << bfs(state) << endl;
system("PAUSE");
return 0;
}
3.圖論
樹和圖是怎麼儲存的
樹和圖有兩種儲存方式
樹是一種特殊的圖,樹是無環連通圖
圖分為有向圖和無向圖
一、只需要考慮有向圖如何儲存:
1.鄰接矩陣(用的比較少) 比較浪費空間
二維陣列 g[a][b] 表示a到b這一條邊 g[a][b]的值就是a到b的權重
2.鄰接表
為圖中的每一個點都開一個單鏈表 儲存這個點可以到達的點,單鏈表中的順序無關緊要
插入一條新的邊從a到b,找到a所在的連結串列,從a的頭把b插入進去
模板:
#include<bits/stdc++.h>
using namespace std;
const int N = 100010, M = N * 2;//圖是雙向的,a可以到b,b也可以到a,n個點的圖最多有2n條邊
int n, m;
//h陣列表示每個點的連結串列的頭節點,e表示節點的值,ne表示節點的next指標,idx表示當前儲存到的位置
int h[N], e[M], ne[M], idx;
void add(int a, int b) {//新建立一條邊,將b這個節點插入到a連結串列頭節點的後面
e[idx] = b;
ne[idx] = h[a];
h[a] = idx;
idx++;
//h[a]=idx++
}
int main()
{
memset(h, -1, sizeof(h));//將頭節點都指向-1(都指向空節點)
return 0;
}
二、樹和圖的遍歷
1.深度優先遍歷
2.寬度優先遍歷
都是每個點只遍歷一次
模板
#include<bits/stdc++.h>
using namespace std;
const int N = 100010, M = N * 2;//圖是雙向的,a可以到b,b也可以到a,n個點的圖最多有2n條邊
int n, m;
//h陣列表示每個點的連結串列的頭節點,e表示節點的值,ne表示節點的next指標,idx表示當前儲存到第幾條邊
int h[N], e[M], ne[M], idx;
bool vis[N];
void add(int a, int b) {//新建立一條邊,將b這個節點插入到a連結串列頭節點的後面
e[idx] = b;
ne[idx] = h[a];
h[a] = idx;
idx++;
//h[a]=idx++
}
//res 當前連通塊的最大值
void dfs(int u) {
vis[u] = true;
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!vis[j])dfs(j);//一條路走到黑
}
}
int main()
{
memset(h, -1, sizeof(h));//將頭節點都指向-1(都指向空節點)
dfs(1);
return 0;
}
最短路徑 只遍歷一次
圖的BFS的應用
求拓撲序:
1、概念
拓撲序列 針對於有向圖->對於每條邊都是起點在終點的前面
如果存在環 則一定不存在拓撲序
一個有向無環圖(拓撲圖)一定存在拓撲序列
入度:一個節點有幾條邊進來
出度: 一個節點有幾條邊出去
2、 拓撲序列的實現
所有入度為0的點都可以作為一個起點
所有入度為0的點入隊
while(q不空){
t=隊頭
列舉t的所有出邊 t->j
刪掉t->j (因為t已經在j的前面了)
d[j]-- d陣列表示為j的入度
if(d[j]==0)
queue <-- j
如果j的入度減為0了則j也可以作為一個起點入隊
}
圖的層次遍歷·
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = N * 2;
int n, m;
int h[N], e[M], ne[M], vis[N], idx;
long long ans = 0x3f3f3f3f;
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void bfs(int num) {
queue<pair<int,long long> > q;
q.push(make_pair(num,0));
vis[num] = 1;
while (!q.empty())
{
pair<int,int> now = q.front();
q.pop();
for (int i = h[now.first]; i != -1; i = ne[i]) {
int j = e[i];
int foot = now.second + 1;
if (j == n)
{
ans = foot;
return;
}
if (vis[j])
continue;
q.push(make_pair(j,foot));
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
for (int i = 1; i <= m; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
}
bfs(1);
if (ans = 0x3f3f3f3f)
cout << -1 << endl;
else cout << ans << endl;
system("PAUSE");
return 0;
}
有向圖的拓撲排序
/*圖的bfs的應用
求拓撲序:
1、概念
拓撲序列 針對於有向圖->對於每條邊都是起點在終點的前面
如果存在環 則一定不存在拓撲序
一個有向無環圖(拓撲圖)一定存在拓撲序列
入度:一個節點有幾條邊進來
出度 : 一個節點有幾條邊出去
2、 拓撲序列的實現
所有入度為0的點都可以作為一個起點
所有入度為0的點入隊
while (q不空) {
t = 隊頭
列舉t的所有出邊 t->j
刪掉t->j(因為t已經在j的前面了)
d[j]--d陣列表示為j的入度
if (d[j] == 0)
queue < --j
如果j的入度減為0了則j也可以作為一個起點入隊
}
一個有向無環圖的拓撲序不是唯一的*/
/*
陣列模擬佇列實現BFS
hh=0,tt=-1 hh代表隊頭 tt 代表隊尾
入隊操作 q[++tt]=a;
獲取隊頭操作 t=q[hh++];
判斷佇列是否為空 while(hh<=tt)
出隊操作q[++tt]=j;
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = 2 * N;
int n, m;
int h[N], e[M], ne[M], idx, d[N], vis[N];
int q[N];
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int topsort()
{
int hh = 0, tt = -1;
for (int i = 1; i <= n; i++)
{
if (d[i] == 0)
{
q[++tt] = i;
}
}
while (hh <= tt) {
int t = q[hh++];
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (--d[j] == 0)
q[++tt] = j;
}
}
return tt == n - 1;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
for (int i = 1; i <= m; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
d[b]++;
}
if (!topsort())
{
cout << -1 << endl;
}
else {
for (int i = 0; i < n; i++)
cout << q[i] << ' ';
cout << endl;
}
return 0;
}