递归的特征
- 调用自身
- 当它调用自身的时候,是为了解决更小的问题(即缩小问题的范围)
- 存在某个足够简单的问题的层次,在这一层算法不需要调用自己就可以直接解答,则返回结果(即基值情况,否则会无限调用下去)
基值情况
导致递归的方法返回而没有再一次进行递归调用,此时称为基值情况。
每个递归方法都必须有一个基值(终止)条件,以防止无限的递归下去,以及由此引发的程序崩溃。
效率
调用方法会存在一定的额外开销。控制必须从这个调用的位置转移到这个方法的开始处。并且,传给这个方法的参数以及这个方法返回的地址都需要被压到一个内部的栈里,目的是这个方法可以访问参数值和知道返回到哪里。
由于系统内存空间存储所有的中间参数和返回值。如果有大量的数据需要存储,会引起栈溢出的问题。
代码示例
三角数字
package com.jikefriend.recursion;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 三角数字
* 数字序列: 1,3,6,10,15,21,...
* 数列中的第n项是由第n-1项加n得到的
* 用于验证结果的公式:
* f(n) = (n^2 + n) / 2
*/
public class TriangleApp {
public static void main(String[] args) throws IOException {
System.out.print("Enter a number: ");
System.out.flush();
int number = getInt();
int answer = triangle2(number);
System.out.println("Triangle = " + answer);
}
/**
* 递归获取第n项的值
* @param n
* @return
*/
public static int triangle(int n)
{
if (n == 1) //基值情况,即终止递归的条件
return 1;
else
return n + triangle(n - 1);
}
/**
* 非递归获取第n项的值
* @param n
* @return
*/
public static int triangle2(int n)
{
int total = 0;
while (n > 0)
{
total += n;
--n;
}
return total;
}
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
return br.readLine();
}
public static int getInt() throws IOException
{
return Integer.parseInt(getString());
}
}
变位字(全排列)
package com.jikefriend.recursion;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 变位字(全排列)
* 给出一个单词,列出其所有的全排列(不管是否是真正的英语单词)
* 比如cat,全排列结果为:cat cta atc act tca tac
*
* 步骤:
* 1、全排列最右边的n-1个字母
* 2、轮换所有n个字母
* 3、重复以上步骤n次
*
* 轮换单词n次,以给每个字母一个排在开头的机会。当选定的字母占据第一个位置时,所有其他的字符会被全排列
* 通过递归调用来全排列最右边的n-1个字母,每次调用都会逼上次少一个字母,直到出现基值情况(即只有一个字母)
* 在遇到基值情况前,方法会把给定词的除了第一个字母之外的所有字母进行全排列,然后接着轮换整个单词
*/
public class AnagramApp {
static int size;
static int count;
static char[] arrChar = new char[100];
public static void main(String[] args) throws IOException{
System.out.print("Enter a word: ");
System.out.flush();
String input = getString();
size = input.length();
count = 0;
arrChar = input.toCharArray();
doAnagram(size);
}
/**
* 进行全排列
* 每次调用自己的时候,词的大小都减少一个字母,并且开始的位置右移一位
*
* @param newSize
*/
public static void doAnagram(int newSize)
{
if (newSize == 1) //如果只有一个字母,则直接返回
return;
for (int i = 0; i < newSize; i++) //遍历每个字母
{
doAnagram(newSize - 1); //重复重排列
if (newSize == 2)
displayWord();
rorate(newSize); //变换单词第一个字母
}
}
/**
* 轮换整个单词
* 向左轮换一个字母
*
* @param newSize
*/
public static void rorate(int newSize)
{
int j;
int position = size - newSize;
char temp = arrChar[position];
for (j = position + 1; j < size; j++)
{
arrChar[j -1] = arrChar[j];
}
arrChar[j -1] = temp;
}
public static void displayWord()
{
if (count < 99)
System.out.print(" ");
if (count < 9)
System.out.print(" ");
System.out.print(++count + " ");
for (int i = 0; i < size; i++)
{
System.out.print(arrChar[i]);
}
System.out.print(" ");
System.out.flush();
if (count % 6 == 0)
System.out.println();
}
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
return br.readLine();
}
}
二分查找
package com.jikefriend.recursion;
import java.util.Random;
/**
* 二分查找
*/
public class BinarySearchArray {
private int[] a;
private int nElems;
public BinarySearchArray(int max)
{
a = new int[max];
nElems = 0;
}
public int size()
{
return nElems;
}
public int find(int searchKey)
{
return recFind2(searchKey, 0, nElems - 1);
}
/**
* 递归实现
*/
private int recFind(int searchKey, int lowerBound, int upperBound)
{
int curIn;
curIn = (lowerBound + upperBound) / 2;
if (a[curIn] == searchKey)
return curIn; //找到返回所在位置
else if (lowerBound > upperBound)
return nElems; //未找到
else
{
if (a[curIn] < searchKey)
return recFind(searchKey, curIn + 1, upperBound); //如果当前值小于搜索值,则说明在当前值的右部
else
return recFind(searchKey, lowerBound, curIn - 1); //否则,说明在当前值的左部
}
}
/**
* 非递归实现
*/
private int recFind2(int searchKey, int lowerBound, int upperBound)
{
int curIn;
while (true)
{
curIn = (lowerBound + upperBound) / 2;
if (a[curIn] == searchKey)
return curIn;
else if (lowerBound > upperBound)
return nElems;
else
{
if (a[curIn] < searchKey)
lowerBound = curIn + 1;
else
upperBound = curIn - 1;;
}
}
}
public void insert(int val)
{
int j = 0;
for (j = 0; j < nElems; j++) //找到要插入的位置,直到比它大的值出现
{
if (a[j] > val)
break;
}
for (int k = nElems; k > j; k--) //将比它大的值及其之后的所有值右移一位
a[k] = a[k - 1];
a[j] = val; //将该值插入之前比它大的值的位置
nElems++;
}
public void display()
{
for (int i = 0; i < nElems; i++)
{
System.out.print(a[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
Random random = new Random(System.nanoTime());
int max = 20;
BinarySearchArray array = new BinarySearchArray(max);
for (int i = 0; i < max - 1; i++) {
array.insert(random.nextInt(max * 2));
}
array.insert(10);
array.display();
int searchKey = 10;
if (array.find(searchKey) != array.size())
System.out.println("Found " + searchKey);
else
System.out.println("Can't find " + searchKey);
}
}
汉诺塔
package com.jikefriend.recursion;
/**
* 汉诺塔
* 假设想要把所有的盘子从源塔座上(称为S)移动到目标塔座(称为D)。有一个可以使用的中介塔座(称为I。
* 假定在塔座S上有n个盘子。算法如下:
* 1、从塔座S移动包含上面的n-1个盘子的子树到塔座I上;
* 2、从塔座S移动剩余的盘子(最大的盘子)到塔座D上;
* 3、从塔座I移动子树到塔座D;
* 4、第一步中的n-1个盘子子树的移动重复步骤1-3。
*
* 基值条件:当移动一个盘子的时候,只要移动它就可以了,没有其他的事情要做
*
* 手动解决问题法则:
* 1、如果试图要移动的子树含有奇数个盘子,开始时直接把最顶端的盘子移动到目标塔座上;
* 2、如果试图要移动的子树含有偶数个盘子,开始时把最顶端的盘子移动到中介塔座上。
*/
public class TowersApp {
static int disks = 3;
/**
* 移动汉诺塔
* @param topN 要移动的盘子个数
* @param from 源塔座
* @param inter 中介塔座
* @param to 目标塔座
*/
public static void doTowers(int topN, char from, char inter, char to)
{
if (topN == 1)
System.out.println("Disk 1 from " + from + " to " + to);
else
{
doTowers(topN - 1, from, to, inter); //将盘子从源塔座移动到中介塔座
System.out.println("Disk " + topN + " from " + from + " to " + to);
doTowers(topN - 1, inter, from, to); //将盘子从中介塔座移动到目标塔座
}
}
public static void main(String[] args) {
doTowers(disks, 'A', 'B', 'C');
}
}
归并
package com.jikefriend.recursion;
import java.util.Random;
/**
* 归并排序,O(N*logN)
*/
public class MergeSort {
private int[] theArray;
private int nElems;
public MergeSort(int max)
{
theArray = new int[max];
nElems = 0;
}
public void insert(int val)
{
theArray[nElems++] = val;
}
public void display()
{
for (int i = 0; i < nElems; i++)
System.out.print(theArray[i] + " ");
System.out.println();
}
public void mergeSort()
{
int[] workSpace = new int[nElems];
recMergeSort(workSpace, 0, nElems - 1);
}
private void recMergeSort(int[] workSpace, int lowerBound, int upperBound)
{
if (lowerBound == upperBound) //如果数组长度为1,无需排序(基值条件)
return;
else
{
int mid = (lowerBound + upperBound) / 2; //计算中间位置
recMergeSort(workSpace, lowerBound, mid); //对数组的左半区进行排序
recMergeSort(workSpace, mid + 1, upperBound); //对数组的右半区进行排序
merge(workSpace, lowerBound, mid + 1, upperBound); //合并两个有序的半区
}
}
private void merge(int[] workSpace, int lowPtr, int highPtr, int upperBound)
{
int i = 0;
int lowerBound = lowPtr;
int mid = highPtr - 1;
int n = upperBound - lowerBound + 1;
while (lowPtr <= mid && highPtr <= upperBound) //两个数组都不为空
{
if (theArray[lowPtr] < theArray[highPtr])
workSpace[i++] = theArray[lowPtr++];
else
workSpace[i++] = theArray[highPtr++];
}
while (lowPtr <= mid) //如果左半区数组已经都放入工作数组中,而右半区数组还有数据,则将右半区数组中的数据依次放入工作数组中
workSpace[i++] = theArray[lowPtr++];
while (highPtr <= upperBound) //如果右半区数组已经都放入工作数组中,而左半区数组还有数据,则将左半区数组中的数据依次放入工作数组中
workSpace[i++] = theArray[highPtr++];
for (i = 0; i < n; i++) //将归并排序后的有序数组放回源数组中
theArray[lowerBound+i] = workSpace[i];
}
public static void main(String[] args) {
Random random = new Random(System.nanoTime());
int maxSize = 20;
MergeSort arr = new MergeSort(maxSize);
for (int i = 0; i < maxSize; i++) {
arr.insert(random.nextInt(100));
}
arr.display();
arr.mergeSort();
arr.display();
}
}