数据结构之--图

1.为什么会有图?

线性表和树,线性表局限于一个直接前驱和一个直接后继的关系,树也只能有一个直接前驱也就是父节点。当我们需要表示多对多的关系时,这里我们就用到了图。

2.图的介绍:

在计算机科学中,一个图就是一些顶点的集合,这些顶点通过一系列边结对(连接)。顶点用圆圈表示,边就是这些圆圈之间的连线。顶点之间通过边连接。

顶点有时也称为节点或者交点,边有时也称为链接。

一个图可以表示一个社交网络,每一个人就是一个顶点,互相认识的人之间通过边联系。

图有各种形状和大小。边可以有权重(weight),即每一条边会被分配一个正数或者负数值。考虑一个代表航线的图。各个城市就是顶点,航线就是边。那么边的权重可以是飞行时间,或者机票价格。

边可以是有方向的。在上面提到的例子中,边是没有方向的。例如,如果 A认识 B,那么 B 也就认识 A。相反,有方向的边意味着是单方面的关系。一条从顶点 X 到 顶点 Y 的边是将 X 联向 Y,不是将 Y 联向 X。

3.为什么要使用图

也许你心里想着,有什么大不了的。

如果你有一个编程问题可以通过顶点和边表示出来,那么你就可以将你的问题用图画出来,然后使用著名的图算法(比如广度优先搜索 或者 深度优先搜索)来找到解决方案。

例如,假设你有一系列任务需要完成,但是有的任务必须等待其他任务完成后才可以开始。你可以通过非循环有向图来建立模型:

每一个顶点代表一个任务。两个任务之间的边表示目的任务必须等到源任务完成后才可以开始。比如,在任务B和任务D都完成之前,任务C不可以开始。在任务A完成之前,任务B和D都不能开始。
现在这个问题就通过图描述清楚了,你可以使用深度优先搜索算法来执行执行拓扑排序。这样就可以将所有的任务排入最优的执行顺序,保证等待任务完成的时间最小化。(这里可能的顺序之一是:A, B, D, E, C, F, G, H, I, J, K)

4.图的表示方式

图就是一堆顶点和边对象而已,但是怎么在代码中来描述呢?

图的表示方式有两种:二维数组表示(邻接矩阵);链表表示(邻接列表)。

5.邻接表:

邻接列表:在邻接列表实现中,每一个顶点会存储一个从它这里开始的边的列表。比如,如果顶点A 有一条边到B、C和D,那么A的列表中会有3条边。

邻接列表只描述了指向外部的边。A 有一条边到B,但是B没有边到A,所以 A没有出现在B的邻接列表中。查找两个顶点之间的边或者权重会比较费时,因为遍历邻接列表直到找到为止。

6.邻接矩阵

在邻接矩阵实现中,由行和列都表示顶点,由两个顶点所决定的矩阵对应元素表示这里两个顶点是否相连、如果相连这个值表示的是相连边的权重。例如,如果从顶点A到顶点B有一条权重为 5.6 的边,那么矩阵中第A行第B列的位置的元素值应该是5.6 

 

往这个图中添加顶点的成本非常昂贵,因为新的矩阵结果必须重新按照新的行/列创建,然后将已有的数据复制到新的矩阵中。

所以使用哪一个呢?大多数时候,选择邻接列表是正确的。下面是两种实现方法更详细的比较。

7.案例:

要求: 代码实现如下图结构

 代码实现:

package graph;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;

/**
 * @author WuChenGuang
 */
public class Graph {

    /**
     * 顶点元素
     */
    private final ArrayList<String> vertexList;

    /**
     * 邻接矩阵
     */
    private final int[][] edges;

    private int edgesNum;

    private final boolean[] isSelected;

    public Graph(int len) {
        this.vertexList = new ArrayList<>(len);
        this.edges = new int[len][len];
        this.isSelected = new boolean[len];
    }

    /**
     * 插入顶点方法
     */
    public void insertVertex(String vertex) {
        this.vertexList.add(vertex);
    }

    /**
     * 添加图边
     */
    public void insertEdges(int x, int y, int w) {
        edges[x][y] = w;
        edges[y][x] = w;

        // 添加边的数量
        edgesNum++;
    }

    /**
     * 返回顶点个数
     */
    public int getVertexSize() {
        return this.vertexList.size();
    }

    /**
     * 返回边的数量
     */
    public int getEdgesSize() {
        return this.edgesNum;
    }

    /**
     * 获取顶点权值   x,y坐标下权值
     */
    public int getWeight(int x, int y) {
        return this.edges[x][y];
    }

    /**
     * 输出邻接矩阵图案
     */
    public void showList() {
        for (int[] arr : edges) {
            System.out.println(Arrays.toString(arr));
        }
    }

    /**
     * 给定一个索引顶点位置,查找当前索引的第一个邻接点
     * 如果存在返回索引位置
     * 如果不存在返回-1
     */
    public int getFirstVertex(int index) {
        for (int i = 0; i < vertexList.size(); i++) {
            if (edges[index][i] > 0) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 根据给定的坐标,获取下一个邻接顶点
     */
    public int getNextVertex(int x, int y) {
        for (int i = y + 1; i < vertexList.size(); i++) {
            if (edges[x][i] > 0) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 访问的顶点
     */
    public String getValueIndex(int i) {
        return this.vertexList.get(i);
    }

    /**
     * 深度优先算法
     */
    public void dfs(boolean[] isSelected, int i) {
        System.out.print(getValueIndex(i));
        // 表示已经被访问过
        isSelected[i] = true;
        // 获取第一个邻接顶点
        int w = getFirstVertex(i);

        while (w != -1) {
            // 存在第一个邻接点
            if (!isSelected[w]) {
                dfs(isSelected, w);
            }
            // 被访问过
            w = getNextVertex(i, w);
        }
    }

    public void dfs() {
        for (int i = 0; i < getVertexSize(); i++) {
            if (!isSelected[i]) {
                dfs(isSelected, i);
            }
        }
    }


    /**
     * 广度优先算法
     */
    public void bfs(boolean[] isSelected, int i) {
        // 头取出结点
        int u;
        // 第一个邻接点
        int w;

        // 创建队列,queue
        LinkedList<Integer> queue = new LinkedList<>();
        System.out.print(getValueIndex(i));
        isSelected[i] = true;
        queue.addLast(i);
        while (!queue.isEmpty()) {
            u = queue.removeFirst();
            w = getFirstVertex(u);
            while (w != -1) {
                if (!isSelected[w]) {
                    System.out.print(getValueIndex(w));
                    isSelected[w] = true;
                    queue.addLast(w);
                }
                w = getNextVertex(u, w);
            }
        }
    }

    public void bfs() {
        for (int i = 0; i < getEdgesSize(); i++) {
            if (!isSelected[i]) {
                bfs(isSelected, i);
            }
        }
    }
}
package graph;

/**
 * @author WuChenGuang
 */
public class Test {
    public static void main(String[] args) {

        Graph graph = new Graph(5);
        String[] vertexes = {"A", "B", "C", "D", "E"};

        for (String v : vertexes) {
            graph.insertVertex(v);
        }

        graph.insertEdges(0, 1, 1);
        graph.insertEdges(0, 2, 1);
        graph.insertEdges(1, 2, 1);
        graph.insertEdges(1, 3, 1);
        graph.insertEdges(1, 4, 1);

        graph.showList();

        // graph.dfs();

        graph.bfs();
    }
}

运行结果:

 8.代码的实现详解:包括了图的深度优先遍历和广度优先遍历

一个图有那么多个结点,如何遍历这些结点,需要特定策略。

一般有两种访问策略: 深度优先遍历,广度优先遍历。

9.深度优先遍历介绍:

        深度优先遍历(Depth-First-Search),从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问,第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点, 可以这样理解每次都在访问完当前结点后首先访问当前结点的第一个邻接结点。

深度优先搜索是一个递归的过程:

  1. 访问初始顶点x,访问后需要标记x已经访问过,不能再次重读访问
  2. 查找顶点x的第一个邻接点y
  3. 如果y存在,则继续执行下面步骤,如果y不存在,则回到第1步,将从x的下一个顶点继续
  4. 如果y未被访问过,对y进行深度优先遍历,则是把y当做另一个x,然后执行123步
  5. 查找顶点x的y邻接点的下一个邻接点,转到步骤3

10.广度优先遍历介绍:

        类似于一个 分层搜索的过程,广度优先遍历(Breadth-First-Search)需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点。

1)访问初始结点 v 并标记结点 v 为已访问。

2) 结点 v 入队列

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

4) 出队列,取得队头结点 u。

5) 查找结点 u 的第一个邻接结点 w。

6) 若结点 u 的邻接结点 w 不存在,则转到步骤 3;否则循环执行以下三个步骤

6.1若结点 w 尚未被访问,则访问结点 w 并标记为已访问。

6.2 结点 w 入队列

6.3 查找结点 u 的继 w 邻接结点后的下一个邻接结点 w,转到步骤 6

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ll520.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值