数据结构-图

简介

在数据结构上,图和树是比较相像的。实际上,树可以说是图的一种。

图包含由边连接的顶点。

图可以表示许多真实世界的情况,如飞机航线、电子线路和工作调度等。

搜索

有两种常用方法:深度优先搜索(DFS)和广度优先搜索(BFS)。最终都会到达所有连通的顶点。

深度优先搜索用栈来实现;广度优先搜索通过队列来实现。

深度优先搜索


规则

  1. 如果可能,访问一个邻接的未访问顶点,标记它,并把它放入栈中;
  2. 当不能执行规则1时,如果栈不空,则从栈中弹出一个顶点;
  3. 如果不能执行规则1和规则2,则整个搜索过程结束。
栈的内容就是从起始顶点到各个顶点访问的整个过程。从起始顶点出发访问下一个顶点时,就把这个顶点入栈。回到起始顶点时出栈。

深度优先搜索算法要得到距离起始顶点最远的顶点,然后在不能继续前进的时候返回。

Java实现

package com.jikefriend.graph;

public class GraphDfs {

    static class StackX {
        private final int SIZE = 20;
        private int[] st;
        private int top;

        public StackX() {
            st = new int[SIZE];
            top = -1;
        }

        public void push(int j) {
            st[++top] = j;
        }

        public int pop() {
            return st[top--];
        }

        public int peek() {
            return st[top];
        }

        public boolean isEmpty() {
            return top == -1;
        }
    }

    static class Vertex {
        public char label;
        public boolean wasVisited;

        public Vertex(char lab) {
            label = lab;
            wasVisited = false;
        }
    }

    static class Graph {
        private final int MAX_VERTS = 20;
        private Vertex[] vertexList;
        private int[][] adjMat;
        private int nVerts;
        private StackX theStack;

        public Graph() {
            vertexList = new Vertex[MAX_VERTS];
            adjMat = new int[MAX_VERTS][MAX_VERTS];
            nVerts = 0;
            for (int i = 0; i < MAX_VERTS; i++) {
                for (int j = 0; j < MAX_VERTS; j++) {
                    adjMat[i][j] = 0;
                }
            }
            theStack = new StackX();
        }

        public void addVertex(char lab) {
            vertexList[nVerts++] = new Vertex(lab);
        }

        public void addEdge(int start, int end) {
            adjMat[start][end] = 1;
            adjMat[end][start] = 1;
        }

        public void displayVertex(int v) {
            System.out.print(vertexList[v].label);
        }

        /**
         * 深度优先搜索,步骤:
         *  1、用peek()方法检查栈顶的顶点;
         *  2、试图找到这个顶点还未访问的邻接点;
         *  3、如果没有找到,出栈;
         *  4、如果找到这样的顶点,访问这个顶点,并把它放入栈。
         */
        public void dfs() {
            vertexList[0].wasVisited = true;  //标记为已访问
            displayVertex(0);
            theStack.push(0);                 //放入栈

            while (!theStack.isEmpty()) {
                /** 试图找到这个顶点还未访问的邻接点 */
                int v = getAdjUnvisitedVertex(theStack.peek());
                if (v == -1)
                    /** 如果没有找到,出栈 */
                    theStack.pop();
                else {
                    /** 如果找到这样的顶点,访问这个顶点,并把它放入栈 */
                    vertexList[v].wasVisited = true;  //标记为已访问
                    displayVertex(v);
                    theStack.push(v);                 //放入栈
                }
            }

            /** 重置所有访问标记 */
            for (int i = 0; i < nVerts; i++) {
                vertexList[i].wasVisited = false;
            }
        }

        /**
         * 找出与顶点邻接且没有访问过的顶点
         *
         * @param v
         * @return
         */
        public int getAdjUnvisitedVertex(int v) {
            for (int i = 0; i < nVerts; i++) {
                if (adjMat[v][i] == 1 && vertexList[i].wasVisited == false)
                    return i;
            }
            return -1;
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph();
        graph.addVertex('A');
        graph.addVertex('B');
        graph.addVertex('C');
        graph.addVertex('D');
        graph.addVertex('E');

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

        System.out.print("Visits: ");
        graph.dfs();
        System.out.println();
    }
}

决策树

首次移动的是根节点,之后有N种可能的移动,那么有N个第二层节点与根节点相连,接下来有N-1中可能的移动,所以每个第二层节点就有N-1个第三层节点与之相连。从根节点到叶节点,为构造这颗树需要有N!条路径。这就是决策树。

广度优先搜索


深度优先搜索是尽快的远离起始顶点。而广度优先搜索则恰恰与之相反,要尽可能的靠近起始顶点。首先访问的是起始顶点的所有邻接点,然后再访问较远的区域。广度优先搜索就像水中的波纹层层扩展。

规则

  1. 访问下一个未访问的邻接点(如果存在),这个顶点必须是当前顶点的邻接点,标记它,并把它插入到队列中;
  2. 如果因为已经没有未访问顶点而不能执行规则1,那么从队列头取一个顶点(如果存在),并使其成为当前顶点;
  3. 如果因为队列为空而不能执行规则2,则搜索结束。

Java实现

package com.jikefriend.graph;

public class GraphBfs {

    static class Queue {
        private final int SIZE = 20;
        private int[] queArray;
        private int front;
        private int rear;

        public Queue() {
            queArray = new int[SIZE];
            front = 0;
            rear = -1;
        }

        public void insert(int j) {
            if (rear == SIZE - 1)
                rear = -1;
            queArray[++rear] = j;
        }

        public int remove() {
            int temp = queArray[front++];
            if (front == SIZE)
                front = 0;
            return temp;
        }

        public boolean isEmpty() {
            return (rear + 1 == front) || (front + SIZE - 1 == rear);
        }

    }

    static class Vertex {
        public char label;
        public boolean wasVisited;

        public Vertex(char lab) {
            label = lab;
            wasVisited = false;
        }
    }

    static class Graph {
        private final int MAX_VERTS = 20;
        private Vertex[] vertexList;
        private int[][] adjMat;
        private int nVerts;
        private Queue theQueue;

        public Graph() {
            vertexList = new Vertex[MAX_VERTS];
            adjMat = new int[MAX_VERTS][MAX_VERTS];
            nVerts = 0;
            for (int i = 0; i < MAX_VERTS; i++) {
                for (int j = 0; j < MAX_VERTS; j++) {
                    adjMat[i][j] = 0;
                }
            }
            theQueue = new Queue();
        }

        public void addVertex(char lab) {
            vertexList[nVerts++] = new Vertex(lab);
        }

        public void addEdge(int start, int end) {
            adjMat[start][end] = 1;
            adjMat[end][start] = 1;
        }

        public void displayVertex(int v) {
            System.out.print(vertexList[v].label);
        }

        public void bfs() {
            vertexList[0].wasVisited = true;
            displayVertex(0);
            theQueue.insert(0);
            int v2;

            while (!theQueue.isEmpty()) {
                int v = theQueue.remove();
                while ((v2 = getAdjUnvisitedVertex(v)) != -1) {
                    vertexList[v2].wasVisited = true;
                    displayVertex(v2);
                    theQueue.insert(v2);
                }
            }

            for (int i = 0; i < nVerts; i++) {
                vertexList[i].wasVisited = false;
            }
        }

        public int getAdjUnvisitedVertex(int v) {
            for (int i = 0; i < nVerts; i++) {
                if (adjMat[v][i] == 1 && vertexList[i].wasVisited == false)
                    return i;
            }
            return -1;
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph();
        graph.addVertex('A');
        graph.addVertex('B');
        graph.addVertex('C');
        graph.addVertex('D');
        graph.addVertex('E');

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

        System.out.print("Visits: ");
        graph.bfs();
        System.out.println();
    }
}

最小生成树


只通过最少量的边来保证顶点彼此连通,这就组成了最小生成树。同一组顶点,可能有多种最小生成树。

最小生成树边E的数量总比顶点V的数量小1:

E = V - 1

使用深度优先搜索的Java实现

package com.jikefriend.graph;

public class GraphMst {

    static class StackX {
        private final int SIZE = 20;
        private int[] st;
        private int top;

        public StackX() {
            st = new int[SIZE];
            top = -1;
        }

        public void push(int j) {
            st[++top] = j;
        }

        public int pop() {
            return st[top--];
        }

        public int peek() {
            return st[top];
        }

        public boolean isEmpty() {
            return top == -1;
        }
    }

    static class Vertex {
        public char label;
        public boolean wasVisited;

        public Vertex(char lab) {
            label = lab;
            wasVisited = false;
        }
    }

    static class Graph {
        private final int MAX_VERTS = 20;
        private Vertex[] vertexList;
        private int[][] adjMat;
        private int nVerts;
        private StackX theStack;

        public Graph() {
            vertexList = new Vertex[MAX_VERTS];
            adjMat = new int[MAX_VERTS][MAX_VERTS];
            nVerts = 0;
            for (int i = 0; i < MAX_VERTS; i++) {
                for (int j = 0; j < MAX_VERTS; j++) {
                    adjMat[i][j] = 0;
                }
            }
            theStack = new StackX();
        }

        public void addVertex(char lab) {
            vertexList[nVerts++] = new Vertex(lab);
        }

        public void addEdge(int start, int end) {
            adjMat[start][end] = 1;
            adjMat[end][start] = 1;
        }

        public void displayVertex(int v) {
            System.out.print(vertexList[v].label);
        }

        public void mst() {
            vertexList[0].wasVisited = true;
            theStack.push(0);

            while (!theStack.isEmpty()) {
                int currentVertex = theStack.peek();
                int v = getAdjUnvisitedVertex(currentVertex);
                if (v == -1)
                    theStack.pop();
                else {
                    vertexList[v].wasVisited = true;
                    theStack.push(v);

                    displayVertex(currentVertex);
                    displayVertex(v);
                    System.out.print(" ");
                }
            }

            for (int i = 0; i < nVerts; i++) {
                vertexList[i].wasVisited = false;
            }
        }

        public int getAdjUnvisitedVertex(int v) {
            for (int i = 0; i < nVerts; i++) {
                if (adjMat[v][i] == 1 && vertexList[i].wasVisited == false)
                    return i;
            }
            return -1;
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph();
        graph.addVertex('A');
        graph.addVertex('B');
        graph.addVertex('C');
        graph.addVertex('D');
        graph.addVertex('E');

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

        System.out.print("Minimum spanning tree: ");
        graph.mst();
        System.out.println();
    }
}

有向图

边有方向的图,称为有向图,如下图所示。


无向图的邻接矩阵是上下三角对称的,而有向图的邻接矩阵是非对称的,用以区分边的方向。

拓扑排序,即某些项目或事件必须按特定的顺序排列或发生,如下图。

拓扑排序算法步骤:

找到一个没有后继的顶点;

从图中删除这个顶点,在列表的前面插入顶点的标记。

重复步骤1和步骤2,知道所有的顶点都从图中删除。此时列表显示的顶点顺序就是拓扑排序的结果。

如果顶点没有后继,那么它肯定是拓扑序列中的最后一个,一旦删除它,剩下的顶点中必然有一个没有后继,所以它成为下一个拓扑序列中的最后一个,以此类推。

如果有N个顶点的图有超过N-1条边,那么它必定存在环。路径的起点和终点都是同一个顶点,则成为环。

拓扑排序必须在无环的有向图中进行。

拓扑排序Java实现

package com.jikefriend.graph;

public class GraphTopo {

    static class Vertex {
        public char label;

        public Vertex(char lab) {
            label = lab;
        }
    }

    static class Graph {
        private final int MAX_VERTS = 20;
        private Vertex[] vertexList;
        private int[][] adjMat;
        private int nVerts;
        private char[] sortedArray;

        public Graph() {
            vertexList = new Vertex[MAX_VERTS];
            adjMat = new int[MAX_VERTS][MAX_VERTS];
            nVerts = 0;
            for (int i = 0; i < MAX_VERTS; i++) {
                for (int j = 0; j < MAX_VERTS; j++) {
                    adjMat[i][j] = 0;
                }
            }
            sortedArray = new char[MAX_VERTS];
        }

        public void addVertex(char lab) {
            vertexList[nVerts++] = new Vertex(lab);
        }

        public void addEdge(int start, int end) {
            adjMat[start][end] = 1;
        }

        public void displayVertex(int v) {
            System.out.print(vertexList[v].label);
        }

        /**
         * 拓扑排序
         */
        public void topo() {
            int orig_nVerts = nVerts;

            while (nVerts > 0) {
                /** 找到任意一个没有后继的顶点 */
                int currentVertex = noSuccessors();
                /** 如果没有这样的顶点,则图必然存在环 */
                if (currentVertex == -1) {
                    System.out.println("Error: Graph has cycles");
                    return;
                }
                /** 如果找到这样的顶点,放入数组,并删除它 */
                sortedArray[nVerts - 1] = vertexList[currentVertex].label;

                deleteVertex(currentVertex);
            }
            System.out.print("Topologically sorted order: ");
            for (int i = 0; i < orig_nVerts; i++)
                System.out.print(sortedArray[i]);
            System.out.println();
        }

        /**
         * 找到一个没有后继的顶点
         * 
         * @return
         */
        public int noSuccessors() {
            boolean isEdge;

            for (int row = 0; row < nVerts; row++) {
                isEdge = false;
                for (int col = 0; col < nVerts; col++) {
                    if (adjMat[row][col] > 0) {
                        isEdge = true;
                        break;
                    }
                }
                if (!isEdge)
                    return row;
            }
            return -1;
        }

        /**
         * 删除顶点,并调整邻接矩阵
         * 
         * @param delVert
         */
        public void deleteVertex(int delVert) {
            if (delVert != nVerts - 1) {
                for (int i = delVert; i < nVerts - 1; i++)
                    vertexList[i] = vertexList[i + 1];

                for (int row = delVert; row < nVerts - 1; row++)
                    moveRowUp(row, nVerts);

                for (int col = delVert; col < nVerts - 1; col++)
                    moveColLeft(col, nVerts - 1);
            }
            nVerts--;
        }

        /**
         * 删除顶点后上移邻接矩阵
         * 
         * @param row
         * @param length
         */
        private void moveRowUp(int row, int length) {
            for (int col = 0; col < length; col++) {
                adjMat[row][col] = adjMat[row + 1][col];
            }
        }

        /**
         * 删除顶点后左移邻接矩阵
         * 
         * @param col
         * @param length
         */
        private void moveColLeft(int col, int length) {
            for (int row = 0; row < length; row++) {
                adjMat[row][col] = adjMat[row][col + 1];
            }
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph();
        graph.addVertex('A');
        graph.addVertex('B');
        graph.addVertex('C');
        graph.addVertex('D');
        graph.addVertex('E');
        graph.addVertex('F');
        graph.addVertex('G');
        graph.addVertex('H');

        graph.addEdge(0, 3);
        graph.addEdge(0, 4);
        graph.addEdge(1, 4);
        graph.addEdge(2, 5);
        graph.addEdge(3, 6);
        graph.addEdge(4, 6);
        graph.addEdge(5, 7);
        graph.addEdge(6, 7);

        graph.topo();
    }
}

摘自《 Java 数据结构 算法 (第二版)》 [美] Robert Lafore 著

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值