【愚公系列】2023年12月 五大常用算法(一)-分治算法

举报
愚公搬代码 发表于 2023/12/31 06:28:08 2023/12/31
【摘要】 分治算法的基本思想是将一个大问题分解成若干个子问题,递归地解决每个子问题,然后将每个子问题的解合并起来得出整个问题的解。分治算法的基本步骤为:

🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏

🚀前言

五大常用算法的特点如下:

  1. 分治:将一个大问题拆分成若干个小问题,分别解决,然后将解决结果合并起来得到整个问题的解。分治算法的特点是递归,效率高,但对数据的规律要求比较高,需要较高的算法设计技巧。常见应用领域为排序、查找和统计等。

  2. 动态规划:将一个大问题分解成若干个小问题,通过寻找子问题之间的递推关系,求解小问题的最优解,然后将小问题的最优解组合起来解决整个大问题。动态规划的特点是可以解决具有重叠子问题的问题,但需要较高的时间和空间复杂度。常见应用领域为求解最大子序列、背包问题等。

  3. 贪心:在处理问题的过程中,每次做出局部最优的选择,希望通过局部最优的选择达到全局最优。贪心算法的特点是快速、简单,但是无法保证每个局部最优解会导致全局最优解。常见应用领域为最小生成树、活动安排等。

  4. 回溯:通过不断尝试局部的解,如果不满足要求就回溯返回,直到找到解为止。回溯算法的特点是可以解决多种类型的问题,但需要搜索所有可能的解,时间复杂度较高。常见应用领域为八皇后问题、排列组合问题等。

  5. 分支限界:与回溯算法相似,但是在搜索的过程中,通过剪枝操作来减少搜索的空间,提高算法效率。常见应用领域为旅行商问题、图着色问题等。

🚀一、分治算法

🔎1.基本思想

分治算法的基本思想是将一个大问题分解成若干个子问题,递归地解决每个子问题,然后将每个子问题的解合并起来得出整个问题的解。分治算法的基本步骤为:

  1. 分解问题:将原问题分解成若干个子问题,这些子问题应该是相互独立的。

  2. 解决子问题:递归地解决每个子问题,直到子问题可以直接求解为止。子问题的求解顺序不影响最终结果。

  3. 合并问题:将每个子问题的解合并起来,得出整个问题的解。

分治算法主要适用于具有重复性的问题,且子问题之间相互独立。常见的分治算法包括归并排序、快速排序、二分查找、最大子数组问题等。

在这里插入图片描述

🦋1.1 分治问题特征

分治问题通常具有以下特征:

  1. 问题可分解为若干个子问题,且子问题与原问题类型相同或类似。

  2. 子问题之间相互独立,即解决一个子问题不影响其他子问题的解决。

  3. 子问题的解可以合并为原问题的解。

  4. 子问题规模能够逐步缩小,最终达到较小规模,可以使用直接求解的方法。

如果一个问题具备以上特征,那么就可以考虑采用分治算法来解决这个问题。

🦋1.2 通过分治提升效率

分治是一种常见的算法设计思想,它将一个大问题划分成多个小问题,递归地解决每个小问题,最终将它们合并成一个解。通过分治思想设计的算法能够提升效率,原因如下:

  1. 减少问题规模:将一个大问题分解成多个小问题,可以减少每个小问题的复杂度,进而降低算法的时间复杂度和空间复杂度。

  2. 利用并行处理:分治算法天然适合并行处理,可以将一个大问题划分给多个处理器,同时解决多个小问题,最终合并得到结果。这样可以节省计算时间,提高算法效率。

  3. 利用缓存:通过分治算法设计的算法可以有效地利用缓存。对于大问题,每次递归的过程中需要重复计算很多数据,如果能够将这些数据缓存下来,在后续递归的过程中直接使用,就可以减少重复计算,提高算法效率。

  4. 便于调试和维护:分治算法将大问题拆分为多个小问题,每个小问题都很清晰明了。这样便于调试和维护,可以快速定位问题并修改。

通过分治算法设计的算法能够有效地提升效率,减少时间复杂度和空间复杂度,利用并行处理和缓存等技术,适用于处理复杂的大问题。

☀️1.2.1 操作数量优化

分治算法是一种将问题划分为多个子问题,并将子问题递归地解决,最终将子问题的解合并成原问题解的算法。在操作数量优化方面,分治算法可以提高算法的效率。

首先,分治算法可以将一个大问题划分为多个小问题,从而减少每个子问题的规模。当问题的规模变小后,算法的复杂度也会降低。

其次,分治算法可以充分利用计算机的多核处理能力。由于每个子问题的解可以独立地计算,因此可以将不同的子问题分配给不同的处理器同时计算,从而加快算法的速度。

最后,分治算法还可以使用递归算法进行优化。由于分治算法本身就是递归地解决子问题,因此可以利用递归算法的特性来简化代码,使代码更加清晰易懂,并且可以降低算法的复杂度。

分治算法在操作数量优化方面具有明显的优势,可以提高算法的效率,减少计算时间和资源开销。

在这里插入图片描述

☀️1.2.2 并行计算优化

分治算法通常可以在并行计算中得到优化。由于分治算法的特点是将问题划分为更小的子问题,并且这些子问题是相互独立的,因此可以将这些子问题分配给不同的处理器或线程进行并行计算。这样可以有效地提高计算效率,缩短计算时间。

在并行计算中,有许多并行编程模型可以用来实现分治算法的并行化。例如,OpenMP和MPI都是常用的并行编程模型,可以用来实现并行分治算法。在这些模型中,可以使用一些技术来实现负载平衡、数据通信和同步等问题,以确保并行计算的正确性和效率。

此外,还有许多优化技术可以用于提高并行分治算法的效率。例如,可以使用内存池和缓存等技术来减少内存分配和访问的开销,从而提高算法的处理速度。还可以使用预取技术来减少计算时的等待时间,以及使用分支预测技术来优化递归算法的执行效率。

分治算法在并行计算中具有很好的优化潜力,可以通过多种技术来提高并行计算的效率和速度。

在这里插入图片描述

🦋1.3 分治常见应用

  1. 寻找最近点对:通过分治算法将点集分为左右两个部分,然后递归求解这两部分中的最近点对,最后考虑跨越两个部分的最近点对。

  2. 大整数乘法:将两个大整数分别划分为较小的部分,然后递归计算每个部分的乘积,最后将这些部分的乘积合并起来。

  3. 矩阵乘法:将两个矩阵分别划分为较小的部分,然后递归计算每个部分的乘积,最后将这些部分的乘积合并起来。

  4. 汉诺塔问题:将 n 个盘子从原始柱子移动到目标柱子,需要将 n-1 个盘子从原始柱子移动到辅助柱子,然后将最后一个盘子从原始柱子移动到目标柱子,最后将 n-1 个盘子从辅助柱子移动到目标柱子,递归执行这个过程即可。

  5. 求解逆序对:将数组划分为两个部分,递归计算每个部分的逆序对数,然后考虑跨越两个部分的逆序对数。可以使用归并排序的思想来实现。在归并的时候,统计两个有序子数组之间的逆序对数,将逆序对数加到最终结果中。

🔎2.分治搜索策略

二分查找也称为折半查找,是一种常用的查找算法,它的优点是查找速度快,时间复杂度为O(logn)。基于分治算法实现二分查找的步骤如下:

  1. 将数组或列表按照中间元素分为两部分,如果中间元素等于目标元素,则查找成功。

  2. 如果中间元素大于目标元素,则在左半部分继续查找,否则在右半部分继续查找。

  3. 重复以上步骤,直到目标元素被找到或查找区间为空。

下面给出一个基于分治算法实现二分查找的Python代码示例:

/**
* File: binary_search_recur.cs
* Created Time: 2023-07-18
* Author: hpstory (hpstory1024@163.com)
*/

namespace hello_algo.chapter_divide_and_conquer;

public class binary_search_recur {
    /* 二分查找:问题 f(i, j) */
    public int dfs(int[] nums, int target, int i, int j) {
        // 若区间为空,代表无目标元素,则返回 -1
        if (i > j) {
            return -1;
        }
        // 计算中点索引 m
        int m = (i + j) / 2;
        if (nums[m] < target) {
            // 递归子问题 f(m+1, j)
            return dfs(nums, target, m + 1, j);
        } else if (nums[m] > target) {
            // 递归子问题 f(i, m-1)
            return dfs(nums, target, i, m - 1);
        } else {
            // 找到目标元素,返回其索引
            return m;
        }
    }

    /* 二分查找 */
    public int binarySearch(int[] nums, int target) {
        int n = nums.Length;
        // 求解问题 f(0, n-1)
        return dfs(nums, target, 0, n - 1);
    }

    [Test]
    public void Test() {
        int target = 6;
        int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 };

        // 二分查找(双闭区间)
        int index = binarySearch(nums, target);
        Console.WriteLine("目标元素 6 的索引 = " + index);
    }
}

在这里插入图片描述

🔎3.构建二叉树问题

分治算法可以用来构建二叉树,具体步骤如下:

  1. 找到子问题:将给定的序列分成两个部分,分别构建二叉树。

  2. 解决子问题:对于每个子问题,递归地调用该算法,直到子问题不能再进一步分割。

  3. 合并子问题的解:将子问题的解合并成整个问题的解。对于左右两个子问题,我们可以将左半部分的序列的中间节点作为根节点,右半部分的序列的中间节点作为其右孩子节点,然后递归地构建左子树和右子树。

  4. 返回结果:返回构建好的二叉树。

在这里插入图片描述

代码实现如下(假设给定的序列是有序的)

public class build_tree {
    /* 构建二叉树:分治 */
    public TreeNode dfs(int[] preorder, Dictionary<int, int> inorderMap, int i, int l, int r) {
        // 子树区间为空时终止
        if (r - l < 0)
            return null;
        // 初始化根节点
        TreeNode root = new TreeNode(preorder[i]);
        // 查询 m ,从而划分左右子树
        int m = inorderMap[preorder[i]];
        // 子问题:构建左子树
        root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);
        // 子问题:构建右子树
        root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);
        // 返回根节点
        return root;
    }

    /* 构建二叉树 */
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        // 初始化哈希表,存储 inorder 元素到索引的映射
        Dictionary<int, int> inorderMap = new Dictionary<int, int>();
        for (int i = 0; i < inorder.Length; i++) {
            inorderMap.TryAdd(inorder[i], i);
        }
        TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.Length - 1);
        return root;
    }

    [Test]
    public void Test() {
        int[] preorder = { 3, 9, 2, 1, 7 };
        int[] inorder = { 9, 3, 1, 2, 7 };
        Console.WriteLine("前序遍历 = " + string.Join(", ", preorder));
        Console.WriteLine("中序遍历 = " + string.Join(", ", inorder));

        TreeNode root = buildTree(preorder, inorder);
        Console.WriteLine("构建的二叉树为:");
        PrintUtil.PrintTree(root);
    }
}

在这里插入图片描述

🔎4.汉诺塔问题

在这里插入图片描述
汉诺塔问题可以通过分治算法来解决。分治算法是将问题分解成若干个小的子问题来解决,最后将子问题的结果合并起来得到最终的解决方案。

在汉诺塔问题中,我们可以将塔分解为三个部分,分别为起始塔、中转塔和目标塔。

  1. 将起始塔的前n-1个盘子移动到中转塔上。

  2. 将起始塔上的第n个盘子移动到目标塔上。

  3. 将中转塔上的n-1个盘子移动到目标塔上。

这个过程可以看做是一个递归过程。我们可以使用递归函数来将问题不断分解为更小的子问题,直到子问题变得简单明了,并求出其解决方案,然后再将子问题的解合并为原问题的解。

下面是使用分治算法来解决汉诺塔问题的代码:

public class hanota {
    /* 移动一个圆盘 */
    public void move(List<int> src, List<int> tar) {
        // 从 src 顶部拿出一个圆盘
        int pan = src[^1];
        src.RemoveAt(src.Count - 1);
        // 将圆盘放入 tar 顶部
        tar.Add(pan);
    }

    /* 求解汉诺塔:问题 f(i) */
    public void dfs(int i, List<int> src, List<int> buf, List<int> tar) {
        // 若 src 只剩下一个圆盘,则直接将其移到 tar
        if (i == 1) {
            move(src, tar);
            return;
        }
        // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf
        dfs(i - 1, src, tar, buf);
        // 子问题 f(1) :将 src 剩余一个圆盘移到 tar
        move(src, tar);
        // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar
        dfs(i - 1, buf, src, tar);
    }

    /* 求解汉诺塔 */
    public void solveHanota(List<int> A, List<int> B, List<int> C) {
        int n = A.Count;
        // 将 A 顶部 n 个圆盘借助 B 移到 C
        dfs(n, A, B, C);
    }

    [Test]
    public void Test() {
        // 列表尾部是柱子顶部
        List<int> A = new List<int> { 5, 4, 3, 2, 1 };
        List<int> B = new List<int>();
        List<int> C = new List<int>();
        Console.WriteLine("初始状态下:");
        Console.WriteLine("A = " + string.Join(", ", A));
        Console.WriteLine("B = " + string.Join(", ", B));
        Console.WriteLine("C = " + string.Join(", ", C));

        solveHanota(A, B, C);

        Console.WriteLine("圆盘移动完成后:");
        Console.WriteLine("A = " + string.Join(", ", A));
        Console.WriteLine("B = " + string.Join(", ", B));
        Console.WriteLine("C = " + string.Join(", ", C));
    }
}

在这里插入图片描述

在这里插入图片描述


🚀感谢:给读者的一封信

亲爱的读者,

我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。

如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。

我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。

如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。

在这里插入图片描述

再次感谢您的阅读和支持!

最诚挚的问候, “愚公搬代码”

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。