[Acwing] 算法基础课总结 三 搜索与图论

DFS

1.排列数字

对于每个节点,只能选择没有用过的数,因此需要一个 s t [ ] st[] st[] 数组,当我们枚举到底层的时候,需要清空状态

void dfs(int u){
	if( u ==  n+1){
		for(int i = 1;i<=n;i++)
		cout<<ans[i]<<" ";
		cout<<endl;
		
		return;
	}
	
	for(int i=1;i<=n;i++){
		if(!st[i]){
			ans[u] = i;
			st[i] = 1;
			dfs(u+1);
			st[i] = 0;
		}
	}
}
2.n-皇后

搜索行然后根据 函数式进行操作

BFS

1. 走迷宫

很典型的 b f s bfs bfs问题,需要两个方向数组,然后我们对每一个走过的点进行标记即可

2.八数码

虽然这个题看着不像是 b f s bfs bfs,但是我们对于每一个状态进行 h a s h hash hash那么还是可以使用 b f s bfs bfs

树与图的深度优先遍历

1.树的重心

树的重心 , 对于树上的每一个点,计算其子树中最大的子树节点,这个值最小的点就是这棵树的重心

int dfs(int u){
    int res = 0 ;//删除当前点,连通块中的最大值
    st[u] = 1;
    int sum = 1 ;//以u为根的 子树

    for(auto x : g[u]){
        if(!st[x]){
            int t = dfs(x);
            res = max(res,t);
            sum+=t; 
        }
    }

    res  = max(res,n-sum);
    ans  = min(ans,res);

    return sum;
}
2. 图中点的层次

利用 b f s bfs bfs寻找 1 − n 1-n 1n的最短路径,但是不怎么怎么 W A WA WA

int d[N];
int n,m;
vector<int> g[N];

void bfs(){
	queue<int> q;
	d[1] = 0 ;
	q.push(1);

	while(!q.empty()){
		auto t  = q.front();
		q.pop();
		
		for(auto x : g[t]){
			if(d[x] == -1){
				d[x] = d[t]+1;
				q.push(x);
				
			}
		}
	}

拓扑排序

1.拓扑排序

无向图 : 记录每个点的入度,然后将所有入度为 0 0 0 的点放进队列中,每次取出队头,然后令其领边的入度 − − -- ,反复如此操作

bool topsort()
{
    int num  =0;

    queue<int> q;
    for(int i=1;i<=n;i++)
    {
        if(!d[i])
            q.push(i);
    }
    while(!q.empty())
    {
        num++;

        int t=q.front();
        ans[dx++] = t;

        q.pop();

        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j =e[i];
            d[j] --;
            if(d[j] ==0)
                q.push(j);
        }
    }
    if(num ==n)
        return true;
    else
        return false;


}

Dijkstra

1.Dijkstra求最短路 I

这里使用的 邻接矩阵 进行建立的图,怪不得这个题是简单

普通的 D i j Dij Dij算法,流程如下 :

  • n n n次迭代
  • 找到最小的并且没有使用的点
  • 利用这个点进行更新
int g[N][N];    //为稠密阵所以用邻接矩阵存储
int dist[N];    //用于记录每一个点距离第一个点的距离
bool st[N];     //用于记录该点的最短距离是否已经确定

int n,m;

int Dijkstra()
{
    memset(dist, 0x3f,sizeof dist);     //初始化距离  0x3f代表无限大

    dist[1]=0;  //第一个点到自身的距离为0

    for(int i=0;i<n;i++)      //有n个点所以要进行n次 迭代
    {
        int t=-1;       //t存储当前访问的点

        for(int j=1;j<=n;j++)   //这里的j代表的是从1号点开始
            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;  //如果第n个点路径为无穷大即不存在最低路径
    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;
    return 0;
}
2.Dijkstra求最短路 II

超级简单.就是使用一个小根堆做到 l o g log log级别的维护集合外的最小的点

// Problem: Dijkstra求最短路 II
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/852/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <vector>
#include <map>
#include <cstring>
#include <queue>
#include <math.h>
#include <set>
#include <stack>
#include <algorithm>
using namespace std;
#define IOS  ios::sync_with_stdio(false);
#define CIT  cin.tie(0);
#define COT  cout.tie(0);

#define ll long long
#define x first
#define y second
#define pb push_back
#define endl '\n'
#define all(x) (x).begin(),x.end()
#define Fup(i,a,b) for(int i=a;i<=b;i++)
#define Fde(i,a,b) for(int i=a;i>=b;i--)

typedef priority_queue<int,vector<int>,greater<int>>  Pri_m;
typedef pair<int,int> pii;
typedef vector<int> VI;
map<int,int> mp;
const int N = 2e5+10,INF = 0x3f3f3f3f3f;
const double eps = 1e-5;
int n,m;
int dist[N];
int st[N];

struct node{
    int to,val;
};

vector<node> g[N];

void dij(){
	memset(dist,0x3f,sizeof dist);
	dist[1] = 0 ;
	priority_queue<pii,vector<pii>,greater<pii>> heap;
	heap.push({0,1});
	
	while(heap.size()){
		auto t =  heap.top();
		heap.pop();
		
		if(st[t.y]) continue;
		st[t.y] =  1;
		
		for(auto x : g[t.y]){
			if(dist[x.to] > t.x + x.val){
				dist[x.to] = t.x + x.val;
				heap.push({dist[x.to],x.to});
			}
		}
	}
}

void solve(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int a,b,c;cin>>a>>b>>c;
		g[a].pb({b,c});
	}
	dij();
	if(dist[n] == INF) cout<<-1<<endl;
	else cout<<dist[n]<<endl;
}

int main(){
    //int t;cin>>t;while(t--)
    solve();
    return 0 ;
}

Bellman-Ford

B e l l m a n − F o r d Bellman-Ford BellmanFord基本思路 :

  • 迭代 n n n
  • 对于每次迭代,我们对所有边进行松弛操作
	for(int i=1;i<=n;i++)//迭代n 次
		for(int j=1;j<=n;j++)//枚举每个点
			for(auto x : g[j])//对所有边进行松弛操作
			dist = min(dist,dist+w);

1.有边数限制的最短路

因为第一层循环的本质是
1 1 1号店开始更新不超过 n n n条边的最短路,因此我们只需要把 n n n改为 k k k

另外第一层也可以理解为
d i s t [ n ] dist[n] dist[n]是走过 k k k步以及以内的最短路

当然这题有一个非常特别的点,就是需要存放上一层的 d i s t dist dist

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510,M=10010;

struct Edge {//只需要将边遍历一遍,用结构体数组储存即可
    int a, b, c;
} edges[M];

int n, m, k;
int dist[N],last[N];

void bellman_ford(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;

    for(int i=0;i<k;i++){//保证第k次时,dist[n]是走过k步及以内的最短路。第k+5次时dist[n]的最短路第k+5次时再确定!!!
        memcpy(last,dist,sizeof dist);//保留上一次迭代的dist的副本,防止跨层数更新

        for(int j=0;j<m;j++){
            auto t=edges[j];
            dist[t.b]=min(dist[t.b],last[t.a]+t.c);//用已确定最短路的点向外延申,类似于dijkstra
        }
    }
}

int main() {
    cin >> n >> m >> k;
    for (int i = 0; i < m; i++) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        edges[i] = {x, y, z};
    }

    bellman_ford();

    if(dist[n]>0x3f3f3f3f/2)//0x3f3f3f3f并非真正的无穷大,会随着dist数值更新而受到影响
        cout<<"impossible";//输出字符串或数字,不能用三目运算符
    else cout<<dist[n];
}

Spfa

每次只把能更新的点放进队列中,一个点可以被放进多次

1.Spfa求最短路

int spfa(){
    queue<PII> q;
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    q.push({0,1});
    st[1]=true;
    while(q.size()){
        PII p=q.front();
        q.pop();
        int t=p.se;
        st[t]=false;//从队列中取出来之后该节点st被标记为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]){//当前已经加入队列的结点,无需再次加入队列,即便发生了更新也只用更新数值即可,重复添加降低效率
                    st[j]=true;
                    q.push({dist[j],j});
                }
            }
        }
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    else return dist[n];
}
2.spfa判断负环

对于每次更新的时候我们多记录一个 c n t [ ] cnt[] cnt[]

如果存在当前点被放进队列 n n n 次,那么即存在负环

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 true;
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

Floyd

1.Floyd求最短路

枚举中间更换点 k k k,然后枚举起点 i i i 到终点 j j j,判断是否可以利用 k k k进行转移

  for(int k1 = 1;k1<=n;k1++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                dist[i][j] = min(dist[i][j],dist[i][k1]+dist[k1][j]);
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值