图论算法

最近好多考试,紧张,感觉还没有学好呢,电子最急了,还没有复习到该到的地方,哎,还不好考,哭唧唧。最近好忙呀!忙的不知道开始干什么好。不多说别的了,今晚要写不完图论了……还是开始加油吧。

基本概念:

  1. 结点的度:无向图中与结点相连的边的数目,称为结点的度。
  2. 结点的入度:在有向图中,以这个结点为终点的有向边的数目。
  3. 结点的出度:在有向图中,以这个结点为起点的有向边的数目。
  4. 权值:边的“费用”,可以形象地理解为边的长度。
  5. 连通:如果图中结点U,V之间存在一条从U通过若干条边、点到达V的通路,则称U、V 是连通的。
  6. 回路:起点和终点相同的路径,称为回路,或“环”。
  7. 完全图:一个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的前驱节点,用来输出路径。

 

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值