搜尋演算法合集 - By DijkstraPhoenix

DijkstraPhoenix發表於2024-10-06

搜尋演算法合集

By DijkstraPhoenix

深度優先搜尋 (DFS)

引入

如果現在有一個迷宮,如何走路徑最短?

方法

走迷宮最簡單粗暴的方法式什麼呢?當然是把所有路都走一遍啦!

如果是手動計算的話,可能會把你手指累得抽筋,但電腦不會,電腦具有強大的算力,這種暴力的事情當然是交給電腦做啦。

深搜的本質:一條路走到底,走到死衚衕再往回走,回到上一個岔口繼續走,直到找到正確的路

實際上,任何一條路都可以看做是一個只有一個岔口的分岔路,所以不需要把路和岔口分開計算。

那麼剛才的例子應該是這麼走(數字代表第幾次嘗試)實際上岔口走的順序是任意的,方法不唯一。

概念:從死衚衕返回的步驟叫做回溯

由於深搜不能保證第一次找到的路徑為最短路徑,所以需要統計所有路線

深搜一般使用遞迴實現,走過的每個位置都要打上標記,同一條路不能再走一遍

主演算法程式碼:

int maze[MAXN][MAXN];//儲存迷宮 0表示當前節點可以走,1表示不能走
bool vis[MAXN][MAXN];//打標記
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};//位移陣列,分別對應 上右下左(如果是八向移動的話要改成對應的)
int n,m,stx,sty,edx,edy;//地圖長寬以及起點和終點的座標
int ans=0x7f7f7f7f;//最短距離,要初始化為極大值

void dfs(int x,int y,int z)//x和y是當前位置的座標,z是走過的步數
{
    if(x==edx&&y==edy)//到了終點
    {
        ans=min(ans,z);//更新答案(如果答案還是極大值,說明無法到達終點)
        return;
    }
    vis[x][y]=true;//打標記
    for(int i=0;i<4;i++)//列舉四個方向
    {
        int nx=x+dx[i],ny=y+dy[i];//下一個應該走到的位置
        if(nx<1||nx>n||ny<1||ny>m)continue;//不能走出地圖(這個要寫在靈魂拷問的最前面,否則訪問陣列要越界)
        if(maze[nx][ny]==1)continue;//不能卡牆裡
        if(vis[nx][ny])continue;//不能走你走過的路
        dfs(nx,ny,z+1);//走到下一個節點
    }
    vis[x][y]=false;//重點!回溯時要清除標記!
}

例題

迷宮

洛谷 P1605

解法見上文

#include<iostream>
#include<cstring>
using namespace std;
int num=0;
int n,m,t,edx,edy,stx,sty;
int maze[10][10];
int vis[10][10];
int dx[]={0,1,0,-1};
int dy[]={1,0,-1,0};
void dfs(int x,int y)
{

    vis[x][y]=1;
    if(x==edx&&y==edy)
    {
        num++;
        vis[x][y]=0;
        return;
    }
    for(int i=0;i<4;i++)
    {
        int nx=x+dx[i];
        int ny=y+dy[i];
        if(nx<1||nx>n||ny<1||ny>m)continue;
        if(maze[nx][ny]==1)continue;
        if(vis[nx][ny]==1)continue;
        dfs(nx,ny);
    }
    vis[x][y]=0;
}
int main(void)
{
    int xjx,xjy;
    memset(vis,0,sizeof(vis));
    memset(maze,0,sizeof(maze));
    cin>>n>>m>>t;
    cin>>stx>>sty>>edx>>edy;
    for(int i=1;i<=t;i++)
    {
        cin>>xjx>>xjy;
        maze[xjx][xjy]=1;
    }
    vis[stx][sty]=1;
    dfs(stx,sty);
    cout<<num;
    return 0;
}

八皇后問題

洛谷 P1219

本題的每一步都決定一個皇后的位置,由輸出格式就可以看出,我們可以按每一列的順序計算。一個皇后會獨佔一行、一列、兩斜線,因為是按列計算的,不需要給列打標記,則需要 3 個標記陣列。

(其實可以看一下洛谷上的題解)

#include<bits/stdc++.h>
using namespace std;
bool vis[15],vis1[35],vis2[35];
int n;
int nod[15];
int sum=0;
void dfs(int k)
{
    if(k>n)
    {
        sum++;
        if(sum<=3)//前3個要輸出方案
        {
            for(int i=1;i<=n;i++)cout<<nod[i]<<" ";
            cout<<endl;
        }
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i])continue;
        if(vis1[i+k-1])continue;
        if(vis2[i-k+13])continue;
        vis[i]=true;
        vis1[i+k-1]=true;
        vis2[i-k+13]=true;//可以手動模擬一下行列座標和斜座標的關係,加13是防止計算出負數
        nod[k]=i;//儲存方案
        dfs(k+1);
        vis[i]=false;
        vis1[i+k-1]=false;
        vis2[i-k+13]=false;
    }
}
int main(void)
{
    cin>>n;
    dfs(1);
    cout<<sum;
    return 0;
}

全排列問題

洛谷 P1706

按照題意模擬搜尋即可

#include<iostream>
#include<cstdio>
using namespace std;
int n,a[1000],vis[1000];
void dfs(int step)
{
	if(step==n+1)
	{
		for(int i=1;i<=n;i++)
		{
			printf("%5d",a[i]);//題目要求格式化輸出
		}
		cout<<endl;
	}
	for(int i=1;i<=n;i++)
	{
		if(vis[i]==1)continue;
		a[step]=i;
		vis[i]=1;
		dfs(step+1);
		vis[i]=0;
	}
}
int main(void)
{
	cin>>n;
	dfs(1);
	return 0;
}

一些建議練習的題

求細胞數量
提示:聯通塊問題,不要清除標記,從每個未標記且是細胞的塊出發,將整個塊打上標記

小貓爬山

選數

單詞接龍

--沒寫完呢--

相關文章