图的深度优先遍历(非递归)

一、流程图

本题示例用关联前向边存储图

Q:访问了节点i后,如何找到下一个可访问节点

A:若该节点有未被访问的邻接节点,则任选一个进行访问;若该节点没有未被访问的邻接节点,则找到最新被访问的节点latestNode,找到节点latestNode的任一未被访问节点进行访问

Q:如何找到某节点的所有邻接节点

A:在关联前向边存储结构中每个Node对象中存储了1)count:从该节点中发出的边;2)该节点发出的第一条边在数组linkedEdges中存储的下标。故遍历该节点发出的所有边,然后找到每条边的exitNodeId,即找到了该节点的所有邻接节点

int beginIndex = g.nodes[curIndex].linkEdgesBeginIndex;
int count = g.nodes[curIndex].count;
// 遍历该节点发出的所有边
for (int i = beginIndex; i < beginIndex + count; i++) {
    int linkNodeNum = g.edges[g.linkedEdges[i]].exitNode.nodeId;
    int linkNodeIndex = getIndex(g, linkNodeNum);
    if (curIndex == -1) {
        System.out.println("邻接节点编号错误");
        return;
    }

}

Q:如何判断某一节点是否被访问过

A:为每个顶点设立访问标志。(建立一个boolean数组,用于存储数组下标对应位置的节点的访问情况)

boolean[] set = new boolean[g.n];

二、代码

1.输入说明

本例输入如下无向图

// 节点个数、边个数(本例是按照有向图来存储图,故下图中的无向图来说有20条边)
9 20
// 一下9行代表节点信息:(节点编号 节点名称)
10 A
11 B
12 C
13 D
14 E
15 F
16 G
17 H
18 I
// 以下10行代表边信息:(起始节点编号 终止节点编号)
10 11
10 12
11 13
11 14
12 15
12 16
15 16
13 17
14 17
17 18
// 从哪个编号的节点开始遍历
10

2.示例代码

非递归深度优先遍历

import java.util.Arrays;
import java.util.Scanner;
import java.util.Stack;

/**
 * <pre>
 *     author : 杨丽金
 *     time   : 2018/11/13
 *     desc   :
 *     version: 1.0
 * </pre>
 */
public class Travel {
    public static void main(String[] args) {
        new Travel().exe();
    }

    Scanner scan = new Scanner(System.in);

    public void exe() {
        MyGraph g = createMyGraph();
        int startNum = scan.nextInt();
        dfs_NonRecursive(g, startNum);
    }

    public MyGraph createMyGraph() {
        // 节点数
        int N = scan.nextInt();
        // 边数
        int M = scan.nextInt();
        MyGraph g = new MyGraph(N, M);
        // 节点信息
        for (int i = 0; i < N; i++) {
            MyGraph.Node node = new MyGraph.Node();
            node.nodeId = scan.nextInt();
            node.nodeName = scan.next();
            g.nodes[i] = node;
        }
        // 边信息
        for (int i = 0; i < M; i += 2) {
            int index1 = scan.nextInt();
            int index2 = scan.nextInt();
            MyGraph.Edge edge = new MyGraph.Edge();
            edge.enterNode = new MyGraph.Node(index1);
            edge.exitNode = new MyGraph.Node(index2);
            g.edges[i] = edge;

            MyGraph.Edge edge2 = new MyGraph.Edge();
            edge2.enterNode = new MyGraph.Node(index2);
            edge2.exitNode = new MyGraph.Node(index1);
            g.edges[i + 1] = edge2;
        }
        // 边关联信息
        // 将节点按照节点编号升序排列,边集按照起始节点的编号升序排列
        Arrays.sort(g.nodes);
        Arrays.sort(g.edges);

        int k = 0;// linkedEdges[]中可用的最新位置
        int index = 0;// 上一节点搜索结束后在弧集中的索引
        for (int i = 0; i < N; i++) {
            // 对于每一个节点:从弧集中找出从该节点发出的弧
            MyGraph.Node node = g.nodes[i];
            int count = 0;
            int beginIndex = k;
            while (index < g.edges.length && g.edges[index].enterNode.nodeId == node.nodeId) {
                // 如果是该节点发出的弧
                count++;// 个数加1
//                g.linkedEdges[k++]=g.edges[index].edgeId;// 将该弧存起来
                g.linkedEdges[k++] = index;// 将该弧在数组中的下标存起来
                index++;// 判断下一个弧如何
            }
            // 所有的弧都判断完后
            if (count == 0) {
                // 该节点没有任何弧发出
                node.count = 0;
                node.linkEdgesBeginIndex = -1;
            } else {
                node.count = count;
                node.linkEdgesBeginIndex = beginIndex;
            }
        }
        return g;

    }

    /**
     * 非递归的方法深度优先遍历
     *
     * @param g
     * @param vNum
     */
    public void dfs_NonRecursive(MyGraph g, int vNum) {
        boolean[] set = new boolean[g.n];
        Stack<Integer> stack = new Stack<>();


        // 最新被访问的节点在数组中的小标
        int curIndex = getIndex(g, vNum);
        if (curIndex == -1) {
            System.out.println("源节点编号输入错误,不在范围内");
            return;
        }
        // 访问源节点
        set[curIndex] = true;
        visit(g, curIndex);
        stack.push(curIndex);

        while (true) {
            // 遍历该节点的邻接节点
            int beginIndex = g.nodes[curIndex].linkEdgesBeginIndex;
            int count = g.nodes[curIndex].count;
            int nextIndex = -1;
            for (int i = beginIndex; i < beginIndex + count; i++) {
                int linkNodeNum = g.edges[g.linkedEdges[i]].exitNode.nodeId;
                int linkNodeIndex = getIndex(g, linkNodeNum);
                if (curIndex == -1) {
                    System.out.println("邻接节点编号错误");
                    return;
                }
                if (!set[linkNodeIndex]) {
                    // 未被访问,则访问
                    set[linkNodeIndex] = true;
                    visit(g, linkNodeIndex);
                    stack.push(linkNodeIndex);
                    nextIndex = linkNodeIndex;
                    curIndex = linkNodeIndex;
                    break;
                }
            }
            if (nextIndex == -1) {
                // 该节点的邻接节点都被访问了
                if (stack.isEmpty()) {
                    return;// 遍历结束
                } else {
//                    stack.pop();
                    curIndex = stack.pop();
                }
            }
        }
    }

    /**
     * 访问数组nodes中下标为curIndex的节点
     *
     * @param g
     * @param curIndex
     */
    private void visit(MyGraph g, int curIndex) {
        System.out.println(g.nodes[curIndex].nodeId + ":" + g.nodes[curIndex].nodeName);
    }

    /**
     * 得到编号为vNum的节点在数组nodes中存储的下标(二分查找)
     *
     * @param g
     * @param vNum
     * @return
     */
    private int getIndex(MyGraph g, int vNum) {
        int low = 0;
        int high = g.nodes.length - 1;
        while (low <= high) {
            int mid = (low + high) / 2;
            if (g.nodes[mid].nodeId == vNum) {
                return mid;
            } else if (g.nodes[mid].nodeId < vNum) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return -1;
    }
}

图的存储结构

package road;

import java.util.Arrays;
import java.util.List;

/**
 * <pre>
 *     author : 杨丽金
 *     time   : 2018/10/30
 *     desc   : 前向边存储结构
 *     version: 1.0
 * </pre>
 */
public class MyGraph {
    // 节点个数
    public int n;
    // 边个数
    public int e;
    // 结点集合
    public Node[] nodes;
    // 所有节点发出的所有边(按照特定的顺序排列起来)
    public int[] linkedEdges;
    // 边集合
    public Edge[] edges;

    public MyGraph(){}

    @Override
    public String toString() {
        return "MyGraph{" +
                "n=" + n +
                ", e=" + e +
                ", nodes=" + Arrays.toString(nodes) +
                ", linkedEdges=" + Arrays.toString(linkedEdges) +
                ", edges=" + Arrays.toString(edges) +
                '}';
    }


    public MyGraph(int n, int e){
        this.n=n;
        this.e=e;
        // 从数组下标0开始存储
        nodes=new Node[n];
        edges=new Edge[e];
        linkedEdges=new int[e];
    }

    public static class Node implements Comparable<Node>{
        // 节点编号
        public int nodeId;

        // 节点名称
        public String nodeName;

        // 经度
        public float longtitude;

        // 纬度
        public float latitude;

        // TODO: 2018/10/30  节点类型
        public int type;

        // 是否有红绿灯
        public boolean hasLed;

        // 从该节点发出的弧个数
        public int count;

        // 该节点发出的第一个弧在linkEdges中的下标
        public int linkEdgesBeginIndex;

        // 该节点处的转向限制
        public List<ForbiddenTurn> forbiddenTurnList;

        // 该节点数据是否为手动采集
        public boolean fromManual;

        public Node(){}

        public Node(int nodeId) {
            this.nodeId = nodeId;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "nodeId=" + nodeId +
                    ", nodeName='" + nodeName + '\'' +
                    ", longtitude=" + longtitude +
                    ", latitude=" + latitude +
                    ", type=" + type +
                    ", hasLed=" + hasLed +
                    ", count=" + count +
                    ", linkEdgesBeginIndex=" + linkEdgesBeginIndex +
                    ", forbiddenTurnList=" + forbiddenTurnList +
                    ", fromManual=" + fromManual +
                    '}';
        }

        // 定义默认访问规则
        @Override
        public int compareTo(Node o) {
            return this.nodeId-o.nodeId;
        }
    }

    public static class Edge implements Comparable<Edge>{
        // 路段编号
        public int edgeId;

        // 路段名称
        public String edgeName;

        // 道路长度
        public double length;

        // 道路宽度
        public double width;

        // 车道数
        public int laneNum;

//         道路等级:高速公路、国道、省道、县道、乡道、城市快速路等

        // 开始节点编号
        public Node enterNode;

        // 结束节点编号
        public Node exitNode;

        // TODO: 2018/10/30 路段属性

        // TODO: 2018/10/30 路段权值
        // 路段权值
        public double weight;

        // 该道路是否连通
        public boolean isConnected;

        // 是否为手动采集
        public boolean fromManual;

        public Node point1;

        public Node point2;

        public Node point3;

        public Node point4;

        public Edge() {

        }

        /*public Edge(int edgeId, int enterNodeNum, int exitNodeNum, double weight) {
            this.edgeId = edgeId;
            this.enterNodeId = enterNodeNum;
            this.exitNodeId = exitNodeNum;
            this.weight = weight;
        }*/

        @Override
        public String toString() {
            return "Edge{" +
                    "edgeId=" + edgeId +
                    ", edgeName='" + edgeName + '\'' +
                    ", length=" + length +
                    ", width=" + width +
                    ", laneNum=" + laneNum +
                    ", enterNode=" + enterNode +
                    ", exitNode=" + exitNode +
                    ", weight=" + weight +
                    ", isConnected=" + isConnected +
                    ", fromManual=" + fromManual +
                    '}';
        }

        @Override
        public int compareTo(Edge o) {
            return this.enterNode.nodeId -o.enterNode.nodeId;
        }
    }

    public static class ForbiddenTurn {
        // 结点编号
        public int nodeId;
        // 进节点编号
        public int enterNodeId;
        // 出节点编号
        public int exitNodeId;

        public ForbiddenTurn(){}

        public ForbiddenTurn(int num, int enterNodeNum, int exitNodeNum) {
            this.nodeId = num;
            this.enterNodeId = enterNodeNum;
            this.exitNodeId = exitNodeNum;
        }

        @Override
        public String toString() {
            return "ForbiddenTurn{" +
                    "nodeId=" + nodeId +
                    ", enterNodeId=" + enterNodeId +
                    ", exitNodeId=" + exitNodeId +
                    '}';
        }
    }
}

3.输出结果

10:A
11:B
13:D
17:H
14:E
18:I
12:C
15:F
16:G

参考文献

图的遍历(深度优先搜索)
图的遍历(搜索)算法(深度优先算法DFS和广度优先算法BFS)
图的深度优先遍历(递归、非递归;邻接表,邻接矩阵)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容