知识点梳理:数据结构与算法——图

知识点梳理:数据结构与算法——图

省略了容易理解的部分,适合复习,预习困难

基本概念:

  • 稀疏图:边条数<完全图边条数x5%
  • 稀疏因子:在mxn的矩阵中,有t个非零元素,则稀疏因子 δ = 1 m × n \delta=\frac{1}{m\times n} δ=m×n1
  • 简单路径:除起点和终点外同一结点不能出现2次
  • 根:一个有向图中,若存在一个顶点 V 0 V_0 V0,从此顶点有路径可以到达图中其他所有顶点,则 V 0 V_0 V0称作图的根
  • 连通分量(连通分支):无向图的极大连通子图
  • 强连通:任意两顶点间双向路径
  • 强连通分量:有向图的极大强连通子图
  • 网络:带权的连通图
  • 自由树:不带有简单回路的无向图,连通的,|V|-1条边

图的储存结构:

  • 无向图的邻接表表示注意同一条边出现两次,修改的时候要改两处
  • 十字链表:十字链表

图的遍历:

  • DFS和BFS的时间复杂度:
    • 邻接表:有向图 Θ ( n + e ) \Theta(n+e) Θ(n+e),无向图 Θ ( n + 2 e ) \Theta(n+2e) Θ(n+2e)
    • 相邻矩阵: Θ ( n + n 2 ) = Θ ( n 2 ) \Theta(n+n^2)=\Theta(n^2) Θ(n+n2)=Θ(n2)

拓扑排序:

  • BFS:选择入度为0输出,删掉其出边,对应顶点indegree–;有环:队列为空时仍有点未访问
  • DFS:逆序序列反向输出;遍历相邻点,调用递归函数,最后再将该点存入result数组中
    • 有环的判断:
void DFS(Graph& G,int V)
{
    A.push(V);S.insert(V);
    visit(G,V);//访问V并进行一些操作
    G.mark[V]=VISITED;
    for(Edge e=G.FirstEdge(V);G.isEdge(e);e=G.nextEdge(e))//访问所有与V相邻的点
    {
        if(G.mark[e.To]==UNVISITED)
            DFS(G,e.To);//递归
        else if(S.count(e.To)>0)//之前已经在S出现过,则说明有环
            cout<<"yes"<<endl;
        int x=A.top();A.pop();
        S.erase(x);//将最后加入的那个点去掉,然后访问下一个相邻结点(换一条路)
    }
}

实际做题中使用:(原理一致)

bool dfs(int v) {
    visit[v] = -1;
    for (int j = rem[v]; j; j = e[j].next) {
        int t = e[j].to;
        if (visit[t] < 0)
            return true;//之前已经在路径上出现过
        else if (!visit[t] && !dfs(t))
            return true;
    }
    visit[i] = 1;
    return false;//no circle
}

最短路径问题:

Dijkstra:

  • 单源最短路径,边权不能为负(解决思路(第4题))
  • 从已生成最短路径的结点集选择距源点 V 0 V_0 V0最近的边延伸(最终生成以 V 0 V_0 V0为根的有向树
  • 时间复杂度(用最小堆且不删除旧值): Θ ( ( ∣ V ∣ + ∣ E ∣ ) l o g ∣ E ∣ ) \Theta((|V|+|E|)log|E|) Θ((V+E)logE)
    • 可以不删除旧值的原因:如果访问到旧值的话对应顶点一定是被标记VISITED的,不可能出现
class Dist{
public:
    int index;
    int length;
    int pre;
}//记录最短路径的相关选择
void Dijkstra(Graph &G,int s,Dist *&D)//s表示从标号为s的顶点开始
{
    D=new Dist[G.VerticesNum()];
    for(int i=0;i<G.VerticesNum();i++)
    {
        G.mark[i]=UNVISITED;
        D[i].length=INFINITY;
        D[i].index=i;
        D[i].pre=s;
    }//初始化
    D[s].length=0;
    MinHeap<Dist>H(G.EdgesNum());//实际做题中多用priority_queue
    H.insert(D[s]);
    for(int i=0;i<G.VerticesNum();i++)
    {
        bool Found=false;
        Dist d;
        while(!H.empty())
        {
            d=H.Min();
            H.RemoveMin();
            if(G.mark[d.index]=UNVISITED)
            {Found=true;break;}
            if(!Found)break;
            int v=d.index;G.mark[v]=VISITED;Visit(v);
            for(Edge e=G.FirstEdge(v);G.IsEdge(e);e=G.NextEdge(e))
            {
                if(D[G.ToVertex(e)].length>(D[v].length()+G.weight(e)))
                {
                    D[G.ToVertex(e)].length=(D[v].length()+G.weight(e));
                    D[G.ToVertex(e)].pre=v;
                    H.insert(D[G.ToVertex(e)]);
                }
            }
        }
    }
}

Floyd:

  • 动态规划,允许边权为负
  • a d j ( k ) [ i , j ] adj^{(k)}[i,j] adj(k)[i,j]: V i V_i Vi V j V_j Vj中间序号不大于k时的最短路径->一直迭代到 a d j ( n ) adj^{(n)} adj(n)
  • 记录最短路径:path[i,j]: V i V_i Vi V j V_j Vj最短路径上 V j V_j Vj前面那个顶点
void Floyd(Graph &G)
{
    for(int i=0;i<G.VerticesNum();i++)
        for(int j=0;j<G.VerticesNum();j++)
            if(i==j)
            {
                D[i][j].length=0;
                D[i][j].pre=i;
            }
            else
            {
                D[i][j].length=INFINITY;
                D[i][j].pre=-1;//初始化表示不能到达
            }
    for(int i=0;i<G.VerticesNum();i++)
        for(Edge e=G.FirstEdge(i);G.IsEdge(e);e=G.NextEdge(e))
        {
            D[i][G.ToVertex(e)].length=G.weight(e);
            D[i][G.ToVertex(e)].pre=i;
        }
    for(int v=0;v<G.VerticesNum();v++)
        for(int i=0;i<G.VerticesNum();i++)
            for(int j=0;j<G.VerticesNum();j++)
                if((D[i][v].length+D[v][j].length)<D[i][j].length)
                {
                    D[i][j].length=D[i][v].length+D[v][j].length;
                    D[i][j].pre=D[v][j].pre;//注意这里不一定是v
                }
}

最小生成树:

不唯一但是最小权值确定

Prim:

  • 任意顶点开始,包括在MST中->一端在MST一端在外面的边中,找权最小的一条边->端点包括进MST
  • 合理性证明:
    在这里插入图片描述在这里插入图片描述
void Prim(Graph& G,int s)
{
    int MSTcnt=0;//计算已经有多少条边入MST
    MinHeap<Edge>H;
    for(int i=0;i<G.VerticesNum();i++)
        G.mark[i]=UNVISITED;
    G.mark[s]=VISITED;
    int v=s;
    while(MSTcnt<G.VerticesNum()-1)
    {
        for(Edge e=G.FirstEdge(v);G.IsEdge(e);e=G.NextEdge(e))
            if(G.mark[G.ToVertex(e)]==UNVISITED)
                H.insert(e);
        bool Found=false;
        while(!H.empty())
        {
            e=H.Min();
            H.removeMin();
            if(G.mark[G.ToVertex(e)]]==UNVISITED)
            { Found=true;break;}
        }
        if(!Found)//非连通图的情况,没有MST,所以前面要有while(MSTcnt<G.VerticesNum()-1)的判断
            return;
        v=G.ToVertex(e);
        G.mark[v]=VISITED;
        AddEdgeToMST(e,MST,MSTcnt++);//这一步就是把e记录进去,这里操作可自行调整,但是MSTcnt++一定要
    }
}

Kruskal:

  • 开始时划分为|V|个等价类,以权的大小为顺序处理各边,某边连接两个不同等价类的顶点,则加入MST,两顶点的等价类合并->最终全部称为一个等价类
  • 时间复杂度(使用路径压缩): O ( ∣ E ∣ l o g ∣ E ∣ ) O(|E|log|E|) O(ElogE)
    (使用路径压缩后对等价类的操作为O(1))
  • 补充说明:如果等价类的数量>1但是堆已空,则说明是非连通图,没有MST
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值