【基础算法】(10)图的概念及相关算法(第一篇)
Auther: Thomas Shen
E-mail: Thomas.shen3904@qq.com
Date: 2017/12/20
All Copyrights reserved !
1. 图的存储结构:
1.1 邻接矩阵:
使用二维数组来存储图的边的信息和权重,如下图所示的4个顶点的无向图:
从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
如果换成有向图,则如图所示的五个顶点的有向图的邻接矩阵表示如下:
1.2 邻接表:
邻接矩阵是一种不错的图存储结构,但是对于边数相对较少的图,这种结构存在空间上的极大浪费,因此找到一种数组与链表相结合的存储方法称为邻接表。
邻接表的处理方法是这样的:
- 图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
- 图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表
如下为无向图的邻接表表示:
从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。
有向图的邻接表表示:
2. 深度优先遍历(DFS):
2.1 基本概念:
它从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。
2.2 算法简述:
基本实现思想:
- 访问顶点v;
- 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
- 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。
2.1.1 递归实现:
1. 访问顶点v;visited[v]=1;//算法执行前visited[n]=0
2. w=顶点v的第一个邻接点;
3. while(w存在)
if(w未被访问)
从顶点w出发递归执行该算法;
w=顶点v的下一个邻接点;
2.1.2 非递归实现:
1. 栈S初始化;visited[n]=0;
2. 访问顶点v;visited[v]=1;顶点v入栈S
3. while(栈S非空)
x=栈S的顶元素(不出栈);
if(存在并找到未被访问的x的邻接点w)
访问w;visited[w]=1;
w进栈;
else
x出栈;
3. 广度优先遍历(BFS):
3.1 基本概念:
它是一个分层搜索的过程和二叉树的层次遍历十分相似,它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。
基本实现思想:
- 顶点v入队列。
- 当队列非空时则继续执行,否则算法结束。
- 出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。
- 查找顶点v的第一个邻接顶点col。
- 若v的邻接顶点col未被访问过的,则col入队列。
- 继续查找顶点v的另一个新的邻接顶点col,转到步骤(5)。
直到顶点v的所有未被访问过的邻接点处理完。转到步骤(2)。
广度优先遍历图是以顶点v为起始点,由近至远,依次访问和v有路径相通而且路径长度为1,2,……的顶点。为了使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问,需设置队列存储访问的顶点。
3.2 算法简述:
1. 初始化队列Q;visited[n]=0;
2. 访问顶点v;visited[v]=1;顶点v入队列Q;
3. while(队列Q非空)
v=队列Q的对头元素出队;
w=顶点v的第一个邻接点;
while(w存在)
如果w未访问,则访问顶点w;
visited[w]=1;
顶点w入队列Q;
w=顶点v的下一个邻接点。
4. 代码实现:
采用邻接矩阵存储图的边信息:
/*
* 定义图的结构
*/
class Graph {
static final int MaxNum=20; //最大节点数目
static final int MaxValue=65535;
char[] Vertex = new char[MaxNum]; //定义数组,保存顶点信息
int GType; //图的类型0:无向图 1:有向图
int VertxNum; //顶点的数量
int EdgeNum; //边的数量
int[][] EdgeWeight = new int[MaxNum][MaxNum]; //定义矩阵保存顶点信息
int[] isTrav = new int[MaxNum]; //遍历标志
}
//创建邻接矩阵图
static void createGraph(Graph g){
int i , j , k;
int weight; //权
char EstartV, EndV; //边的起始顶点
System.out.println("输入途中各顶点的信息");
for(i=0; i < g.VertxNum; i ++)
{
System.out.println("第" + (i+1) + "个顶点");
g.Vertex[i] = (scan.next().toCharArray() )[0];
}
System.out.println("输入构成个遍的顶点和权值");
for(k=0;k<g.EdgeNum;k++)
{
System.out.println("第" + (k+1) + "条边:");
EstartV = scan.next().charAt(0);
EndV = scan.next().charAt(0);
weight = scan.nextInt();
for(i=0; EstartV!=g.Vertex[i] ; i++); //在已有顶点中查找开始节点
for(j=0; EndV != g.Vertex[j]; j++); //在已有节点上查找终结点
g.EdgeWeight[i][j] = weight; //对应位置保存权重,表示有一条边
if(g.GType == 0) //如果是无向图,在对角位置保存权重
g.EdgeWeight[j][i] = weight;
}
}
//清空图
static void clearGraph(Graph g){
int i,j;
for(i=0; i< g.VertxNum; i++)
for(j =0; j<g.VertxNum; j++)
g.EdgeWeight[i][j] = Graph.MaxValue; //设置矩阵中各院素的值为MaxValue
}
//输出邻接矩阵
static void OutGraph(Graph g){
int i,j;
for(j = 0; j < g.VertxNum;j ++)
System.out.print("\t" + g.Vertex[j]); //在第一行输入顶点信息
System.out.println();
for(i =0 ;i <g.VertxNum; i ++)
{
System.out.print( g.Vertex[i]);
for(j = 0;j < g.VertxNum; j++)
{
if(g.EdgeWeight[i][j] == Graph.MaxValue) //若权值为最大值
System.out.print("\tZ"); //Z 表示无穷大
else
System.out.print("\t" + g.EdgeWeight[i][j]); //输出边的权重
}
System.out.println();
}
}
//遍历图
static void DeepTraOne(Graph g,int n){//从第n个节点开始遍历
int i;
g.isTrav[n] = 1; //标记为1表示该顶点已经被处理过
System.out.println("—>" + g.Vertex[n]); //输出节点数据
//添加处理节点的操作
for(i = 0; i< g.VertxNum; i++)
{
//if(g.EdgeWeight[n][i] != g.MaxValue && g.isTrav[n] == 0)
if(g.EdgeWeight[n][i] != g.MaxValue && g.isTrav[i] == 0)
{
DeepTraOne(g, i); //递归进行遍历
}
}
}
//深度优先遍历
static void DeepTraGraph(Graph g){
int i;
for(i = 0; i< g.VertxNum; i++)
{
g.isTrav[i]= 0;
}
System.out.println("深度优先遍历:");
for(i = 0; i< g.VertxNum ; i++)
{
if(g.isTrav[i] == 0)
DeepTraOne(g,i);
}
System.out.println();
}
主函数:
public static void main(String[] args) {
// TODO Auto-generated method stub
Graph g = new Graph();
System.out.println("输出生成图的类型:");
g.GType = scan.nextInt(); //图的种类
System.out.println("输入图的顶点数量:");
g.VertxNum = scan.nextInt();
System.out.println("输入图的边数量:");
g.EdgeNum = scan.nextInt();
clearGraph(g); //清空图
createGraph(g); //生成邻接表结构的图
System.out.println("该图的邻接矩阵数据如下:");
OutGraph(g); //输出图
DeepTraGraph(g); //深度优先遍历图
}