【圖】——遍歷、最小生成樹、拓撲排序、關鍵路徑、最短路徑

一、遍歷


深度優先遍歷

深度優先遍歷是對樹的先序遍歷

廣度優先遍歷

廣度優先遍歷是對樹的層次遍歷(很多地方說後序遍歷,然後後續遍歷和層次遍歷是不一樣的)




二、最小生成樹


Kruskal算法描述

  1. 新建一個圖

  2. 將帶權值的邊放入集合中,並對集合按權值大小排序(可以用隊列存,在入隊列的時候加限制,保證隊列是有序的)

  3. (在while循環中,集合不爲空的條件下)取權值最小的邊並移出集合,並找到權的tailhead(可以遍歷現有的圖的頂點集合找到它們)

  4. (在新圖中)先插入tail頂點和tail頂點(如果存在就不插入,不能拋出異常),再插入這條邊

  5. 判斷插入後是否夠成環(可以用深度優先或者拓撲序列驗證是否成環)

    1. 如果成環就撤銷插入操作(不撤銷移出集合),下一個循環
    2. 如果不成環,下一個循環。



三、拓撲排序


意義

如果圖的所有頂點都在這個圖的拓撲序列中,則這個圖不存在環

算法描述

  1. 隨機找一個入度爲0的頂點添加到resultList

  2. 移除這個頂點和與這個頂點相關的邊

  3. (頂點不爲空的情況下)循環




四、最長路徑/關鍵路徑/至少需要的時間的路徑


AOE網(Activity On Edge)的意義

可以求解完成一個複雜的工程時至少需要的時間和哪些活動是影響工程進度的關鍵活動

實現

推薦博客 博客1 || 博客2




五、最短路徑


AOV網(Acticity On Vertex)的意義

可以實現地圖中NPC自動尋路功能(通過最短路徑來找到玩家,然後…)

Floyd算法

Floyd算法是一個經典的動態規劃算法

用通俗的語言來描述的話,首先我們的目標是尋找從點i到點j的最短路徑

從動態規劃的角度看問題,我們需要爲這個目標重新做一個詮釋(這個詮釋正是動態規劃最富創造力的精華所在)

1、算法思想

從任意節點i到任意節點j的最短路徑不外乎2種可能

  1. 直接從ij
  2. i經過若干個節點kj

所以,我們假設Dis(i,j)爲節點u到節點v的最短路徑的距離。對於每一個節點k,我們檢查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立。如果成立,證明從i到k再到j的路徑比i直接到j的路徑短,我們便設置Dis(i,j) = Dis(i,k) + Dis(k,j)。這樣一來,當我們遍歷完所有節點kDis(i,j)中記錄的便是i到j的最短路徑的距離

2、算法描述
  1. 從任意一條單邊路徑開始。所有兩點之間的距離是邊的權,如果兩點之間沒有邊相連,則權爲無窮大
  2. 對於每一對頂點uv,看看是否存在一個頂點 w 使得從 uw 再到 v 比己知的路徑更短。如果是則更新它。
3、代碼實現

接口

import java.util.List;
/**
 * TODO
 * @author IceFery
 * @see GraphImpl
 * @date 2019/6/24
 */
public interface Graph<E, W> {
    /**
     * 默認最大頂點容量
     */
    int DEFAULT_MAX_ELEMENT_NUM = 10;

    /**
     * 添加一個元素
     * @param element
     * @throws Exception
     */
    void addElement(E element) throws Exception;

    /**
     * 添加一條邊
     * @param weight
     * @param tailElement
     * @param headElement
     * @throws Exception
     */
    void addEdge(W weight, E tailElement, E headElement) throws Exception;

    /**
     * 移除一個頂點
     * @param element
     * @throws Exception
     */
    void removeElement(E element) throws Exception;

    /**
     * 移除一條邊
     * @param tailElement
     * @param headElement
     * @throws Exception
     */
    void removeEdge(E tailElement, E headElement) throws Exception;

    /**
     * 取得某個頂點的入度和入度並放入數組中,第一個值爲入度,第二個值爲出度
     * @param element
     * @throws Exception
     */
    int[] getDegree(E element) throws Exception;

    /**
     * 打印邊表
     * @throws RuntimeException
     */
    void displayEdges() throws RuntimeException;

    /**
     * 對圖進行深度優先遍歷,並返回遍歷的序列
     * @param startElement
     * @return traverseList
     * @throws Exception
     */
    List<E> DFSTraverse(E startElement) throws Exception;

    /**
     * 對圖進行廣度優先遍歷,並返回遍歷的序列
     * @param startElement
     * @return traverseList
     * @throws Exception
     */
    List<E> BFSTraverse(E startElement) throws Exception;

    /**
     * 獲得圖的拓撲序列
     * @return
     */
    List<E> getTopologicalList();

    /**
     * 獲得兩個頂點的關鍵路徑序列
     * @param startElement
     * @param endElement
     * @return
     * @throws Exception
     */
    Object[] getCriticalPath(E startElement, E endElement) throws Exception;

    /**
     * 獲得兩個頂點的最短路徑序列
     * @param startElement
     * @param endElement
     * @return resultMap[0] = pathList
     * @return resultMap[1] = pathLength
     * @throws Exception 
     */
    Object[] getShortestPath(E startElement, E endElement) throws Exception;
}

實現類

import java.util.*;
/**
 * TODO
 * @author IceFery
 * @date 2019/6/24
 * @see Graph
 */
public class GraphImpl<E, W> implements Graph<E, W> {
    private boolean       isDirected   = true;
    private int           elementNum   = 0;
    private int           edgeNum      = 0;
    private List<E>       elementArray = null;
    private List<List<W>> edgeArray    = null;

    public GraphImpl(boolean isDirected) {
        this.isDirected   = isDirected;
        this.elementArray = new ArrayList<>(DEFAULT_MAX_ELEMENT_NUM);
        this.edgeArray    = new ArrayList<>(DEFAULT_MAX_ELEMENT_NUM);
        for (int i = 0; i < DEFAULT_MAX_ELEMENT_NUM; i++) {
            ArrayList<W> headElementArray = new ArrayList<>(DEFAULT_MAX_ELEMENT_NUM);
            for (int j = 0; j < DEFAULT_MAX_ELEMENT_NUM; j++) {
                headElementArray.add(null);
            }
            edgeArray.add(headElementArray);
        }
    }


    @Override
    public void addElement(E element) throws Exception {
        this.elementArray.add(element);
        this.elementNum++;
    }


    @Override
    public void addEdge(W weight, E tailElement, E headElement) throws Exception {
        int tailElementIndex = this.elementArray.indexOf(tailElement);
        int headElementIndex = this.elementArray.indexOf(headElement);
        if (-1 == tailElementIndex || -1 == headElementIndex) {
            throw new Exception(tailElement + "或者" + headElement + "頂點未找到異常");
        }
        this.edgeArray.get(tailElementIndex).set(headElementIndex, weight);
        if (!this.isDirected) {
            this.edgeArray.get(headElementIndex).set(tailElementIndex, weight);
        }
        this.edgeNum++;
    }


    @Override
    public void removeElement(E element) throws Exception {
        int index = this.elementArray.indexOf(element);
        if (-1 == index) {
            throw new Exception(element + "頂點未找到異常");
        }
        this.elementArray.remove(index);
        this.elementNum--;
        for (int elementIndex = 0; elementIndex < this.elementNum; elementIndex++) {
            this.edgeArray.get(elementIndex).remove(index);
        }
        this.edgeArray.remove(index);

        int newEdgeNum = 0;
        for (int tailElementIndex = 0; tailElementIndex < this.elementNum; tailElementIndex++) {
            for (int headElementIndex = 0; headElementIndex < this.elementNum; headElementIndex++) {
                if (null != this.edgeArray.get(tailElementIndex).get(headElementIndex)) {
                    newEdgeNum++;
                }
            }
        }
        this.edgeNum = newEdgeNum;
    }


    @Override
    public void removeEdge(E tailElement, E headElement) throws Exception {
        int tailElementIndex = this.elementArray.indexOf(tailElement);
        int headElementIndex = this.elementArray.indexOf(headElement);
        if (-1 == tailElementIndex || -1 == headElementIndex) {
            throw new Exception(tailElement + "或" + headElement + "頂點未找到異常");
        }
        this.edgeArray.get(tailElementIndex).set(headElementIndex, null);
        this.edgeNum--;
    }


    @Override
    public int[] getDegree(E element) throws Exception {
        int elementIndex = this.elementArray.indexOf(element);
        if (-1 == elementIndex) {
            throw new Exception(element + "頂點未找到異常");
        }
        int[] degrees = new int[2];
        Arrays.fill(degrees, 0);
        for (int index = 0; index < this.elementNum; index++) {
            if (null != this.edgeArray.get(elementIndex).get(index)) {
                degrees[0]++;
            }
            if (null != this.edgeArray.get(index).get(elementIndex)) {
                degrees[1]++;
            }
        }
        return degrees;
    }


    @Override
    public void displayEdges() throws RuntimeException {
        for (int tailElementIndex = 0; tailElementIndex < this.elementNum; tailElementIndex++) {
            System.out.print("以" + this.elementArray.get(tailElementIndex) + "爲起點的邊有:");
            for (int headElementIndex = 0; headElementIndex < this.elementNum; headElementIndex++) {
                if (null != this.edgeArray.get(tailElementIndex).get(headElementIndex)) {
                    System.out.print("<" + this.elementArray.get(tailElementIndex) + ", " + this.edgeArray.get(tailElementIndex).get(headElementIndex) + ", " + this.elementArray.get(headElementIndex) + ">");
                    System.out.print("  ");
                }
            }
            System.out.println();
        }
    }


    @Override
    public List<E> DFSTraverse(E startElement) throws Exception {
        List<E>        resultList = new ArrayList<>(this.elementNum);
        Stack<Integer> stack      = new Stack<>();
        boolean[]      visited    = new boolean[this.elementNum];
        Arrays.fill(visited, false);
        int startElementIndex = this.elementArray.indexOf(startElement);
        if (-1 == startElementIndex) {
            throw new Exception(startElement + "頂點未找到異常");
        }
        stack.push(startElementIndex);
        while (!stack.isEmpty()) {
            int tailElementIndex = stack.pop();
            if (!visited[tailElementIndex]) {
                resultList.add(this.elementArray.get(tailElementIndex));
                visited[tailElementIndex] = true;
            }
            for (int headElementIndex = 0; headElementIndex < this.elementNum; headElementIndex++) {
                if (null != this.edgeArray.get(tailElementIndex).get(headElementIndex) && !visited[headElementIndex]) {
                    stack.push(headElementIndex);
                }
            }
        }
        return resultList;
    }


    @Override
    public List<E> BFSTraverse(E startElement) throws Exception {
        List<E>        resultList = new ArrayList<>(this.elementNum);
        Queue<Integer> queue      = new LinkedList<>();
        boolean[]      visited    = new boolean[this.elementNum];
        Arrays.fill(visited, false);
        int startElementIndex = this.elementArray.indexOf(startElement);
        if (-1 == startElementIndex) {
            throw new Exception(startElement + "頂點未找到異常");
        }
        queue.offer(startElementIndex);
        while (!queue.isEmpty()) {
            int tailElementIndex = queue.poll();
            if (!visited[tailElementIndex]) {
                resultList.add(this.elementArray.get(tailElementIndex));
                visited[tailElementIndex] = true;
            }
            for (int headElementIndex = 0; headElementIndex < this.elementNum; headElementIndex++) {
                if (null != this.edgeArray.get(tailElementIndex).get(headElementIndex) && !visited[headElementIndex]) {
                    queue.offer(headElementIndex);
                }
            }
        }
        return resultList;
    }


    @Override
    public List<E> getTopologicalList() {
        //TODO
        return null;
    }


    @Override
    public Object[] getCriticalPath(E startElement, E endElement) throws Exception {
    	//TODO
        return null;
    }


    @Override
    public Object[] getShortestPath(E startElement, E endElement) throws Exception {
        int startElementIndex = this.elementArray.indexOf(startElement);
        int endElementIndex   = this.elementArray.indexOf(endElement);
        if (-1 == startElementIndex || -1 == endElementIndex) {
            throw new Exception(startElement + "或" + endElement + "頂點未找到異常");
        }

        /*
         * 用一個2個位置的數組充當一個map
         * 第一個維度存路徑的element的集合  pathList
         * 第二個維度存整個路徑的長度 distance[startElementIndex][endElementIndex]
         */
        Object[] results = new Object[2];
        Arrays.fill(results, null);
        List<E>     pathList = new ArrayList<>(this.elementNum);

        /*
         * 用原來的Integer的MAX_VALUE 或者 從MAX_VALUE / 10 * 6開始,也就是隻要大於這個值的一半,就會在參與運算後無法正常顯示,目前沒搞懂原因。
         * 所以用 MAX_VALUE / 10 * 5來表示這個最大值,然後在後面就它換成null
         */
        final int MY_MAX_VALUE = Integer.MAX_VALUE / 2;

        /*
         * 下面是比較大衆的最短路徑算法
         * 需要一個path[][]數組,k = path[i][j]的意義是這條路徑是:(i --> ... --> k -- >j)遍歷的時候需要像對樹一樣中序遍歷,然後就可以得到結果
         * 還需要一個distance[][]數組,distance[i][j]表示i -- > j 的最短路徑, 所以最開始就可以初始化爲  i -- > j  的的權值
         */
        Integer[][] path     = new Integer[this.elementNum][this.elementNum];
        Integer[][] distance = new Integer[this.elementNum][this.elementNum];
        for (int tailElementIndex = 0; tailElementIndex < this.elementNum; tailElementIndex++) {
            for (int headElementIndex = 0; headElementIndex < this.elementNum; headElementIndex++) {
                distance[tailElementIndex][headElementIndex] = (null == this.edgeArray.get(tailElementIndex).get(headElementIndex)) ? MY_MAX_VALUE : (Integer) this.edgeArray.get(tailElementIndex).get(headElementIndex);
                path[tailElementIndex][headElementIndex]     = null;
            }
        }
        for (int k = 0; k < this.elementNum; k++) {
            for (int i = 0; i < this.elementNum; i++) {
                for (int j = 0; j < this.elementNum; j++) {
                    Integer newDistance = distance[i][k] + distance[k][j];
                    if (newDistance < distance[i][j]) {
                        distance[i][j] = newDistance;
                        path[i][j]     = k;
                    }
                }
            }
        }

        /*
         * 將MY_MAX_VALUE還原爲null,因爲null比較好判斷
         */
        for (int i = 0; i < this.elementNum; i++) {
            for (int headElementIndex = 0; headElementIndex < this.elementNum; headElementIndex++) {
                distance[i][headElementIndex] = (MY_MAX_VALUE == distance[i][headElementIndex]) ? null : distance[i][headElementIndex];
            }
        }

        /*
         * 將路徑添加到集合中
         * 因爲 k 只表示 i --> j 中j的前驅,所以只是startElementIndex -- > endElementIndex中間的路徑,所以需要再集合的隊頭和隊尾添加上起點和終點
         * 下面是我把那個遞歸的中序遍歷寫成的非遞歸形式
         * 也是用棧保存一個map,暫時不會用map,那就用數組吧
         */
        pathList.add(this.elementArray.get(startElementIndex));
        int              m     = startElementIndex;
        int              n     = endElementIndex;
        Integer          k     = path[m][n];
        Stack<Integer[]> stack = new Stack<>();
        while (null != k || !stack.isEmpty()) {
            if (null != k) {
                stack.push(new Integer[]{m, n});
                n = k;
                k = path[m][n];
            } else {
                Integer[] map = stack.pop();
                m = map[0];
                n = map[1];
                k = path[m][n];
                pathList.add(this.elementArray.get(k));
                k = path[k][n];
            }
        }
        pathList.add(this.elementArray.get(endElementIndex));

        /*
         * 將路徑集合和路徑長度添加進待返回的 resultMap中
         */
        results[0] = pathList;
        results[1] = distance[startElementIndex][endElementIndex];

        return results;
    }
}

Main.java

import java.util.List;
/**
 * TODO
 * @author IceFery
 * @date 2019/6/25
 */
public class Main {
    public static void main(String[] args) throws Exception {
        Graph<String, Integer> g = new GraphImpl<>(true);
        g.addElement("V1");
        g.addElement("V2");
        g.addElement("V3");
        g.addElement("V4");
        g.addElement("V5");
        g.addElement("V6");
        g.addElement("V7");
        g.addElement("V8");
        g.addElement("V9");

        g.addEdge(6, "V1", "V2");
        g.addEdge(4, "V1", "V3");
        g.addEdge(5, "V1", "V4");
        g.addEdge(1, "V2", "V5");
        g.addEdge(1, "V3", "V5");
        g.addEdge(2, "V4", "V6");
        g.addEdge(9, "V5", "V7");
        g.addEdge(7, "V5", "V8");
        g.addEdge(4, "V6", "V8");
        g.addEdge(2, "V7", "V9");
        g.addEdge(4, "V8", "V9");

        g.displayEdges();

        System.out.println("入度和出度:" + g.getDegree("V1")[0] + "," + g.getDegree("V1")[1]);

        //  System.out.println("移除V1到V2的邊:");
        //  g.removeEdge("V1", "V2");
        //  g.displayEdges();

        //  System.out.println("移除V1頂點:");
        //  g.removeElement("V1");
        //  g.displayEdges();

        System.out.println("深度優先遍歷和廣度優先遍歷的結果爲:");
        List<String> list = g.DFSTraverse("V1");
        for (String str : list) {
            System.out.print(str + "  ");
        }
        System.out.println();
        list = g.BFSTraverse("V1");
        for (String str : list) {
            System.out.print(str + "  ");
        }
        System.out.println();

        System.out.print("最短路徑爲:");
        Object[]     shortest     = g.getShortestPath("V1", "V9");
        List<String> shortestPath = (List<String>) shortest[0];
        System.out.println();
        System.out.println();
        for (String s : shortestPath) {
            System.out.print(s + "  ");
        }
        System.out.println(shortest[1]);
    }
}

運行截圖

在這裏插入圖片描述

Floyd算法輸出路徑的遞歸算法這個博客裏面

	//遞歸算法
    private void  dfs(Integer[][] path, List<E> pathList, int i, int j) {
        Integer k = path[i][j];
        if (null == k) {
            return;
        } else {
            this.dfs(path, pathList, i, k);
            pathList.add(this.elementArray.get(k));
            this.dfs(path, pathList, k, j);
        }
    }

將遞歸轉換爲非遞歸(以V1 --> V9的最短路徑)

path矩陣如下

0 1 2 3 4 5 6 7 8
0 null null null null 2 3 4 5
1 null null null null null null 4 4
2 null null null null null null 4 4
3 null null null null null null null 5
4 null null null null null null null null
5 null null null null null null null null
6 null null null null null null null null
7 null null null null null null null null
8 null null null null null null null null

二叉樹的中序遍歷非遞歸算法如下

void InOrderTraversal(BinTree BT){ 
    BinTree T = BT;
    Stack S = CreatStack(MaxSize); //創建並初始化堆棧S
    while(T || !IsEmpty(S)){
      while(T) {   //一直向左並將沿途節點壓入堆棧
          Push(S,T);
          T = T->Left;
      }
      if(!IsEmpty(S)){
          T = Pop(S);                //節點彈出堆棧
          printf("%d\n", T->Data);    //(訪問) 打印結點
          T = T->Right;              //轉向右子樹
      }
  }
}

圖示
在這裏插入圖片描述
改成非遞歸算法

	int              m     = startElementIndex;
	int              n     = endElementIndex;
	Integer          k     = path[m][n];
	Stack<Integer[]> stack = new Stack<>();
	while (null != k || !stack.isEmpty()) {
	    if (null != k) {
	        stack.push(new Integer[]{m, n});
	        n = k;
	        k = path[m][n];
	    } else {
	        Integer[] map = stack.pop();
	        m = map[0];
	        n = map[1];
	        k = path[m][n];
	        pathList.add(this.elementArray.get(k));
	        k = path[k][n];
	    }
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章