图论算法小结

 

一、图搜索算法

1.广度优先搜索
  给定图G和特定的原点s,BFS系统的搜索G中的边,以期发现可以从s出发到达的所有顶点,并计算所有这些可达顶点之间的距离。
该算法同事还能生成一颗根为s、且包括所有s可达顶点的广度优先树。对s可达的顶点v,广度优先树从s到v的路径对应于图G中s到v的最短路径,即包含边最少的路径。该算法对有向和无向图都适用。 

以最简单的一维bfs作为模板



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

//以为的bfs,用step记录从原点开始到每一个点的步数
//map记录该点是否被访问过
int step[100001];
int map[100001];

//判断下一步要走的点是否在搜索范围内,并且有没有被访问到过
int judge(int a)
{
    if(a<0 || a>100000 || map[a] == 1)
        return 0;
    return 1;
}

int main()
{
    int k, n, y, m;
    queue<int>x;
    memset(map, 0, sizeof(map));
    memset(step, 0, sizeof(step));

    cin>>n>>k;
    x.push(n);//把起始点放入队列中
    step[n] = 0;
    map[n] = 1;
    while(!x.empty())//只要队列中有元素就必须继续处理
    {
        y=x.front();//每次取队首元素进行处理
        x.pop();
        map[y]=1;

        if(y==k)//如果已经到达目的地就退出
            break;

        m = y-1;//下一步可能到达的位置 可能+1,-1,*2
        if(judge(m))//如果是下一步的位置合法
        {
            x.push(m);//就把它放入队列中
            step[m]=step[y]+1;//到下一点的步数是这一点的步数加一
            map[m]=1;//标记为访问过
        }
        m = y+1;
        if(judge(m))
        {
            x.push(m);
            step[m]=step[y]+1;
            map[m]=1;
        }
        m = y*2;
        if(judge(m))
        {
            x.push(m);
            step[m]=step[y]+1;
            map[m]=1;
        }
    }
    cout<<step[y]<<endl;//读出目的节点的步数就是从源节点到目的节点所需的最少步数
    return 0;
}



一维,二维,三维的BFS,原理都是一样的

 

 2.深度优先搜索
  该算法是尽可能深地搜索一个图,对于最新发现的顶点,如果他还有以此为起点而未探测到的边,就沿着此边一直探测下去。
  当顶点v所有的边都被探索过之后,搜索将回溯到发现顶点v有起始点的那些边。这一过程一直进行到已发现从原定点可达的所有顶点为止,如果还有未达到的顶点,则选择其中一个作为原定点,并重复上述过程。整个过程反复进行直到发现所有顶点为止。
  深度优先搜索的先辈图形形成了一个由数棵深度优先树所组成的深度优先森林。



#include<iostream>
using namespace std;

int MAX;
int map[30][30];//记录某个节点是否走到过
int resx[1000];//记录结果,可以用不同的形式
int resy[1000];
int flag;
int p, q;
//定义下一步走的位置
int movey[8] = {-1,1,-2,2,-2,2,-1,1};
int movex[8] = {-2,-2,-1,-1,1,1,2,2};
//判断下一步要走的位置是不是合法
int judge(int a, int b)
{
    if(a<0||b<0||a>=p||b>=q)
        return 0;
    return 1;
}

void dfs(int num, int cur, int x, int y)
{
    resx[cur] = x;//先记录这一步的位置
    resy[cur] = y;
    if(flag)
        return;
    if(num == MAX)//如果已经遍历图上每一个点,就把结果输出
    {
        for(int i=0;i<MAX;i++)
        {
            cout<<char(resx[i]+'A')<<resy[i]+1;
        }
        cout<<endl;
        flag = 1;
        return;
    }
    for(int j=0;j<8;j++)//图还没有遍历完,就探测从s节点出发下一步可能到达的位置
    {
        int a = x+movex[j];
        int b = y+movey[j];
        if(judge(a, b) && map[a][b]==0)//如果该该位置合法
        {
            map[a][b]=1;
            dfs(num+1,cur+1,a,b);//从下一位置开始深搜
            map[a][b]=0;//回溯,探测从s节点出发下一步可能走的其他位置,此时需要将前一次试探时用到的标记改回来
        }
    }
}

int main()
{
    int n;    
    int i, j;
    freopen("e:\\data.txt", "r", stdin);   
    freopen("e:\\out.txt", "w", stdout); 

    cin>>n;
    for(int k=1; k<=n;k++)
    {
        cin>>q>>p;
        MAX = p*q;
        cout<<"Scenario #"<<k<<":"<<endl;
        memset(map, 0, sizeof(map));
        flag=0;
        map[0][0]=1;
        dfs(1,0,0,0);            
        if(flag==0)
            cout<<"impossible"<<endl;
        cout<<endl;
    }
    return 0;
}



 

 

二、最小生成树

1.kruskal算法
找出森林中连接任意两棵树的所有边中,具有最小权值的边作为安全边,并把它添加到正在生长的森林中。
是一种贪心算法,每一步添加到森林中的边要尽可能的小。



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

int n, m;
int mmax;//定义最小生成树的总长度
int num;//边的总数
struct node//定义每一条边,起始,结束,权重
{
    int x, y;
    int w;
};
node edge[10005]; //所有的边
int map[101][101];//用邻接矩阵表示的图
int father[101];//每个节点的父节点

int comp(node a, node b)
{
    return a.w<b.w;
}

void make_set(int n)//初始化 把每一个节点都作为一棵树
{
    for(int i=0;i<n;i++)
    {
        father[i] = i;
    }
}

int find_set(int a)//寻找某节点的父节点
{
    if(father[a] != a)
    {
        father[a] = find_set(father[a]);
    }
    return father[a];
}

void union_set(int a, int b, int w)//将两棵树合并
{
    father[a] = b;
    mmax+=w;//总长度增加 也可以求最小生成树的最大边或者其他参量
}

void kruskal()
{
    for(int i=0;i<n;i++)//把邻接矩阵中的边都取出来
    {
        for(int j=0;j<n;j++)
        {
            if(i<j)//适用于无向图
            {
                edge[num].x = i;
                edge[num].y=j;
                edge[num].w=map[i][j];
                num++;
            }
        }
    }
    sort(edge, edge+num, comp);//将边按权重排序
    mmax = 0;
    for(int i=0;i<num;i++)//每次检查当前的权重最小的边
    {
        int p = find_set(edge[i].x);//检查该边两端的节点是否在同一棵树中
        int q = find_set(edge[i].y);
        if(p!=q)//如果不在同一棵树中
        {
            union_set(p, q, edge[i].w);//就将这两个节点合并到一棵树中
        }
    }
}



 

2.prim算法
集合A中的边总是形成单棵树,树从任意跟顶点r开始形成,并逐渐扩大直到覆盖整个树。
每次加入的边都是对A来说安全的边,并且每次添加到树中的边都是使树的权尽可能小的边。



double map[510][510];//用邻接矩阵表示的图
double ans[510];//记录结果
double d[510];//辅助数组,记录当前已经探测到的节点到各个节点的最小距离
int mark[510];//记录节点是否已经被探测到过

int n, p;
int cur;
const double INF = 99999999.0;//设置一个极大值

void prim()
{
    int i, j;
    double mini;
    int pos;
    for(i = 1; i <= p; i++)//初始化都为极大值
    {
        d[i] = INF;
    }
    for(i = 1; i <= p; i++)//默认从第一个开始
    {
        d[i] = map[1][i];
    }
    mark[1] = 1;
    d[1] = 0;
    for(i = 1; i <= p - 1; i++)//因为一个图中有p个点,所以只要搜索p-1次就可以连通p个点
    {
        mini = INF;
        pos = 0;
        for(j = 1; j <= p; j++)//在已探测到节点到各个节点的最小边中取出权重最小的一条边
        {
            if(mark[j] != 1 && d[j] < INF && d[j] < mini)
            {
                mini = d[j];
                pos = j;
            }
        }
        ans[cur] = mini;//记录新加入的边
        cur++;
        mark[pos] = 1;
        
        for(j = 1; j <= p; j++)//对于新加入的节点,重新扫描跟这个点相关的每一条边,如果有到达某点的距离比现有的距离更小的,就更新
        {
            if(mark[j] != 1 && map[pos][j] < d[j] && map[pos][j] < INF)
                d[j] = map[pos][j];
        }
    }
}




 

 

三、单源最短路径

1.bellman-ford
能在一般情况下(存在负权边的情况下),解决单源最短路径问题。
最后可以返回一个值,表明是否存在一个从原点可达的权为负的回路,若存在这样的回路,则说明该问题无解。若不存在,则算法会产生最短路及其权值。
运用松弛技术,对每个顶点v,逐步减少从源到v的最短路径的权值的估值,直到达到实际的最短路径。



#include <iostream>
using namespace std;
int n,m ;//n为顶点总数,m为边的总数
struct node//记录每一条边 可以用不同的方式来记录
{
    int s,e;
    double edge ;
};
node edge[1000] ;
double dist[1000] ;//从原点开始到每一个节点的距离

int bellman ()
{
    for (int i=1 ; i<=n ; i++) 
        dist[i] = INF;//初始化为最大值
    dist[1] = 1;
    for (int i=1 ; i<n ; i++)//对n个节点来说,需要进行n-1次操作就可以完成该算法
    {
        for (int j=1 ; j<=m ; j++)//检查每一条边,如果现在该边两端的节点间的距离要大于该边的长度,就对其进行更新
        {
            if (dist[edge[j].e]>dist[edge[j].s]+edge[j].edge)
            {
                dist[edge[j].e] = dist[edge[j].s]+edge[j].edge ;
            }
        }
    }
    for (int i=1 ; i<=m ; i++)//检查每一条边,如果还可以继续松弛,就说明存在一条从原点可达的权为负的回路,如果存在,则说明该问题无解
    {
        if (dist[edge[i].e] > dist[edge[i].s] + edge[i].edge ) 
            return 1 ;
    }
    return 0 ;
}



 

 

2.dijkstra算法
设置一顶点集合S,从原点s到集合中的顶点的最终最短路径的权值均已确定。算法反复选择具有最短路径估计的顶点u,并将u加入S中,对u的所有出边进行松弛。
要求所有边的权值非负。



#include<iostream>
using namespace std;
const int N = 100;
int map[N][N];//用邻接矩阵表示的图
int d[N];//辅助数组,记录当前已经探测到的节点到各个节点的最小距离
int mark[N];//记录节点是否已经被探测到过

int p;//顶点数 
const int INF = INT_MAX;//设置一个极大值

void dijkstra(int v)
{
    int i, j;
    int mini;
    int pos = v;
    memset(mark, 0, sizeof(mark));
    for(i=1; i<=p; i++)//初始化都为极大值
    {
        d[i] = INF;
    }
    for(i=1; i<=p; i++)
    {
        d[i] = map[pos][i];
    }
    mark[v] = 1;
    d[v] = 0;
    for(i=1; i<p; i++)
    {
        mini = INF;
        for(j=1; j<=p; j++)
        {
            if(mark[j]!=1 && d[j]<INF && d[j]<mini)
            {
                mini = d[j];
                pos = j;
            }
        }
        mark[pos] = 1;
        
        for(j=1; j<=p; j++)//对于新加入的节点,重新扫描跟这个点相关的每一条边,进行松弛操作 
        {
            if(mark[j]!=1 && map[pos][j]+d[pos]<d[j] && map[pos][j]<INF)
                d[j] = map[pos][j] + d[pos]; 
        }
    }
}



 

四、每对顶点之间的最短距离

1.floyd算法



#include<iostream>
using namespace std;

int adj[1010][1010];
int n;

void floyd()
{
    int i, j, k;
    for(i=1; i<=n;i++)
    {
        for(j=1;j<=n;j++)
        {
            for(k=1;k<=n;k++)
            {
                if(adj[i][j] != INF && adj[i][k] != INF)
                {
                      if(adj[i][j] + adj[i][k] < adj[j][k])
                          adj[k][j] = adj[j][k] = adj[i][j] + adj[i][k];
                }

            }
        }
    }
}