java 数据结构 图

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

  图的定义

         图是由顶点的有穷非空集合和顶点之间边的集合组成,通过表示为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)都在一个子图里面,不满足条件,至此已经找到最小生成树的

           所有边。

 

 

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

  

  1 /**
  2  * java数据结构无向图的实现
  3  * 2016/4/29
  4  */
  5 package cn.Link;
  6 
  7 import java.util.ArrayList;
  8 import java.util.LinkedList;
  9 import java.util.Queue;
 10 public class Graph {
 11             
 12     final static int MAX = 65535;  //两个定点之间没有路径时的长度
 13     int verticts; //顶点数
 14     int sides;      //顶点为verticts的连通图的边数值 
 15     int[][] arc;//存储图的二维数组 
 16     String[] vex;
 17     Graph(int verticts){
 18         this.verticts = verticts;
 19         this.sides = 0;
 20         for(int i = verticts-1;i>0;i--){
 21             this.sides +=i;     //获得连通图的边数值
 22         }
 23         this.arc = new int[verticts][verticts];
 24         this.vex = new String[verticts];
 25         //初始化一个有向图,所有边设为最大权值(即不可能达到的值)
 26         for(int i = 0; i < verticts;i++){
 27             for(int j = 0; j < verticts;j++){
 28                 if(i!=j){
 29                    this.arc[i][j] = MAX;
 30                 }else{
 31                     this.arc[i][j]=0;
 32                 }
 33                 
 34             }
 35         }
 36     }
 37     
 38     //假设有这样一个图
 39     public void addGraph(){
 40         //顶点数据
 41         this.vex[0] = "beijing";
 42         this.vex[1] = "shanghai";
 43         this.vex[2] = "tianjing";
 44         this.vex[3] = "chengdu";
 45         this.vex[4] = "changsha";
 46         this.vex[5] = "chongqing";
 47 
 48         //边的权值
 49         for(int i = 1; i < verticts;i++){
 50             for(int j = 0; j < i;j++){
 51                 int n = (int)(Math.random()*100);    //随机生成权值
 52                 if(n > 0){
 53                 this.arc[i][j]=this.arc[j][i] = n;
 54                 }else if(n == 0){
 55                     this.arc[i][j]=this.arc[j][i] = MAX;
 56                 }
 57                 
 58             }
 59         }
 60     }
 61 
 62     //利用图的二维数组输出
 63     public void printGraph(){
 64         for(int i = 0; i < verticts;i++){
 65             //输出第一行的名称
 66             if(i == 0){
 67                 System.out.print("*         ");
 68                 for(int x=0;x<verticts;x++){
 69                     System.out.print(this.vex[x]+"  ");
 70                 }
 71                 System.out.println();
 72                 System.out.println("         ==============================================================");
 73             }
 74             //给每行前面输出地址
 75                 System.out.print(this.vex[i]+"     ");
 76             for(int j = 0; j < verticts;j++){
 77                 System.out.print(this.arc[i][j]+"        ");
 78             }
 79             System.out.println();
 80             System.out.println();
 81         }
 82     }
 83 
 84     //深度优先遍历输出
 85     public void DFS(Graph G,int i,boolean[] visited){
 86         int j;
 87         visited[i] = true;
 88         System.out.print(G.vex[i]+"  ");   //打印顶点的值
 89         for(j = 0;j < G.verticts;j++){
 90             if(G.arc[i][j]>0 && !visited[j]){
 91                 DFS(G,j,visited);       //对访问的邻接顶点递归调用
 92             }
 93         }
 94         
 95     }
 96     public void DFSTraverse(Graph  G){
 97         boolean[] visited = new boolean[G.verticts];
 98         for(int i = 0;i < G.verticts;i++){
 99             visited[i] = false;         //初始状态所有顶点都是未访问过的状态
100         }
101         for(int i = 0;i < G.verticts;i++){
102             if(!visited[i])
103                 DFS(G,i,visited);       //对未访问过的顶点调用DFS  如果是连通图,则只会执行一次
104         }
105     }
106 
107 
108 
109     //广度优先遍历输出
110     public void BFS(Graph G){
111         int i, j;
112         Queue<Integer> Q = new LinkedList<Integer>();
113         boolean[] visited = new boolean[G.verticts];
114         for(i = 0;i < G.verticts;i++){
115             visited[i] = false;
116         }
117         
118         for(i = 0; i < G.verticts;i++){       //对每一个顶点都进行循环
119             if(!visited[i]){
120                 visited[i] = true;
121                 System.out.print("##"+G.vex[i]+"  ");
122                 Q.offer(i);
123                 while(Q != null){
124                     if(Q.peek() != null){
125                     i = Q.poll(); //将队首元素赋值给i  然后出队列
126                     }else{return ;}
127                     for(j = 0;j < G.verticts;j++){
128                         //判断其他顶点与当前顶点存在边但未被访问过
129                         if(G.arc[i][j] > 0 && !visited[j]){
130                             visited[j] = true;
131                             System.out.print("##"+G.vex[j]+"  ");
132                             Q.offer(j);
133                         }
134                     }
135                 }
136             }
137         }
138     }
139 
140     //得到最小生成树之普利姆(Prim)算法
141     public void MiniSpanTree_Prim(Graph G){
142         int min, i, j, k;
143         int [] adjvex = new int[G.verticts];           //保存相关顶点下表
144         int [] lowcost = new int[G.verticts];          //保存相关顶点间边的权值
145         lowcost[0] = 0;                         //初始化第一个权值为0 ,即vex[0]已经加入到生成树
146         adjvex[0] = 0;                          //初始化第一个顶点下标为0
147         for(i = 1;i < G.verticts;i++){
148             lowcost[i] = G.arc[0][i];           //将vex[0]顶点与之有边的权值存入数组
149             adjvex[i] = 0;                      //初始化都为vex[0]的下标
150         }
151 
152         for(i = 1;i < G.verticts;i++){
153             min =  MAX;                     //初始化最小权值为MAX:65535  
154             j = 1;
155             k = 0;
156             //循环所有顶点
157             while(j < G.verticts){
158                 if(lowcost[j] != 0 && lowcost[j] < min){    //如果权值不为0,而且小于最小值
159                     min = lowcost[j];
160                     k = j;
161                 }
162                 j++;
163             }
164 
165             System.out.println("("+k+", "+adjvex[k]+")"+"   权长:"+G.arc[adjvex[k]][k]);        //打印当前顶点边中权值最小的边
166             lowcost[k] = 0;                 //将当前顶点的权值设为0,表示此顶点已将完成任务
167             for(j = 1;j < G.verticts;j++){  //循环所有顶点
168                 if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j]){       //如果下标为k的顶点各边权值小于此前这些顶点未被加入生成权值
169                     lowcost[j] = G.arc[k][j];           //将较小的权值存入lowcost
170                     adjvex[j] = k;          //将下标为k的顶点存入adjvex
171                 }
172             }
173         }
174     }
175 
176 
177     //得到最小生成树之克鲁斯卡尔(Kruskal)算法
178     //得到边集数组并按权由大到小排序 这一步是利用Edge类来实现的
179     //注意 此函书还有错误,有时候会输出6条边,尚待解决(MinispanTree_kruskal在寻找的过程中不可能形成环路,所以不可能多一条边)
180     //上述错误已经解决,有两个地方出来问题,第一:Edge的begin必须小于end,否则在Find函数中判断将出现错误,因为如果end小于begin的话,end有可能
181     //出现等于0的情况,第二:循环每一条边时,i应该小于G.sides;而我之前写成了i<G.verticts
182     public void MiniSpanTree_Kruskal(Graph G){
183         Edge edge = new Edge(); 
184         edge.Edge_1(G);
185         //edge.PrintEdge();
186         int i, n, m;
187         int num = 0;  //记录找到了多少条边
188         int parent[] = new int[G.verticts];   //定义一个数组用来判断边与边是否形成回路
189         for( i = 0;i < G.verticts;i++){
190             parent[i] = 0;      //初始化数组为-1
191         }
192         for(i = 0;i < G.sides;i++){        // 循环每一条边,15为顶点
193             n = Find(parent,edge.edge[i].begin);
194             m = Find(parent,edge.edge[i].end);
195             if(n != m ){         //n不等于m  说明边与边没有形成环路
196                 parent[n] = m;//将此边的尾节点放入下标为起点的parent中
197                 System.out.println("("+edge.edge[i].begin+","+edge.edge[i].end+")"+"  权长:"+edge.edge[i].weight);
198                 num++;
199                 //for(int j = 0;j < G.verticts;j++){
200                     //System.out.print("  !!!!"+parent[j]);      //初始化数组为0
201                  //}
202             }
203             if(num >= G.verticts)  break;     //如果找到了(顶点数-1)条边,并且没有构成回路,就已经完成任务了,不用再找了,
204             
205         }
206     }
207     public int Find(int[] parent,int f){        //查找连线顶点的尾部下表
208         while(parent[f] > 0){
209             f = parent[f];
210         }
211         return f;
212     }
213 
214 
215     //测试函数
216     public static void main(String[] args){
217         Graph graph = new Graph(6);  //创建一个顶点个数为6的图
218         graph.addGraph();
219         System.out.println("将图以二维矩阵的方式输出");
220         graph.printGraph(); 
221         System.out.println("深度优先搜索结果");
222         graph.DFSTraverse(graph); 
223         System.out.println();
224         System.out.print("广度优先搜索结果");
225         System.out.println();
226         graph.BFS(graph);
227         System.out.println();
228         System.out.println("最小生成树之普利姆算法Prim  ");
229         graph.MiniSpanTree_Prim(graph);
230         System.out.println();
231         System.out.println("最小生成树之克鲁斯卡尔算法Kruskal   ");
232         graph.MiniSpanTree_Kruskal(graph);
233 
234     }
235 }
236 
237 
238 
239 
240 //Edge类  利用深度优先遍历得到树的所有路径以及这些路径的权值,并根据权值的大小进行从小到大排序
241 class Edge{
242     public int begin;           //这两个顶点的开始顶点
243     public int end;             //这两个顶点的结束顶点
244     public int weight;          //两个顶点之间的权值
245     Edge edge[] = new Edge[15]; //edge数组  图的边数没有传入,计算最大值
246     Edge(){}
247     public void Edge_1(Graph G){
248         DFSTraverse_1(G);       //得到edge数组
249         sortEdge();             //对dege进行排序
250     }
251     
252     public void SetEdge(int begin,int end,int weight){
253         this.begin = begin;
254         this.end = end;
255         this.weight = weight;
256         
257     }
258     int k=0;        //用于数组赋值是计数
259     //利用深度优先遍历得到edge数组
260     public void  DFS_1(Graph G,int i,boolean[] visited){
261         int j;
262         
263         visited[i] = true;
264         //System.out.print(G.arc[i][i+1]+"  ");   //打印顶点的值
265         for(j = 0;j < G.verticts;j++){
266             if(G.arc[i][j]>0 && !visited[j]){
267                 //System.out.print(G.arc[i][j]+"  ");
268                 DFS_1(G,j,visited);       //对访问的邻接顶点递归调用
269             }
270             if(G.arc[i][j] > 0  && i > j){
271                 this.edge[this.k] = new Edge();
272                 edge[this.k].SetEdge(j,i,G.arc[i][j]);
273                 this.k++;
274             }
275             
276         }
277         
278     }
279     public void DFSTraverse_1(Graph  G){
280         boolean[] visited = new boolean[G.verticts];
281         for(int i = 0;i < G.verticts;i++){
282             visited[i] = false;         //初始状态所有顶点都是未访问过的状态
283         }
284         for(int i = 0;i < G.verticts;i++){
285             if(!visited[i])
286                 DFS_1(G,i,visited);       //对未访问过的顶点调用DFS  如果是连通图,则只会执行一次
287         }
288     }
289     //对得到的数组进行排序
290     public void sortEdge(){
291         Edge newEdge = new Edge();
292         newEdge.edge[0] = new  Edge();
293         for(int i = 0;i < this.edge.length;i++){
294             for(int j =  i;j < this.edge.length;j++){
295                 if(this.edge[i].weight > this.edge[j].weight){
296                     newEdge.edge[0] = this.edge[i];
297                     this.edge[i] = this.edge[j];
298                     this.edge[j] = newEdge.edge[0];
299                 }
300             }
301         }
302     }
303     //输出Edge数组,用以测试Edge是否创建、赋值成功
304     public void PrintEdge(){
305         for(int i = 0; i < this.edge.length;i++){
306             System.out.println("数组"+i+":    "+this.edge[i].begin+"  "+this.edge[i].end+"  "+this.edge[i].weight);
307         }
308     }
309 }

 

posted on 2016-04-30 22:04  snail-lb  阅读(3841)  评论(1编辑  收藏  举报