图算法总结

邻接矩阵
V和E集合。因此,用一个一维数组存放图中所有顶点数据;用一个二维数组存放顶点间关系(边或弧)的数据,这个二维数组称为邻接矩阵。邻接矩阵又分为有向图邻接矩阵和无向图邻接矩阵
代码

int i,j,k,w;
scanf("%d%d",&G->n,&G->e); //输入顶点数和边数
for(i = 0;i < n;i++) //读人顶点信息,建立顶点表
{
    G->vexs=getchar();
}
for(i = 0;i < G->n;i++)
{
    for(j = 0;j < G->n;j++)
    {
        G->edges[i][j] = 0; //邻接矩阵初始化
    }
}
for(k = 0;k < G->e;k++)
{//读入e条边,建立邻接矩阵
    scanf("%d%d%d",&i,&j,&w); //输入边(v i ,v j )上的权w
    G->edges[i][j]=w;
}
}//CreateMGraph

邻接表
邻接表由表头结点和表结点两部分组成,其中图中每个顶点均对应一个存储在数组中的表头结点。
代码

#include <vector>
#include <iostream>
using namespace std;
int main()
{
    vector<vector<size_t>> graph(5);
    graph[0].push_back(1);//V0->V1.
    graph[1].push_back(4);//V1->V4.
    graph[1].push_back(0);//V1->V0.
    graph[2].push_back(1);//V2->V1.
    graph[2].push_back(3);//V2->V3.
    graph[3].push_back(0);//V3->V0.
    graph[4].push_back(3);//V4->V3.
    //假定要访问点1.
    for(const auto &ele:graph[1])//对于全部属于graph[1]这个容器的元素
    std::cout<<ele<<'';
    std::cout<<std::endl;
    //程序运行后输出40,表示点1连接了4点和0点。
    return 0;
}

并查集
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

1 for i←1 to N
2 do SUB-Make-Set(i)
3 for i←1 to M
4 do if SUB-Find-Set(ai) != SUB-Find-Set(bi)
5 then SUB-Union(ai, bi)
6 for i←1 to Q
7 do if SUB-Find-Set(ci)=SUB-Find-Set(di)
8 then output “Yes?”
9 else output “No?”

最小生成树
Prim算法
任取一个顶点加入生成树;
在那些一个端点在生成树里,另一个端点不在生成树里的边中,取权最小的边,将它和另一个端点加进生成树。
重复上一步骤,直到所有的顶点都进入了生成树为止。

programprim;
const
map:array[1..6,1..6]ofinteger=((0,6,1,5,0,0), (6,0,5,0,3,0), (1,5,0,5,6,4), (5,0,5,0,0,2), (0,3,6,0,0,6), (0,0,4,2,6,0));//样例输入
var
i,j,l:integer;
min,minn:longint;
f,d:array[1..6]ofinteger;
s:array[1..6,1..3]ofinteger;
v,p:setof1..6;
begin
l:=1;
p:=[];
v:=[];
fori:=2to6do v:=v+[i];
p:=p+[1];
fori:=1to6do f[i]:=1000;//还可以写成filldword(f,sizeof(f)div2,1000)
f[1]:=0;
fori:=1to6dod[i]:=0;//还可以写成fillchar(d,sizeof(d),0);
s[1,3]:=0;
fori:=1to5do begin min:=1000;
forj:=1to6do begin if(f[j]>map[l,j])and(jinv)and(map[l,j]<>0)then begin f[j]:=map[l,j];
d[j]:=l;
end;
if(f[j]<min)and(f[j]>0)then
begin
min:=f[j];
minn:=j;
end;
writeln(d[j]);
end;
f[minn]:=0;
v:=v-[minn];
p:=p+[minn];
s[i,1]:=d[minn];
l:=minn;
s[i,2]:=minn;
s[i,3]:=min;
end;
fori:=1to5dowrite(s[i,1],'to',s[i,2],'=',s[i,3],'-->');
readln;
end.

Kruskal算法
对所有边从小到大排序;
依次试探将边和它的端点加入生成树,如果加入此边后不产生圈,则将边和它的端点加入生成树;否则,将它删去;
直到生成树中有了n-1条边,即告终止。
算法的时间复杂度O(eloge)

#include "stdio.h"
#include "stdlib.h"
struct edge
{
    int m;
    int n;
    int d;
}a[5010];
int cmp(const void *a,const void *b)//按升序排列
{
    return((struct edge*)a)->d - ((struct edge*)b)->d;
}
int main(void)
{
    int i,n,t,num,min,k,g,x[100];
    printf("请输入顶点的个数:");
    scanf("%d",&n);
    t = n * ( n - 1 ) / 2;
    for(i=0;i<=n;i++)
        x[i]=i;
    printf("请输入每条边的起始端点、权值:/n");
    for(i=0;i<t;i++)
    scanf("%d%d%d",&a[i].m,&a[i].n,&a[i].d);//输入每条边的权值
    qsort(a,t,sizeof(a[0]),cmp);
    min=num=0;
        for(i=0;i<t && num < n-1;i++)
        {
            for(k=a[i].m;x[k]!=k;k=x[k])//判断线段的起始点所在的集合
                x[k]=x[x[k]];
            for(g=a[i].n;x[g]!=g;g=x[g])//判断线段的终点所在的集合
                x[g]=x[x[g]];
            if(k!=g)//如果线段的两个端点所在的集合不一样
            {
                x[g]=k;
                min+=a[i].d;
                num++;
                printf("最小生成树中加入边:%d%d/n",a[i].m,a[i].n);
            }
        }
    printf("最小生成树的权值为:%d/n",min);
    system("pause");
    return 0;
}

最短路径
Dijkstra算法
基本思想:设置一个集合S存放已经找到最短路径的顶点,S的初始状态只包含源点v,对vi∈V-S,假设从源点v到vi的有向边为最短路径。以后每求得一条最短路径v, …, vk,就将vk加入集合S中,并将路径v, …, vk , vi与原来的假设相比较,取路径长度较小者为最短路径。重复上述过程,直到集合V中全部顶点加入到集合S中。

#include<stdio.h>
#include<stdlib.h>
#define max 11000000000
inta[1000][1000];
intd[1000];//d表示某特定边距离
intp[1000];//p表示永久边距离
inti,j,k;
intm;//m代表边数
intn;//n代表点数
intmain()
{
scanf("%d%d",&n,&m);
intmin1;
intx,y,z;
for(i=1;i<=m;i++) { scanf("%d%d%d",&x,&y,&z); a[x][y]=z; a[y][x]=z; } for(i=1;i<=n;i++) d[i]=max1; d[1]=0; for(i=1;i<=n;i++) { min1=max1; for(j=1;j<=n;j++) if(!p[j]&&d[j]<min1) { min1=d[j]; k=j; } p[k]=j; for(j=1;j<=n;j++) if(a[k][j]!=0&&!p[j]&&d[j]>d[k]+a[k][j])
d[j]=d[k]+a[k][j];
}
for(i=1;i<n;i++) printf("%d->",p[i]);
printf("%d\n",p[n]);
return0;
}

Bellman-Ford算法思想
Bellman-Ford算法构造一个最短路径长度数组序列dist 1 [u], dist 2 [u], …, dist n-1 [u]。其中:
dist 1 [u]为从源点v到终点u的只经过一条边的最短路径长度,并有dist 1 [u] =Edge[v][u];
dist 2 [u]为从源点v最多经过两条边到达终点u的最短路径长度;
dist 3 [u]为从源点v出发最多经过不构成负权值回路的三条边到达终点u的最短路径长度;
……
dist n-1 [u]为从源点v出发最多经过不构成负权值回路的n-1条边到达终点u的最短路径长度;
算法的最终目的是计算出dist n-1 [u],为源点v到顶点u的最短路径长度。

bool Bellman-Ford(G,w,s) //图G ,边集 函数 w ,s为源点
1 for each vertex v ∈ V(G) //初始化 1阶段
2 d[v] ←+∞;
3 d[s] ←0; //1阶段结束
4 for(int i=1;i<|v|;i++) //2阶段开始,双重循环。
5 for each edge(u,v) ∈E(G) //边集数组要用到,穷举每条边。
6 if(d[v]> d[u]+ w(u,v))//松弛判断
7 d[v]=d[u]+w(u,v); //松弛操作2阶段结束
8 for each edge(u,v) ∈E(G)
9 if(d[v]> d[u]+ w(u,v))
10 return false;
11 return true;

spfa算法
算法:
1.队列Q={s}
2.取出队头u,枚举所有的u的临边 .若d(v)>d(u)+w(u,v)则改进 ,pre(v)=u,由于d(v)减少了,v可能在以后改进其他的点,所以若v不在Q中,则将v入队。
3.一直迭代2,直到队列Q为空(正常结束),或有的点的入队次数>=n(含有负圈)。
一般用于找负圈(效率高于Bellman-Ford),稀疏图的最短路

#include<iostream>
#include<vector>
#include<deque>
using namespace std;
struct Edge
{
    int to,length;
};
bool spfa(const int &beg,//出发点
          const vector<vector<Edge> > &adjlist,//邻接表,通过传引用避免拷贝
          vector<int> &dist,//出发点到各点的最短路径长度
          vector<int> &path)//路径上到达该点的前一个点
//C++习惯上函数异常返回非零值,未异常才返回0(想想main函数),因此出现负权回路返回1!
//福利:这个函数没有调用任何全局变量,可以直接复制!
{
    const int &INF=0x7FFFFFFF,&NODE=adjlist.size();//用邻接表的大小传递顶点个数,减少参数传递
    dist.assign(NODE,INF);//初始化距离为无穷大
    path.assign(NODE,-1);//初始化路径为未知
    deque<int> que(1,beg);//处理队列
    vector<bool> flag(NODE,0);//标志数组,判断是否在队列中
    vector<int> cnt(NODE,0);//记录各点入队次数,用于判断负权回路
    dist[beg]=0;//出发点到自身路径长度为0
    ++cnt[beg];//开始计数
    flag[beg]=1;//入队
    while(!que.empty())
    {
        const int now=que.front();//当前处理的点,由于后面被删除,不可定义成常量引用
        que.pop_front();
        flag[now]=0;//将该点拿出队列
        for(int i=0; i!=adjlist[now].size(); ++i)//遍历所有与当前点有路径的点
        {
            const int &next=adjlist[now][i].to;//目标点,不妨定义成常量引用,稍稍快些
            if(dist[now]<INF&&//若距离已知(否则下面右式计算结果必爆int),且
                   //注:与运算先判断左式是否成立,若不成立则右式不会被判断
                   dist[next]>dist[now]+adjlist[now][i].length)//优于当前值
            {
                dist[next]=dist[now]+adjlist[now][i].length;//更新
                path[next]=now;//记录路径
                if(!flag[next])//若未在处理队列中
                {
                    if(++cnt[next]==NODE)return 1;//计数后出现负权回路
                    if(que.empty()||//空队列,或(或运算实现原理类似与运算)
                           dist[next]<dist[que.front()])//优先级高于队首(SLF)
                        que.push_front(next);//放在队首
                    else que.push_back(next);//否则放在队尾
                    flag[next]=1;//入队
                }
            }
        }
    }
    return 0;
}
int main()
{
    int n_num,e_num,beg;//含义见下
    cout<<"输入点数、边数、出发点:";
    cin>>n_num>>e_num>>beg;
    vector<vector<Edge> > adjlist(n_num,vector<Edge>());//默认初始化邻接表
    for(int i=0,p; i!=e_num; ++i)
    {
        Edge tmp;
        cout<<"输入第"<<i+1<<"条边的起点、终点、长度:";
        cin>>p>>tmp.to>>tmp.length;
        adjlist[p].push_back(tmp);
    }
    vector<int> dist,path;//用于接收最短路径长度及路径各点
    if(spfa(beg,adjlist,dist,path))cout<<"图中存在负权回路\n";
    else for(int i=0; i!=n_num; ++i)
        {
            cout<<beg<<"到"<<i<<"的最短距离为"<<dist[i]<<",反向打印路径:";
            for(int w=i; path[w]>=0; w=path[w])
                cout<<w<<"<-";
            cout<<beg<<'\n';
        }

你可能感兴趣的:(图算法总结)