基础图论算法

        图论知识是算法当中的一个非常重要的模块,同时也可以算是对算法的学习正式进入入门的阶段。有关于图论的基础知识在这里就不做过多的介绍了,这一部分的知识详见树与二叉树这两篇以数据结构为基础的文章。本篇博客的题目来源全部来源于团体程序设计天梯赛-练习集中,主要是以模板为主,为今后做题做好准备。


Part 1 并查集

        并查集可以算是图上的最常见的搜索算法,通俗来讲,并查集可以解决一些“溯源”的问题,比如说有关于连通图的问题,就可以拿并查集来解决。并查集的套路也非常固定,而且非常的实用。下面的题目都是基于并查集来做的(当然这些题目的解法不唯一,在这里只是展现用并查集处理问题的方法)。

//并查集模板

void init()//初始化
{
    for (int i=1;i<=n;i++)
        pre[i]=i;
}

int find(int x)//溯源
{
    return pre[x]==x?x:find(pre[x]);//这一部分也可以写成非递归的形式
}

void union(int x,int y)//合并
{
    x=find(x);
    y=find(y);
    if (x!=y)
        pre[y]=x;
}

L2-010 排座位

题目链接

         这道题是一个非常经典的并查集模型题,“朋友的朋友就是朋友”就已经得到一个信息:基于“朋友”建图,我们可以根据朋友的关系去建立这一张关系网,进而根据不同的人物关系得到相应的结果,具体代码如下:

#include <bits/stdc++.h>
using namespace std;

int pre[105];
int g[105][105];
int n,m,q;
void init()
{
	for (int i=1;i<=n;i++)
		pre[i]=i;
}
int find(int x)
{
	return pre[x]==x?x:find(pre[x]);
}
void union(int x,int y)
{
	x=find(x);
	y=find(y);
	if (x!=y)
		pre[y]=x;
}

int main()
{
    cin>>n>>m>>q;
    init();
    while (m--)
    {
    	int a,b,f;
    	cin>>a>>b>>f;
    	g[a][b]=g[b][a]=f;
    	if (f==1)
    		union(a,b);
	}
	int x1,y1;
	while (q--)
	{
		int x,y;
		cin>>x>>y;
		x1=find(x);
		y1=find(y);
		if (g[x][y]!=-1&&x1==y1)
			cout<<"No problem"<<endl;
		else if (g[x][y]==0)
			cout<<"OK"<<endl;
		else if (g[x][y]==-1&&x1==y1)
			cout<<"OK but..."<<endl;
		else if (g[x][y]==-1&&x1!=y1)
			cout<<"No way"<<endl;
	}
    return 0;
}

        类似有关并查集的题目还有L2-013 红色警报L2-024 部落等。


Part 2 树上的操作

        树的操作相信大家有了数据结构的基础后应该有所了解了,但实际上我们在写代码的时候,其实是不会采用数据结构中的那种写法,而是用一种更加直观的方式去建树,并且去访问树中的节点(四种遍历)。

//建立一棵二叉树

struct node
{
    int data;
    node *l, *r;
};

int N, pre[maxn], in[maxn], post[maxn];//存储节点数组

node* create(int postL,int postR,int inL,int inR)
{
    if(postL>postR)
        return NULL;

    node* root=new node;
    root->data=post[postR];//先序这里等式右端为pre[preL]
    int k;
    for (k=inL;k<=inR;k++)
        if (in[k]==post[postR])
            break;
    int numLeft=k-inL;
    root->l=create(postL,postL+numLeft-1,inL,k-1);
    root->r=create(postL+numLeft,postR-1,k+1,inR);
    return root;
}
//层次遍历二叉树

void Bfs(node* root)
{
    queue<node*> q;
    q.push(root);
    while(!q.empty())
    {
        node* cur=q.front();
        q.pop();
        cout<<cur->data<<" ";

        if(cur->l!=NULL)
            q.push(cur->l);
        if(cur->r!=NULL)
            q.push(cur->r);
    }
}

L2-006 树的遍历

题目链接

        建树相信大家都不陌生,这里建树的过程和上面的代码一致,采用数组存储相应的顺序,最后按照一定的递归规则进行建树,层次遍历时采用BFS思想,利用一个队列进行存储,具体代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn=35;
struct node
{
    int data;
    node *l, *r;
};

int N, pre[maxn], in[maxn], post[maxn];//存储节点数组

node* create(int postL,int postR,int inL,int inR)
{
    if(postL>postR)
        return NULL;

    node* root=new node;
    root->data=post[postR];
    int k;
    for (k=inL;k<=inR;k++)
        if (in[k]==post[postR])
            break;
    int numLeft=k-inL;
    root->l=create(postL,postL+numLeft-1,inL,k-1);
    root->r=create(postL+numLeft,postR-1,k+1,inR);
    return root;
}
int indx = 1;
void Bfs(node* root)
{
    queue<node*> q;
    q.push(root);
    while (!q.empty())
    {
        node* cur=q.front();
        q.pop();
        if (indx++!=1)
            cout<<' '<<cur->data;
        else
            cout<<cur->data;

        if(cur->l!=NULL)
            q.push(cur->l);
        if(cur->r!=NULL)
            q.push(cur->r);
    }
}
int main()
{
    cin>>N;
    for (int i=0;i<N;i++)
        cin>>post[i];
    for (int i=0;i<N;i++)
        cin>>in[i];

    node* root=create(0,N-1,0,N-1);
    Bfs(root);

	return 0;
}

        类似的题目还有L2-004 这是二叉搜索树吗? 、L2-011 玩转二叉树等。


Part 3 最短路径Dijkstra

        迪杰斯特拉算法是求最短路径中最常用的一种算法,具体该算法的推理过程在这里就不详细说明了,不同于数据结构中的写法,在这里采用了一种比较简洁的形式。

void dijkstra()
{
    memset(vis,0,sizeof(vis));
    for (int i=0;i<n;i++)
		dist[i]=G[s][i];
    int Min=inf;
    int v=-1;
    for (int i=0;i<n;i++)
    {
        Min=inf;
        v=-1;
        for (int j=0;j<n;j++)
        {
            if (!vis[j]&&dist[j]<Min)
            {
                Min=dist[j];
                v=j;
            }
        }
        vis[v]=1;
        for (int j=0;j<n;j++)
        {
            if(!vis[j]&&dist[j]>dist[v]+G[v][j])
                dist[j]=dist[v]+G[v][j];
        }
    }
}

L2-001 紧急救援

题目链接

        这是一道非常经典的最短路径算法问题,本题还要求输出路径,所以最后还要用DFS进行深度优先搜索,将答案记录在容器中一并输出。具体代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 550;
int n,m,s,d,Maxv,cnt;
int val[maxn],vis[maxn],dist[maxn];
int G[maxn][maxn];
vector<int> ans,pre;
void dijkstra()
{
    memset(vis,0,sizeof(vis));
    for (int i=0;i<n;i++)
		dist[i]=G[s][i];
    int Min=inf;
    int v=-1;
    for (int i=0;i<n;i++)
    {
        Min=inf;
        v=-1;
        for (int j=0;j<n;j++)
        {
            if (!vis[j]&&dist[j]<Min)
            {
                Min=dist[j];
                v=j;
            }
        }
        vis[v]=1;
        for (int j=0;j<n;j++)
        {
            if(!vis[j]&&dist[j]>dist[v]+G[v][j])
                dist[j]=dist[v]+G[v][j];
        }
    }
}
void dfs(int x, int w)
{
    if(x == d)
    {
        if (w>Maxv)
        {
            Maxv=w;
            ans=pre;
        }
        cnt++;
        return;
    }
    for (int i=0;i<n;i++)
    {
        if(G[x][i]&&!vis[i]&&dist[i]==dist[x]+G[x][i])
        {
            vis[i]=1;
            pre.push_back(i);
            dfs(i,w+val[i]);
            pre.pop_back();
            vis[i]=0;
        }
    }
}
int main()
{
    int u,v,w;
    cin>>n>>m>>s>>d;
    for (int i=0;i<n;i++)
    	cin>>val[i];
    for (int i=0;i<n;i++)
    {
        for (int j=0;j<n;j++)
        {
            if (i!=j)
				G[i][j] = inf;
            else
				G[i][j] = 0;
        }
    }
    for (int i=0;i<m;i++)
    {
        cin>>u>>v>>w;
        G[u][v]=w;
        G[v][u]=w;
    }
    dijkstra();
    Maxv=0;
    cnt=0;
    memset(vis,0,sizeof(vis));
    pre.push_back(s);
    dfs(s,val[s]);
    cout<<cnt<<" "<<Maxv<<endl;
    int len=ans.size();
    for (int i=0;i<len;i++)
    {
        if (i==0)
			cout<<ans[i];
        else
        	cout<<" "<<ans[i];
    }
    return 0;
}

Part 4 DFS与BFS

        DFS与BFS都是图上非常重要的搜索算法,其中DFS应用较广泛,在基础的算法题目中经常见到(其实是本菜鸟确实不大会BFS),其基本的思想是回溯法,根据不同的题目,不同的要求去写相应的DFS函数,这里给出一个非常基础的DFS模板:

L2-020 功夫传人

题目链接

 

        这道题其实并没有利用到回溯思想,但确实是一道非常好的dfs裸题,每次递归的参数都有所变化,利用了一个二维vector记录变量,具体代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,k,x,vis[100010];
double sum,z,r;
vector<vector<int>>v;
void dfs(int index,double power)
{
	if(vis[index])
	{
		sum+=power*v[index][0];
		return ;
	}
	for(int i=0;i<v[index].size();i++)
		dfs(v[index][i],power*(1-r/100));
}
int main()
{
	cin>>n>>z>>r;
	v.resize(n);
	for(int i=0;i<n;i++)
	{
		cin>>k;
		if(!k)
		{
			cin>>x;
			vis[i]=1;
			v[i].push_back(x);
		}
		while(k--)
		{
			cin>>x;
			v[i].push_back(x);
		}
	}
	dfs(0,z);
	cout<<(int)sum;
}

 L2-038 病毒溯源

题目链接

         光看标题就知道这是一个深度优先搜索的题目,在众多输入中找到一条最长的变异链。所以很明显,有关DFS算法的题目基本上都是要求最长的…、或者输出某一条路径等等这类关键字。和第一部分说明的并查集类似,并查集是一个建图的过程,这个是一个遍历的过程,所以如果发现这二者结合起来的时候也不必惊讶。具体代码如下:

#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<set>
#include<stack>
#include<string>
#include<queue>
#include<vector>
#include<cctype>
#include<map>
using namespace std;
int n;
const int maxn=10001;
vector<int>v[maxn];
vector<int>temp;
int t[maxn];
void Dfs(int index,vector<int>&p){//这里需要用到&p,否则会内存过大
	if(p.size()>temp.size()){//找到更深的,更新temp数组
		temp.clear();
		temp=p;
	}
	for(int i=0;i<v[index].size();i++){//标号为index的孩子节点
		p.push_back(v[index][i]);//先将孩子节点入p
		Dfs(v[index][i],p);//深搜孩子节点
		p.pop_back(); //记得回溯!!
	}
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		int k;
		cin>>k;
		while(k--){//当k等于0 时,不执行循环,所以不需要特殊考虑
			int x;
			cin>>x;
			v[i].push_back(x);
			t[x]=1;//将所有的孩子节点的t都设为1,这样只需要找到不是1的节点,那个节点就是根节点!
		}
		if(v[i].size()){
			sort(v[i].begin(),v[i].end());//每次放完孩子都需要排序,以求得最小的编号
		}
	}
	for(int i=0;i<n;i++){
		if(!t[i]){//这一步的目的是找到根节点进行深搜,
			vector<int>p;
			p.push_back(i);
			Dfs(i,p);//从根节点开始搜索,每次更新数组p
			break;
		}
	}
	cout<<temp.size()<<endl;
	for(int i=0;i<temp.size();i++){
		if(!i) cout<<temp[i];
		else cout<<" "<<temp[i];
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值