【算法总结】拓扑排序

简介: 【算法总结】拓扑排序

拓扑排序

理论基础

  • 对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u 和v ,若边< u , v > ∈ E ( G ) ,**则u 在线性序列中出现在v 之前。**通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。


  • 拓扑排序主要用来解决有向图中的依赖解析(dependency resolution)问题。


  • 举例来说,如果我们将一系列需要运行的任务构成一个有向图,图中的有向边则代表某一任务必须在另一个任务之前完成这一限制。那么运用拓扑排序,我们就能得到满足执行顺序限制条件的一系列任务所需执行的先后顺序。当然也有可能图中并不存在这样一个拓扑顺序,这种情况下我们无法根据给定要求完成这一系列任务,这种情况称为循环依赖(circular dependency)。


  • 拓扑排序存在的前提


  • 当且仅当一个有向图为有向无环图(directed acyclic graph,或称DAG)时,才能得到对应于该图的拓扑排序。每一个有向无环图都至少存在一种拓扑排序。


  • 如何得到一个有向无环图的拓扑排序?


  • 要想完成拓扑排序,我们每次都应当从入度为0的结点开始遍历。因为只有入度为0的结点才能够成为拓扑排序的起点。否则根据拓扑排序的定义,只要一个结点v的入度不为0,则至少有一条边起始于其他结点而指向v,那么这条边的起点在拓扑排序的顺序中应当位于v之前,则v不能成为当前遍历的起点。


  • BFS


与普通的广度优先遍历唯一的区别在于需要维护每一个节点对应的入度,并在遍历的每一层时选取入度为0的节点开始遍历(而普通的广度优先遍历则无此限制,可以从每一层任意一个节点开始遍历)。这个算法描述如下:

  1. 统计图的每一个节点的入度存储与数组inDeg。
  2. 选取入度为0的节点加入队列【贪心】
  3. 从队列中取出一个节点,

将该节点加入输出

将该节点的所有邻接点的入度树减1,减1后入度数变为0的节点加入队列

  1. 重复步骤3,直到遍历完所有的结点。
  2. 如果无法遍历完所有的结点,则意味着当前的图不是有向无环图。不存在拓扑排序。


在基于广度优先搜索的拓扑排序中,可以根据最终拓扑排序输出列表的长度是否等于图的节点数,来判断输入图是否存在拓扑排序。


  • DFS


使用深度优先搜索实现拓扑排序的基本思想是:对于一个特定节点,如果该节点的所有相邻节点都已经搜索完成,则该节点也会变成已经搜索完成的节点,在拓扑排序中,该节点位于其所有相邻节点的前面。一个节点的相邻节点指的是从该节点出发通过一条有向边可以到达的节点。


由于拓扑排序的顺序和搜索完成的顺序相反,因此需要使用一个栈存储所有已经搜索完成的节点。深度优先搜索的过程中需要维护每个节点的状态,每个节点的状态可能有三种情况:


  • 0:未访问;
  • 1:访问中;
  • 2:已访问;
  • 初始时,所有节点的状态都是「未访问」。

每一轮搜索时,任意选取一个「未访问」的节点 u,从节点 u 开始深度优先搜索。将节点 u 的状态更新为「访问中」,对于每个与节点 u 相邻的节点 v,判断节点 v 的状态,执行如下操作:


  • 如果节点 v 的状态是「未访问」,则继续搜索节点 v;
  • 如果节点 v 的状态是「访问中」,则找到有向图中的环,因此不存在拓扑排序;
  • 如果节点 v 的状态是「已访问」,则节点 v 已经搜索完成并加入输出排序列表,节点 u 尚未完成搜索,因此节点 u 的拓扑顺序一定在节点 v 的前面,不需要执行任何操作。
  • 当节点 u 的所有相邻节点的状态都是「已访问」时,将节点 u 的状态更新为「已访问」,并将节点 u 加入输出排序列表。
  • 当所有节点都访问结束之后,如果没有找到有向图中的环,则存在拓扑排序,所有节点从栈顶到栈底的顺序即为拓扑排序。

BSF实现【掌握】

int[] topoSort(int k, int[][] edges) {
        List<Integer>[] g = new ArrayList[k];
        Arrays.setAll(g, e -> new ArrayList<>());
        var inDeg = new int[k];// 入度
        for (var e : edges) {
            int x = e[0] - 1, y = e[1] - 1; // 顶点编号从 0 开始,方便计算
            g[x].add(y);
            ++inDeg[y];
        }
        var order = new ArrayList<Integer>();
        var q = new ArrayDeque<Integer>();
        for (var i = 0; i < k; ++i)
            if (inDeg[i] == 0) q.push(i);
        while (!q.isEmpty()) {
            var x = q.pop();
            order.add(x);
            for (var y : g[x])
                if (--inDeg[y] == 0) q.push(y);
        }
        return order.stream().mapToInt(x -> x).toArray();
    }
作者:灵茶山艾府
链接:https://leetcode.cn/problems/build-a-matrix-with-conditions/solutions/1781092/by-endlesscheng-gpev/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

image.png

相关题目

课程表【LC207】

你这个学期必须选修 numCourses 门课程,记为 0numCourses - 1

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai必须 先学习课程 bi

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false

思路

由于在选修课程 ai必须 先选修 bi ,因此我们可以构建一条由bi 指向 ai的边,然后使用拓扑排序,优先遍历入度为0的节点,

  • 并将其加入拓扑排序数组中,如果最后数组的大小与节点个数不相同,那么证明有向图中有环存在,不可能完成所有课程
  • 实现
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        Deque<Integer> queue = new LinkedList<>();
        int[] inDeg = new int[numCourses];
        List<Integer>[] after = new List[numCourses];
        Arrays.setAll(after, e -> new ArrayList<>());
        for (int[] pre : prerequisites){
            inDeg[pre[0]]++;
            after[pre[1]].add(pre[0]);
        }
        List<Integer> sorted = new ArrayList<>();
        for (int i = 0; i < numCourses; i++){
            if (inDeg[i] == 0){
                queue.addLast(i);
            }
        }
        while(!queue.isEmpty()){
            int x = queue.pollFirst();
            sorted.add(x);
            for (int y : after[x]){
                inDeg[y]--;
                if (inDeg[y] == 0){
                    queue.addLast(y);
                }
            }
        }
        return sorted.size() == numCourses;
    }
}


image.png

课程表 II【LC210】

现在你总共有 numCourses 门课需要选,记为 0numCourses - 1。给你一个数组

prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai必须 先选修 bi

  • 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1]

返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组

思路:拓扑排序

由于在选修课程 ai必须 先选修 bi ,因此我们可以构建一条由bi 指向 ai的边,然后使用拓扑排序,优先遍历入度为0的节点,

  • 并将其加入拓扑排序数组中,如果最后数组的大小与节点个数不相同,那么证明有向图中有环存在,不可能完成所有课程
  • 实现
class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] inDeg = new int[numCourses];
        List<Integer>[] g = new ArrayList[numCourses];
        Arrays.setAll(g, e -> new ArrayList<>());
        for (int[] p : prerequisites){
            int u = p[1], v = p[0];
            g[u].add(v);
            inDeg[v]++;
        }
        Deque<Integer> queue = new LinkedList<>();
        List<Integer> order = new ArrayList<>();
        for (int i = 0; i < numCourses; i++){
            if (inDeg[i] == 0){
                queue.addLast(i);
            }
        }
        while (!queue.isEmpty()){
            int u = queue.pollFirst();
            order.add(u);
            for (int v : g[u]){
                inDeg[v]--;
                if (inDeg[v] == 0){
                    queue.addLast(v);
                }
            }
        }
        return order.size() == numCourses ? order.stream().mapToInt(x -> x).toArray() : new int[]{};
    }
}

image.png

并行课程 III【LC2050】

给你一个整数 n ,表示有 n 节课,课程编号从 1n 。同时给你一个二维整数数组 relations ,其中 relations[j] = [prevCoursej, nextCoursej]

表示课程 prevCoursej 必须在课程 nextCoursej   之前 完成(先修课的关系)。同时给你一个下标从 0 开始的整数数组 time ,其中 time[i] 表示完成第 (i+1) 门课程需要花费的 月份 数。


请你根据以下规则算出完成所有课程所需要的 最少 月份数:

  • 如果一门课的所有先修课都已经完成,你可以在 任意 时间开始这门课程。
  • 你可以 同时任意门课程

请你返回完成所有课程所需要的 最少 月份数。

**注意:**测试数据保证一定可以完成所有课程(也就是先修课的关系构成一个有向无环图)。

  • 思路:拓扑排序+dp


image.png

class Solution {
    public int minimumTime(int n, int[][] relations, int[] time) {
        List<Integer>[] g = new List[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        int[] costs = new int[n];
        int[] inDeg = new int[n];
        Deque<Integer> queue = new LinkedList<>();
        // 邻接表
        for (int[] relation : relations){
            int u = relation[0] - 1, v = relation[1] - 1;
            g[u].add(v);
            inDeg[v]++;
        }
        // dp计算最少月份数 修完某课程的最少月份数为其先修课的最大时间+其完成需要的时间
        // dp[v] = time[v] + max(dp[u]);
        int res = 0;        
        // 入度
        for (int i = 0; i < n; i++){
            if (inDeg[i] == 0){
                queue.addLast(i);
            }
        }
        // 拓扑排序
        while (!queue.isEmpty()){
            int u = queue.pollFirst();
            costs[u] += time[u];
            res = Math.max(res, costs[u]);
            // order[index++] = u;
            for (int v : g[u]){
                costs[v] = Math.max(costs[v], costs[u]);
                if (--inDeg[v] == 0){
                    queue.addLast(v);
                }
            }             
        }
        return res;
    }
}

image.png

给定条件下构造矩阵【LC2392】

给你一个 整数 k ,同时给你:

  • 一个大小为 n 的二维整数数组 rowConditions ,其中 rowConditions[i] = [abovei, belowi]
  • 一个大小为 m 的二维整数数组 colConditions ,其中 colConditions[i] = [lefti, righti]

两个数组里的整数都是 1k 之间的数字。

你需要构造一个 k x k 的矩阵,1k 每个数字需要 恰好出现一次 。剩余的数字都是 0

矩阵还需要满足以下条件:

对于所有 0 到 n - 1 之间的下标 i ,数字 abovei 所在的 行 必须在数字 belowi 所在行的上面。

对于所有 0 到 m - 1 之间的下标 i ,数字 lefti 所在的 列 必须在数字 righti 所在列的左边。

返回满足上述要求的 任意 矩阵。如果不存在答案,返回一个空的矩阵。

思路

  • 通过rowConditionscolConditions获得数字所在行和所在列的拓扑排序,如果不存在拓扑排序,那么返回空矩阵
  • 然后通过拓扑排序将数字填入相应位置,先遍历行的拓扑排序记录每个数字应填入的行,然后再遍历列拓扑,将每个数字填入对应的位置
  • 实现
class Solution {
    public int[][] buildMatrix(int k, int[][] rowConditions, int[][] colConditions) {
        int[] rowOrder = topoSort(k, rowConditions);
        int[] colOrder = topoSort(k, colConditions);
        if (rowOrder.length < k || colOrder.length < k) return new int[][]{};
        int[][] res = new int[k][k];
        int[] pos = new int[k + 1];// 记录数字i位于的行
        for (int i = 0; i < k; i++){
            pos[rowOrder[i]] = i;
        }
        for (int i = 0; i < k; i++){
            res[pos[colOrder[i]]][i] = colOrder[i];
        }
        return res;
    }
    public int[] topoSort(int k, int[][] edges){
        List<Integer>[] g = new List[k + 1];
        Arrays.setAll(g, e -> new ArrayList<>());
        int[] inDeg = new int[k + 1];
        Deque<Integer> q = new LinkedList<>();
        List<Integer> res = new ArrayList<>();
        for (int[] edge : edges){
            int u = edge[0], v = edge[1];
            g[u].add(v);
            inDeg[v]++;
        }
        for (int i = 1; i <= k; i++){
            if (inDeg[i] == 0){
                q.addLast(i);
            }
        }
        while(!q.isEmpty()){
            int u = q.pollFirst();
            res.add(u);
            for (int v : g[u]){
                if (--inDeg[v] == 0){
                    q.addLast(v);
                }
            }
        }
        return res.stream().mapToInt(x -> x).toArray();
    }
}

image.png

收集树中金币【LC2603】

给你一个 n 个节点的无向无根树,节点编号从 0n - 1 。给你整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点 aibi 之间有一条

边。再给你一个长度为 n 的数组 coins ,其中 coins[i] 可能为 0 也可能为 11 表示节点 i 处有一个金币。

一开始,你需要选择树中任意一个节点出发。你可以执行下述操作任意次:

  • 收集距离当前节点距离为 2 以内的所有金币,或者
  • 移动到树中一个相邻节点。

你需要收集树中所有的金币,并且回到出发节点,请你返回最少经过的边数。

如果你多次经过一条边,每一次经过都会给答案加一。


思路:拓扑排序

  • 首先,我们可以去掉不包含金币的子树,因为访问其中任何一个点都毫无意义。
  • 如果所有在叶子上的金币全部都能收集到,那么我们可以收集到树上所有金币。因此可以去除两轮叶子,剩余的结点即为必须经过的结点【两轮拓扑排序】
  • 从距离叶子为2的节点处出发【局部最优】,收集树中所有的金币,并且回到出发节点时经过的边数最少【全局最优】。
  • 实现
  • 无向图,节点度为1时为叶子
class Solution {
    public int collectTheCoins(int[] coins, int[][] edges) {
        int n = coins.length;
        List<Integer> g[] = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        var deg = new int[n];
        for (var e : edges) {
            int x = e[0], y = e[1];
            g[x].add(y);
            g[y].add(x); // 建图
            ++deg[x];
            ++deg[y];
        }
        // 用拓扑排序「剪枝」:去掉没有金币的子树
        var q = new ArrayDeque<Integer>();
        for (int i = 0; i < n; ++i)
            if (deg[i] == 1 && coins[i] == 0) // 无金币叶子
                q.add(i);
        while (!q.isEmpty()) {
            int x = q.peek();
            q.pop();
            for (int y : g[x])
                if (--deg[y] == 1 && coins[y] == 0)
                    q.add(y);
        }
        // 再次拓扑排序
        for (int i = 0; i < n; ++i)
            if (deg[i] == 1 && coins[i] == 1) // 有金币叶子
                q.add(i);
        if (q.size() <= 1) return 0; // 至多一个有金币的叶子,直接收集
        var time = new int[n];
        while (!q.isEmpty()) {
            int x = q.peek();
            q.pop();
            for (int y : g[x])
                if (--deg[y] == 1) {
                    time[y] = time[x] + 1; // 记录入队时间
                    q.add(y);
                }
        }
        // 统计答案
        int ans = 0;
        for (var e : edges)
            if (time[e[0]] >= 2 && time[e[1]] >= 2)
                ans += 2;
        return ans;
    }
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/collect-coins-in-a-tree/solutions/2191371/tuo-bu-pai-xu-ji-lu-ru-dui-shi-jian-pyth-6uli/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

image.png

  • 升级:如果把题目中的 2 换成 0,1,2,3,⋯ ,n−1,你能把这些情况对应的答案全部算出来吗?
    遍历所有的边,如果两个节点的入队时间均大于q(最大距离),那么表示这条边必须要经过,结果+2

有向图中最大颜色值【LC1857】

给你一个 有向图 ,它含有 n 个节点和 m 条边。节点编号从 0n - 1

给你一个字符串 colors ,其中 colors[i] 是小写英文字母,表示图中第 i 个节点的 颜色

(下标从 0 开始)。同时给你一个二维数组 edges ,其中 edges[j] = [aj, bj] 表示从节点 aj 到节点 bj 有一条 有向边

图中一条有效 路径 是一个点序列 x1 -> x2 -> x3 -> ... -> xk ,对于所有 1 <= i < k ,从 xixi+1 在图中有一条有向边。路径的 颜色值 是路径中 现次数最多 颜色的节点数目。

请你返回给定图中有效路径里面的 最大颜色值 **。**如果图中含有环,请返回 -1

image.png

  • 每个节点在其对应的颜色处数量+1,然后更新最大颜色值
  • 实现
class Solution {
    public int largestPathValue(String colors, int[][] edges) {
        int n = colors.length();
        int[] inDeg = new int[n];
        List<Integer>[] g = new List[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        Deque<Integer> q = new LinkedList<>();
        List<Integer> order = new ArrayList<>();
        int[][] dp = new int[n][26]; // dp[i][c] 以节点i为终点的所有路径中,包含c的节点数量的最大值
        int res = 0;
        for (int[] edge : edges){
            int u = edge[0], v = edge[1];
            g[u].add(v);
            inDeg[v]++;
        }
        for (int i = 0; i < n; i++){
            if (inDeg[i] == 0){
                q.addLast(i);
            }  
        }
        while (!q.isEmpty()){
            int u = q.pollFirst();
            order.add(u);
            dp[u][colors.charAt(u) - 'a']++;
            res = Math.max(res, dp[u][colors.charAt(u) - 'a']);
            for (int v : g[u]){
                if (--inDeg[v] == 0){
                    q.addLast(v);
                }
                for (int i = 0; i < 26; i++){
                    dp[v][i] = Math.max(dp[v][i], dp[u][i]);
                    res = Math.max(res, dp[v][i]);
                }
            }
        }
        if (order.size() < n) return -1;
        return res;
    }
}

image.png

火星字典【LC269】

现有一种使用英语字母的火星语言,这门语言的字母顺序对你来说是未知的。

给你一个来自这种外星语言字典的字符串列表 wordswords 中的字符串已经 按这门新语言的字母顺序进行了排序

如果这种说法是错误的,并且给出的 words 不能对应任何字母的顺序,则返回 ""

否则,返回一个按新语言规则的 字典递增顺序 排序的独特字符串。如果有多个解决方案,则返回其中 任意一个

  • 实现
class Solution {
    public String alienOrder(String[] words) {
        // 拓扑排序
        // 某个字母之前必须有其他某些拓扑排序在它之前的字母
        // 根据相邻单词字母顺序找到拓扑排序,找到第一个不一样的字母,words[i-1][j]出现在 words[i][j]之前,如果words[i-1]包含words[i],那么非法
        // 用mask表示 某位为1时,对应字母必须在该字母之前
        Map<Character, Integer> inDeg = new HashMap<>();
        // 先初始化每个字母
        for (String s : words){
            for (int i = 0; i < s.length(); i++){
                inDeg.putIfAbsent(s.charAt(i), 0);
            }
        }
        // 每个单词与前一个单词的顺序
        for (int i = 1; i < words.length; i++){
            int j = 0;
            while (j < words[i].length() && j < words[i - 1].length() &&
                    words[i].charAt(j) == words[i - 1].charAt(j)){
                j++;
            }
            if (j < words[i].length() && j < words[i - 1].length() &&
                words[i].charAt(j) != words[i - 1].charAt(j)){
                int mask = inDeg.getOrDefault(words[i].charAt(j), 0);
                mask |= (1 << (words[i - 1].charAt(j) - 'a'));
                inDeg.put(words[i].charAt(j), mask);
            }else if (j < words[i - 1].length()){// 特殊判断,后一个字符串包含在前一个字符串中
                return "";
            }
        }
        // 拓扑排序
        Deque<Character> queue = new LinkedList<>();
        StringBuilder sb = new StringBuilder();
        int size = inDeg.size();
        for (char c = 'a'; c <= 'z'; c++){
            if (inDeg.containsKey(c) && inDeg.get(c) == 0){
                queue.addLast(c);
                inDeg.remove(c);
            }
        }
        while (!queue.isEmpty()){
            Character u = queue.pollFirst();
            sb.append(u);
            int val = u - 'a';
            for (char c = 'a'; c <= 'z'; c++){
                if (inDeg.containsKey(c)){
                    int mask = inDeg.get(c);
                    if (((inDeg.get(c) >> val) & 1) == 1){
                        mask ^= (1 << val);
                    }
                    if (mask == 0){
                        queue.addLast(c);
                        inDeg.remove(c);
                    }else{
                        inDeg.put(c, mask);
                    }
                }
            } 
        }
        return sb.length() == size ? sb.toString() : "";
    }
}


目录
相关文章
|
6月前
|
算法 C++
【洛谷 P1223】排队接水(贪心算法+结构体排序)
该问题要求安排$n$个人的排队顺序以最小化平均等待时间。每个人接水需时$T_i$,解决方案是让接水时间短的人优先。给定$n\leq1000$和$t_i\leq10^6$,代码示例使用C++实现,通过排序使时间从小到大排列,然后计算平均等待时间。样例输入为10个人的时间数组,输出为优化后的排队顺序及平均等待时间(291.90)。
62 0
|
4月前
|
算法
【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置
【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
127 7
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
106 8
|
2月前
|
搜索推荐 Shell
解析排序算法:十大排序方法的工作原理与性能比较
解析排序算法:十大排序方法的工作原理与性能比较
86 9
|
2月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
41 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
2月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
34 0
|
2月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
82 0
|
4月前
|
搜索推荐 算法 Java
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
该博客文章通过UML类图和Java源码示例,展示了如何使用适配器模式将QuickSort类和BinarySearch类的排序和查找功能适配到DataOperation接口中,实现算法的解耦和复用。
56 1
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
|
4月前
|
算法 搜索推荐 Java
算法实战:手写归并排序,让复杂排序变简单!
归并排序是一种基于“分治法”的经典算法,通过递归分割和合并数组,实现O(n log n)的高效排序。本文将通过Java手写代码,详细讲解归并排序的原理及实现,帮助你快速掌握这一实用算法。
49 0