之前因为自己不是搞图论这一块的,所以这一块的知识点有些欠缺一直也没来的及总结
虽然大家都学过了,但总是没有其他同学理解的深入,所以慢慢来做一些总结,包括之前看的一些博客啦
图的定义
图:顶点集合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)
{