自定义有向图的深度优先遍历(DFS)

何为图(Graph)

形似如下抽象结构:
抽线图

这看起来的确有点抽象 ̄□ ̄||。。

我们再看一张:
抽象树

这是一张二叉树,我在之前图的基础上减去了几根“联系”就变成了树。

所以在一定程度上,可以把图理解为树的延伸(图进一步的打破了树的“规矩”)。

程序中的“图”

图的存储

  • 需要一个集合(Set)来存储我们的节点元素。
  • 需要一个映射(HashMap)来存储节点是否被访问过。
  • 需要一个 HashMap<T, ArrayList> 来存储节点间的通路。

如下图所示:
图的内存模型

代码实现

DFS深度优先遍历算法也在里面。


import java.util.*;

/**
 * @ClassName ArrayGraph
 * @Description 自定义“有向图”class,不允许有重复的元素
 * @Author SkySong
 * @Date 2021-05-16 17:14
 */
public class ArrayGraph<T> {
    //存放节点元素
    private Set<T> vars;
    //标记节点是否被访问过
    private HashMap<T, Boolean> visit;
    //节点间的通路
    private HashMap<T, ArrayList<T>> accesses = new HashMap<>();

    /**
     * 清空访问
     */
    public void clearVisit() {
        try {
            vars.forEach((k) -> visit.put(k, false));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化节点集合
     *
     * @param vars 节点集合
     */
    public ArrayGraph(Set<T> vars) {
        this.vars = vars;
        visit  = new HashMap<>();
        clearVisit();
    }

    /**
     * 添加节点间通路
     *
     * @param from 出发节点
     * @param to   目的节点
     */
    public void addAccess(T from, T to) {
        if (!vars.contains(from) && !vars.contains(to)) {
            return;
        }
        ArrayList<T> ts = accesses.get(from);
        if (ts == null) {
            ts = new ArrayList<>();
        }
        ts.add(to);
        accesses.put(from, ts);
    }

    /**
     * DFS 深度优先遍历
     *
     * @param head 起始节点
     * @return 图 “变” 数组
     */
    public List<T> DFSOrder(T head) {
        if (!vars.contains(head)) {
            return null;
        }
         //创建一个list,用来存放最终的有序 序列
        ArrayList<T> list = new ArrayList<>();
        //毫无疑问,第一个遍历的节点元素一定是 我们传进去的 head
        list.add(head);
        //确定 head 的通路(head通向的节点)
        ArrayList<T> ts = accesses.get(head);
        if (ts == null || ts.isEmpty()){
            visit.put(head,true);
            return list;
        }
        //改变 head 的访问状态
        visit.put(head,true);
        Stack<T> stack = new Stack<>();
        stack.push(head);
        while (!stack.isEmpty()){
            int index = 0;
            for (T t : ts) {
                //如果此节点已经访问过了,我们就不做任何操作
                if (visit.get(t)){
                    continue;
                }
                //如果此节点没有访问过,访问之
                list.add(t);
                //改变节点访问状态
                visit.put(t,true);
                //探寻此节点的下一层“通路”
                ts = accesses.get(t);
                if (ts != null){
                    //如果此节点下一层有“通路”,便将此节点放入栈中
                    stack.push(t);
                    //并改变标志位,跳过出栈操作
                    index++;
                }
                break;
            }
            //如果此节点没有下一层可以访问,则触发出栈操作,去上一层寻找
            if (index == 0){
                T pop = stack.pop();
                ts = accesses.get(pop);
            }
        }
        return list;
    }
}

针对这个DFS,我们大致阐述一下思路。

首先明确一点,图的遍历是需要确定一个起始节点的。(这也是我们这个DFS方法的参数)。

  • 首先我们需要一个栈,这个栈是完成我们“深度优先”的主要工具

    何为“深度优先”,当我们在访问过程中,遇到分支了,优先选择下一层的分支进行访问。

    我们利用栈“先进后出”的特性,将当前节点的“上级”们先逐层保存起来。确保我们能向上逐层找到他们。

  • 我们开始进行访问,每层访问一个节点便向下层访问,当我们发现不能向下了(与当前节点相连的节点都被访问过了),就往上倒一层,然后重复上述过程。

测试

public static void main(String[] args) {
        ArrayGraph<Integer> graph = new ArrayGraph<>(Sets.newHashSet(1,2,3,4,5));
        graph.addAccess(1,2);
        graph.addAccess(1,3);
        graph.addAccess(2,4);
        graph.addAccess(3,4);
        graph.addAccess(5,1);
        
        System.out.println(graph.DFSOrder(5).toString());
    }

结果:

[5, 1, 2, 4, 3]

附上图,大家自行脑补验证:
有向图

拓展

本例说的是有向图,其实也可以当做无向图来用:

当我们在添加“通路”时,顺便把反向的也添加进去,便可以实现了。

本例说的是不允许重复元素,其实可以变向思考一下这个问题。

在图的实际应用中,元素经常是引用对象,是比较复杂的数据结构,也许他们里面的内容相同,但他们的引用地址不同,所以在一定程度上“相同”也是可以实现的。

That’s all,thank you ! ! !

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
深度优先遍历是一种遍历图的方法,它从给定的起点开始,沿着一条路径尽可能深地访问图中的顶点,直到到达不能继续访问的顶点为止,然后回溯到前一个顶点,继续访问它的其他邻接顶点,重复上述过程,直到遍历完所有可以到达的顶点。 对于给定的有向图深度优先遍历的过程中,可以利用栈来保存当前节点的"上级"们,以确保能够向上逐层找到它们。 在深度优先遍历的过程中,我们选择编号最小的待访问顶点,以顶点0为遍历起点。 具体的深度优先遍历算法可以按照以下步骤进行: 1. 将起点0入栈,并标记起点为已访问。 2. 当栈不为空时,取出栈顶元素作为当前节点。 3. 遍历当前节点的邻接顶点,如果邻接顶点未被访问过,则将它入栈,并标记为已访问。 4. 如果当前节点没有未被访问过的邻接顶点,则回溯到上一个节点,继续遍历其其他邻接顶点。 5. 重复步骤2-4,直到栈为空。 这样就完成了有向图深度优先遍历。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [图深度优先遍历](https://blog.csdn.net/m0_63040123/article/details/127656322)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [自定义有向图深度优先遍历DFS)](https://blog.csdn.net/weixin_43415201/article/details/117229439)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

少歌

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

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

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

打赏作者

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

抵扣说明:

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

余额充值