ACM-图论总结

这篇博客主要总结了ACM竞赛中的图论知识,包括图的定义、存储方式如邻接矩阵和邻接表,以及图的类型如欧拉图和哈密顿图的性质。还涉及拓扑排序、最短路算法(Dijkstra、Bellman-Ford、Floyd、SPFA)、最小生成树(Prim、Kruskal)、二分图的概念和性质,以及最大匹配、连通图等相关内容。
摘要由CSDN通过智能技术生成

之前因为自己不是搞图论这一块的,所以这一块的知识点有些欠缺一直也没来的及总结

虽然大家都学过了,但总是没有其他同学理解的深入,所以慢慢来做一些总结,包括之前看的一些博客啦

 

图的定义

   图:顶点集合V和一个顶点间关系的集合E组成,记为G=(V,E);

          存在一个结点v,可能含有多个前趋结点和后继结点。

   顶点;

   边;

ACM图的存储

    邻接矩阵  :使用一个矩阵来表述一个图,对于矩阵的第i行第j列的值,表示为编号i的顶点到编号j的顶点的权值。            


//代码实现

#include <stdio.h>
#include <string.h>

// 最大顶点数
const int V = 1000;

// 邻接矩阵的定义
// mat[i][j] 表示 顶点`i`到顶点`j`的权值

int mat[V][V];


// 邻接矩阵的初始化操作
// 假设权值为零表示没有该边

memset(mat, 0, sizeof(mat))


// 增加边
// 新增顶点`i`到顶点`j`的边,权值为`w`

mat[i][j] = w;


// 删除边
// 删除顶点`i`到顶点`j`的边

mat[i][j] = 0;


// 查询边
// 查询顶点`i`到顶点`j`的边权

mat[i][j];

    邻接表:对于每个顶点使用不定长的链表来存储以该点出发的边的情况。因此对于第i个链表的第j个值实际上存储的是

                  从编号为i的顶点出发的第j条边的情况。

                  使用STL里vector作为链表来实现邻接表的情况比较常见

#include <vector>

using namespace std;

// 最大顶点数
const int V = 100000;


// vector实现的邻接表的定义
// 不考虑边权,存储类型为int型

vector<int> e[V];


// 邻接表的初始化操作
// 将起点为`i`的边链表全部清空

e[i].clear();


// 增加边
// 新增顶点`i`到顶点`j`的边

e[i].push_back(j);


// 查询边

e[i][0];    // 查询以`i`为起点的第一条边`i->e[i][0]`

for (int j=0; j<(int)e[i].size(); ++j) {
    if (e[i][j] == k) {     // 查询边`i->k`
        // do something.
    }
}

    链式前向星:存图方式的数据结构主要是边集数组,顾名思义,图的边是用数组来存储的
                         当然想要完美表示图结构,光有一个边集数组还不够,还要有一个数组存储指向每一个点的第一条边的“指针”。
                         而每一条边都需要存储接下来一条边的“指针”,这样就能够像类似邻接表一样方便遍历每一个点的所有边了。

    

#include <stdio.h>
#include <string.h>

// 最大顶点数
const int V = 100000;
// 最大边数
const int E = 100000;

// 边结构体的定义
struct Edge {
    int to;         // 表示这条边的另外一个顶点
    int next;       // 指向下一条边的数组下标,值为-1表示没有下一条边
};


// head[i] 表示顶点`i`的第一条边的数组下标,-1表示顶点`i`没有边
int head[V];
Edge edge[E];


// 链式前向星初始化,只需要初始化顶点数组就可以了
memset(head, -1, sizeof(head));


// 增加边的方式
// 新增边 a -> b,该边的数组下标为`id`
inline void AddEdge(int a, int b, int id)
{
    edge[id].to = b;
    edge[id].next = head[a];    // 新增的边要成为顶点`a`的第一条边,而不是最后一条边
    head[a] = id;
    return;
}

// 遍历从`a`点出去的所有边
for (int i=head[a]; i!=-1; i=e[i].next) {
    // e[i] 就是你当前遍历的边 a -> e[i].to
}

 

图的类型和性质

1.欧拉图

定义:

         欧拉图是指通过图(有向图或者无向图)中所有边并且每条边只通过一次通路,相应的回路称为欧拉回路。

性质:

         1.无向连通图G是欧拉图,当且仅当G不含奇数度结点;

         2.无向连通图G含有欧拉通路,当且仅当G有零个或两个奇数度结点;

         3.有向连通图G是欧拉图,当且仅当该图为连通图且D中每个结点的入度=出度;

         4.有向连通图G含有欧拉通路,当且仅当G为连通图且G中除两个结点外,其余每个结点的入度=出度,且

             起始点s的入度=出度-1,结束点t的出度=入度-1或两个点的出度=入度;

         5.一个非平凡连通图是欧拉图当且仅当它的每条边属于奇数个环;

         6.如果图G是欧拉图且 H = G - uv,则H有奇数个u,v-迹仅在最后访问v;同时,在这一序列的u,v-迹中,

            不是路径的迹的条数是偶数。

(里面涉及到的基本概念:非平凡连通图,迹)

判断欧拉图

void dfs(int now)
{
     int k;
     for(k=p[now];k!=-1;k=e[k].next)
     {
         if(!vst[k])
         {
             vst[k]=true;
             vst[k^1]=true;
             dfs(e[k].to);
             ans[ansi++]=k;
         }
     }
}

//dfs结束后,ans中存储的就是欧拉图,可通过vst判断图的联通性,每个点都被更新则全联通

哈密顿图

  通过图G的每个结点一次,且仅一次的通路(回路),就是哈密顿通路(回路)。存在哈密顿回路的图就是哈密顿图。

  性质:

       若图的最小度不小于顶点数的一半,则图是哈密顿图

       若图中每一对不相邻的顶点的度数之和不小于顶点数,则图是哈密顿图

//求汉密尔顿回路函数
int Hanmilton(){
    int path[1000] = {0};
    int cur_vertex = 0;     //作为保存当前结点
    int length = 0;         //汉密尔顿回路长度
    int min = 10000;        //最小长度
    for(int i = 1 ; i < this->Nv+1 ; i++){//对每个顶点为初始点进行比遍历寻找汉密尔顿回路
        length = 0;     //重新设置最端长度为0
        memset(this->isvisited,0,sizeof(this->isvisited[0])*(this->Nv+1));  //重新初始化访问数组为0
        this->isvisited[i] = 1;     //标记当前结点为已访问
        path[1] = i;        //保存到临时路径数组的第一个
        cur_vertex = i;     //保存当前顶点
        for(int j = 2 ; j < this->Nv+1 ; j++){//访问剩余的结点
            int k = 0;
            //寻找到第一个未访问的结点
            for(k = 2 ; k < this->Nv+1 ; k++){
                if(this->isvisited[k] == 0){
                    break;
                }
            }
            int tmp = this->data[cur_vertex][k];        //保存当前顶点到该结点的路径长度
            for(int m = k+1 ; m < this->Nv+1 ; m++){//向后寻找有没有路径更短的节点
                if((!this->isvisited[m]) && (tmp > this->data[cur_vertex][m])){
                    tmp = this->data[cur_vertex][m];//更新当前最短路径
                    k = m;//更新第一个未被访问的结点
                }
            }
            path[j] = k;    //保存路径上的结点
            this->isvisited[k] = 1; //标记为已访问
            cur_vertex = k;     //跟新当前结点
            length += tmp;      //跟新长度
            if(length > min){   //当前长度大于最小长度,则改路径无效,跳出循环
                break;
            }
        }   
        length += this->data[cur_vertex][i];
        if(min > length){       //更新最小长度并保存最佳路径
            min = length;
            for(int m = 0 ; m < this->Nv+1 ; m++){
            this->best_path[m] = path[m];
            }
        }
    }
            //返回最小长度
        return min;
}

        //打印最佳汉密尔顿回路
void Print_Best_Path(){
    cout<<this->best_path[1];
    for(int i = 2 ; i < this->Nv+1 ; i++){
        cout<<" -> "<<this->best_path[i];
    }
    cout<<" -> "<<this->best_path[1];
}

拓扑排序

     关于图的拓扑序、判断是否有环、判断是否有孤立点

#include <iostream>
#include <string.h>
#include <queue>
using namespace std;
const int MAX_N=10000;
struct edge
{
    int to,next;
}e[MAX_N];
int p[MAX_N],eid;
void init()
{
    eid=0;
    memset(p,-1,sizeof(p));
}
void insert(int u,int v)
{
    e[eid].to=v;
    e[eid].next=p[u];
    p[u]=eid++;
}
int indegree[MAX_N];
int n;
int topo()
{
    queue<int>Q;
    for(int i=1;i<=n;i++)
    {
        if(indegree[i]==0)
            Q.push(i);
    }
    while(!Q.empty())
    {
        int now=Q.front();
        cout<<"visting"<<now<<endl;
        Q.pop();
        for(int i=p[now];i!=-1;i=e[i].next)
        {
            int v=e[i].to;
            indegree[v]--;
            if(indegree[v]==0)
                Q.push(v);
        }
    }
}

最短路

  1.Dijkstra

      1.1 优先队列优化    不能处理负权边,O(E*logV)

#include <bits/stdc++.h>
#include <vector>
#include <queue>
using namespace std;

const int MAX_N=1000;
const int MAX_V=1000;
const int INF=0x3f3f3f3f;
struct edge
{
    int to,cost;
};
typedef pair<int,int>P;    //first是最短距离,second是顶点编号
int V;
vector<edge>G[MAX_N];
int d[MAX_V];

void Dijstra(int s)
{
    //通过指定greater<P>参数,堆按照first从小到大顺序取出值
    priority_queue< P,vector<P>,greater<P> > que;
    fill(d,d+V,INF);
    d[s]=0;
    que.push(P(0,s));
    while(!que.empty())
    {
        P now=que.top();
        que.pop();
        int v=now.second;
        if(d[v]<now.first) continue;
        for(int i=0;i<G[v].size();i++)
        {
            edge e=G[v][i];
            if(d[e.to]>d[v]+e.cost)
            {
                d[e.to]=d[v]+e.cost;
                que.push(P(d[e.to],e.to));
            }
        }
    }
}

  1.2堆优化

const int MAX_N = 10000;
const int MAX_M = 100000;
const int inf = 0x3f3f3f3f;
struct edge {
    int v, w, next;
} e[MAX_M];
int p[MAX_N], eid;
int n;                        //顶点数设为全局变量
void mapinit() {
    memset(p, -1, sizeof(p));
    eid = 0;
}
void insert(int u, int v, int w) {  // 插入带权有向边
    e[eid].v = v;
    e[eid].w = w;
    e[eid].next = p[u];
    p[u] = eid++;
}
void insert2(int u, int v, int w) {  // 插入带权双向边
    insert(u, v, w);
    insert(v, u, w);
}
typedef pair<int, int> PII;
set<PII, less<PII> > min_heap;  // 用 set 来伪实现一个小根堆,并具有映射二叉堆的功能。堆中 pair<int, int> 的 second 表示顶点下标,first 表示该顶点的 dist 值
set<PII,less<PII> >:: iterator iter;
int dist[MAX_N];  // 存储单源最短路的结果
bool vst[MAX_N];  // 标记每个顶点是否在集合 U 中
bool dijkstra(int s) {
    // 初始化 dist、小根堆和集合 U
    memset(vst, 0, sizeof(vst));
    memset(dist, 0x3f, sizeof(dist));
    min_heap.insert(make_pair(0, s));
    dist[s] = 0;
    for (int i = 0; i < n; ++i) {
        if (min_heap.size() == 0) {  // 如果小根堆中没有可用顶点,说明有顶点无法从源点到达,算法结束
            return false;
        }
        // 获取堆顶元素,并将堆顶元素从堆中删除
        iter = min_heap.begin();
        int v = iter->second;
        min_heap.erase(*iter);
        vst[v] = true;
        // 进行和普通 dijkstra 算法类似的松弛操作
        for (int j = p[v]; j != -1; j = e[j].next) {
            int x = e[j].v;
            if (!vst[x] && dist[v] + e[j].w < dist[x]) {
                // 先将对应的 pair 从堆中删除,再将更新后的 pair 插入堆
                min_heap.erase(make_pair(dist[x], x));
                dist[x] = dist[v] + e[j].w;
                min_heap.insert(make_pair(dist[x], x));
            }
        }
    }
    return true;  // 存储单源最短路的结果
}

1.3 路径还原:基于Dijkstra ,使用邻接矩阵

int prev[MAX_V];
int d[MAX_V];
int V;
int cost[MAX_V][MAX_V];
bool used[MAX_V]
void Dijkstra(int s)
{
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值