最近好多考试,紧张,感觉还没有学好呢,电子最急了,还没有复习到该到的地方,哎,还不好考,哭唧唧。最近好忙呀!忙的不知道开始干什么好。不多说别的了,今晚要写不完图论了……还是开始加油吧。
基本概念:
- 结点的度:无向图中与结点相连的边的数目,称为结点的度。
- 结点的入度:在有向图中,以这个结点为终点的有向边的数目。
- 结点的出度:在有向图中,以这个结点为起点的有向边的数目。
- 权值:边的“费用”,可以形象地理解为边的长度。
- 连通:如果图中结点U,V之间存在一条从U通过若干条边、点到达V的通路,则称U、V 是连通的。
- 回路:起点和终点相同的路径,称为回路,或“环”。
- 完全图:一个n 阶的完全无向图含有n*(n-1)/2 条边;一个n 阶的完全有向图含有n*(n-1)条边;
图分为有向图和无向图,有向图例如:可以从1走到2,却不一定可以从2走到1。无向图则若可以从1走到2,则必可以从2 走到1。比如(a)就是一个有向图,1与3不互通,(b)就是无向图,只要有连线就可以互通。
图的存储结构:
1.二维数组存储:
定义int g[100][100];
这样存储十分简单易懂,点i与j之间的值就是边。但是也有明显的缺点:存储空间过大,当有很多的点但是联通道路很少时,空间利用率不高,耗费大。而题目一般使用这个(可能暂时没能力做到第二种,有点难)。
2.数组模拟邻接表存储:
又称链式存储法。哦,fuck,有点搞不懂。
#include <iostream>
using namespace std;
const int maxn=1001,maxm=100001;
struct Edge
{
int next; //下一条边的编号
int to; //这条边到达的点
int dis; //这条边的长度
}edge[maxm];
int head[maxn],num_edge,n,m,u,v,d;
void add_edge(int from,int to,int dis) //加入一条从from到to距离为dis的单向边
{
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
edge[num_edge].dis=dis;
head[from]=num_edge;
}
图的遍历:
1.深度优先遍历:(常用)
void dfs(int i) //图用数组模拟邻接表存储,访问点i
{
visited[i] = true; //标记为已经访问过
for (int j = 1; j <= num[i]; j++) //遍历与i相关联的所有未访问过的顶点
if (!visited[g[i][j]])
dfs(g[i][j]);
}
主程序如下:
int main()
{
……
memset(visited,false,sizeof(visited));
for (int i = 1; i <= n; i++) //每一个点都作为起点尝试访问
if (!visited[i]) //没有被访问过,且随便某个点开始深搜
dfs(i);
……
return 0;
}
2.广度优先遍历
不好使,把一个点放进去找子节点继续放进队列中即可。
一笔画问题:
如果一个图存在一笔画,则一笔画的路径叫做欧拉路;如果最后又回到起点,那这个路径叫做欧拉回路。
定理1:存在欧拉路的条件:图是连通的,有且只有2个奇点。
定理2:存在欧拉回路的条件:图是连通的,有0个奇点。
简单例题,算法实现:
第一行n,m,有n个点,m条边,以下m行描述每条边连接的两点。
5 5
1 2
2 3
3 4
4 5
5 1
样例输出:欧拉路或欧拉回路
1 5 4 3 2 1
这个回路具体实现见代码:
#include<iostream>
#include<cstring>
using namespace std;
#define maxn 101
int g[maxn][maxn]; //此图用邻接矩阵存储
int du[maxn]; //记录每个点的度,就是相连的边的数目,看奇偶
int circuit[maxn]; //用来记录找到的欧拉路的路径
int n,e,circuitpos,i,j,x,y,start;
void find_circuit(int i) //这个点深度优先遍历过程寻找欧拉路
{
int j;
for (j = 1; j <= n; j++)
if (g[i][j] == 1) //从任意一个与它相连的点出发
{
g[j][i] = g[i][j] = 0;
find_circuit(j);
}
circuit[++circuitpos] = i; //记录下路径
}
int main()
{
memset(g,0,sizeof(g));
cin >> n >> e;
for (i = 1; i <= e; i++)
{
cin >> x >> y;
g[y][x] = g[x][y] = 1;
du[x]++; //统计每个点的度
du[y]++;
}
start = 1; //如果有奇点,就从奇点开始寻找,这样找到的就是
for (i = 1; i <= n; i++) //欧拉路。没有奇点就从任意点开始,
if (du[i]%2 == 1) //这样找到的就是欧拉回路。(因为每一个点都是偶点)
start = i;
circuitpos = 0;
find_circuit(start);
for (i = 1; i <= circuitpos; i++)
cout << circuit[i] << ' ';
cout << endl;
return 0;
}
二.哈密尔循环:
欧拉回路是指不重复地走过所有路径的回路,而哈密尔顿环是指不重复地走过所有的点,并且最后还能回到起点的回路。
使用简单的深度优先搜索,就能求出一张图中所有的哈密尔顿环。
#include<iostream>
#include<cstring>
using namespace std;
int start,length,x,n;
bool visited[101],v1[101];
int ans[101], num[101];
int g[101][101];
void print()//输出函数而已
{ int i;
for (i = 1; i <= length; i++)
cout << ' ' << ans[i];
cout << endl;
}
void dfs(int last,int i) 深搜
{
visited[i] = true; //标记为已经访问过
v1[i] = true; //标记为已在一张图中出现过
ans[++length] = i; //记录下答案
for (int j = 1; j <= num[i]; j++)
{
if (g[i][j]==x&&g[i][j]!=last) //回到起点,构成哈密尔顿环
{
ans[++length] = g[i][j];
print(); //这里说明找到了一个环,则输出ans数组。
length--;
break;
}
if (!visited[g[i][j]])
dfs(i,g[i][j]);
}
length--;
visited[i] = false;
}
int main()
{
memset(visited,false,sizeof(visited));
memset(v1,false,sizeof(v1));
for (x = 1; x <= n; x++) //每一个点都作为起点尝试访问,因为不是从任何一点开始都能找过整个图的
if (!v1[x]) //如果点x不在之前曾经被访问过的图里。
{
length = 0; //定义一个ans数组存答案,length记答案的长度。
dfs(x);
}
return 0;
}
最短路径算法(重点!!):
1.Floyed-Warshall算法 O(N3) :
最简单!最好记!耗时最长!适用于出现负边权的情况。
//初始化:点u、v如果有边相连,则dis[u][v]=w[u][v]。
//如果不相连则dis[u][v]=0x7fffffff
For (k = 1; k <= n; k++)
For (i = 1; i <= n; i++)
For (j = 1; j <= n; j++)
If (dis[i][j] >dis[i][k] + dis[k][j])
dis[i][j] = dis[i][k] + dis[k][j];
// 算法结束:dis[i][j]得出的就是从i到j的最短路径。
适用于样例较少的情况,因为o(n^3)的耗时不是一般题目用得起的。
2.Dijkstra算法O (N2)
用来计算从一个点到其他所有点的最短路径的算法,是一种单源最短路径算法。也就是说,只能计算起点只有一个的情况。这样就感觉用途局限了。
Dijkstra的时间复杂度是O (N2),它不能处理存在负边权的情况。有点贪心的想法,先找最小的权值的路径,再利用这个路径缩短其他路径的距离。
设起点为s,dis[v]表示从s到v的最短路径,pre[v]为v的前驱节点,用来输出路径。
初始化:dis[v]=∞(v≠s); dis[s]=0; pre[s]=0; s是开始,设为0
For (i = 1; i <= n ; i++)
1.在没有被访问过的点中找一个顶点u使得dis[u]是最小的。
2.u标记为已确定最短路径
3.For 与u相连的每个未确定最短路径的顶点v
if (dis[u]+w[u][v] < dis[v])
{
dis[v] = dis[u] + w[u][v];
pre[v] = u;
}
c)算法结束:dis[v]为s到v的最短距离;pre[v]为v的前驱节点,用来输出路径。