数据结构 - 图 - 笔记

24 篇文章 0 订阅

1.图的定义、实现与基本操作

1.1 有关图的重要定义

  1. 稀疏图,密集图,完全图
  2. 标号图
  3. 相邻的,邻接点
  4. 权,带权图
  5. 路径,简单路径,路径长度
  6. 回路,简单回路
  7. 子图
  8. 连通的,连通分量(最大连通子图)
  9. 无环图,有向无环图
  10. 自由树

1.2 图的两种表示方式、ADT及其实现

使用哪种方式取决于边的数目
密集图:相邻矩阵
稀疏图:邻接表

1.2.2 图的ADT
  1. 没有使用模板
  2. 顶点用索引值描述
  3. ※:所示代码假设了图的顶点数是定值
class Graph {
private:
    void operator=(const Graph&) {}
    Graph(const Graph&) {}
public:
    Graph() {}
    virtual ~Graph() {}

    virtual void Init(int n) = 0;

    virtual int n() = 0;
    virtual int e() = 0;

    //访问当前顶点的第一个邻居
    virtual int first(int v) = 0;
    //访问当前顶点的下一个邻居
    virtual int next(int v,int w) = 0;

    //设置一条边的权,权必须>0
    virtual void setEdge(int v1,int v2,int wght) = 0;
    virtual void delEdge(int v1,int v2) = 0;

    //查看在顶点i和顶点j之间的边是否存在
    virtual bool isEdge(int i,int j) = 0virtual int weight(int v1,int v2) = 0;

    virtual int getMark(int v) = 0;
    virtual void setMark(int v) = 0;
};

1.2.2 图的两种实现
1.2.2.1 使用相邻矩阵表示图
#include<iostream>
#include<cstdio>
#define UNVISITED 0
#define Assert(a,b) assert((a)&&(b))
using namespace std;

class Graph {
private:
    void operator=(const Graph&) {}
    Graph(const Graph&) {}
public:
    Graph() {}
    virtual ~Graph() {}

    virtual void Init(int n) = 0;

    //顶点和边的数目
    virtual int n() = 0;
    virtual int e() = 0;

    //访问当前顶点的第一个邻居
    virtual int first(int v) = 0;
    //访问当前顶点的下一个邻居
    virtual int next(int v,int w) = 0;

    //设置一条边的权,权必须>0
    virtual void setEdge(int v1,int v2,int wght) = 0;
    virtual void delEdge(int v1,int v2) = 0;

    //查看在顶点i和顶点j之间的边是否存在
    virtual bool isEdge(int i,int j) = 0virtual int weight(int v1,int v2) = 0;

    virtual int getMark(int v) = 0;
    virtual void setMark(int v,int val) = 0;
};

//邻接表
class Graphm : public Graph {
private:
    int numVertex, numEdge;
    int **matrix;   //二维的表结构
    int *mark;
public:
    Graphm(int numVertex) { Init(numVertex); }
    virtual ~Graphm() {
        delete [] mark;
//        for(int i=0;i<numVertex;i++)
//            for(int j=0;j<numVertex;j++)
//                delete matrix[i][j];
        for(int i=0;i<numVertex;i++)
            delete [] matrix[i];
        delete [] matrix;
    }

    void Init(int n) {
        int i;
        numVertex=n;
        numEdge=0;
        for(i=0;i<numVertex;i++)
            mark[i]=UNVISITED;
        for(i=0;i<numVertex;i++)
            matrix[i] =(int**) new int*[numVertex]; //
        for(i=0;i<numVertex;i++)
            for(int j=0;j<numVertex;j++)
                matrix[i][j]=0;
    }

    virtual int n() { return numVertex; }
    virtual int e() { return numEdge; }

    //访问当前顶点的第一个邻居
    virtual int first(int v) {
        for(int i=0;i<numVertex;i++)
            if(matrix[v][i]!=0) return i;
        return numVertex;
    }
    //访问当前顶点的下一个邻居
    virtual int next(int v,int w) {
        for(int i=w+1;i<numVertex;i++)
            if(matrix[v][i]!=0) return i;
        return numVertex;
    }

    //设置一条边的权,权必须>0
    virtual void setEdge(int v1,int v2,int wt) {
        Assert(wght>0,"Illegal weight value");
        if(matrix[v1][v2]==0) numEdge++;
        matrix[v1][v2]=wt;
    }
    virtual void delEdge(int v1,int v2) {
        if(matrix[v1][v2]!=0) numEdge--;
        matrix[v1][v2]=0;
    }

    //查看在顶点i和顶点j之间的边是否存在
    virtual bool isEdge(int i,int j) {
        return matrix[i][j]!=0;
    }
    virtual int weight(int v1,int v2) {
        return matrix[v1][v2];
    }

    virtual int getMark(int v) {
        return mark[v];
    }
    virtual void setMark(int v,int val) {
        mark[v]=val;
    }
};


1.2.2.2 使邻接表表示图
  1. 邻接表:一个以链表为元素的数组(数组内存放指向链表的指针)
    ※:是树结构“子节点表”表示法的推广
  2. 链表内结点是图中顶点所指向的顶点,其顺序为按顶点序号排列

1.3 图的遍历

  • 从一个起始顶点出发,试图访问其余顶点。
  • 需处理的情况:
  1. 从起点出发可能到不了所有顶点(如非连通图)
  2. 确保算法不会因为回路而陷入循环

=> 设置标志位mark
(遍历结束后检查mark数组中是否还有顶点UNVISITED,然后从未被标记的顶点再开始遍历)

图的遍历函数

void graphTraverse(Graph* G) {
        int v;
        for(int v=0;v<numVertex;v++)
            G->setMark(v,UNVISITED);
        for(v=0;v<G->n;v++) {
            if(G->getMark(v)==UNVISITED)
                doTraverse(G,v);
        }
    }
1.3.1 深度优先搜索(DFS)
  • 栈结构
  • 将产生一棵深度优先搜索树
  • 适用于有向图和无向图
void PreVisit(Graph* G,int v) {
}
void PostVisit(Graph* G,int v) {
}

//深度优先DFS
void DFS(Graph* G,int v) {
    PreVisit(G,v);
    G->setMark(v,VISITED);
    for(int w=G->first(v);w<G->n();w=G->next(v,w))
        if(G->getMark(v)==UNVISITED)
            DFS(G,v);
    PostVisit(G,v);
}
1.3.2 广度优先搜索(BFS)
  • 队列
  • 由顶到底逐层访问(树的层次遍历)
在这里插入代码片
1.3.3 拓扑排序
  • 有向无环图(DAG)建模:
    (※:只有DAG有拓扑排序)
  1. 有向:任务间存在相互依赖关系
  2. 无环:回路中隐含了相互冲突的依赖条件
  • 拓扑排序定义:将一个DAG中所有顶点在不违反前置依赖条件规定的基础上,排成线性序列
    将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。
  • 拓扑排序应用场景:在进行步骤v之前,必须完成步骤u
    在这里插入图片描述
1.3.3.1 用DFS实现拓扑排序
  • 得到逆拓扑序列
void printout(int v) {}
void topsort(Graph* G) {
    int i;
    for(i=0;i<G->n();i++)
        G->setMark(i,UNVISITED);
    for(i=0;i<G->n();i++)
        if(G->getMark(i)==UNVISITED)
            tophelp(G,i);
}

//DFS实现拓扑排序
void tophelp(Graph* G,int v) {
    G->setMark(v,VISITED);
    for(int w=G->first(v);w<G->n();w=G->next(v,w))
        if(G->getMark(w)== UNVISITED)
            tophelp(G,w);
    printout(v);
}
1.3.3.2 队列实现拓扑排序
  • 不使用递归
void topsort(Graph* G) {
    int cnt[G->n()];
    int v,w;
    for(v=0;v<G->n();v++)   cnt[v]=0;
    for(v=0;v<G->n();v++)
        for(w=G->first(v);w<G->n();w=G->next(v,w))
            cnt[w]++;
    queue<int> q;
    for(v=0;v<G->n();v++)
        if(cnt[v]==0)
            q.push(v);
    while(q.size()) {
        int v=q.front();
        q.pop();
        printout(v);
        for(w=G->first(v);w<G->n();w=G->next(v,w)) {
            cnt[w]--;
            if(cnt[w]==0)   q.push(w);
        }
    }
}

2. 最短路径问题

  • 单源最短路径:在图G中给定一顶点s,找出从s到G的任何一个顶点的最短路径

2.1 单源最短路径:Dijkstra算法

深入理解 Dijkstra 算法实现原理

  1. 每次最短路径所经过的顶点,总在已经确认的最短路径收录顶点集中取
  2. 每次从未收录的顶点中取距离最小的顶点收录进去

顶点编号从小到大,每次扩一条对于起始顶点而言路径最短的边 -> 把与该边连接的顶点放入顶点集 -> 将该顶点能够到达的顶点所在的最短路径更新(若经过该顶点的最短路径更小,则用经过该顶点的最短路径替换原算出的最短路径) -> 将图的所有顶点全部按照上述方法遍历一遍 -> 单元最短路径计算完成
在这里插入图片描述
下述代码只算了路径长度,没有记录实际路径,但可以改代码实现实际路径的记录

//单源最短路径:Dijkstra算法
void Dijkstra(Graph* G,int* D,int s) {  //D为从s到各个点的最短路径长度
    int i,v,w;
    for(i=0;i<G->n();i++) { //顺序处理每个顶点
        v=minVertex(G,i);
        if(D[v]==INFINITY)  return;
        G->setMark(v,VISITED);
        for(w=G->first(v);w<G->n();w=G->next(v,w))
            if(D[w]>(D[v]+G->weight(v,w)))
                D[w]=D[v]+G->weight(v,w);
    }
    //此时D中已经存储了s到所有顶点的最短路径
}
//找出相对顶点i具有最短距离的顶点
int minVertex(Graph* G,int *D) {
    int i;
    int v=-1;
    for(i=0;i<G->n();i++)
        if(G->getMark(i)==UNVISITED) {
            v=i;
            break;
        }
    for(i++;i<G->n();i++)
        if((G->getMark(i)==UNVISITED)&&(D[i]<D[v]))
            v=i;
    return v;
}

改动后:可记录路径和路径长度

    void initD(int* D,Graphm& G) {
        D[0]=0;
        for(int i=1;i<G.n();i++) {
            if(G.isEdge(0,i)) {
                D[i]=G.weight(0,i);
            }
            else    D[i]=INFINITY;
        }
    }
    void initP(int* path,int n) {
        for(int i=0;i<n;i++)
            path[i]=0;
    }
    //单源最短路径:Dijkstra算法
    void Dijkstra(Graphm& G) {  //D为从s到各个点的最短路径长度
        int i,v,w;
        unvisit(G);
        int *D,*path;
        D=new int[G.n()];
        path=new int[G.n()];
        initD(D,G);
        initP(path,G.n());
        for(i=0;i<G.n();i++) { //顺序处理每个顶点
            v=minVertex(G,D);
            if(D[v]>=INFINITY) { cout<<0<<endl; return; }
            G.setMark(v,VISITED);
            for(w=G.first(v);w<G.n();w=G.next(v,w))
                if(D[w]>(D[v]+G.weight(v,w))) {
                    D[w]=D[v]+G.weight(v,w);
                    path[w]=v;
                }
        }//此时D中已经存储了s到所有顶点的最短路径
        for(int i=1;i<G.n();i++) {
            printPath(path,i);
            cout<<D[i]<<endl;
        }

    }
    //找出未被访问过的、相对起始顶点具有最短距离的顶点
    int minVertex(Graphm& G,int *D) {
//        cout<<"minVertex"<<endl;
        int i,v=-1;
        for(i=0;i<G.n();i++)
            if(G.getMark(i)==UNVISITED) { v=i; break; }
        for(i++;i<G.n();i++)
            if((G.getMark(i)==UNVISITED)&&(D[i]<D[v]))  v=i;
        return v;
    }

2.2 多源最短路径:Floyd算法

3. 最小支撑树(minimum-cost spanning tree,MST)

  • 别名:最小生成树
  • 输入:一个每条边都带权的连通无向图G
  • 输出:MST,其特点为:
    1. 包括图G中的所有顶点和一部分边
    2. 是连通图
    3. 所有边权之和最小
    4. 没有回路,是有 |V|-1 条边的自由树的结构

3.1 Prim算法

在这里插入图片描述

3.2 Kruskal算法

在这里插入图片描述
路径压缩
判断当前节点是否为根,如果不是(即 没有找到),就把以当前节点为根的子树整体向上挪,挪到当前节点的父亲的父亲
,下次再判断当前节点的父亲(即 原来的祖先)是否为根 …
相当于跳了一步,所以叫 路径压缩

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值