在图上寻找路径
策略:路径遍历
- 只要能发现没走过的点,就走到它;
- 多个点可走就随便挑一个
- 如果无路可走就回退,再看看有没有走过的点可走
深度优先搜索定义(DFS
从起点出发,走过的点都做标记,发现没走过的点就随意挑一个往前走,走不了就回退
int main(){
将所有的点标记为新点
起点=1
终点=8
cout<<Dfs(起点)
}
bool Dfs(V){//从V出发能不能走到终点
if(V为终点){
return true;
}
if(V为旧点){
return false;
}//V为旧点说明,从V出发过,没成功,可以直接返回从这个V出发不行
将V标记为旧点;
对和V相邻的每个结点U{
if(Dfs(U)==true){
return true;
}
}//是循环
return false;
}
不仅要判断能不能走,而且需要记录路径
Node path[MAX_LEN];//MAX_LEN取节点总数即可
int depth;
bood Dfs(V){
if(V为终点){
path[depth]=V;
return ture;
}
if(V为旧点){
return false;
}
将V标记为旧点;
path[depth]=V;
depth++;
对和V相邻的每个节点U{
if(Dfs(U)==true){
return ture;
}
}
depth--;
return false;//相当于是回退一个节点
}
int main(){
将所有点标记为新点
depth=0;
if(Dfs(起点)){
for(int i=0;i<=depth;i++){
cout<<path[i]<<endl;
}
}
}
遍历图上所有节点//理解DFS的最坏时间复杂度
Dfs(V){
if(V是旧点) return;
将V标记为旧点;
对和V相邻的每个点U{
Dfs(U);
}
}
int main(){
将所有点标记为新点;
while(在图中能找到新店k) Dfs(k);
}
图的表示方法
邻接矩阵
用一个二维数组G存放图,G[i] [j]表示节点i和节点j之间边的情况(如有无边,边方向,权值大小等)
因此 G[i] [j]可以是一个Struct结构体类型
遍历复杂度是O(N^2)
邻接表
还可以让每个节点对应一个一维数组(Vector),里面存放从V连出去的边,边的信息包括另一顶点,还可能包括边权值等
遍历复杂度O(n+e),n为节点数目,e为边数目
例题——城堡问题2815//1164
建模思想:能把平面数字转化为图
解题思路:
- 将方块看作是节点,相邻两个方块之间如果没有墙,则在方块之间连一条边,这样城堡就能转换成一个图
- 求房间个数,实际上就是求图中有多少个极大连通子图
- 一个连通子图,往里头加任何一个图里的其他点,都会变得不连通,那么这个连通子图就是极大连通子图
- 就是自己形成自己一个圈
#include<iostream>
#include<cstring>
using namespace std;
/*问题一,将数字转化为东西南北的墙*/
//题中墙的标准是二进制,利用与运算,判断哪个位有0,就说明可以走
int R,C;//行列数
int rooms[55][55];//存原始房间
int color[55][55];//进行方块标记,识别是否为同一个房间的空间
int maxRoomArea=0,roomNum=0;//最大房间面积和表示第几个房间的记录
int roomArea;//房间面积的记录
void Dfs(int i,int k){//第i排第k列的房间为起点的房间面积
if(color[i][k]) return;//已经走过了,标记过了
roomArea++;
color[i][k]=roomNum;
if((rooms[i][k]&1)==0) Dfs(i,k-1);//西墙为空
if((rooms[i][k]&2)==0) Dfs(i-1,k);//北墙为空
if((rooms[i][k]&4)==0) Dfs(i,k+1);//东墙为空
if((rooms[i][k]&8)==0) Dfs(i+1,k);//南墙为空
}
int main(){
cin>>R>>C;
for(int i=1;i<=R;i++){
for(int j=1;j<=C;j++){
cin>>rooms[i][j];
}
}
memset(color,0,sizeof(color));
for(int i=1;i<=R;i++){
for(int k=1;k<=C;k++){
if(!color[i][k]){//标记为0,说明未占这个房间
roomNum++;
roomArea=0;
Dfs(i,k);
maxRoomArea=max(roomArea,maxRoomArea);
}
}
}
cout<<roomNum<<endl;
cout<<maxRoomArea<<endl;
}
例题——踩方格4103
从递归思路出发:
- 从(i,j)出发,走n步的方案数,等于以下三项之和:
- 从(i+1,j)出发,走n-1步的方案数
- 从(i,j+1)出发,走n-1步的方案数
- 从(i,j-1)出发,走n-1步的方案数
#include<iostream>
#include<cstring>
using namespace std;
int visited[30][50];
int ways(int i,int j,int n){
if(n==0) return 1;//步数走完了,这个走法是成立的
visited[i][j]=1;//标记为走过了
int num=0;
if(!visited[i][j-1]) num+=ways(i,j-1,n-1);
if(!visited[i][j+1]) num+=ways(i,j+1,n-1);
if(!visited[i+1][j]) num+=ways(i+1,j,n-1);
visited[i][j]=0;//重新规划新出发的路
return num;
}
int main(){
int n;
cin>>n;
memset(visited,0,sizeof(visited));
cout<<ways(0,25,n)<<endl;
}
开始引入最优题——最短,最长等
例题——寻路问题1724
N个城市,编号为1->N
城市间有R条单向道路
每条道路连接两个城市,有长度和过路费两个属性
Bob只有k块钱,他想从城市1走到城市N。
问最短共需要走多长的路,如果到不了N,输出-1;//也能延伸出新问题——最少需要多少过路费
即优先级应该是,找短路,短路里找能给的起过路费的
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
int K,N,R;
struct Road{
int d,L,t;
};
vector< vector<Road> > G(110);//因为每个起点可能有很多个终点
int minL[110][10010];//从起点走到i,总花费为j的最优长度
int minLen;//最短路径
int totalLen;//目前这条路的长度
int totalCost;//目前这条路的花费
int visited[110];//标记城市走没走过
void dfs(int s){
if(s==N){
minLen=min(minLen,totalLen);
return;
}
for(int i=0;i<G[s].size();i++){
Road r=G[s][i];
if(totalCost+r.t>K){
continue;
}
if(!visited[r.d]&&totalLen+r.L<minLen){
//运用最优性剪枝,减少遍历
//如果它之前也来过这个路径,而且他上一次来,路径更短,就没必要再往这个路径下去
if(totalLen+r.L>=minL[r.d][totalCost+r.t]){
continue;
}
minL[r.d][totalCost+r.t]=totalLen+r.L;
totalLen+=r.L;
totalCost+=r.t;
visited[r.d]=1;
dfs(r.d);
visited[r.d]=0;
totalLen-=r.L;
totalCost-=r.t;
}
}
}
int main(){
cin>>K>>N>>R;
for(int i=0;i<R;i++){
int s;
Road r;
cin>>s>>r.d>>r.L>>r.t;
if(s!=r.d){
G[s].push_back(r);
}
}
memset(visited,0,sizeof(visited));
totalLen=0;
minLen=1<<30;//最短长度最开始是一个很大的数
totalCost=0;
visited[1]=1;
for(int i=0;i<110;i++){
for(int j=0;j<10010;j++){
minL[i][j]=1<<30;
}
}
dfs(1);
if(minLen<(1<<30)){
cout<<minLen<<endl;
}else{
cout<<"-1"<<endl;
}
return 0;
}
例题——生日蛋糕1190
描述:制作一个体积为NΠ的M层蛋糕,每层都是一个圆柱体
设从下往上数,第i(1<=i<=M)层蛋糕半径是Ri,高度为Hi的圆柱。当i<M时,要求Ri>Ri+1&&Hi>Hi+1
要求蛋糕外表面(最下一层的下底面除外)的面积Q最小
令Q=SΠ
N与M都是整数
深度搜索枚举的本质是,枚举每一层可能的高度和半径
确定搜索范围:底层蛋糕的最大可能半径和最大可能高度
搜索顺序:从底层往上搭蛋糕
如何剪枝提高效率:
#include<iostream>
using namespace std;
int N, M;//体积,层数
int minArea = 1 << 30;//最小面积,初始化为很大的数
inline int min_v(int m)//用于计算搭建剩余蛋糕所需要的最小体积
{//传入的参数m是已搭建的层数
int v = 0;
//假设剩余层的半径都是最小值,半径和高度都是1,2,3……M-m
for (int R = M - m; R >= 1; R--)
{
v += R * R * R;
}
return v;
}
inline int max_v(int r, int h, int m)
{//传入参数是:m+1层的半径,高度和已经盖了m层
int v = 0;
//因为上层半径和高度都必须比下层小,所以每一层半径和高度的最大取值都是下层的半径和高度-1
for (int R = r, H = h; m <= M; R--, H--, m++)
{
v += R * R * H;
}
return v;
}
inline void Dfs(int r, int h, int m, int s, int v)
{//搭建该层的半径,高度,层数,目前已用表面积,目前已用体积
if (m == M && N == v && s < minArea)
{//层数用完,体积用完,总面积比最小的还小
minArea = s;
return;
}
if (r == 0 || s + 2 * (N - v) / r > minArea)
{
return;
}//如果预估蛋糕的所用面积已经大于目前确立的最小面积,就剪枝
if (min_v(m) > N - v)
{
return;
}//剩余层数所需的体积已经大于剩余体积,就说明不够用,根本搭不成
if (max_v(r, h, m) < N - v)
{
return;
}//剩余层数能达到的最大体积,已经小于剩余体积,说明体积花不完,无法达到预期体积
for (int R = r - 1; R >= M - m; R--)
{//最大是r-1,最小也应该有当前层数那么大
for (int H = h - 1; H >= M-m; H--)
{
Dfs(R, H, m + 1, s + 2 * R * H, v + R * R * H);
}
}
}
int main() {
cin >> N >> M;
for (int r1 = M; r1 * r1 <= N; r1++)
{
for (int h1 = N / (r1 * r1); h1 >= M; h1--)
{
int s = r1 * r1 + r1 * h1 * 2;
int v = r1 * r1 * h1;
Dfs(r1, h1, 1, s, v);
}
}
if (minArea < (1 << 30))
{
cout << minArea << endl;
}
else
{
cout << 0 << endl;
}
}
天梯校内选拔——金币问题
#include<iostream>
#include<cstring>
using namespace std;
bool visited[105][105];
int rmp[105][105];
void nuit()
{
rmp[0][0]=0;
for (int i = 0; i <= 99; i++)
{
rmp[i][0] = i / 10 + i % 10;
for (int j = 1; j <= 99; j++)
{
if (j % 10 > 0)
rmp[i][j] = rmp[i][j - 1] + 1;
else
rmp[i][j] = rmp[i][j - 1] - 9 + 1;
}
}
rmp[100][0] = 1;
rmp[0][100] = rmp[0][99] + 1;
for (int i = 1; i <= 99; i++)
{
if (i % 10 > 0)
rmp[100][i] = rmp[100][i - 1] + 1;
else
rmp[100][i] = rmp[100][i - 1] - 9 + 1;
rmp[i][100] = rmp[i][99] + 1;
}
rmp[100][100] = 2;
}
int cnt=0;
void Dfs(int sx,int sy,int ex,int ey,int k)
{
if(sx==ex-1 && sy==ey-1)
{
cnt++;
visited[sx][sy]=true;
return;
}
cnt++;
visited[sx][sy]=true;
if(sx-1>=0 && !visited[sx-1][sy] && rmp[sx-1][sy]<=k)
Dfs(sx-1,sy,ex,ey,k);
if(sy-1>=0 && !visited[sx][sy-1] && rmp[sx][sy-1]<=k)
Dfs(sx,sy-1,ex,ey,k);
if(sx+1<ex && !visited[sx+1][sy] && rmp[sx+1][sy]<=k)
Dfs(sx+1,sy,ex,ey,k);
if(sy+1<ey && !visited[sx][sy+1] && rmp[sx][sy+1]<=k)
Dfs(sx,sy+1,ex,ey,k);
}
int main()
{
int m,n,k;
cin>>m>>n>>k;
memset(visited,false,sizeof(visited));
nuit();
Dfs(0,0,m,n,k);
cout<<cnt<<endl;
}