第三章算法速刷-图论
第三章算法速刷-图论

第三章算法速刷-图论

dfs

#include<iostream>
using namespace std;
const int N=10;
int n;
int path[N];//存放每一步的数字
bool vis[N];//表示该数字是否被访问


void dfs(int u)
{
    if(u==n)//u==n表示每一步已经放好了数字,输出
    {
        for(int i=0;i<n;i++)cout<<path[i]<<" ";
        puts("");
    }
    else{
        for(int i=1;i<=n;i++)
        {
            if(!vis[i])//判断数字是否被访问
            {
                path[u]=i;
                vis[i]=true;
                dfs(u+1);
                vis[i]=false;
            }
        }
    }
}



int main()
{
    cin>>n;
    dfs(0);
}

n皇后

#include<iostream>
using namespace std;
const int  N=30;
char m[N][N];
bool r[N],l[N],x1[N],x2[N];

void dfs(int u,int n)
{
    
    if(u==n){
   
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
                cout<<m[i][j];
            puts("");            
        }
        puts("");

    }
    else{
        
        for(int i=0;i<n;i++)
        {
            
            if(!r[u]&&!l[i]&&!x1[u+i]&&!x2[i-u+n-1])
            {
                m[u][i]='Q';
                r[u]=true;
                l[i]=true;
                x1[u+i]=true;
                x2[i-u+n-1]=true;
                dfs(u+1,n);
                r[u]=false;
                m[u][i]='.';
                l[i]=false;
                x1[u+i]=false;
                x2[i-u+n-1]=false;
            }
        }
    }
}

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            m[i][j]='.';
    dfs(0,n);
}

首先思路,依次给每一行放皇后,判断能否放,能放要满足行列对角线无皇后,然后设置对应数组为true,然后放下一行,这个遍历后要回溯,即把之前改变的变回去

bfs

走迷宫

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef pair<int,int> PII;//定义打包,这个要学会
int n,m;
const int N=110;
int a[N][N],d[N][N];

int dfs()
{
    memset(d,-1,sizeof d);//d初始化为-1
    d[0][0]=0;
    queue<PII> q;
    q.push({0,0});
    int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};//用数组表示偏移
    while(q.size())
    {
        PII tmp=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int x=tmp.first+dx[i],y=tmp.second+dy[i];
            if(x>=0&&x<n&&y>=0&&y<m&&d[x][y]==-1&&a[x][y]==0)
            {
                d[x][y]=d[tmp.first][tmp.second]+1;
                q.push({x,y});
            }
        }
    }
    return d[n-1][m-1];
}


int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)scanf("%d",&a[i][j]);
    cout<<dfs();
}
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<unordered_map>
using namespace std;

string s1,s2;

int bfs()
{
    queue<string> q;
    unordered_map<string,int> d;
    q.push(s1);
    int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
    d[s1]=0;
    s2="12345678x";
    while(q.size())
    {
        auto tmp=q.front();
        q.pop();

        int sx=tmp.find('x');
        if(s2==tmp)return d[s2];
        
        for(int i=0;i<4;i++)
        {
            int x=sx/3+dx[i],y=sx%3+dy[i];
            if(x>=0&&y>=0&&x<3&&y<3)
            {
                int distance=d[tmp];
                // cout<<distance<<endl;
                swap(tmp[sx],tmp[3*x+y]);
                if(!d.count(tmp))
                {
                    q.push(tmp);
                    d[tmp]=distance+1;
                }
             swap(tmp[sx],tmp[3*x+y]);   
            }
            
            
        }
    }
    return -1;
    
}


int main()
{
    char s[2];

    for (int i = 0; i < 9; i ++ )
    {
        cin >> s;
        s1 += *s;
    }

    cout << bfs() << endl;

    return 0;
}

关于邻接表的学习

一、首先 弄清楚 几个数组的含义,然后再去 看 邻接表的表示代码,发现容易理解一些,在这里再次回顾下几个数组的含义:

h[N] : 表示 第 i 个节点的 第一条边的 idx
ne[M] : 表示 与 第 idx 条边 同起点 的 下一条边 的 idx
e[M] : 表示 第idx 条边的 终点

N : 节点数量
M:边的数量
i : 节点的下标索引
idx : 边的下标索引

二、然后结合代码模版理解定义

变量初始化定义:

int h[N], e[M], ne[M], idx;


当我们加入一条边的时候:

public static void add(int a,int b){
e[idx] = b; // 记录 加入的边 的终点节点
ne[idx] = h[a]; // h[a] 表示 节点 a 为起点的第一条边的下标,ne[idx] = h[a] 表示把 h[a] 这条边接在了 idx 这条边的后面,其实也就是把 a 节点的整条链表 接在了 idx 这条边 后面;目的就是为了下一步 把 idx 这条边 当成 a 节点的单链表的 第一条边,完成把最新的一条边插入到 链表头的操作;
h[a] = idx++; // a节点开头的第一条边置为当前边,idx移动到下一条边
}

树和图的深度优先遍历

//对于每个点k,开一个单链表,存储k所有可以走到的点,h[k]存储这个单链表的头节点
//    h[N] : 表示 第 i 个节点的 第一条边的 idx
///   ne[M] : 表示 与 第 idx 条边 同起点 的 下一条边 的 idx
//    e[M] : 表示 第idx 条边的 终点
//    int h[N],e[N],ne[N],idx;
//    N : 节点数量
//    M:边的数量
//    i : 节点的下标索引
//    idx : 边的下标索引

//增加一条边a->b;
void add(int a,int b)
{
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
 } 
 
 //初始化
 idx=0;
 memset(h,-1,sizeof h);
 
 int dfs(int u){
 	st[u]=true;//st[u]表示u已经被遍历过
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int i=0;
		int j=e[i];
		if(!st[j])dfs(j);
	 } 
 } 

例题acw846树的重心

#include<iostream>
using namespace std;
#include<cstring>
#include <algorithm>
const int N=100010,M=2*N;
int e[M],h[N],ne[M],idx;//h表示该顶点的第一条边,e[M]表示该边的终点,ne表示同起点的下一条边
bool st[N];

int ans=N,n;


void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int dfs(int u)
{
    int size=0;
    int sum=0;
    
    st[u]=true;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!st[j])
        {
            int s=dfs(j);
            size=max(size,s);
            sum+=s;

        }
    }
    size=max(size,n-sum-1);
    ans=min(size,ans);
    return sum+1;
}


int main()
{
    cin>>n;
    memset(h, -1, sizeof h);
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        scanf("%d%d\n",&a,&b);
        add(a,b);
        add(b,a);
    }
    dfs(1);//顶点定义的是1-n,所以要从1开始dfs;
    cout<<ans;
    return 0;
    
}

树和图的广度优先遍历

模板

##bfs
queue<int>q;
st[1]=true;//表示第一个节点被遍历过 
q.push(1);//从1开始 

while(q.size())
{
	int t=q.front;
	q.pop();//出队 
	for(int i=h[t];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(!st[j])//将下一步的点都入队 
		{
			st[j]=true;
			q.push(j);
		}
	}
 } 

例题acw847图中点的层次

#include<iostream>
using namespace std;
#include<queue>
#include<cstring>
#include<algorithm>

const int N=100010;
int h[N],ne[N],e[N],idx,d[N];

void add(int a,int b)//插入
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}

int main()
{
    int m,n;
    cin>>n>>m;
    memset(h,-1,sizeof h);//不初始化下面会超时
    memset(d,-1,sizeof d);//初始化,注意位置,一定要先初始化
    while(m--)
    {
        int a,b;
        scanf("%d%d\n",&a,&b);
        add(a,b);
    }

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

    while(q.size())
    {
        int a=q.front();
        q.pop();
            
        for(int i=h[a];i!=-1;i=ne[i])
        {
            // cout<<"dsdffdfd[a]";
            int j=e[i];//
            if(d[j]==-1){//改变距离,重边和自环无法入队
                d[j]=d[a]+1;
                
                q.push(j);
            }
        }
    }
    cout<<d[n];
    
    
    
}

另外加一种我的尝试,目前无法解决自环问题

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N=100010,M=N*2;
int h[N],ne[M],e[M],idx,d[N];
bool st[N];
int n,m;
typedef pair<int,int> PII;


void add(int a,int b)
{
    e[idx]=b;ne[idx]=h[a],h[a]=idx++;
}

int main()
{
    // typedef pair<int,int> PII;
    // unordered_map<PII,int> mymap;
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);//对不起我是SB这里是初始化h,你不初始化永远跳不出去
    for(int i=0;i<m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        // if(!mymap[{a,b}])
        // {
        add(a,b);
            // mymap[{a,b}]++;
        // }
    }

    st[1]=true;
    queue<PII> q;
    q.push({1,0});
    //注意了你这里并不是和你想的一样,首先第一次确实它们都是距离为1,但是第二步呢,每次出队一个边距离就++这时不对的,这并不是距离只能说第几个被遍历的
    memset(d, -1, sizeof d);
    while(q.size())
    {
        PII b=q.front();
        int dis=b.second;
        int a=b.first;
        q.pop();
        for(int i=h[a];i!=-1;i=ne[i])
        {
            int j=e[i];
            
            if(!st[j])
            {
                st[j]=true;
                q.push({j,dis+1});
                d[j]=dis+1;
            }
        }
    }
    cout<<d[n];
    
}
 

拓扑排序

bool topsort()
{
    int hh = 0, tt = -1;

    // d[i] 存储点i的入度
    for (int i = 1; i <= n; i ++ )
        if (!d[i])
            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;
}

dijkstra 算法

int g[N][N];//存储每条边
int dist[N];//存储1号点到每条边的最短距离
bool st[N];//存储每个点的最短路径是否已经确定;

//求1号点到n号点的最短路,如果不存在返回-1 
int dijkstra()
{
	memset(dist,0x3f,sizeof dist);
	dist[1]=0;
	for(int i=0;i<n-1;i++)//先找出现在离的最近的且没有放进去的点 
	{
		int t=-1;
		for(int j=1;j<=n;j++)//一开始的时候一个点都没有,先确定了1这个点,然后更新1到其他点的距离 ,
		    if(!st[j]&&(t==-1||dist[t]>dist[j]))
		        t=j;
		
		for(int j=1;j<=n;j++)//更新距离 
		    dist[j]=min(dist[j],dist[t]+g[t][j]);//第一次循环加入了1这个点,然后开始更新到其他点的距离 
		st[t]=true; 
	}
	if(dist[n]=0x3f3f3f3f)return -1;
	return dist[n];
 }

bellman_ford

算法思想遍历所有边,遍历k次找到比较最短路径为k步的最短路径

int n,m;//n表示点数,m表示边数 
int dist[N],last[N];//dist[x]存储1到x的最短路径 

struct Edge
{
	int a,b,w;//a表示入点,b表示出点,w表示权重 
}edges[M];

//求1到n的最短路径,如果无法从1走到n,则返回-1;
int bellman_ford()
{
	memset(dist,0x3f,sizeof dist);
	dist[1]=0;
	
	for(int i=0;i<n;i++)
	{
		memcpy(last,dist,sizeof dist);//有时候为了避免串联,备份一个上一次的 
		for(int j=0;j<m;j++)
		{
			int a=edges[j].a,b=edges[j].b,w=edges[i].w;
			if(dist[b]>last[a]+w)
			    dist[b]=last[a]+w; 
		}
	 } 
	 if(dist[n]>0x3f3f3f3f/2)return -1;
	 return dist[n];
 } 

spfa

int n;//总点数
int h[N],w[N],e[N],ne[N],idx;//邻接表相关信息
int dist[N],cnt[N];//dist表示最短距离,cnt表示最短路径上经过点数
bool st[N];//表示判断是否在队列

//存在负环返回true,否则返回false;
bool spfa()
{
	//不需要初始化dist[]数组
	//原理:如果某条最短路径上有n个点,那么加上自己之后一共有n+1个点,那么一定存在负权回路
	queue<int> q;
	for(int i=1;i<=n;i++)
	{
		q.push(i);
		st[i]=true;
	 }
	 while(q.size())
	 {
	 	auto 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算法

#floyd算法,想象一下以k作为中间节点不断优化 
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]);

prim算法

int n;      // n表示点数
int g[N][N];        // 邻接矩阵,存储所有边
int dist[N];        // 存储其他点到当前最小生成树的距离
bool st[N];     // 存储每个点是否已经在生成树中


// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
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[t] > dist[j]))
                t = j;

        if (i && dist[t] == INF) return INF;

        if (i) res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}

Kruskal算法

#Kruskal
int n,m;//n为点数,m为边数
int p[N];//并查集的父节点数组 
struct Edge//存储边 
{
	int a,b,w;
 }edges[M];
 bool  cmp(Edge a,Edge b)
 {
 	return a.w<b.w;
  } 
  
  int find(int x)//并查集查找 
  {
  	if(p[x]!=x)p[x]=find(p[x]);
  	return p[x];
  }
  
  int kruskal()
  {
  	sort(edges,edges+m,cmp);
  	for(int i=1;i<=n;i++);p[i]=i;//并查集初始化 
  	int res=0,cnt=0;//权值和边数 
  	for(int i=0;i<m;i++)//一共m条边 
  	{
  		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 n;//n表示点数
int h[N],e[M],ne[M],idx;//邻接表存储图
int color[N];//表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色

//参数:u表示当前节点,c表示当前颜色
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]==-1)//未染色看以后是否冲突 
		{
			if(!dfs(j,!c))return false;
		}
		else if(color[j]==c)return false;//已染色判断是否相同,相同则冲突 
	}
	return true;
 } 
 bool check()
 {
 	memset(color,-1,sizeof color);
 	bool flag=true;
 	for(int i=1;i<=n;i++)
 	    if(color[i]==-1)
 	        if(!dfs(i,0))
 	        {
 	        	flag=false;
 	        	break;
			 }
	return flag;
 }

匈牙利算法二分图最大匹配

int n1,n2;//n1表示第一个集合中的点数,n2表示第二个集合中的点数 
int h[N],e[M],ne[M],idx;
int match[N];//存储第二个集合中每个点当前匹配的第一个集合中是哪个点 
bool st[N];//表示第二个集合中的每个点是否被遍历过 
int 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]==0||find(match[j]))
			{
				match[j]=x;
				return true;
			}
		}
	}
	return false;
 } 
//求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res=0;
for(int i;i<=n1;i++)//也就是最大匹配数 
{
	memset(st,false,sizeof st);
	if(find(i))res++;
 } int n1,n2;//n1表示第一个集合中的点数,n2表示第二个集合中的点数 
int h[N],e[M],ne[M],idx;
int match[N];//存储第二个集合中每个点当前匹配的第一个集合中是哪个点 
bool st[N];//表示第二个集合中的每个点是否被遍历过 
int 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]==0||find(match[j]))
			{
				match[j]=x;
				return true;
			}
		}
	}
	return false;
 } 
//求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res=0;
for(int i;i<=n1;i++)//也就是最大匹配数 
{
	memset(st,false,sizeof st);
	if(find(i))res++;
 } 

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注