搜索与图论

搜索与图论

DFS

两个经典的暴搜

Q1:数字排序

题目描述

给定一个整数 n,将数字 1∼n排成一排,将会有很多种排列方法。现在,请你按照字典序将所有的排列方法输出。

#include <bits/stdc++.h>
using namespace std;
const int maxn=10;
int q[maxn],vis[maxn],n;
void dfs(int u)
{
    if(u==n)
    {
        for(int i=0;i<n;i++)cout<<q[i]<<" ";
        puts("");
    }
    for(int i=1;i<=n;i++)
    {
        if(!vis[i])
        {
            vis[i]=1;
            q[u++]=i;
            dfs(u);
            vis[i]=0;
            u--;
        }
    }
}
int main()
{
    cin>>n;
    dfs(0);
    return 0;
}

Q2:n-皇后问题

题目描述

n−n−皇后问题是指将 nn 个皇后放在 n×nn×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

1_597ec77c49-8-queens.png

现在给定整数 nn,请你输出所有的满足条件的棋子摆法。

#include <bits/stdc++.h>
using namespace std;
const int maxn=20;
int n,col[maxn],row[maxn],udg[maxn],dg[maxn];
char mp[maxn][maxn];
void dfs(int x,int y,int s)
{
    if(y==n)y=0,x++;
    if(x==n)
    {
        if(s==n)
        {
            for(int i=0;i<n;i++)
            {
                puts(mp[i]);
            }
            puts("");
        }
        return;
    }
    //放置皇后
    dfs(x,y+1,s);
    //不放置皇后
    if(!col[x]&&!row[y]&&!dg[x-y+n]&&!udg[x+y])
    {
        col[x]=row[y]=dg[x-y+n]=udg[x+y]=1;
        mp[x][y]='Q';
        dfs(x,y+1,s+1);
        col[x]=row[y]=dg[x-y+n]=udg[x+y]=0;
        mp[x][y]='.';
    }

}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            mp[i][j]='.';
        }
    }
    dfs(0,0,0);
    system("pause");
    return 0;
}

BFS

两个经典的广搜

Q1:走迷宫

题目描述

给定一个 n×m的二维整数数组,用来表示一个迷宫,数组中只包含 0或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。

最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。

数据保证 (1,1) 处和 (n,m) 处的数字为 0,且一定至少存在一条通路。

#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
typedef pair<int,int>PII;
int n,m;
int dx[]={1,0,-1,0};
int dy[]={0,-1,0,1};
int vis[maxn][maxn],g[maxn][maxn],d[maxn][maxn];
bool judge(int x,int y)
{
    if(x<=0||x>n||y<=0||y>m)return false;
    return true;
}
int bfs()
{
    memset(vis,0,sizeof(vis));
    memset(d,0,sizeof(d));
    queue<PII>q;
    q.push({1,1});
    vis[1][1]=1;
    while(!q.empty())
    {
        auto tmp=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int x=tmp.first+dx[i];
            int y=tmp.second+dy[i];
            if(!vis[x][y]&&!g[x][y]&&judge(x,y))
            {
                d[x][y]=d[tmp.first][tmp.second]+1;
                vis[x][y]=1;
                q.push({x,y});
            }
        }
    }
    return d[n][m];
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>g[i][j];
        }
    }
    cout<<bfs()<<endl;
    system("pause");
    return 0;
}

Q2:八数码

题目描述

在一个 3×3 的网格中,1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3×3 的网格中。

例如:

1 2 3
x 4 6
7 5 8

在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 x
#include <bits/stdc++.h>
using namespace std;
unordered_map<string,int>d;
int dx[]={1,0,-1,0};
int dy[]={0,1,0,-1};
int bfs(string start)
{
    string end="12345678x";
    queue<string>q;
    q.push(start);
    d[start]=0;
    while(!q.empty())
    {
        auto t=q.front();
        q.pop();
        int distance=d[t];
        if(t==end)return distance;
        int k=t.find('x');
        int x=k/3,y=k%3;
        for(int i=0;i<4;i++)
        {
            int xx=x+dx[i],yy=y+dy[i];
            if(xx>=0&&xx<3&&yy>=0&&yy<3)
            {
                swap(t[k],t[xx*3+yy]);
                if(!d.count(t))
                {
                    d[t]=distance+1;
                    q.push(t);
                }
                swap(t[k],t[xx*3+yy]);
            }
        }
    }
    return -1;
}
int main()
{
    string start;
    for(int i=0;i<9;i++)
    {
        char ch;
        cin>>ch;
        start+=ch;
    }
    cout<<bfs(start)<<endl;
    return 0;
}

树与图的搜索

深搜框架

#include <bits/stdc++.h>
using namespace std;
const int N=100010,M=N*2;
int n,m;
int h[N],e[M],ne[M],idx;
bool st[N];
void add(int a,int b)//连接a和b
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
    st[u]=true;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!st[j])dfs(j);
    }
}
int main()
{
    memset(h,-1,sizeof h);
    dfs(1);
}

宽搜框架

#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
int n,m;
int h[maxn],e[maxn],ne[maxn],idx;
int d[maxn],q[maxn];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int bfs()
{
    memset(d,-1,sizeof d);
    queue<int>q;
    q.push(1);
    d[1]=0;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]==-1)
            {
                d[j]=d[t]+1;
                q.push(j);
            }
        }
    }
    return d[n];
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    cout<<bfs()<<endl;
    return 0;
}

根据后序与中序遍历构造树

#include <bits/stdc++.h>
using namespace std;
const int MAX_size = 10010;
int post_order[MAX_size], in_order[MAX_size];
int n, minsum = 1000000000, ans = 1;
typedef struct node
{
	int data;
	struct node* left;
	struct node* right;
}node, * pNode;
//通过后序遍历和中序遍历重构二叉树(递归实现)
//读取函数 sstream
bool read_list(int* a)
{
	string line;
	if (!getline(cin, line))return false;
	stringstream ss(line);
	n = 0;
	int x;
	while (ss >> x)
	{
		a[n++] = x;
	}
	return n > 0;

}
//构造结点
node* creat_node(int key)
{
	node* newnode = (node*)malloc(sizeof(node));
	newnode->data = key;
	newnode->left = NULL;
	newnode->right = NULL;
	return newnode;
}
//构造二叉树
pNode build_tree(int* post_order, int* in_order, int N)
{
	if (N == 1)
	{
		return creat_node(*post_order);
	}
	else if (N > 1)
	{
		//找到后序的最后一个建立根节点
		node* root = creat_node(*(post_order + N - 1));
	    //找到根节点在中序中的位置
		int index = 0;
		for (int i = 0; i < N; i++)
		{
			if (*(in_order + i) == *(post_order + N - 1))
			{
				index = i;
				break;
			}
		}
		//递归求左右子树
		root->left = build_tree(post_order, in_order, index);
		root->right = build_tree(post_order + index, in_order + index + 1, N - index - 1);
		return root;
	}
	else
	{
		return NULL;
	}
}
//按深度搜索
void dfs(node* root, int sum)
{
	if (root->left == NULL && root->right == NULL)
	{
		sum += root->data;
		if (sum < minsum || (sum == minsum && ans > root->data))
		{
			minsum = sum;
			ans = root->data;
		}
		return;
	}
	if (root->left != NULL)dfs(root->left, root->data + sum);
	if (root->right != NULL)dfs(root->right, root->data + sum);
}
int main()
{
	while (read_list(in_order))
	{
		read_list(post_order);
		node* root = build_tree(post_order, in_order, n);//构造二叉树
		minsum = 1000000000;
		dfs(root, 0);
		cout << ans << endl;
	}
	return 0;
}

拓扑排序

算法思想

  1. 在有向图中选一个没有前驱的顶点并输出
  2. 从图中删除该顶点和所有以它为尾的弧
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m;
int h[N],e[N],ne[N],idx;
int d[N];
int topo[N];
queue<int>q;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int k=0;
bool toposort()
{
    for(int i=1;i<=n;i++)
    {
        if(!d[i])q.push(i);
    }
    while(!q.empty())
    {
        int t=q.front();
        q.pop();topo[k++]=t;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            d[j]--;
            if(!d[j])q.push(j);
        }
    }
    return k==n;
}
int main()
{
 	scanf("%d%d", &n, &m);
	memset(h, -1, sizeof h);
	for (int i = 0; i < m; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		d[b]++;
	}
	if (toposort())
	{
		for (int i = 0; i < k; i++) { cout << topo[i] << " "; }
		puts("");
	}
	else puts("-1");   
    return 0;
}

最短路问题

n是顶点数,m是边数

单源最短路

所有边权都是正数

朴素Dijkstra算法(稠密图 O(n^2))
#include <bits/stdc++.h>
using namespace std;
const int N=510;
int g[N][N];//稠密图用邻接矩阵
int dist[N];//记录每个点和起点的距离
bool st[N];//标记最短距离是否确定
int n,m;
int Dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=0;i<n;i++)
    {
        int t=-1;
        //找到距离未确定的点更新现在最小的距离点
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1||dist[t]>dist[j]))
                t=j;
        st[t]=true;
        //更新每个点到当前最小点的距离
        for(int j=1;j<=n;j++)
            dist[j]=min(dist[j],dist[t]+g[t][j]);
    }
    if(dist[n]==0x3f3f3f3f)return -1;
    else return dist[n];
}
int main()
{
    cin>>n>>m;
    memset(g,0x3f,sizeof g); 
    while(m--)
    {
        int x,y,z;
        cin>>x>>y>>z;
        g[x][y]=min(g[x][y],z);
    }
    cout<<Dijkstra()<<endl;
}
堆优化的Dijkstra算法(稀疏图 O(mlogn))
#include <bits/stdc++.h>
using namespace std;
const int N=150010;
typedef pair<int,int>PII;
int h[N],ne[N],e[N],idx;
int w[N];
int dist[N];
bool st[N];
int n,m;
void add(int a,int b,int c)
{
    w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    priority_queue<PII,vector<PII>,greater<PII>>heap;
    heap.push({0,1});//距离,点编号
    while(heap.size())
    {
        PII k=heap.top();
        heap.pop();
        int ver=k.second,dis=k.first;
        if(st[ver])continue;
        st[ver]=true;
        for(int i=h[ver];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[ver]+w[i])
            {
                dist[j]=dist[ver]+w[i];
                heap.push({dist[j],j});
            }
        }
    }
    if(dist[n]==0x3f3f3f3f)return -1;
    else return dist[n];
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    while(m--)
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
    }
    cout<<dijkstra()<<endl;
    return 0;
}

存在负权边

Bellman-Ford算法(O(nm))
#include <bits/stdc++.h>
using namespace std;
const int N=510,M=10010;
int n,m,k;
int dist[N],last[N];//last数组在这里是防止连带更新,保证一次只走一步
struct Edge
{
    int a,b,w;
}edges[M];
void bellman_ford()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=0;i<k;i++)
    {
        memcpy(last,dist,sizeof dist);
        for(int j=0;j<m;j++)
        {
            auto e=edges[j];
            dist[e.b]=min(dist[e.b],last[e.a]+e.w);
        }
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<m;i++)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        edges[i]={a,b,w};
    }
    bellman_ford();
    if(dist[n]>0x3f3f3f3f/2)puts("impossiable");
    else printf("%d\n",dist[n]);
    return 0;
}
SPFA(O(m),最坏O(nm))
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];bool st[N];
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    queue<int>q;
    q.push(1);
    st[1]=true;//记录当前点已经在队列中 
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                if(!st[j])
                {
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return dist[n];
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof(h));
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    int t=spfa();
    if(t==0x3f3f3f3f)puts("impossible");
    else printf("%d\n",t);
    return 0;
}
SPFA判断负环
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m;
int h[N],e[N],ne[N],w[N],idx;
int dist[N];bool st[N];
int cnt[N];
void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool spfa()
{
    queue<int>q;
    for(int i=1;i<=n;i++)
    {
        st[i]=true;q.push(i);
    }
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n)return false;
                if(!st[j])
                {
                    st[j]=true;
                    q.push(j);
                }
            }

        }
    }
    return true;
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof(h));
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    if(spfa())puts("No");
    else puts("Yes");
    return 0;
}

多源汇最短路

Floyd算法(O(n^3))

//基于动态规划
#include <bits/stdc++.h>
using namespace std;
const int N=210,INF=1e9;
int n,m,Q;
int d[N][N];
void floyd()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
        
}
int main()
{
    scanf("%d%d%d",&n,&m,&Q);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(i==j)d[i][j]=0;
            else d[i][j]=INF;
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        d[a][b]=min(d[a][b],c);
    }
    floyd();
    while(Q--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        int t=d[a][b];
        if(t>INF/2)puts("impossible");
        else printf("%d\n",t);
    }
    return 0;
}

最小生成树

Prim算法

朴素版Prim(O(n^2))(稠密图)

#include <bits/stdc++.h>
using namespace std;
const int N=510,inf=0x3f3f3f3f;
int n,m;
int g[N][N],dist[n];
bool st[N];
int prim()
{
    memset(dist,0x3f,sizeof dist);
    int res=0;
    for(int i=0;i<n;i++)
    {
        int t=-1;
        //找到距离集合最小的点
        for(int j=1;j<=n;j++)
        	if(!st[j]&&(t==-1||dist[j]<dist[t]))
                t=j;
        if(i&&dist[t]==inf)return inf;
        if(i)res+=dist[t];
        st[t]=true;
        for(int i=1;i<=n;i++)dist[j]=min(dist[j],g[t][j]);
    }
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(g,0x3f,sizeof g);
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        g[a][b]=g[b][a]=min(g[a][b],c);
    }
    int t=prim();
    if(t==0x3f3f3f3f)puts("impossible");
    else cout<<t<<endl;
    return 0;
}

堆优化版Prim(O(mlogn))

乐色别学,去用优雅的克鲁斯卡尔

Kruskal(O(mlogm))(稀疏图)

思路

  1. 将所有边按权重从小到大排序
  2. 从小到大枚举每条边的a,b,c,如果ab不连通,那么就将边加入集合中
#include <bits/stdc++.h>
using namespace std;
const int N=100010,M=200010,inf=0x3f3f3f3f;
int n,m;
int p[N];
struct Edge
{
    int a,b,w;
    bool operator<(const Edge &W)const
    {
        return w<W.w;
    }
}edges[M];
int find(int x)
{
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];
}
int kruskal()
{
    sort(edges,edges+m);
    for(int i=1;i<=n;i++)p[i]=i;
    int res=0,cnt=0;
    for(int i=0;i<m;i++)
    {
        int a=edges[i].a,b=edges[i].b,w=edges[i].w;
        a=find(a),b=find(b);
        if(a!=b)
        {
            p[a]=b;
            res+=w;
            cnt++;
        }
    }
    if(cnt<n-1)return inf;
    return res;
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        int a,b,w;
        cin>>a>>b>>w;
        edges[i]={a,b,w};
    }
    int t=kruskal();
    if(t==inf)puts("impossible");
    else printf("%d\n",t);
    return 0;
}

知识点补充:c++重载运算符

<返回类型说明符>operator<运算符符号>(<参数表>)
{
    <函数体>
}
例:
bool operator<(const Edge &W)const
{
    return w<W.w;
}

二分图

定义:当且仅当图中不含有奇数环

染色法(O(n+m))

#include <bits/stdc++.h>
using namespace std;
const int N=100010,M=200010;
int n,m;
int h[N],e[M],ne[M],idx;
int color[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool dfs(int u,int c)
{
    color[u]=c;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!color[j])
        {
            if(!dfs(j,3-c))return false;
        }
        else if(color[j]==c)return false;
    }
    return true;
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    bool flag=true;
    for(int i=1;i<=n;i++)
    {
        if(!color[i])
        {
            if(!dfs(i,1))
            {
                flag=false;
                break;
            }
        }
    }
    if(flag)puts("Yes");
    else puts("No");
    return 0;
}

匈牙利算法(O(mn))

#include <bits/stdc++.h>
using namespace std;
const int N=510,M=100010;
int n1,n2,m;
int h[N],e[M],ne[M],idx;
int match[N];
bool st[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
//让x参与配队,能不能成功
bool find(int x)
{
    for(int i=h[x];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!st[j])
        {
            st[j]=true;
            if(!match[j]||find(match[j]))
            {
                match[j]=x;
                return true;
            }
        }
    }
    return false;
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n1>>n2>>m;
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    int res=0;
    for(int i=1;i<=n1;i++)
    {
        memset(st,false,sizeof st);
        if(find(i))res++;
    }
    cout<<res<<endl;
    return 0;
}
posted @ 2022-10-26 11:14  URMSTAR  阅读(26)  评论(0)    收藏  举报