当前位置: 首页 > news >正文

算法系列之分治算法

_20250223230836.jpg

分治算法(Divide and Conquer)是一种解决复杂问题的非常实用的策略,广泛应用于计算机科学中的各个领域。它的核心思想是将一个复杂的问题分解成若干个相同或相似的子问题,递归地解决这些子问题,然后将子问题的解合并,最终得到原问题的解。分治算法的典型应用包括归并排序、快速排序、二分查找等。

本文将详细介绍分治算法的基本思想以及如何在Java中实现分治算法。

基本思想

分治算法的基本思想可以概括为以下三个步骤:

  • 分解(Divide):将原问题分解成若干个规模较小的子问题,这些子问题与原问题相似,但规模更小。

  • 解决(Conquer):递归地解决这些子问题。如果子问题的规模足够小,则直接求解。

  • 合并(Combine):将子问题的解合并成原问题的解。

分治算法的关键在于如何将问题分解成子问题,以及如何将子问题的解合并。

适用场景

分治算法所能解决的问题一般具有以下几个特征:

  • 问题可分解:
    问题能够分解为多个相似的子问题,且子问题相互独立。

  • 子问题可解:
    分解后的子问题可以直接求解。

  • 子问题解可合并:
    子问题的解能够合并为原问题的解。

  • 效率提升:
    分解和合并的复杂度低于直接求解原问题。

典型应用场景

  • 排序算法:如归并排序、快速排序、求解逆序对。

  • 查找算法:如二分查找。

  • 数学计算:如大整数乘法、矩阵乘法(Strassen算法)。

  • 几何问题:如最近点对问题。

  • 图算法:如快速傅里叶变换(FFT)。

归并排序介绍及Java实现

我们本文中以归并排序来介绍及示例分治算法。

归并排序是一种分治策略的排序算法,它的核心思想是将数组分成两个子数组,递归地对子数组进行排序,然后将排序好的子数组合并起来,最终得到有序的数组。如下图所示:

_20250223220512.jpg

java 代码实现归并排序:

/**
 * 归并排序
 *
 */
public class MergeSort {

    // 归并排序的入口方法
    public static void mergeSort(int[] arr) {
        int[] temp = new int[arr.length];
        sort(arr, 0, arr.length - 1,temp);

    }

    // 归并排序的核心排序方法(递归调用的方法)
    public static void sort(int[] arr, int left, int right,int[] temp) {
        // 递归终止条件,直至单个元素
        if (left < right) {
            int mid = (left + right) / 2;
            //左边部分递归
            sort(arr, left, mid, temp);
            //右边部分递归
            sort(arr, mid + 1, right, temp);
            //归并
            merge(arr,left,mid,right, temp);
        }
    }

    //归并排序的核心归并方法
    public static void merge(int[] arr, int left, int mid, int right,int[] temp) {
        int i = left;
        int j = mid + 1;
        int k = left;
        // 比较左右两部分的元素,并将较小的元素放入临时数组([left,right]区间元素排序)
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }
        //如果右边元素先放完,则将左边剩余的元素逐个放入临时数组中
        while (i <= mid) {
            temp[k++] = arr[i++];
        }

        //如果左边元素先放完,则将右边剩余的元素逐个放入临时数组中
        while (j <= right) {
            temp[k++] = arr[j++];
        }

        // 将临时数组的结果复制回原数组
        for (int l = left; l <= right; l++) {
            arr[l] = temp[l];
        }
    }
    public static void main(String[] args) {
        int[] arr = new int[]{7,5,2,3,6,4};
        System.out.println("原始数组:"+ Arrays.toString(arr));
        mergeSort(arr);
        System.out.println("排序后的数组:"+ Arrays.toString(arr));
    }
}

数组中的逆序对

  • 题目描述:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007

数据范围: 对于50%的数据,size <= 1 0 4 10^4 104

对于100%的数据,size <= 1 0 5 10^5 105

数组中所有数字的值满足 0<= val <= 1 0 9 10^9 109

要求:空间复杂度 O(n) ,时间复杂度 O( l o g log log n)

输入描述:
题目保证输入的数组中没有的相同的数字

  • 示例

示例1

输入:[1,2,3,4,5,6,7,0]
返回值:7

示例2

输入:[1,2,3]
返回值:0
  • 代码
public class InversePairs {
    //初始化逆序对的个数
    public static int ret = 0;
    // 归并排序的入口方法
    public static int mergeSort(int[] arr) {
        int[] temp = new int[arr.length];
        sort(arr, 0, arr.length - 1,temp);
        return  ret;
    }

    // 归并排序的核心排序方法(递归调用的方法)
    public static void sort(int[] arr, int left, int right,int[] temp) {
        // 递归终止条件,直至单个元素
        if (left < right) {
            int mid = (left + right) / 2;
            //左边部分递归
            sort(arr, left, mid, temp);
            //右边部分递归
            sort(arr, mid + 1, right, temp);
            //归并
            merge(arr,left,mid,right, temp);
        }
    }

    //归并排序的核心归并方法
    public static void merge(int[] arr, int left, int mid, int right,int[] temp) {
        int i = left;
        int j = mid + 1;
        int k = left;
        // 比较左右两部分的元素,并将较小的元素放入临时数组([left,right]区间元素排序)
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
                // 奥妙之处,右边元素左移为逆序对,区间合并的时候每个区间都是有序的
                ret += (mid - i + 1);
                ret %= 1000000007;
            }
        }
        //如果右边元素先放完,则将左边剩余的元素逐个放入临时数组中
        while (i <= mid) {
            temp[k++] = arr[i++];
        }

        //如果左边元素先放完,则将右边剩余的元素逐个放入临时数组中
        while (j <= right) {
            temp[k++] = arr[j++];
        }

        // 将临时数组的结果复制回原数组
        for (int l = left; l <= right; l++) {
            arr[l] = temp[l];
        }
    }
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5,6,7,0};
        System.out.println("原始数组:"+ Arrays.toString(arr));
        int ret = mergeSort(arr);
        System.out.println("排序后的数组:"+ Arrays.toString(arr));
        System.out.println("统计的逆序对的个数:"+ ret);
    }

}

总结

分治算法是一种非常强大的算法设计思想,能够有效地解决许多复杂的问题。通过将问题分解成更小的子问题,递归地解决这些子问题,然后将子问题的解合并,我们可以高效地解决许多实际问题。本文通过归并排序和逆序对两个经典例子,展示了分治算法的基本思想和Java实现。

相关文章:

  • 从底层驱动到 OpenCV:深入解析 Linux 摄像头完整技术栈
  • 安全生产月安全知识竞赛主持稿串词
  • 基于Python和Neo4j开发的医疗辅助诊断系统的详细实现步骤和代码示例
  • Python--函数进阶(上)
  • Unity制作游戏——前期准备:Unity2023和VS2022下载和安装配置——附安装包
  • ESP32S3:解决RWDT无法触发中断问题,二次开发者怎么才能使用内部RTC看门狗中断RWDT呢?
  • 力扣热题100——滑动窗口
  • 【java】类和对象
  • 使用 AndroidNativeEmu 调用 JNI 函数
  • k8s学习记录:环境搭建(基于Kubeadmin)
  • 从入门到精通Rust:资源库整理
  • 【YOLOv11改进- 主干网络】YOLOv11+RepViT: 从ViT的角度重新审视Mobile的CNN助力YOLOv11有效涨点;
  • 解决Spring Boot中Druid连接池“discard long time none received connection“警告
  • DeepSeek R1的崛起与挑战:技术创新 与 普通人的反思
  • HTML之JavaScript DOM编程获取元素的方式
  • 制造业革命:数字化转型从哪里开始?
  • 【大模型系列】使用docker安装向量数据库Milvus问题备忘
  • Docker 与 Serverless(无服务器架构)
  • 玩客云OneCloud部署Zerotier虚拟局域网
  • 跨平台公式兼容性大模型提示词模板(飞书 + CSDN + Microsoft Word)
  • 新华时评:防范安全事故须臾不可放松
  • 湖北鄂州通报4所小学学生呕吐腹泻:供餐企业负责人被采取强制措施
  • 五一去哪儿|外国朋友来中国,“买买买”成为跨境旅游新趋势
  • 科学家为AI模型设置“防火墙”,以防止被不法分子滥用
  • 辽宁省全力开展辽阳一饭店火灾事故救援处置工作
  • 三大白电巨头去年净利近900亿元:美的持续领跑,格力营收下滑