以下内容主要来自大话数据结构之中,部分内容参考互联网中其他前辈的博客,主要是在自己理解的基础上进行记录。

  图的定义

图是由顶点的有穷非空集合和顶点之间边的集合组成,通过表示为G(V,E),其中,G标示一个图,V是图G中顶点的集合,E是图G中边的集合。

无边图:若顶点Vi到Vj之间的边没有方向,则称这条边为无项边(Edge),用序偶对(Vi,Vj)标示。

有向图:若从顶点Vi到Vj的边是有方向的,则成这条边为有向边,也称为弧(Arc)。用有序对(Vi,Vj)标示,Vi称为弧尾,Vj称为弧头。如果任意两条边之间都是有向的,则称该图为有向图。

有向图G2中,G2=(V2,{E2}),顶点集合(A,B,C,D),弧集合E2={<A,D>,{B,A},<C,A>,<B,C>}.

权(Weight):有些图的边和弧有相关的数,这个数叫做权(Weight)。这些带权的图通常称为网(Network)。

    

  图的存储结构

  图的存储结构一般分为邻接矩阵和十字链表

  邻接矩阵:图的邻接矩阵存储方式是用两个数组来标示图。一个一位数组存储图顶点的信息,一个二维数组(称为邻接矩阵)存储图中边或者弧的信息。

  设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

  

  十字链表:

  顶点表结点结构:

  

  firstin:表示入边表头指针,指向该顶点的入边表中第一个结点。

  firstout:表示出边表头指针,指向该顶点的出边表中的第一个结点。

  边表结点结构:

  

  tailvex:指弧起点在顶点表的下标。

  headvex:指弧终点在顶点表中的下标。

  headlink:指入边表指针域。

  taillink:指边表指针域。

  如果是网,还可以再增加一个weight域来存储权值。

  

  蓝线表示出度,红线表示入度

  十字链表的优点

  十字链表是把邻接表和逆邻接表整合在一起,这样既容易找到以Vi为尾的弧,也容易找到以Vi为头的弧,

  因而容易求的顶点的出度和入度。

  

  图的搜索:

  深度优先遍历:也有称为深度优先搜索,简称DFS。其实,就像是一棵树的前序遍历。它从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有      路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。

  基本实现思想:

  (1)访问顶点v;

  (2)从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;

  (3)重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。

  

  广度优先遍历:也称广度优先搜索,简称BFS。BFS算法是一个分层搜索的过程,和树的层序遍历算法类同,它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。

  基本实现思想:

  (1)顶点v入队列。

  (2)当队列非空时则继续执行,否则算法结束。

  (3)出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。

  (4)查找顶点v的第一个邻接顶点col。

  (5)若v的邻接顶点col未被访问过的,则col入队列。

  (6)继续查找顶点v的另一个新的邻接顶点col,转到步骤(5)。

直到顶点v的所有未被访问过的邻接点处理完。转到步骤(2)。

  广度优先遍历图是以顶点v为起始点,由近至远,依次访问和v有路径相通而且路径长度为1,2,……的顶点。为了使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问,需设置队列存储访问的顶点。

  最小生成树

    我们把构造连通网的最小代价生成的树称为最小生成树,即权值最小的生成树。

  实现方式:

  1、普利姆算法(Prim)

    基本思想:假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:

    在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。

   此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。

    Prim算法的核心:始终保持TE中的边集构成一棵生成树。

   注意:prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。

    示例:

    

  (1)图中有6个顶点v1-v6,每条边的边权值都在图上;在进行prim算法时,我先随意选择一个顶点作为起始点,当然我们一般选择v1作为起始点,好,现在我们设U集合为当前所找到最小生成树里面的   顶点,TE集合为所找到的边,现在状态如下:

      U={v1}; TE={};

  (2)现在查找一个顶点在U集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。

  

  通过图中我们可以看到边v1-v3的权值最小为1,那么将v3加入到U集合,(v1,v3)加入到TE,状态如下:

U={v1,v3}; TE={(v1,v3)};

(3)继续寻找,现在状态为U={v1,v3}; TE={(v1,v3)};在与红线相交的边上查找最小值。

  

  我们可以找到最小的权值为(v3,v6)=4,那么我们将v6加入到U集合,并将最小边加入到TE集合,那么加入后状态如下:

  U={v1,v3,v6}; TE={(v1,v3),(v3,v6)}; 如此循环一下直到找到所有顶点为止。

  (4)下图像我们展示了全部的查找过程:

  

    

  2、克鲁斯卡尔算法(Kruskal)

    

  假设连通网N=(V,{E})。则令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通分量。在E中选择最小代价的边,若该边依附的顶点落在T中不同的连通分       量中,则将该边加入到T中,否则舍去此边而选择下一条代价最小的边,依次类推,直到T中所有顶点都在同一连通分量上为止。

  示例如下:

  

    图中先将每个顶点看作独立的子图,然后查找最小权值边,这条边是有限制条件的,边得两个顶点必须不在同一个图中,如上图,第一个图中找到最小权值边为(v1,v3),且满足限制条件,继续查找边              (v4,v6),(v2,v5),(v3,v6),当查找到最后一条边时,仅仅只有(v2,v3)满足限制条件,其他的如(v3,v4),(v1,v4)都在一个子图里面,不满足条件,至此已经找到最小生成树的

所有边。

  上述所有的具体代码实现:

  

 /**
* java数据结构无向图的实现
* 2016/4/29
*/
package cn.Link; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class Graph { final static int MAX = 65535; //两个定点之间没有路径时的长度
int verticts; //顶点数
int sides; //顶点为verticts的连通图的边数值
int[][] arc;//存储图的二维数组
String[] vex;
Graph(int verticts){
this.verticts = verticts;
this.sides = 0;
for(int i = verticts-1;i>0;i--){
this.sides +=i; //获得连通图的边数值
}
this.arc = new int[verticts][verticts];
this.vex = new String[verticts];
//初始化一个有向图,所有边设为最大权值(即不可能达到的值)
for(int i = 0; i < verticts;i++){
for(int j = 0; j < verticts;j++){
if(i!=j){
this.arc[i][j] = MAX;
}else{
this.arc[i][j]=0;
} }
}
} //假设有这样一个图
public void addGraph(){
//顶点数据
this.vex[0] = "beijing";
this.vex[1] = "shanghai";
this.vex[2] = "tianjing";
this.vex[3] = "chengdu";
this.vex[4] = "changsha";
this.vex[5] = "chongqing"; //边的权值
for(int i = 1; i < verticts;i++){
for(int j = 0; j < i;j++){
int n = (int)(Math.random()*100); //随机生成权值
if(n > 0){
this.arc[i][j]=this.arc[j][i] = n;
}else if(n == 0){
this.arc[i][j]=this.arc[j][i] = MAX;
} }
}
} //利用图的二维数组输出
public void printGraph(){
for(int i = 0; i < verticts;i++){
//输出第一行的名称
if(i == 0){
System.out.print("* ");
for(int x=0;x<verticts;x++){
System.out.print(this.vex[x]+" ");
}
System.out.println();
System.out.println(" ==============================================================");
}
//给每行前面输出地址
System.out.print(this.vex[i]+" ");
for(int j = 0; j < verticts;j++){
System.out.print(this.arc[i][j]+" ");
}
System.out.println();
System.out.println();
}
} //深度优先遍历输出
public void DFS(Graph G,int i,boolean[] visited){
int j;
visited[i] = true;
System.out.print(G.vex[i]+" "); //打印顶点的值
for(j = 0;j < G.verticts;j++){
if(G.arc[i][j]>0 && !visited[j]){
DFS(G,j,visited); //对访问的邻接顶点递归调用
}
} }
public void DFSTraverse(Graph G){
boolean[] visited = new boolean[G.verticts];
for(int i = 0;i < G.verticts;i++){
visited[i] = false; //初始状态所有顶点都是未访问过的状态
}
for(int i = 0;i < G.verticts;i++){
if(!visited[i])
DFS(G,i,visited); //对未访问过的顶点调用DFS 如果是连通图,则只会执行一次
}
} //广度优先遍历输出
public void BFS(Graph G){
int i, j;
Queue<Integer> Q = new LinkedList<Integer>();
boolean[] visited = new boolean[G.verticts];
for(i = 0;i < G.verticts;i++){
visited[i] = false;
} for(i = 0; i < G.verticts;i++){ //对每一个顶点都进行循环
if(!visited[i]){
visited[i] = true;
System.out.print("##"+G.vex[i]+" ");
Q.offer(i);
while(Q != null){
if(Q.peek() != null){
i = Q.poll(); //将队首元素赋值给i 然后出队列
}else{return ;}
for(j = 0;j < G.verticts;j++){
//判断其他顶点与当前顶点存在边但未被访问过
if(G.arc[i][j] > 0 && !visited[j]){
visited[j] = true;
System.out.print("##"+G.vex[j]+" ");
Q.offer(j);
}
}
}
}
}
} //得到最小生成树之普利姆(Prim)算法
public void MiniSpanTree_Prim(Graph G){
int min, i, j, k;
int [] adjvex = new int[G.verticts]; //保存相关顶点下表
int [] lowcost = new int[G.verticts]; //保存相关顶点间边的权值
lowcost[0] = 0; //初始化第一个权值为0 ,即vex[0]已经加入到生成树
adjvex[0] = 0; //初始化第一个顶点下标为0
for(i = 1;i < G.verticts;i++){
lowcost[i] = G.arc[0][i]; //将vex[0]顶点与之有边的权值存入数组
adjvex[i] = 0; //初始化都为vex[0]的下标
} for(i = 1;i < G.verticts;i++){
min = MAX; //初始化最小权值为MAX:65535
j = 1;
k = 0;
//循环所有顶点
while(j < G.verticts){
if(lowcost[j] != 0 && lowcost[j] < min){ //如果权值不为0,而且小于最小值
min = lowcost[j];
k = j;
}
j++;
} System.out.println("("+k+", "+adjvex[k]+")"+" 权长:"+G.arc[adjvex[k]][k]); //打印当前顶点边中权值最小的边
lowcost[k] = 0; //将当前顶点的权值设为0,表示此顶点已将完成任务
for(j = 1;j < G.verticts;j++){ //循环所有顶点
if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j]){ //如果下标为k的顶点各边权值小于此前这些顶点未被加入生成权值
lowcost[j] = G.arc[k][j]; //将较小的权值存入lowcost
adjvex[j] = k; //将下标为k的顶点存入adjvex
}
}
}
} //得到最小生成树之克鲁斯卡尔(Kruskal)算法
//得到边集数组并按权由大到小排序 这一步是利用Edge类来实现的
//注意 此函书还有错误,有时候会输出6条边,尚待解决(MinispanTree_kruskal在寻找的过程中不可能形成环路,所以不可能多一条边)
//上述错误已经解决,有两个地方出来问题,第一:Edge的begin必须小于end,否则在Find函数中判断将出现错误,因为如果end小于begin的话,end有可能
//出现等于0的情况,第二:循环每一条边时,i应该小于G.sides;而我之前写成了i<G.verticts
public void MiniSpanTree_Kruskal(Graph G){
Edge edge = new Edge();
edge.Edge_1(G);
//edge.PrintEdge();
int i, n, m;
int num = 0; //记录找到了多少条边
int parent[] = new int[G.verticts]; //定义一个数组用来判断边与边是否形成回路
for( i = 0;i < G.verticts;i++){
parent[i] = 0; //初始化数组为-1
}
for(i = 0;i < G.sides;i++){ // 循环每一条边,15为顶点
n = Find(parent,edge.edge[i].begin);
m = Find(parent,edge.edge[i].end);
if(n != m ){ //n不等于m 说明边与边没有形成环路
parent[n] = m;//将此边的尾节点放入下标为起点的parent中
System.out.println("("+edge.edge[i].begin+","+edge.edge[i].end+")"+" 权长:"+edge.edge[i].weight);
num++;
//for(int j = 0;j < G.verticts;j++){
//System.out.print(" !!!!"+parent[j]); //初始化数组为0
//}
}
if(num >= G.verticts) break; //如果找到了(顶点数-1)条边,并且没有构成回路,就已经完成任务了,不用再找了, }
}
public int Find(int[] parent,int f){ //查找连线顶点的尾部下表
while(parent[f] > 0){
f = parent[f];
}
return f;
} //测试函数
public static void main(String[] args){
Graph graph = new Graph(6); //创建一个顶点个数为6的图
graph.addGraph();
System.out.println("将图以二维矩阵的方式输出");
graph.printGraph();
System.out.println("深度优先搜索结果");
graph.DFSTraverse(graph);
System.out.println();
System.out.print("广度优先搜索结果");
System.out.println();
graph.BFS(graph);
System.out.println();
System.out.println("最小生成树之普利姆算法Prim ");
graph.MiniSpanTree_Prim(graph);
System.out.println();
System.out.println("最小生成树之克鲁斯卡尔算法Kruskal ");
graph.MiniSpanTree_Kruskal(graph); }
} //Edge类 利用深度优先遍历得到树的所有路径以及这些路径的权值,并根据权值的大小进行从小到大排序
class Edge{
public int begin; //这两个顶点的开始顶点
public int end; //这两个顶点的结束顶点
public int weight; //两个顶点之间的权值
Edge edge[] = new Edge[15]; //edge数组 图的边数没有传入,计算最大值
Edge(){}
public void Edge_1(Graph G){
DFSTraverse_1(G); //得到edge数组
sortEdge(); //对dege进行排序
} public void SetEdge(int begin,int end,int weight){
this.begin = begin;
this.end = end;
this.weight = weight; }
int k=0; //用于数组赋值是计数
//利用深度优先遍历得到edge数组
public void DFS_1(Graph G,int i,boolean[] visited){
int j; visited[i] = true;
//System.out.print(G.arc[i][i+1]+" "); //打印顶点的值
for(j = 0;j < G.verticts;j++){
if(G.arc[i][j]>0 && !visited[j]){
//System.out.print(G.arc[i][j]+" ");
DFS_1(G,j,visited); //对访问的邻接顶点递归调用
}
if(G.arc[i][j] > 0 && i > j){
this.edge[this.k] = new Edge();
edge[this.k].SetEdge(j,i,G.arc[i][j]);
this.k++;
} } }
public void DFSTraverse_1(Graph G){
boolean[] visited = new boolean[G.verticts];
for(int i = 0;i < G.verticts;i++){
visited[i] = false; //初始状态所有顶点都是未访问过的状态
}
for(int i = 0;i < G.verticts;i++){
if(!visited[i])
DFS_1(G,i,visited); //对未访问过的顶点调用DFS 如果是连通图,则只会执行一次
}
}
//对得到的数组进行排序
public void sortEdge(){
Edge newEdge = new Edge();
newEdge.edge[0] = new Edge();
for(int i = 0;i < this.edge.length;i++){
for(int j = i;j < this.edge.length;j++){
if(this.edge[i].weight > this.edge[j].weight){
newEdge.edge[0] = this.edge[i];
this.edge[i] = this.edge[j];
this.edge[j] = newEdge.edge[0];
}
}
}
}
//输出Edge数组,用以测试Edge是否创建、赋值成功
public void PrintEdge(){
for(int i = 0; i < this.edge.length;i++){
System.out.println("数组"+i+": "+this.edge[i].begin+" "+this.edge[i].end+" "+this.edge[i].weight);
}
}
}

java 数据结构 图的更多相关文章

  1. java数据结构----图

    1.图:.在计算机程序设计中,图是最常用的数据结构之一.对于存储一般的数据问题,一般用不到图.但对于某些(特别是一些有趣的问题),图是必不可少的.图是一种与树有些相像的数据结构,从数学意义上来讲,树是 ...

  2. Java数据结构——图的DFS和BFS

    1.图的DFS: 即Breadth First Search,深度优先搜索是从起始顶点开始,递归访问其所有邻近节点,比如A节点是其第一个邻近节点,而B节点又是A的一个邻近节点,则DFS访问A节点后再访 ...

  3. Java数据结构——图的基本理论及简单实现

    1. 图的定义图(graph)是由一些点(vertex)和这些点之间的连线(edge)所组成的:其中,点通常被成为"顶点(vertex)",而点与点之间的连线则被成为"边 ...

  4. Java数据结构——图

    点 //类名:Vertex //属性: //方法: class Vertex{ public char label; //点的名称,如A public boolean wasVisited; publ ...

  5. Java数据结构和算法(四)赫夫曼树

    Java数据结构和算法(四)赫夫曼树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 赫夫曼树又称为最优二叉树,赫夫曼树的一个 ...

  6. 数据结构--图 的JAVA实现(上)

    1,摘要: 本系列文章主要学习如何使用JAVA语言以邻接表的方式实现了数据结构---图(Graph),这是第一篇文章,学习如何用JAVA来表示图的顶点.从数据的表示方法来说,有二种表示图的方式:一种是 ...

  7. 数据结构--图 的JAVA实现(下)

    在上一篇文章中记录了如何实现图的邻接表.本文借助上一篇文章实现的邻接表来表示一个有向无环图. 1,概述 图的实现与邻接表的实现最大的不同就是,图的实现需要定义一个数据结构来存储所有的顶点以及能够对图进 ...

  8. Java数据结构之队列的实现以及队列的应用之----简单生产者消费者应用

    Java数据结构之---Queue队列 队列(简称作队,Queue)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在 ...

  9. 【Java数据结构学习笔记之二】Java数据结构与算法之栈(Stack)实现

      本篇是java数据结构与算法的第2篇,从本篇开始我们将来了解栈的设计与实现,以下是本篇的相关知识点: 栈的抽象数据类型 顺序栈的设计与实现 链式栈的设计与实现 栈的应用 栈的抽象数据类型   栈是 ...

随机推荐

  1. .Net Core Identity外面使用Cookie中间件

    1.在 app.UseMvc 前面加上app.UseCookieAuthentication app.UseCookieAuthentication(new CookieAuthenticationO ...

  2. JavaScript 部分对象方法记叙 ing...

  3. ckeditor 插件

    dialog 下面 建立一个 插件.js CKEDITOR.dialog.add("about", function (a) { var aaa = "<form& ...

  4. Openjudge-计算概论(A)-晶晶赴约会

    描述 晶晶的朋友贝贝约晶晶下周一起去看展览,但晶晶每周的1.3.5有课必须上课,请帮晶晶判断她能否接受贝贝的邀请,如果能输出YES:如果不能则输出NO. 输入输入有一行,贝贝邀请晶晶去看展览的日期,用 ...

  5. git提交失败

    git push "提示:更新被拒绝,因为您当前分支的最新提交落后于其对应的远程分支."的解决办法 本地已存在项目,需要先获取远端更新并与本地合并,再git push具体操作如下: ...

  6. NYOJ-71 独木舟上的旅行 (贪心)

    独木舟上的旅行 时间限制:3000 ms  |  内存限制:65535 KB 难度:2   描述 进行一次独木舟的旅行活动,独木舟可以在港口租到,并且之间没有区别.一条独木舟最多只能乘坐两个人,且乘客 ...

  7. 网络传输中的三张表,MAC地址表、ARP缓存表以及路由表

    一:MAC地址表详解 说到MAC地址表,就不得不说一下交换机的工作原理了,因为交换机是根据MAC地址表转发数据帧的.在交换机中有一张记录着局域网主机MAC地址与交换机接口的对应关系的表,交换机就是根据 ...

  8. nmon 安装

    安装: mkdir /usr/local/nmon cd /usr/local/nmon wget http://sourceforge.net/projects/nmon/files/nmon_li ...

  9. Python编码介绍——encode和decode

    在 python 源代码文件中,如果你有用到非ASCII字符,则需要在文件头部进行字符编码的声明,声明如下: # code: UTF-8 因为python 只检查 #.coding 和编码字符串,所以 ...

  10. linux下修改MAC地址方法

    一.修改MAC地址方法linux环境下:需要用 #ifconfig eth0 down 先把网卡禁用 再用ifconfig eth0 hw ether 1234567890ab 这样就可以改成功了要想 ...