分而治之 D&C 快速排序 Java实现 非传统方法

本文详细介绍了递归和分而治之(D&C)的概念,以及如何在求和问题和快速排序算法中应用这两种方法。通过递归函数的基线条件和递归条件,解释了如何避免无限循环并逐步解决问题。在快速排序的实现中,阐述了选择基准值、划分数组、以及合并排序子数组的过程。最后,提供了快速排序的Java代码实现,并讨论了分区函数在该算法中的作用。
摘要由CSDN通过智能技术生成

递归引入

首先要了解递归函数中重要的两个概念:
1、不符合递归的条件。基线条件(base case)编写递归函数时,必须告诉它何时停止递归。基线条件则指的是函数不再调用自己,从而避免形成无限循环。
2、符合递归的条件。递归条件(recursive case)。递归条件指的是函数调用自己。

分而治之

D&C解决问题的过程包括两个步骤。
(1) 找出基线条件,这种条件必须尽可能简单。
(2) 不断将问题分解(或者说缩小规模),直到符合基线条件。

D&C的工作原理:
(1) 找出简单的基线条件;
(2) 确定如何缩小问题的规模,使其符合基线条件。
D&C并非可用于解决问题的算法,而是一种解决问题的思路。我们再来看一个例子。
给定一个数字数组。 计算它的元素的和。

使用最基本的方法:

public static int sum(int[] arry){
        int total = 0;
        for (int i = 0; i < arry.length; i++) {
            total = total+arry[i];
        }
        return total;
    }

使用D&C工作的原理的方案图
例如对数字数组 2,4,6 进行求和

在这里插入图片描述

第一步:找出基线条件。最简单的数组什么样呢?请想想这个问题,再接着往下读。如果数
组不包含任何元素或只包含一个元素。

第二步:每次递归调用都必须离空数组更近一步。如何缩小问题的规模呢?
将数组的元素个数逐渐减少。
1、将第一个元素拿出来后数组只剩2个元素的新数组A,
2、重复第一步 第二次再将第一个元素拿走,新数组A只剩1一个元素的新数组B,
3、重复第一步 第三次再将第一个元素拿走,
4、新数组B没有元素,数组为空,此时符合基线条件。不能重复跳出返回结果。

使用D&C工作的原理的方法实现:

public static int sum(int[] arry){
        //基线条件
        if (arry.length == 0){
            return 0;
        }else {
        	//递归条件
            int arry_new[] = new int[arry.length-1]; //一个新数组,不包含第一个元素外的其他所有元素
            int index = 0;
            for (int i = 1; i < arry.length; i++) {
                arry_new[index] = arry[i];
                index++;
            }
            return arry[0] + sum(arry_new);
        }
    }

递归 和 分而治之的结合应用 快速排序

对分而治之的概念有所理解后,现在来看看D&C在快速排序中应用

对数组进行排序,想想什么样的数组排序速度最快?只有一个元素或者0个元素的数组排序是最快的!在这种情况下,只需原样返回数组——根本就不用排序。
伪代码

def quicksort(arry)
	if arry.length < 2:
		return arry

我们来看看更长的数组。对包含两个元素的数组进行排序也很容易。
比较2个元素的大小即可。如果第一个元素小于第二个元素交换他们位置即可。
在这里插入图片描述
包含三个元素的数组呢?
在这里插入图片描述
别忘了,你要使用D&C,因此需要将数组分解,直到满足基线条件。下面介绍快速排序的工
作原理。首先,从数组中选择一个元素,这个元素被称为基准值(pivot)。
暂时先将第一个元素作为基准值。
接下来,找出比基准值小的元素以及比基准值大的元素。
在这里插入图片描述
现有一个小于基准值的子数组,基准值,大于基准值的子数组。
如果子数组是有序的,就可以像下面这样合并得到一个有序的数组:
左边的数组 + 基准值 + 右边的数组。
在这里,就是[10, 15] + [33] + [],结果为有序数组[10, 15, 33]。

如何对子数组进行排序呢?对于包含两个元素的数组(左边的子数组)以及空数组(右边的
子数组),快速排序知道如何将它们排序,因此只要对这两个子数组进行快速排序,再合并结果,
就能得到一个有序数组!伪代码如下:

quicksort([15, 10]) + [33] + quicksort([])

不管将哪个元素用作基准值,这都管用。假设你将15用作基准值。
在这里插入图片描述

这个子数组都只有一个元素,而你知道如何对这些数组进行排序。现在你就知道如何对包含
三个元素的数组进行排序了,步骤如下。
1、找出基准值
2、将数组分成两个子数组,小于基准值的数组和大于基准值的数组
3、对两个子数组进行快速排序。

下面根据基本步骤来写相应伪代码


int provit = arry[0];//定义基准值
less[] //小于基准值的子数组
greater[]//大于基准值的子数组
for(int x = 0; x < arry.length; x++){
	if arry[x] > provit{
		greater.add(arry[x])
	}else if arr[x] < provit{
		less.add(arry[x])
	}else if x > 0{
		//如果等于基准值,且下标不是基准值
		less.add(arry[x])
	}
}
//左边的数组 + 基准值 + 右边的数组
quickSort(less) + provit + quickSort(greater);

基于以上理论的 Java代码实现

public static List quicksort(List<Integer> arry){
        if (arry.size() < 2){
            return arry;
        }
        int x = arry.size()/2;
        Integer provit = arry.get(x);//找出基准值
        ArrayList less = new ArrayList<Integer>();//构建小于基准值的子数组
        ArrayList greater = new ArrayList<Integer>();//构建大于基准值的子数组
        for (int i = 0; i < arry.size(); i++) {
            if (arry.get(i) < provit){
                less.add(arry.get(i));
            }else if (arry.get(i) > provit){
                greater.add(arry.get(i));
            }else if (i != x){ //如果等于基准值,但不是基准值的下标也要添加到子数组,以免漏数据,如果没有这个判断的添加数据那么数组中相同的元素就会被漏掉
                less.add(arry.get(i));
            }
        }
        //左边数组加基准值和右边数组合并
        List<Integer> list = new ArrayList<>();
        list.addAll(quicksort(less));
        list.add(provit);
        list.addAll(quicksort(greater));
        return list;
    }

public static void main(String[] args) {
        int[] array = {2,15,6,2,4,9,4};
        ArrayList<Integer> luanList = new ArrayList<>();
        for (int i = 0; i < array.length; i++) {
            luanList.add(array[i]);
        }
        System.out.println("排序前:"+luanList);
        System.out.println("排序后:"+quicksort(luanList));
    }

在这里插入图片描述
来看看网上常见的快速排序代码,原理不变,也是将数组中找一个基准值,然后一分为2,左边和右边分别进行快排。其中涉及到一个核心问题如何将数组一分二。需要一个分区函数来解决这个问题。
分区函数 构建首先要明确输入和输出。
输入的肯定会有一个数组,如果我对这个数组的部分元素进行分组就需要输入数组的开始和结束。
输出是什么呢?输出的是数组中基准值的正确下标。此时该下标的左边都是小于基准值的,下标的右边都是大于基准值的。代码如下:

public static int partition(int arr[], int lowIndex, int highIndex) {
        // 定义数组中的基准值 输入的结束下标为基准值
        int pivot = arr[highIndex];
        // 定义数组中的最后一个最小元素的下标
        int i = (lowIndex - 1);
        for (int j = lowIndex; j < highIndex; j++) {
            // 如果当前元素小于或者等于基准值
            if (arr[j] <= pivot) {
                i++; // 增量 i
                // 交换元素
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        // 将基准值移动到数组的中间位置左边的全是小于基准值的,右边的全部大于基准值
        int temp = arr[i + 1];
        arr[i + 1] = pivot;
        arr[highIndex] = temp;
        return i + 1;
    }

下面是网上常见的快速排序Java代码

 public static void sortq(int arr[], int lowIndex, int highIndex) {
        if (lowIndex < highIndex) {
            int pi = partition(arr, lowIndex, highIndex);
            sortq(arr, lowIndex, pi - 1);
            sortq(arr, pi + 1, highIndex);
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值