第四专题总结(图算法)

1.定义

①树(Tree):n(n≥0)个结点的有限集。若 n = 0,称为空树;若n>0,则它满足如下两个条件:

(1)有且仅有一个特定的称为根 (Root) 的结点;

(2)  其余结点可分为 m (m≥0) 个互不相交的有限集 T1, T2,T3, …, Tm,其中每一个集合本身又是一棵树,并称为 根的子树(SubTree)。

②图 (Graph) :一种复杂的非线性数据结构,由顶点集合及顶点间的关系(也称弧或边)集合组成。可以表示为:G=(V, {VR})其中 V 是顶点的有穷非空集合; VR 是顶点之间关系 的有穷集合,也叫做弧或边集合。弧是顶点的有序对,边是顶点的无序对。

③度:无向图中顶点 v 的度是和 v 相关联的边的数目,记为TD(v)。

入度:有向图中以顶点 v 为终点的弧数目称为 v 的入度,记ID(v)

出度:有向图中以顶点 v 为起点的弧数目称为 v 的出度,记OD(v)。 

其中:TD(v) = ID(v) + OD(v)

④连通分量:无向图的极大连通子图;任何连通图的连通分量只有一个,即其本身;非连通图有多个连通分量(非连通图的每一个连通部分)。

强连通图:有向图G中,若对于V(G)中任意两个不同的顶点vi和vj,都存在从vi到vj以及从vj到vi的路径,则称G是强连通图。
强连通分量:有向图的极大强连通子图;任何强连通图的强连通分量只有一个,即其本身;非强连通图有多个强连通分量。

2.图的存储方式

图的存储结构之数组表示法(邻接矩阵表示法)

对于一个具有n个顶点的图,可用两个数组存储。其中一个一维数组存储数据元素(顶点)的信息,另一个二维数组(图的邻接矩阵)存储数据元素之间的关系(边或弧)信息。

邻接矩阵:设 G = (V, {VR}) 是具有 n 个顶点的图,顶点的顺序依次为 {v1, v2, …, vn},则 G 的邻接矩阵是具有如下性质的 n 阶方阵:

特点:无向图的邻接矩阵对称,可压缩存储;有n个顶点的无向图需存储空间为 n(n-1)/2。

有向图邻接矩阵不一定对称;有n个顶点的有向图需存储空间为n²,空间复杂度O(n2),用于稀疏图时空间浪费严重。

无向图中顶点vi的度 TD(vi) 是邻接矩阵中第i行1的个数。

有向图中顶点vi的出度是邻接矩阵中第i行1的个数。有向图中顶点vi的入度是邻接矩阵中第i列1的个数。

邻接矩阵使用场合:

数据规模不大n <= 1000,m越大越好

稠密图最好用邻接矩阵

图中不能有多重边出现

②邻接表(类似于树的孩子链表表示法)

特点:若无向图中有n个顶点、e条边,则其邻接表需n个顶点表结点和2e个边表结点。适宜存储稀疏图。无向图中顶点 vi 的度为第 i 个单链表中的结点数。

邻接表使用场合:

顶点数很多n>1000,边数却不多。

采用邻接表存储后,很多算法的复杂度也都是跟边数有关。

连通性的问题很多情况边数不多,多采用邻接表存储方式

3.并查集

英文:Disjoint Set,即“不相交集合”

将编号分别为1…N的N个对象划分为不相交集合,

在每个集合中,选择其中某个元素代表所在集合。

常见两种操作:

合并两个集合

查找某元素属于哪个集合

4.具体问题

①最小生成树问题

生成树:由G的n-1条边构成的无环的子图,这些边的集合成为生成树。

最小生成树:所有生成树中权值最小的一个边集T为最小生成树,确定树T的问题成为最小生成树问题。

Prim算法

设G=(V,E)是连通带权图,V={1,2,…,n}。

构造G的最小生成树的Prim算法的基本思想是:首先置S={1},然后,只要S是V的真子集,就作如下的贪心选择:选取满足条件iÎS,jÎV-S,且c[i][j]最小的边,将顶点j添加到S中。这个过程一直进行到S=V时为止。

在这个过程中选取到的所有边恰好构成G的一棵最小生成树。

Kruskal算法

将边按权值从小到大排序后逐个判断,如果当前的边加入以后不会产生环,那么就把当前边作为生成树的一条边。

最终得到的结果就是最小生成树。并查集

Kruskal算法步骤:

把原始图的N个节点看成N个独立子图

每次选取当前最短的边(前提操作是?),看两端是否属于不同的子图;若是,加入;否则,放弃;

循环操作该步骤二,直到有N-1条边

#include <stdio.h>

#include <iostream>

using namespace std;

#include <algorithm>

const int N=105;

int father[N];

int find(int x){

    if(x!=father[x])

        father[x]=find(father[x]);

    return father[x];

}

struct edge{

    int x,y,v;

}e[N*(N-1)/2];

int cmp(edge e1,edge e2){

    return e1.v<e2.v;

}

int main(){

    int n;

    while(scanf("%d",&n)!=EOF&&n){

        for(int i=0;i<=n;i++){

            father[i]=i;

        }

        n=n*(n-1)/2;

        for(int i=0;i<n;i++){

            scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].v);

        }

        sort(e,e+n,cmp);

int res=0;

        for(int i=0;i<n;i++){

            int x=find(e[i].x);

            int y=find(e[i].y)

            if(x!=y) {

                res+=e[i].v;

                father[x]=y;

            }

        }

        printf("%d\n",res);

    }

    return 0;

}

②单源点最短路径问题

问题描述:给定带权有向图G=(V, E)和源点v∈V,求从v到G中其余各顶点的最短路径。

 

Dijkstra算法

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

Dijkstra算法——伪代码

1. 初始化数组dist、path和s;

2. while (s中的元素个数<n)

     2.1 在dist[n]中求最小值,其下标为k;

     2.2 输出dist[j]和path[j];

     2.3 修改数组dist和path;

     2.4 将顶点vk添加到数组s中;

计算单源最短路径问题的Dijkstra算法

#define NUM 100

#define maxint 10000

//顶点个数n,源点v,有向图的邻接矩阵为c

//数组dist保存从源点v到每个顶点的最短特殊路径长度

//数组prev保存每个顶点在最短特殊路径上的前一个结点

void dijkstra(int n, int v, int dist[], int prev[], int c[][NUM])

{

  int i,j;

  bool s[NUM];     //集合S

  //初始化数组

  for(i=1; i<=n; i++)

  {

    dist[i] = c[v][i];

    s[i] = false;

    if (dist[i]>maxint) prev[i] = 0;

    else prev[i] = v;

  }

  //初始化源结点

  dist[v] = 0;

  s[v] = true;

//其余顶点

  for(i=1; i<n; i++)

  {

    //在数组dist中寻找未处理结点的最小值

    int tmp = maxint;

    int u = v;

    for(j=1; j<=n; j++)

      if( !(s[j]) && (dist[j]<tmp))

      {

        u = j;

        tmp = dist[j];

      }

    s[u] = 1;     //结点u加入s中

 

//利用结点u更新数组dist

    for(j=1; j<=n; j++)

      if(!(s[j]) && c[u][j]<maxint)

      {

           //newdist为从源点到该点的最短特殊路径

        int newdist = dist[u]+c[u][j];

        if (newdist<dist[j])

        {

              //修正最短距离

          dist[j] = newdist;

              //记录j的前一个结点

          prev[j] = u;

        }

      }

  }

}

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的最短路径长度。

算法实现:

#define MAX_VER_NUM 10 //顶点个数最大值

#define MAX 1000000

int Edge[MAX_VER_NUM][MAX_VER_NUM]; //图的邻接矩阵

int vexnum; //顶点个数

 

void BellmanFord(int v) //假定图的邻接矩阵和顶点个数已经读进来了

{

int i, k, u;

for(i=0; i<vexnum; i++)

{

dist[i]=Edge[v][i]; //对dist[ ]初始化

if( i!=v && dis[i]<MAX ) path[i] = v; //对path[ ]初始化

else path[i] = -1;

}

for(k=2; k<vexnum; k++) //从dist1[u]递推出dist2[u], …,distn-1[u]

{

for(u=0; u< vexnum; u++)//修改每个顶点的dist[u]和path[u]

{

if( u != v )

{

for(i=0; i<vexnum; i++)//考虑其他每个顶点

{

if( Edge[i][u]<MAX &&

    dist[u]>dist[i]+Edge[i][u] )

{

dist[u]=dist[i]+Edge[i][u];

path[u]=i;

}

}

}

}

}

}

 

 

 

        

 

 

 

 

 

 

  

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值