许多有用的算法在结构上是递归的:为了解决一个给定的问题,算法一次或多次的递归调用其自身已解决紧密相关的若干子问题。这些算法典型的遵循分治法的思想:既将原问题分解为几个规模较小但类似于原问题的子问题,递归的求解这些子问题,然后再合并这些子问题的解来建立原问题解
分治模式在每层递归时有三个步:
- 分解:分解原问题为若干问题。这些子问题是原问题的规模较小的实例
- 解决:解决这些子问题递归地求解每个子问题,然后若子问题的规模足够小就直接求解。
- 合并:合并这些子问题的解推出原问题的解。
嗯,上面几句就我们老师给我们讲的,字很少,但听不懂对吧~>_< ~ 我开始也是这样,
咱就讲个实例吧,话不多说,开始
因为我也是简单的了解,所以就不像隔壁大佬那样上纲上线,我们从最基础的归并排序来食用(了解)分治模式
归并排序作为学生必须掌握的几个算法之一(反正我老师是这么说的,我是看完书再跑过来唠嗑的)套用上面三个步骤
- 分解:将待排序的n个元素序列分成俩个子序列,每个序列n/2个元素
- 解决:使用我们的归并排序递归地将子序列两个两个排序
- 合并:合并两个已排好的子序列变成一个新的序列
(问一下:合并是什么时候合并?)
ps:当我们的字问题无法再分出子问题的时候
所以当出现n 个子序列(它们的长度为1)时,我们就开始将分化出去的若干子问题“回升”合并,而我们合并子序列的前提是什么?----------(没错,就是子序列排好序了的时候);提一句一个序列里只有一个元素它是不是已经排好了呢?当然是啦(你当它是最大值最小值都无所谓,反正就一个数)
然后,重点来了, 归并排序的关键体现在“合并”上,所以我们就定义一个辅助过程Merge(int A[], int p, int q, int r), (懒癌晚期,变量名就这样用吧)解释一下:
/**
* Merge(int A[], int p, int q, int r);实现合并两个已经排好顺序的序列
*
*@param A int数组类型数据 原序列的子集
*@param p int类型数据 可以看成子序列A[]第一个元素在初始序列中对应的下标
*@param q int类型数据 可以看成子序列A[]中间元素在初始序列中对应的下标
*@param r int类型数据 可以看成子序列A[]最后一个元素在初始序列中对应的下表
*/
先过一遍伪码吧
Merge(A, p, q,r)
//得到A序列划分俩个子序列的个数
n1 = q -p+1
n2 = r-q
//将A序列分成俩个新的子序列(多留一个位子存放哨兵)
let L[1..n1+1] and R[1..n2+1] be new arrys
for i== 1 to n1
L[i] = A[p+i-1]
for j = 1 to n2
R[j] = A[q+j]
//∞主要是用来在排序过程中表示该序列是否到底了,当然你也不可能取到无穷,这里主要是为了理解
L[n1+1] == ∞
R[n2+1] == ∞
i = 1
j = 1
//排序合并两个子序列
for k = p to r
if L[i]<=R[j]
A[k] = L[i]
i=i+1;
else A[k] =R[j]
i=j+1
递归调用Merge(A,p,q,r)
MergeSort(A,p,r)
//跳出递归的条件,即p>=r时表明序列已达到最小,无法在分
if p<r
//获取序列中间位子下标(向下取整)
q=(p+r)/2
//原序列左边进行归并排序
MergeSort(A,p,q)
//原序列右边进行归并排序
MergeSort(A,q+1,r)
//归并两个有序子序列,ok
Merge(A,p,q,r)
到这里大体思路都出来了
然后就是具体实现了:
//不会起名,就Demo凑合着用吧
class Demo
{
//为了省点事我直接把需要操作的序列数组声明成全局范围的静态变量所以形参里的int A[]我就不要了(哈哈,偷点懒)
public static int A[] = {1,6,3,8,5,2,7,9,4};
public static void main(String[] args)
{
System.out.print("初始序列: ");
showValueOfArry(A);
MergeSort(0,8);
System.out.print("排序结果: ");
showValueOfArry(A);
}
/**
* Merge(int A[], int p, int q, int r);实现合并两个已经排好顺序的序列
*
*@param p int类型数据 可以看成子序列A[]第一个元素在初始序列中对应的下标
*@param q int类型数据 可以看成子序列A[]中间元素在初始序列中对应的下标
*@param r int类型数据 可以看成子序列A[]最后一个元素在初始序列中对应的下表
*/
public static void Merge(int p, int q, int r){
//得到序列划分的俩个子序列的各个元素数量
int n1 = q-p+1;
int n2 = r-q;
//创建对应大小的int数组用来存放分化后的两个子序列(说白了就是原来一个数组我对半拆成两个,)
int L[] = new int[n1+1];
int R[] = new int[n2+1];
//注意啦,往这两个数组里填值,这里有点坑,开始忘了减1,然后运行给了我一大堆0,最逐行System.out.println(...)才找到原因,(为什么不debug?。。。。。我自己也不知道)
for(int i=0; i<=n1-1; i++){
L[i] = A[p+i];
}
for(int j=0; j<=n2-1; j++){
R[j] = A[q+j+1];
}
/*因为个人比较懒,所以采用下面方式合并数组,当然你自己也可以多写几个循环分情况合并,但我还是觉得下面的看起来舒服,
逻辑性很强,刚开始学的时候下面这种写法我好久弄明白(不是代码可读性差哈),讲远了,因为下面需要用到一个大的数字作
为‘哨兵’放在数组末尾,多大?正无穷?。。。。。表示不出来,所以我取了 2147483647(2^31 -1)int最大值*/
L[n1] = 2147483647;
R[n2] = 2147483647;
int i = 0;
int j = 0;
//因为'哨兵'的存在我们在比较过程作就不用时时刻刻去检测数组是否到头了
for(int k=p; k<=r; k++){
if(L[i]<=R[j]){
A[k] = L[i++];
}else{
A[k] = R[j++];
}
}
}
/**
*递归调用MergeSort方法,对应三个步骤中的:解决
*
*@param p int类型数据 表示序列数组起始下标
*@param r int类型数据 表示序列数组起始结束下标
*/
public static void MergeSort(int p, int r){
//跳出递归的条件,即p>=r时表明序列已达到最小,无法在分
if (p<r)
{
//获取序列中间位子下标(向下取整)
int q = (p+r)/2;
//原序列左边进行归并排序
MergeSort(p,q);
//原序列右边进行归并排序
MergeSort(q+1,r);
//归并两个有序子序列,ok
Merge(p,q,r);
}
}
//显示数组序列中数值
public static void showValueOfArry(int A[]){
for(int i=0; i<A.length; i++){
System.out.print(A[i]+" ");
}
System.out.println();
}
}
讲到这里就算了,按惯例来张运行结果图镇宅:
没错,写了这么多就是为了输出这几个数