Ⅰ、三角数字

  首先我们来看一组数字:1,3,6,10,15,21.....,在这个数列中第n项是由n-1项加n得到的,这个序列中的数字称为三角数字因为他们可以形象化地表示成一个三角形排列。如下图

通过上面的图首先我们就可以想到使用循环来查找第n项的数值,下面代码就是从高度为n的列加到高度为1的列

int triangle(int n){
int total = 0;
while(n>0){
total = total + n;
--n;
}
return total;
}

上面方法循环了n次,第一次加n,第二次加n-1,一直加到1从而可以算出第n项的值。

使用递归的思想查找第n项的值,此时我们将三角数字看做是第一列和剩余所有列的和,如下图所示,可以写出triangle()方法

int triangle(int n){
//1. return (n + sumAllColums(n-1)) //这里我们可以发现sumAllColums方法做的事情和triangle做的事情一模一样
return (n + triangle(n-1)) //这里我们可以将上面步骤换为此步骤,从而得到求三角数字的递归算法
}

  此时上面的递归代码是不会终止的,所以我们的每一个递归代码要有一个我们称之为基值(base case)以防止无限递归导致程序崩溃,所以上面求三角函数代码的基值就是1,

int triangle(int n){
if(n==1){
return 1; //求三角数字的基值
}else{
return (n+triangle(n-1)) ; //进行递归逻辑
}
}

  下图表示了递归triangle方法的执行过程,假设传入n=5,从5开始每次减1,不断进入方法自身,直到减到1时,方法进行返回,直到返回到最外层。注意,在方法返回1之前,实际上同时有5个不同的triangle()方法实例存在,最外层传入的参数是5,最内层传入的参数是1

综上我们可以总结出递归方法的特征:

  1. 调用自身
  2. 调用自身为了解决更小的问题
  3. 存在足够简单的层次,即上面说的基值

  递归的效率:递归的过程中控制必须从调用的位置转移到方法的开始处,除此之外这个方法的参数以及返回值会压入到一个内部栈中,从而知道访问的参数值和返回到哪里。所以递归效率较低,我们常常采用递归,是因为他从概念上简化了问题,而不是它更有效率。

II、归并排序

接下来我们讲讲与递归相关的排序算法,归并排序:

  首先这种排序在时间上更有效,时间复杂度为O(N*logN),如果排序数据项N为10000,那么一般的简单排序N2就是100000000,而N*logN只是40000,意思就是若使用归并排序需要40s,那么使用插入排序需要近28小时。归并排序的缺点是需要在存储器中有另一个大小等于被排序的数据项数目的数组,所以排序对数组的大小有一定的限制。

  归并算法的核心是归并两个已经有序的数组,我们假设数组A有4个数据项,数组B有6个数据项,他们要被归并到C中,开始的时候C有10个空的存储空间。下表显示了归并进行的必要的比较,每一次比较将较小的数据项复制到数组C中,表中B数组在第八步之后是空的,所以不需要再进行比较,直接将A数组复制到C中去即可。

接下来我们给出归并的java代码:

public int[] merge(int[] arrayA,int sizeA,int[] arrayB,int sizeB,int[] arrayC){
int aDex=0,bDex=0,cDex=0; //记录三个数组当前的脚标
while(aDex<sizeA&&bDex<sizeB){ //此时还需进行比较
if(arrayA[aDex] < arrayB[bdex])
arrayC[cDex++] = arrayA[aDex++];
else
arrayC[cDex++] = arrayA[bDex++];
}
while(aDex<sizeA) //说明数组a中还有剩余数据,拷贝到数组c
arrayC[cDex++] = arrayA[aDex++];
while(bDex<sizeB) //说明数组b中还有剩余数据,拷贝到数组c
arrayC[cDex++] = arrayB[bDex++];
return arrayC;
}

  归并排序简单来讲就是反复地将数组进行分割,利用递归的思想,直到子数组只含有一个数据项(基值),在归并排序方法中每一次调用自身的时候排列都会被分成两部分,并且每一次返回时都会把两个较小的排列合并成一个更大的排列,接下来我们给出归并排序的代码:

public class DArray {
private long[] theArray; //存储数据的数组
private int nElems; //填充数据个数索引 public DArray(int max) {
theArray = new long[max];
nElems = 0;
}
/***插入数据*/
public void insert(long value){
theArray[nElems++] = value;
}
/***归并排序的方法*/
public void mergeSort() {
long[] workSpace = new long[nElems];
recMergeSort(workSpace, 0, nElems - 1);
} /**
* 利用递归进行归并排序
* @param workSpace 工作区
* @param lowerBound 归并区域的起始索引
* @param upperBound 归并区域的结束索引
*/
private void recMergeSort(long[] workSpace, int lowerBound, int upperBound) {
if (lowerBound == upperBound) {
return;
} else {
//找到中间分界点
int mid = (lowerBound + upperBound) / 2;
//首先递归调用自己将前半部分的数据归并为有序
recMergeSort(workSpace, lowerBound, mid);
//然后递归调用自己将后半部分的数据归并为有序
recMergeSort(workSpace, mid + 1, upperBound);
//调用归并算法将上面归并有序后的数据进行归并
merge(workSpace, lowerBound, mid + 1, upperBound);
}
} /**
* 归并算法
* @param workSpace 工作区
* @param lowPtr 首段归并区域的初始索引
* @param highPtr 末端归并区域的初始索引
* @param upperBound 末端归并区域的结束索引
*/
private void merge(long[] workSpace, int lowPtr, int highPtr, int upperBound) {
int j = 0; //工作区的index
int lowerBound = lowPtr; //首段归并区域的初始索引(复制到theArray的初始索引)
int mid = highPtr - 1; //对应索引较小区域的结束位置索引
int n = upperBound - lowerBound + 1; //归并的此段区域所含有的数据项的个数 //将对应范围lowPtr到upperBound的数据复制到工作区
while (lowPtr <= mid && highPtr <= upperBound) {
if (theArray[lowPtr] < theArray[highPtr]) {
workSpace[j++] = theArray[lowPtr++];
} else {
workSpace[j++] = theArray[highPtr++];
}
}
//将对应还未复制完的数据复制到工作区中
while (lowPtr <= mid) {
workSpace[j++] = theArray[lowPtr++];
}
while (highPtr <= upperBound) {
workSpace[j++] = theArray[highPtr++];
}
//此步骤相当于是将对象内的theArray数组变成lowPtr到upperBound局部有序,将归并到工作区的数据放入theArray的对应位置
for (j = 0; j < n; j++) {
theArray[lowerBound + j] = workSpace[j];
}
}
}

归并的效率:(假设复制和比较是最耗时的操作)

复制次数:

  上表中可以看出来当N为2的乘方的时候的操作次数,我们可以这样来理解需要的复制次数,log2N表示将N对半分解为我们归并的基值1的时候需要的步数,然后每一步我们都需要将N个数据项复制到我们的工作区,所以复制到工作区的次数就应该是N*log2N,这些数据复制到工作区之后还需要复制到原数组中所以复制次数会增加一倍

比较次数:

  上图中我们可以前面表示进行归并的时候进行的最多和最少的比较次数,后表列举出了包含8个数据项进行归并排序的比较次数,对于八个数据项需要七次归并的操作,对于每一次归并最大比较次数是数据项减1,最小比较次数是数据项的一半,加在一起可算出归并排序需要的比较次数在12到17之间。

至此关于递归的思想,和关于递归的归并排序就结束了。

JAVA算法之递归的更多相关文章

  1. Java算法之递归打破及在真实项目中的使用实例

    开心一笑 刚才领导问开发:"你觉得这个项目的最大风险是什么",开发说:"加班猝死" , 气氛尴尬了一分钟!!! 提出问题 1.递归算法简单复习 2.如何实现递归 ...

  2. Java实现 蓝桥杯VIP 算法提高 递归倒置字符数组

    算法提高 递归倒置字符数组 时间限制:1.0s 内存限制:512.0MB 问题描述 完成一个递归程序,倒置字符数组.并打印实现过程 递归逻辑为: 当字符长度等于1时,直接返回 否则,调换首尾两个字符, ...

  3. JAVA算法系列 快速排序

    java算法系列之排序 手写快排 首先说一下什么是快排,比冒泡效率要高,快排的基本思路是首先找到一个基准元素,比如数组中最左边的那个位置,作为基准元素key,之后在最左边和最右边设立两个哨兵,i 和 ...

  4. JavaScript算法 ,Python算法,Go算法,java算法,系列之【归并排序】篇

    常见的内部排序算法有:插入排序.希尔排序.选择排序.冒泡排序.归并排序.快速排序.堆排序.基数排序等.用一张图概括: 归并排序(英语:Merge sort,或mergesort),是创建在归并操作上的 ...

  5. java算法-数学之美一

    巧用数学的思想来解决程序算法问题,这样的代码如诗般优美.通过数学思想来看问题,也能将程序简单化.“斐波那契数列”对于java程序员来说一定不陌生.当然这个问题的解决方案也有很多.用一个例子说明数学思想 ...

  6. Java中的递归运算

    Java中的递归运算是一种在自己的方法内部调用自己的方法 递归的设计思想是:把一个复杂的问题,分解为若干个等同的子问题,重复执行,直到之问题能够简单到直接求解,这样复杂的问题就得以解决. 递归运算有两 ...

  7. Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法

    Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法 于是我们可以把上面的语法改写成如下形式:1 合并前缀1 语法分析有自上而下和自下而上两种分析 ...

  8. JAVA算法系列 冒泡排序

    java算法系列之排序 手写冒泡 冒泡算是最基础的一个排序算法,简单的可以理解为,每一趟都拿i与i+1进行比较,两个for循环,时间复杂度为 O(n^2),同时本例与选择排序进行了比较,选择排序又叫直 ...

  9. 汉诺塔算法的递归与非递归的C以及C++源代码

    汉诺塔(又称河内塔)问题其实是印度的一个古老的传说. 开天辟地的神勃拉玛(和中国的盘古差不多的神吧)在一个庙里留下了三根金刚石的棒,第一根上面套着64个圆的金片,最大的一个在底下,其余一个比一 个小, ...

随机推荐

  1. apach hadoop2.6 集群利用Phoenix 4.6-hbase 批量导入并自动创建索引

    基础环境: 1.安装apach 版本hadoop2.6 2.部署hbase1.0.0 3.下载phoenix-4.6.0-HBase-1.0.下载地址(http://mirror.nus.edu.sg ...

  2. background-size的值cover、contain和100%

    图1 给一个宽600px,高600px的div添加一张宽480px,高360px的背景图片.不重复显示的情况下,默认显示为图1. 1.background-size: 100% 100%; 会将图片的 ...

  3. 41-Ubuntu-用户管理-06-su切换用户

    su 切换用户 序号 命令 作用 说明 01 su - 用户名 切换用户,并且切换家目录 '-'可以切换到用户家目录,否则保持位置不变 02 exit 退出当前登录账户 返回上一级用户 图:su与ex ...

  4. Vuex 源码解析

    先来看一下这张Vuex的数据流程图,熟悉Vuex使用的同学应该已经有所了解. Vuex实现了一个单向数据流,在全局拥有一个State存放数据,所有修改State的操作必须通过Mutation进行,Mu ...

  5. python 对象的删除

  6. 笔记51 Mybatis快速入门(二)

    Mybatis的CRUD 1.修改配置文件Category.xml,提供CRUD对应的sql语句. <?xml version="1.0" encoding="UT ...

  7. 16. 继承(extends)

    1.语法 class 类名1 extends 类名2{ //成员变量和成员方法 } 2.继承要注意的事项: 1)千万不要为了减少重复代码而去继承,只有真正存在着继承关系的时候才去继承. 2)父类私有的 ...

  8. 使用node搭建服务时,服务可以启动,但是无法访问

    一开始搭建的是没有问题,能够正常启动和访问,然后吃了个饭回来就变成只能启动,浏览器访问不了了. 说真的,这是一个常识问题.使用node搭建服务访问本地文件,服务搭建的没有问题,能够正常启动.但是在浏览 ...

  9. c++ TCP 获取客户端IP

    #include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #i ...

  10. CSS——外观样式及应用

    CSS注释  CSS规则是使用 /* 需要注释的内容 */ 进行注释的,即在需要注释的内容前使用 “/*” 标记开始注释,在内容的结尾使用 “*/”结束. 例如: p { font-size: 14p ...