定义

G=V,E

  • (graph)
  • 顶点(vertex),V是顶点的集合,非空。
  • (edge),E是边的集合。
  • 无向图,顶点之间的关系都是对称的。
  • 有向图
  • (arc),有向图的边
  • 完全图(complete graph),任意两点之间都有边。
  • 子图(subgraph)
  • 带权图
  • 稀疏图(sparse graph)
  • 稠密图(dense graph)
  • 路径(path)
  • 路径长度(length)
  • 简单路径(simple path),顶点不重复出现。
  • 回路,又叫(circle)
  • 简单回路(simple circle),顶点不重复出现(第一个和最后一个除外)
  • 无环图(acyclic graph)
  • (root),有向图中可以到达所有结点的结点
  • 有向无环图(Directed Acyclic Graph, DAC)
  • 连通(connected)
  • 连通图
  • 连通分量(connected component),(无向图的)极大连通子图
  • 强连通,如果两个顶点之间互有有向路径。
  • 强连通分量,有向图的极大强连通子图。
  • 生成树,含有全部顶点的极小连通子图
  • 网络,带权的连通图
  • 自由树,不带简单回路的无向图
  • 有向树,只有一个顶点入度为0,其他所有顶点入度为1.

抽象数据类型

class Graph{
public:
    int VerticesNum();
    int EdgesNum();

    Edge FirstEdge(int oneVertex);
    Edge NextEdge(Edge preEdge);
    bool SetEdge(int fromVertex, int toVertex, int weight);
    bool DelEdge(int fromVertex, int toVertex);
    bool IsEdge(Edge oneEdge);
    int FromVertex(Edge oneEdge);
    int ToVertex(Edge oneEdge);
    int Weight(Edge oneEdge);
}

存储结构

相邻矩阵adjacent matrix

  • 二维数组
  • n 个顶点的图的空间代价为 O(n2)

邻接表

  • 两个部分组成:顺序存储的顶点表+n个链接存储的边表
  • 顶点表有两个域:数据域+指向该顶点边表的指针域
  • 边表有三个域:该边的另一个顶点+指向下一条边的指针域+该边的权

示例
图_第1张图片
图_第2张图片
图_第3张图片
图_第4张图片
图_第5张图片

  • 无向图, n 个顶点 e 条边,需要 n 个顶点结点和 2e 个边结点
  • 有向图, n 个顶点 e 条边,邻接表需要 n 个顶点结点和 e 个边结点
  • 有向图, n 个顶点 e 条边,逆邻接表需要 n 个顶点结点和 e 个边结点
  • 边表中往往按编号大小排序

十字链表Orthogonal List

  • 顶点结点有3个域:数据域+指向第一条以该顶点为始点的指针域+指向第一条以该顶点为终点的指针域
  • 弧的结点有5个域:始点+终点+指向下一条以该边始点为始点的指针域+指向下一条以该边终点为终点的指针域+权
  • 把邻接表和逆邻接表结合起来
    图_第6张图片

周游

图的周游(graph traversal)

深度优先周游(DFS)

过程

  • 递归
  • 从任意顶点开始
  • 访问过做标记,每个顶点只访问一次
  • 如果当前顶点没有未被访问过,标记,访问,访问所有未被访问的后级顶点。

代码

void DFS(Graph G, int v)
{
    G.Mark[v] = VISITED;
    Visit(G,v);
    for(Edge e = G.FirstEdge(e); G.IsEdge(e);e = G.NextEdge(e))
    {
        if(G.Mark[G.ToVertex(e)] == UNVISITED)
            DFS(G,G.ToVertex(e));    //使用递归
    }
}

复杂度分析

  • 时间主要花在搜索每个顶点所有邻接点的过程
  • 如果用相邻矩阵存储,O( n2 )
  • 如果用邻接表存储,O( n+e )

广度优先周游(BFS)

过程

  • 从任意顶点开始
  • 访问过做标记,每个顶点只访问一次
  • 如果当前顶点没有未被访问过,标记,访问,把所有的未被访问过的子顶点加入队列,从队列中取出下一个顶点,进行BFS

代码

void BFS(Graph G,int v)
{
    using std::queue;
    queue<int> Q;
    Visit(G,v);
    G.Mark[v] = VISITED;
    Q.push(v);
    while(!Q.IsEmpty)       //使用循环
    {
        int w = Q.front();
        Q.pop();
        for(Edge e = G.FirstEdge(w); G.IsEdge(e); e = G.NextEdge(e))
        {
            if(G.Mark[G.ToVertex(e)] != VISITED)
            {
                G.Mark[G.ToVertex(e)] = VISITED;
                Visit(G,G.ToVertex(e));
                Q.push(G,G.ToVertex(e));
            }
        }
    }
}

复杂度分析

和DFS相同

拓扑排序

  • 有向无环图DAG
  • 拓扑排序(topological sorting),根据有向图建立拓扑序列的过程。解决先决条件问题。
  • 拓扑序列,如果有向图中存在 vi vj 的路径,那么在序列中 vi 一定在 vj 之前。
  • DAG所有顶点一定可以排成拓扑序列。

方法

  • 将DAG排成拓扑序列。
    • 从有向图中选取一个入度为0的顶点,输出
    • 删除顶点及其引出的边。
    • 循环进行
    • 序列不一定唯一。

代码

//用队列实现拓扑排序

void TopsortByQueue(Graph & G)
{
    for(int i = 0;i < G.VerticesNum(); i++)
        G.Mark[i] = UNVISITED;
    using std::queue;
    queue<int> Q;
    for(int i = 0;i < G.VerticesNum(); i++) //查找入度为0的顶点入队
    {
        if(G.InDegree(i) == 0)
        {
            Q.push(i);
            G.Mark[i] = VISITED;
        }
    }
    while(!Q.empty()) //逐次取出队列中顶部元素
    {
        int v = Q.front();
        Q.pop();
        for(Edge e = G.FirstEdge(v); G.IsEdge(e); e = G.NextEdge(e))
        {
            G.InDegree(G.ToVertex(e)) --;
            if(G.InDegree(G.ToVertex(e)) == 0)
            {
                Q.push(G.ToVertex(e));
                G.Mark[G.ToVertex(e)] = VISITED;
            }
        }
    }
    for(int i = 0;i < G.VerticesNum(); i ++)//判断图有无环
    {
        if(G.Mark[i] == UNVISITED)
        {
            cout << "There is a circle int the graph!" << endl;
            break;
        }
    }
}

算法复杂度

O(n+e)

最短路径

单源最短路径

问题

  • 带权图 G=<V,E> ,权 W[vi,vj] 为非负实数
  • 给定一个源点
  • 从源点到任一个结点的最短路径

算法

  • Dijkstra算法
  • 把图的顶点划分成两个集合 S,VS S 表示最短路径已经确定的顶点集合。
  • 初始时 S={s}
  • 特殊路径,从源点 s 到顶点 v 中间只经过集合 S 中顶点的路径。
  • 用数组 D 记录当前所找到的从源点s到每个顶点的最短特殊路径的长度
  • VS 中取出一个最短特殊路径长度最小的顶点 u ,加入 S ,并且修改 D
  • D 的初始状态, s v 有弧,则 D[v] 为弧长,否则为无穷
  • 重复上述过程,直到 S=V
//Precedure Dijkstra
begin 
    S := {1};
    for i:=2 to n do
        D[i] = C[1,i]
    for i:=1 to n-1 do
    begin
        choose a vertex w in V-S such that D[w] is a minimum, add w to S
        for each vertex v in V-S do
            D[v] := min(D[v],D[w]+C[w,v])
    end
end
  • 复杂性 O(n2)

每对顶点之间的最短路径

  • 如果用 Dijkstra 算法,复杂度 O(n3)

Floyd算法

  • 用相邻矩阵adj表示带权有向图
  • 初始化矩阵
  • 每次考虑一个顶点 k adj 中的一个位置 (i,j) (三重循环),如果 adj[i,k]+adj[k,j]<adj[i,j] ,就修改 adj[i,j]

最小生成树

  • 生成树,极小连通子图
  • 一般不唯一
  • n 个结点的图的生成树有 n1 条边
  • 最小生成树,带权连通图树上所以权值的和最小的生成树。

MST性质

  • 将顶点集合 V 随意划分成两个顶点集合 U,VU
  • 如果 (u,v) 属于 E ,且 (u,v) 是符合条件的边中权值最小的,那么一定存在一个包含这条边的最小生成树。
  • 用反证法证明。

Prim算法

  • 利用MST性质
  • 初始取任意顶点 v , U={v} TE=
  • 从连接 U VU 的所有边中,取一个权值最小的,将该边在 VU 一侧的顶点添加到U中,边添加到 TE 集合中
  • 重复上述过程,直到 U=V

Kruskal算法

  • 使用贪心准则
  • 从剩下的边中选择具有最小权值且不会产生环路的边加入到生成树中。
  • 需要判断当前图中是否存在环:用树的父指针表示法及并查集。
  • 初始n个顶点n个连通分量;
  • 在E中选择代价最小的边,如果该边两个顶点在同一个连通分量里,舍弃,继续这个操作;否则选中这条边。
  • 直到所有顶点都在同一个连通分量里面。
  • 复杂度 O(eloge)

Thanks to Prof. Wang

你可能感兴趣的:(数据结构与算法)