【Java高阶数据结构】图补充-拓扑排序

简介: Java高阶数据结构 & 图补充-拓扑排序1. 什么是拓扑排序 讲得很好哦!

Java高阶数据结构 & 图补充-拓扑排序

1. 什么是拓扑排序


图片来源:简单、快速地带你了解图论以及拓扑排序!_哔哩哔哩_bilibili


   讲得很好哦!


这里我以羊了个羊小游戏这款砖块消除类小游戏为例:



有一个规则:



   当上层砖块覆盖下层砖块的时候,下层砖块不可以被选中移动(暗)

   上层砖块移走后,下层砖块才能被移动(亮)


则可以用如下图表示这种逻辑结构:



   A->B,代表A覆盖B

   即一种无环不带权有向图


   带回路的话,那么这个顶点就相当于自己覆盖了自己,不合理

   无向的话,一条边则是双向的,同1不合理

   箭头代表覆盖关系,权值不重要



那么我们给每个砖块一个layer值:



要求:


   layer值大的砖块显示上,覆盖layer值小的砖块

   即A->B,layer(A) > layer(B)


   在开发过程中需要这个layer,至于用处是什么,不做解释,这里这不是重点。


       至于这款游戏的原理,感兴趣的可以去网上找找资料研究一下


我们只需要找到一个序列,保证上方的覆盖者要在被覆盖者前方,再为其安排一个递减的序列



那么问题是:如何生成这个序列呢?


   这个排序,就叫做 “拓扑排序”


类似的,如果你需要学习一系列的课程,那么一些知识一定需要另一些知识作为基础~



   而显然的,符合实际的是:这种序列不应该唯一

       学习路径肯定不一定单一呀~

   事实上也如此~

       一个图的拓扑排序很有可能有多解


2. 拓扑排序算法思想-卡恩算法


   本文章用邻接表的存储结果!


基础知识传送门:Java高阶数据结构 & 图 & 图的表示与遍历_s:103的博客-CSDN博客


例子:



前面提到,如果成环则没有拓扑序列,那么我们也可以通过能否拓扑排序,来判断一个图是否带环


   这也算是拓扑排序的一个额外得到功能吧


这里只讲解一种简单直接的算法,卡恩算法


   重点在于每个顶点的入度


步骤:


   遍历一遍邻接表,计算所有顶点的入度

   挑选一个入度为0的顶点,并输出

   刚才挑中的顶以及其指向的顶点的入度都减1

       -1的入度代表此顶点被删除

   挑选一个入度为0的顶点,并输出

   刚才挑中的顶以及其指向的顶点的入度都减1

   重复这个操作,直到所有顶点都被删除(入度都为-1)

       如果最终是因为没有入度为0的顶点而不是全部顶点都被删除而停止的循环,则说明存在环


疑问:


1. 你很快会意识到,如果出现多个入度为0的顶点,应该怎么办?


答:先挑选谁都无所谓,因为这两个入度为0的顶点,是一种并列的关系,不会相互影响。即使他们有可能分支下去会有公共顶点,这也不会导致“乱了规则”的现象,因为“汇聚的第一个公共顶点”入度至少为2,只有其入度为0的时候才能被选中


   也就是说,只有如果先选中的入度为0的顶点输出后,诞生新的入度为0的顶点,一定也与原来入度为0的顶点也是并列

   这也是序列不唯一的原因~


2. 为什么入度为0的顶点先被选中?


答:这很显然,入度为0,代表没人指向它


   也就是说,它没被覆盖,而只覆盖别人!

   万物之源


算法复杂度分析:


       每个顶点和每条边都刚好被访问一次


   V因为顶点数,E为边数


       则时间复杂度为O(V + E)


动图演示:


3. 拓扑排序代码实现


   我这边直接延用之前实现邻接表的代码(自制API)

   获取:Java高阶数据结构 & 图 & 图的表示与遍历_s:103的博客-CSDN博客


以下代码就不做过多解释了,完全依照刚才的算法思想~


3.1 遍历链表计算入度


//获取所有顶点的入度
public int[] getDevs() {
    int n = arrayV.length;
    int[] arr = new int[n];
    for (int i = 0; i < n; i++) {
        Node cur = edgeList.get(i);
        while(cur != null) {
            arr[cur.dest]++;
            cur = cur.next;
        }
    }
    return arr;
}




3.2 挑选一个入度为0的顶点


   你也可以结合isvVsted数组(标记数组)和堆去存储,节约时间

   这里用最简单的遍历法

public int getFirstZero(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        if(arr[i] == 0) {
            return i;
        }
    }
    return -1;
}



3.3 输出顶点


   这里我将顶点输出到队列里了

public void outputV(int index, int[] arr, Queue<Character> queue) {
    queue.offer(arrayV[index]);
    arr[index]--;
    Node cur = edgeList.get(index);
    while(cur != null) {
        arr[cur.dest]--;
        cur = cur.next;
    }
}


 


3.4 判断循环结束是否为全-1


public boolean isContainCir(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        if(arr[i] != -1) {
            return true;
        }
    }
    return false;
}


 


3.4 kahn方法


public boolean kahn(Queue<Character> queue) {
    int[] arr = getDevs();
    int index = getFirstZero(arr);
    while(index != -1) {
        outputV(index, arr, queue);
        index = getFirstZero(arr);
    }
    return isContainCir(arr);
}



3.5 测试


public static void main(String[] args) {
    //定义与构建图
    char[] chars = "012345678".toCharArray();
    GraphByList graph = new GraphByList(chars.length, true);
    graph.initArrayV(chars);
    graph.addEdge('0', '1', 1);
    graph.addEdge('0', '2', 1);
    graph.addEdge('1', '3', 1);
    graph.addEdge('2', '3', 1);
    graph.addEdge('2', '4', 1);
    graph.addEdge('4', '3', 1);
    graph.addEdge('6', '0', 1);
    graph.addEdge('7', '0', 1);
    graph.addEdge('7', '6', 1);
    graph.addEdge('8', '5', 1);
    //定义队列
    Queue<Character> queue = new LinkedList<>();
    boolean flag = graph.kahn(queue);
    System.out.println(flag ? "带环" : "不带环");
    System.out.println(queue);
}




目录
相关文章
|
5月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
153 1
|
3月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
189 3
|
5月前
|
存储 Java 编译器
Java 中 .length 的使用方法:深入理解 Java 数据结构中的长度获取机制
本文深入解析了 Java 中 `.length` 的使用方法及其在不同数据结构中的应用。对于数组,通过 `.length` 属性获取元素数量;字符串则使用 `.length()` 方法计算字符数;集合类如 `ArrayList` 采用 `.size()` 方法统计元素个数。此外,基本数据类型和包装类不支持长度属性。掌握这些区别,有助于开发者避免常见错误,提升代码质量。
434 1
|
7月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
280 29
|
8月前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
195 10
|
8月前
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
146 10
|
8月前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
196 7
|
9月前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
137 5
|
10月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
165 6
|
10月前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。