类比二叉树先序遍历与图深度优先搜索

在引入图的深度优先搜索之前,为了更加容易理解.先考究一种特殊的图---二叉树的深度优先搜索算法---即二叉树的递归遍历方法.

二叉树的前序遍历算法:

void TreeWalk(node* root)
{if(root){visit(root);TreeWalk(root->left);TreeWalk(root->right);}
}

对于二叉树来说,步骤如下:

1.如果root不为空,先访问root,

2再递归下降地访问root->left和root->right.

在二叉树中,与root邻接的结点只有left and right.所以,每次只需要递归下降访问这两个节点即可.

对于图来说,可以类比二叉树(特殊情况),总结出一般规律:

1先选定任意一个点vex做为生成树的根结点.访问该根点,

2再递归下降地访问与vex邻接的而且未被访问过的所有结点.

在图中,与vex邻接的结点是不像二叉树一样只有left 和 right,因为是不确定的,因此需要循环地查找判断哪些点邻接并且尚未被访问过.

伪代码:

void DFS(Graph& g,int vex)
{Visit vex and Mark vex is visited;for w=FirstAdjacencyVertex to LastAdjacencyVertex(which is not visited before)DFS(g,w)
}

对应的C++代码:

//深度优先算法,可以形成一棵树
void DFS(Graph& g,int vex)
{cout<<vex<<' ';visited[vex]=true;//is visitedint w=FirstAdjVertex(g,vex);while(w>0){DFS(g,w);w=NextAdjVertex(g,vex,w);}
}

其中,FirstAdjVertex(g,vex)可以返回vex的第一个未被访问的邻接结点w.NextAdjVertex(g,vex,w)可以返回vex的下一个未被访问的邻接结点,在编号上在w之后.(小编号结点优先原则).

//找第一个未被访问过的邻接结点,找不到返回0
int FirstAdjVertex(Graph& g,int vex)
{for(int i=1;i<=g.vnum;++i)//从点1开始找与vex邻接的第一个结点{if(g.edge[vex][i]==1 && !visited[i])return i;}return 0;
}
//找下一个未被访问过的邻接结点
int NextAdjVertex(Graph& g,int vex,int w)
{for(int i=w;i<=g.vnum;++i)//从w开始,找下一个未被访问过的邻接结点{if(g.edge[vex][i]==1 && !visited[i])//若i与vex邻接且i未访问过,下一次遍历,就从这个点往下.return i;}return 0;//点都从1开始,返回0则所以点已访问过
}

对于无向连通图或者强有向强连通图,可以调用DFS形成一棵深度优先搜索树.但是,对于非连通图或者非强连通图.需要在生成一棵树之后,继续寻找是否还存在未被访问到的点,在这些点上继续调用DFS,最终形成森林.

void DFS_traver(Graph& g)
{for(int i=1;i<=g.vnum;++i){if(!visited[i])//如果i结点未被访问过,形成森林DFS(g,i);}
}

深度优先搜索完整代码:

#include<iostream>
using namespace std;
const int MAX=20;//最大点数
bool visited[MAX]={false};//点标记域:标志是否被访问过.
struct Graph
{int vertex[MAX];//点int edge[MAX][MAX];//边int vnum;//点数
};
//创建图
void CreateGraph(Graph& g)
{cin>>g.vnum;for(int i=1;i<=g.vnum;++i)g.vertex[i]=i;for(int i=1;i<=g.vnum;++i){for(int j=1;j<=g.vnum;++j)cin>>g.edge[i][j];}
}
//打印图
void PrintGraph(Graph& g)
{for(int i=1;i<=g.vnum;++i)cout<<g.vertex[i]<<' ';cout<<endl;for(int i=1;i<=g.vnum;++i){for(int j=1;j<=g.vnum;++j)cout<<g.edge[i][j]<<' ';cout<<endl;}
}
//找第一个未被访问过的邻接结点,找不到返回0
int FirstAdjVertex(Graph& g,int vex)
{for(int i=1;i<=g.vnum;++i)//从点1开始找与vex邻接的第一个结点{if(g.edge[vex][i]==1 && !visited[i])return i;}return 0;
}
//找下一个未被访问过的邻接结点
int NextAdjVertex(Graph& g,int vex,int w)
{for(int i=w;i<=g.vnum;++i)//从w开始,找下一个未被访问过的邻接结点{if(g.edge[vex][i]==1 && !visited[i])//若i与vex邻接且i未访问过,下一次遍历,就从这个点往下.return i;}return 0;//点都从1开始,返回0则所以点已访问过
}
//深度优先算法,可以形成一棵树
void DFS(Graph& g,int vex)
{cout<<vex<<' ';visited[vex]=true;//is visitedint w=FirstAdjVertex(g,vex);while(w>0){DFS(g,w);w=NextAdjVertex(g,vex,w);}
}void DFS_traver(Graph& g)
{for(int i=1;i<=g.vnum;++i){if(!visited[i])//如果i结点未被访问过,形成森林DFS(g,i);}
}
void main()
{Graph g;CreateGraph(g);PrintGraph(g);DFS_traver(g);
}

深度优先链式表示法

#include<iostream>
using namespace std;
const int MAX=6;
struct node
{int value;node* next;bool visited;node(int v):value(v),visited(false),next(NULL){}
};
struct Graph
{node* A[MAX];//定义一个指针数组,数组的每一个元素都是指针Graph(){for(int i=0;i<MAX;++i){A[i]=NULL;}}
};
node* insertList(node* head,node* x)
{if(!head)//if(!head)=if(head==NULL){head=x;return head;}head->next=insertList(head->next,x);return head;
}
void CreateGraph(Graph& g)
{int inter;for(int i=1;i<MAX;++i){while(cin>>inter && inter !=0)//以0为结束g.A[i]=insertList(g.A[i],new node(inter));//g.A[i]是表头}
}void printGraph(Graph& g)
{node* p=NULL;for(int i=1;i<MAX;++i){p=g.A[i];while(p){cout<<p->value<<' ';p=p->next;}cout<<endl;}
}node* FindAdjVertex(Graph& g,node* vex)
{while(vex)//从点1开始找与vex邻接的第一个结点{if(!g.A[vex->value]->visited)//查看vex是否被访问过,访问标志保存在表头g.A[i]return vex;vex=vex->next;}return NULL;
}
/*
//找下一个未被访问过的邻接结点
node* FindAdjVertex(Graph& g,node* w)
{while(w)//从w开始,找下一个未被访问过的邻接结点{if(!g.A[w->value]->visited)//若i与vex邻接且i未访问过,下一次遍历,就从这个点往下.return w;w=w->next;}return NULL;//点都从1开始,返回0则所有点已访问过
}
*/
void DFS(Graph& g,node* vex)
{cout<<vex->value<<' ';//vex->visited=true;//is visitedg.A[vex->value]->visited=true;//设置标志node* w=FindAdjVertex(g,g.A[vex->value]);while(w){DFS(g,w);w=FindAdjVertex(g,g.A[w->value]);}
}void DFS_traver(Graph& g)
{for(int i=1;i<MAX;++i){if(!g.A[i]->visited)//如果i结点未被访问过,形成森林DFS(g,g.A[i]);}
}
void main()
{Graph g;CreateGraph(g);printGraph(g);//DFS(g,g.A[1]);DFS_traver(g);
}

非递归版DFS

/*从顶点i开始对图进行深度优先搜索遍历,图由其邻接表adjlist表示*/
void DFS2(Graph& g,node* vex)
{node* p=vex;stack<node*> st;while (p || !st.empty()){while (p) {if(!g.A[p->value]->visited)//如果未被访问过{cout<<p->value<<' ';//visitg.A[p->value]->visited=true;//设为已访问,纵深探索st.push(p);//同时压栈p=g.A[p->value];//找下一个点}elsep = p->next;//接着找未被访问过的节点}if(!st.empty()){p=st.top();st.pop();p = p->next;}}
}

图片来自算法导论第二版:

广度优先搜索算法:来源:点击打开链接

图的广度优先遍历

  图的广度优先遍历BFS算法是一个分层搜索的过程,和树的层序遍历算法类同,它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。 
1.连通图的广度优先遍历算法思想。
(1)顶点v入队列。
(2)当队列非空时则继续执行,否则算法结束。
(3)出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。
(4)查找顶点v的第一个邻接顶点col。
(5)若v的邻接顶点col未被访问过的,则col入队列。
(6)继续查找顶点v的另一个新的邻接顶点col,转到步骤(5)。直到顶点v的所有未被访问过的邻接点处理完。转到步骤(2)。
【例】下面以图( a )为例说明广度优先搜索的过程。首先从起点 v1 出发访问 v1。v1 有两个未曾访问的邻接点 v2 和 v3 。先访问 v2 ,再访问 v3 。然后再先访问 v2 的未曾访问过的邻接点 v4、v5 及 v3 的未曾访问过的邻接 v6 和 v7 ,最后访问 v4 的未曾访问过的邻接点 v8 。至此图中所有顶点均已被访问过。得到的顶点访问序列为:
          
       

代码:特别鸣谢@tommyhu111

void BFS(Graph& g,int vex)
{queue<int> q;visited[vex] = true;                               //标记已被访问过q.push(vex);                                       //初始化队列while(!q.empty()){vex=q.front();q.pop();                                        //取出队头数据cout<<vex<<' ';for ( int j=1; j<=g.vnum; ++j ) {if( g.edge[vex][j] == 1 && !visited[j] )     //邻接未被访问过的结点入队{visited[vex] = true;                    //标记已被访问过q.push(j);}}}
}
void BFS_traver(Graph& g)
{for(int i=1;i<=g.vnum;++i){if(!visited[i])                                  //如果i结点未被访问过,形成森林BFS(g,i);}
}

Dijkstra算法

广度优先搜索的经典应用:Dijkstra算法。Dijkstra算法的思想是BFS+贪心策略。即从源结点向外辐射状地扩展结点,并且每一次都选择最优的结点进行扩展。如果有n个结点,则循环n次就可以将所有结点的最短路径求出来,缺点是无法对权重为负数的图进行并且搜索的结点多效率相对较低。

先直接贴出代码,后面再解释算法运行的原理。

#include<stdio.h>#define MAX_VERTEX_NUM 100 //最大顶点数
#define MAX_INT 10000 //无穷大 typedef int AdjType;
typedef char VType; //设顶点为字符类型struct PathType
{int route[MAX_VERTEX_NUM];//存放v到vi的一条最短路径int End;
};//邻接矩阵表示的图
struct Graph
{VType V[MAX_VERTEX_NUM]; //顶点存储空间 AdjType A[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //邻接矩阵
};//Dijkstra算法
//求G(用邻接矩阵表示)中源点v到其他各顶点最短路径,n为G中顶点数
void Dijkstra(Graph * G,PathType path[],int dist[],int v,int n)
{int i,j,count,min,minIndex;//visited[n]用来标志源点到某顶点的最短路径是否求出int *visited=new int[n];for(i=0;i<n;i++){ //1.初始化visited[i]=0;dist[i]=G->A[v][i];//v到它的邻接顶点的权为当前最短路径,用这个权值初始化dist[],优先队列(每次找小)path[i].route[0]=v; //开始结点都是vpath[i].End=0;   //路径末尾索引}dist[v]=0;visited[v]=1;//源点到源点本身的最短路径已经求出 count=1;while(count<=n-1){    //求n-1条最短路径min=MAX_INT;// MAX_INT为无穷大值,需要实际情况设置 //这一部分实现的是优先队列的功能,找最小值for(j=0;j<n;j++){   //找当前最短路径长度 //如果j未被访问过且当前j的距离更短,更新min和minIndexif( !visited[j] && dist[j]<min ){minIndex=j;min=dist[j]; }}//当visited[]数组全是1,即合被访问过时,可以退出了。if(min==MAX_INT)break;//最短路径求完(不足n-1)条,跳出while循环,考虑非连通图//贪心选择,选择当前最min的路径int pEnd = ++path[minIndex].End;path[minIndex].route[pEnd]=minIndex ;visited[minIndex]=1;//表示V到minIndex最短路径求出,标记为已被访问过for(j=0;j<n;j++){ //minIndex求出后,修改dist和path向量,只更新那些未处理过的点 if(!visited[j] && dist[minIndex]+G->A[minIndex][j] < dist[j] ){dist[j]=dist[minIndex]+G->A[minIndex][j];//更新最短路径长度path[j]=path[minIndex];//更新为更短的路径} }count++;}delete []visited;
}int main()
{int i,j,v=0,n=6;//v为起点,n为顶点个数 Graph G;//v到各顶点的最短路径向量PathType* path = new PathType[n];//v到各顶点最短路径长度向量 int* dist =new int[n];AdjType a[MAX_VERTEX_NUM][MAX_VERTEX_NUM]={{MAX_INT,7,9,MAX_INT,MAX_INT,14},{7,MAX_INT,10,15,MAX_INT,MAX_INT},{9,10,MAX_INT,11,MAX_INT,2},{MAX_INT,15,11,MAX_INT,6,MAX_INT},{MAX_INT,MAX_INT,MAX_INT,6,MAX_INT,9},{14,MAX_INT,2,MAX_INT,9,MAX_INT} };for(i=0;i<n;i++){for(j=0;j<n;j++){G.A[i][j]=a[i][j];}} Dijkstra(&G,path,dist,v,n);for(i=0;i<n;i++){printf("%d到%d的最短路径:",v+1,i+1);for(j=0;j<=path[i].End;j++){printf("%d ",path[i].route[j]+1);}printf("\n");printf("最短路径长度:%d",dist[i]);//输出为MAX_INT则表示两点间无路径printf("\n");}delete []path;delete []dist;return 0;
}

用一个具体的图作为例子:

图的领接矩阵(带权短阵):

{MAX_INT,7,9,MAX_INT,MAX_INT,14},
{7,MAX_INT,10,15,MAX_INT,MAX_INT},
{9,10,MAX_INT,11,MAX_INT,2},
{MAX_INT,15,11,MAX_INT,6,MAX_INT},
{MAX_INT,MAX_INT,MAX_INT,6,MAX_INT,9},
{14,MAX_INT,2,MAX_INT,9,MAX_INT}

图如下:

Dijkstra算法的第一步:初始化

将所有的结点visited[i]设为0,即各个结点还没有计算出最短路径。

dist[i]表示结点i的权值,也可以看作是从源点v到结点i的最短路径长度。v到它的邻接顶点的权为当前最短路径.初始化比如,dist[0]=0,dist[1]=7,dist[2]=9,dist[5]=14,dist[4]=MAX,dist[3]=MAX(结点从0数起)

path[i]的开头结点都设为源结点v,路径末尾索引都设为0,因为当前只有一个元素。

并且dist[v] =0 ,visited[v]=1,源点到源点的最短路径已经求出。

初始化后是:

第二步:贪心选择当前未被计算出最短路径的dist[i]的最小值,如上图,现在dist[]数组是{0,7,9,14,MAX,MAX},0已被访问过,则从其余结点中贪心选择最短的一个结点1,(这一部分实现的是一个优先队列的功能,可以用STL的优先队列实现)

第三步:第二步贪心选择好一个结点后,需要更新dist[]数组和path[]数组,因为基于启发式搜索,有可能新发现的路径比原来的路径更短。

(如图:在7,9,14,MAX,MAX中贪心选择最短路径7,即结点1.标记为已计算出最短路径visited[i]=1,然后更新未visited过的结点的路径.对于结点3,有7+15<MAX,则用22更新结点3的权,同时更新最短路径paht[]。对于结点2,7+10>9,新发现的路径比原来的更小,无须更新。对4,5这些和1并不邻接的结点有MAX+7 > MAX,无须更新,同时MAX不能设为INT_MAX)

第四步:重复上第二步和第三步的步骤n-1次,就可以求出源点到所有结点的最短路径。

最短路练手:HDU1874

#include<iostream>
#include<string.h>
#include<limits.h>
using namespace std;#define MAX_INT 2139062143int map[205][205];
int n,s,v;
int visited[205],dist[205];void Dijkstra()
{//第一步:初始化int i,min,v;memset(visited,false,sizeof(visited));memset(dist,127,sizeof(dist));//强大的近似初始化最大值dist[s] = 0;while(1){//第二步:找最小min = MAX_INT,v = s;for(i=0; i<n; ++i){if(!visited[i] && dist[i] < min)min = dist[i],v = i;}if(min == MAX_INT)break;visited[v] = true;//第三步:更新路径长度for(i=0; i<n; ++i){if(!visited[i] && map[v][i]<MAX_INT && dist[v] + map[v][i] < dist[i])dist[i] = dist[v] + map[v][i];}}
}int main()
{int m,i,x,y,res;while(cin>>n>>m){memset(map,127,sizeof(map));for(i=0; i<m; ++i){cin >> x >> y >> res;if(map[x][y]>res)//注意重边,选最小的边map[x][y] = map[y][x] = res;}cin >> s >> v;Dijkstra();if( dist[v] == MAX_INT )cout << "-1" << endl;elsecout << dist[v] << endl;}return 0;
}

图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra相关推荐

  1. 二叉树的深度和广度优先搜索算法

    Java实现二叉树的深度和广度优先搜索算法 1 package com.java; 2 3 import java.util.ArrayDeque; 4 5 /** 6 * 广度优先搜索 7 * 算法 ...

  2. DFS深度优先搜索算法/BFS广度优先搜索算法(c/c++)

    深度优先搜索算法(DFS) 深度优先搜索算法思路:(有点贪心算法的意思) 1,从某个给定结点a出发,访问它 2,查找关于a的邻接点,查找到a的第一个邻接点b之后,对b结点进行DFS搜索,也就是对b结点 ...

  3. Java实现图的深度和广度优先遍历算法

    概述: 最近要学习写网络爬虫,所以把图的深度和广度搜索都再温习一下. 图结构展示: 实现过程: 首先,我们来看看图结构在代码中的实现.有三块逻辑: 1.图中的节点: public class Grap ...

  4. Java数据结构之图的基本概念和算法,深度优先遍历DFS,广度优先遍历BFS(图解)

    文章目录 前言 一.图的基本概念 1.图的定义 2.基本术语 二.图的基本算法 1.初始化图 2.插入顶点和边 3.矩阵打印 4.返回第一个邻接结点的下标 5.返回第一个邻接结点的下一个结点的下标 三 ...

  5. 网状结构(图)图的存储(邻接矩阵、邻接表)、图的遍历(深度DFS、广度BFS)、图的最短路径

    图 多对多关系 是一种网状数据结构,图是由非空的顶点集合和一个描述顶点之间关系的集合组成 其定义 Graph = (V, E) V={x | x ∈某个数据对象} E = {<u, v> ...

  6. 七十九、深度和广度优先搜索算法

    @Author:Runsen 编程的本质来源于算法,而算法的本质来源于数学,编程只不过将数学题进行代码化. ---- Runsen 深度优先搜索和广度优先搜索作为应用广泛的搜索算法,一般是必考算法. ...

  7. 图的深度和广度优先遍历(C语言)

    要求:图用邻接表方式存储,输入一个无向图的信息并存储,实现图的深度优先遍历及广度优先遍历.(输入输出均使用文件的方式实现) 图的示例,该图为不连通图 1.开始的准备工作如下 #include<s ...

  8. 图的遍历:广度优先搜索(BFS)

    广度优先搜索(Breadth First Search) 广度优先搜索遍历类似于树的按层序遍历 第一个结点A入队,检查与结点A相连的结点B和结点F,结点A已经处理完,将结点A出队, 检查与结点B相连的 ...

  9. 广度优先搜索算法BFS讲解以及python 实现

    一.图简介 假设你居住在旧金山,要从双子峰前往金门大桥,你想乘公交车前往. 为找出换乘最少的乘车路线,你将使用怎样的算法? 金门大桥未突出,因此一步无法到达那里.两步能吗? 金门大桥未突出,两步步无法 ...

最新文章

  1. (chap4 IP协议) IP基础知识
  2. pytorch微调bert_香侬读 | RoBERT: 没错,我就是能更强——更大数据规模和仔细调参下的最优BERT
  3. 过滤器用到了java哪个模式_设计模式之过滤器模式——Java语言描述
  4. 《科技之巅2》序——机器智能数据智能:工具之王
  5. 商业计划书最好就是十页篇幅
  6. JQuery Datatables editor进行增删改查操作(一)
  7. docker更新容器命令 ,自启
  8. js中while死循环语句_如何检测JavaScript中的死循环?
  9. java map 对象作为key_Java 将自定义的对象作为HashMap的key
  10. C++11右值引用和std::move语句实例解析
  11. 编译OpenJDK8:configure: could not find or use freetype at location
  12. Atitit 架构的艺术 目录 1. 按照技术站分类 1 1.1. LAMP架构,到IOE架构,再到分布式架构 1 1.2. Ssh ssm 1 2. Bs cs web hybrid架构 1 3.
  13. Python小白的数据库入门
  14. 高级售前客户服务专员题库
  15. 机器学习实战(1)-文本分类
  16. 计算机网络共享自动关,启用网络发现,重新打开“高级共享设置”对话框,显示仍是关闭状态...
  17. 【软考系统架构设计师】2015年下系统架构师案例分析历年真题
  18. 联发科mt8516价格_一颗神U创造历史:联发科MT8516
  19. 使用POI操作Excel的基本读写
  20. orge terrain

热门文章

  1. GET http://localhost:8082/ net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)
  2. 自学2 MATLAB图形处理
  3. 【新知实验室】腾讯云TRTC验证测试
  4. 畅想小组KTV点歌系统简介
  5. 微信小程序激活账号时,提示“此帐号已激活,请使用帐号密码直接登录”
  6. C Primer Plus 第十二章 课后答案
  7. Python强化知识之获取网络资源 Urllib(一)
  8. 2D-X光图像重建3D-CT图像项目总结—后续补充
  9. 什么是嵌入式软件开发?
  10. 考研380分什么水平计算机,考研总分500考380难吗 考研380分是什么水平