• 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 }
  • 相关阅读:
    机器视觉
    视觉感知
    计算机视觉
    模板匹配
    Kinect
    手势识别
    三维重建
    单元化理解
    [面试经] Java
    [面试经]Java中final、finally、finalize有什么不同?
  • 原文地址:https://www.cnblogs.com/snail-lb/p/5449557.html
Copyright © 2020-2023  润新知