数据结构与算法笔记(六)

蓝咒 提交于 2021-01-04 08:35:40

一、图

1.什么是图

    由之前的学习,我们知道,非线性结构一般分为两类,树(一对多关系)和图(多对多关系);树前篇已经介绍过了,现在我们来看看图。生活中,当我们要去某个陌生的地方时,我们通常会使用地图导航来帮助我们规划路线,那问题来了,地图上这么多路,这么多地址,是怎么存储的,数据结构又是什么样的,各个地址之间的关系是如何体现的呢?

 

    答案是采用数据结构是图,图的存储结构一般也是两种:链式存储和顺序存储。

2.图的定义

    图是一种多对多的网状数据结构,由非空的顶点集合和一个描述顶点之间关系的结合组成。一般表示为G=(V,E);V是数据元素的集合,其中数据元素通常称为顶点。E则是两个顶点之间的关系集合,一般称为边集,每一条边可用<u,v>(u和v分别为起点和终点)来表示。

    若有任意<u,v>属于E,且u到v之间的连线是有方向的,则成这样的图为有向图。

    若有任意<u,v>属于E,u到v之间的连线是没有方向的,即<u,v>=<v,u>,这种图称为无向图。

 

    无向图实际也可看做有向图,即双向图。

    但实际中运用更多的一般是加权图/带权图。因为单单知道顶点之间有关联是满足不了实际运用的,因此,通常都将每一条边与一个实数相关联,这个数就称为权。权值可以表示的信息很多,比如顶点之间的距离、花费的时间或其他量等信息。

 

3.图的存储

    与其他数据结构相似,图的存储也分为顺序存储和链式存储。

    顺序存储一般用邻接矩阵来表示,即使用二维数组来表示。

    邻接矩阵的基本表示方法:顶点之间有连系的用1表示(有权值则使用权值表示),顶点之间(包括顶点本身)没有连系的则用0或∞来表示。则可知:

  • 无向图的邻接矩阵必然是关于对角线对称的(如上图所示)。

  • 对角线必然都是0/∞。

    

    链式存储则一般用邻接表,即是用一维数组加链表来表示。

 

    其中,数组中顺序存储着图中的所有顶点元素,数组的结构是数据域(存放数据)+指针域(存放有连系数据的链表地址),下标表示顶点在数组中的位置。而链表的存储着与所有与当前数组元素相关连的顶点信息,链表的结构是数组索引域(关连结点的数组索引)+指针域(链表下个结点的地址)+权值域(图有权值则有,无权值则没有)。


 

/**

* 图的邻接表表示

* Created by bzhang on 2019/3/1.

*/

public class MyGraph {

      private DataArr[] data;       //存储邻接表


      public DataArr[] getData() {

            return data;

      }


      public void setData(DataArr[] data) {

            this.data = data;

      }

}

//图邻接表的数组部分定义

class DataArr{

      private Object data;    //存储数据元素

      private GNode start;    //关连顶点的链表起点


      public DataArr(Object data, GNode start) {

            this.data = data;

            this.start = start;

      }


      public GNode getStart() {

            return start;

      }


      public void setStart(GNode start) {

            this.start = start;

      }


      public Object getData() {

            return data;

      }


      public void setData(Object data) {

            this.data = data;

      }

}



class GNode {

      private int index;      //关连在数组中的下标索引


      private int weight;     //权值


      private GNode next;     //下个结点



      public GNode(int index, int weight, GNode next) {

            this.index = index;

            this.weight = weight;

            this.next = next;

      }


      public int getIndex() {

            return index;

      }


      public void setIndex(int index) {

            this.index = index;

      }


      public int getWeight() {

            return weight;

      }


      public void setWeight(int weight) {

            this.weight = weight;

      }


      public GNode getNext() {

            return next;

      }


      public void setNext(GNode next) {

            this.next = next;

      }
}

4.图的遍历

    图的遍历是从图的某个顶点出发按照某种方法对图中所有顶点进行访问,并且只访问一次。遍历是解决图其他问题(拓扑排序,连通性,最短距离等)的基础。图的遍历方法一般分为两种:

  • 深度优先遍历(DFS):类似二叉树的先序遍历,即一条路走到黑(不到黄河心不死)方式。(实现可使用递归或借助栈)

  • 广度优先遍历(BFS):类似二叉树的层次遍历,同层都遍历过才在继续深入(可借助队列实现)。

    上图中,无向图的遍历:

    DFS:v1-v2-v4-v8-v5-v3-v6-v7。

    BFS:v1-v2-v3-v4-v5-v6-v7-v8。

5.图的应用

    图在许多领域都有广泛的应用,特别是带权图;在交通出行领域,通信领域应用频繁。这里权值通常表示顶点之间的通信成本或者交通费用/路程等。这类问题通常都可以归纳为最短路径问题:

    例如:求下图中A到F的最短路径?

 

  • 求A到F的中转次数最少的路径(权值无关)

    • 此时应该使用广度优先(BFS)方法来遍历,实现方式使用队列。步骤如下

 

 

  • 求A到F权值最小的最短路径

    • 使用迪克斯特拉算法

start数组

A

 

 

 

 

temp数组

B

C

D

E

F

 

 

 

 

 

 

 

前一个顶点

当前顶点

权值

 

 

 

 

B

-1

 

 

 

 

C

-1

 

 

 

 

D

-1

 

 

 

 

E

-1

 

 

 

 

F

-1

 

 

 

 

 

 

 

 

    1.新建两个数组start和temp,start中初始只存放起点A,其余顶点全都放在temp中;并建立如上表格。

 

start数组

A

C

 

 

 

temp数组

B

 

D

E

F

 

 

 

 

 

 

 

前一个顶点

当前顶点

权值

 

 

 

A

B

6

 

 

 

A

C

3

 

 

 

 

D

-1

 

 

 

 

E

-1

 

 

 

 

F

-1

 

 

 

 

 

 

 

 

 

    2.将起点A相邻的顶点的权值更新到表中,并将当前表中权值最小(初始值-1不计)的顶点有temp中转移到start中,即C移入start中。

 

start数组

A

C

 

 

 

temp数组

B

 

D

E

F

 

 

 

 

 

 

 

前一个顶点

当前顶点

权值

 

 

 

A

B

6

2

2+3

 

A

C

3

 

 

 

 

D

-1

3

3+3

 

 

E

-1

4

3+4

 

 

F

-1

 

 

 

 

 

 

 

 

    3.以C为起点寻找temp中与其相邻的顶点:B,D且权值为2,3;由上一步可知A到C的权值为3,则此时A经C到B和D的权值分别为5和6。

 

start数组

A

C

 

 

 

temp数组

B

 

D

E

F

 

 

 

 

 

 

 

前一个顶点

当前顶点

权值

 

 

 

C

B

5

 

 

 

A

C

3

 

 

 

C

D

6

 

 

 

C

E

7

 

 

 

 

F

-1

 

 

 

 

 

 

 

 

    4.将A经由C到当前顶点的权值与顶点的原本权值相比较,若比原值小(无权值也更新)则跟新权值,并记录顶点的前一个顶点为C,若大于则不处理。

 

start数组

A

C

B

 

 

temp数组

 

 

D

E

F

 

 

 

 

 

 

 

前一个顶点

当前顶点

权值

 

 

 

C

B

5

 

 

 

A

C

3

 

 

 

C

D

6

5

5+5

 

C

E

7

 

 

 

 

F

-1

 

 

 

 

 

 

 

 

 

    5.再将temp中当前权值最小的顶点B移入start中,并计算其与temp中其他相邻顶点的权值,即B与D之间的权值为5,而D经由B和C到A的权值为5+5=10;相较于D经由C到A的权值6大,故不更新权值。

 

start数组

A

C

B

D

 

temp数组

 

 

 

E

F

 

 

 

 

 

 

 

前一个顶点

当前顶点

权值

 

 

 

C

B

5

 

 

 

A

C

3

 

 

 

C

D

6

 

 

 

C

E

7

2

8

 

 

F

-1

3

9

 

 

 

 

 

 

    6.再将temp中当前权值最小的D移入start中,并计算其与temp中相邻元素E,F的权值,分别为2,3;则可得到E,F经由D,最终到A的路径最小权值为8,9,其中E经由D到A的权值大于E经由C到A的权值(8>7)。因此不更新,仅更新F。

 

start数组

A

C

B

D

E

temp数组

 

 

 

 

F

 

 

 

 

 

 

 

前一个顶点

当前顶点

权值

 

 

 

C

B

5

 

 

 

A

C

3

 

 

 

C

D

6

 

 

 

C

E

7

 

 

 

D

F

9

5

7+5

 

 

 

 

 

 

 

    7.在从temp中将权值最小的E移入start中,并计算其余temp中相邻定点F的权值;则可得到F经由E最终到A的最小权值是12,大于F经由D到A的权值9,因此不更新。最终得到如下表:

 

 

前一个顶点

当前顶点

权值

 

C

B

5

 

A

C

3

 

C

D

6

 

C

E

7

 

D

F

9

    由此表可得到A为起点到任意顶点的最小权值。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!