文章目录
1.递归的概念
- 直接或间接地调用自身的算法称为递归算法
- 用函数自身给出的定义的函数称为递归函数
1.1 阶乘函数
public class FactorialDemo {
public static void main(String[] args) {
System.out.println("1!="+factorial(1));
System.out.println("2!="+factorial(2));
System.out.println("3!="+factorial(3));
System.out.println("4!="+factorial(4));
}
public static int factorial(int n) {
if(n==0)
return 1;
return n*factorial(n-1);
}
}
1.2 Fibonacci数列
public class Fibonacci {
/**
*
* f(n)=1 n=0,1
* f(n)=f(n-1)+f(n-2) n>1
*/
public static void main(String[] args) {
System.out.println("f(0)="+fibonacci(0));
System.out.println("f(1)="+fibonacci(1));
System.out.println("f(2)="+fibonacci(2));
System.out.println("f(3)="+fibonacci(3));
}
public static int fibonacci(int n) {
if(n==0||n==1)
return 1;
return fibonacci(n-1)+fibonacci(n-2);
}
}
1.3 全排列
public class FullPermutation {
public static void main(String[] args) {
int list[]= {1,2,3};
System.out.println("[1 2 3]的全排列:");
perm(list, 0, list.length-1);
}
public static void perm(int list[],int k,int m) {
if(k==m)//遍历到最后一个元素
{
for(int i=0;i<=m;i++)
System.out.print(list[i]);
System.out.println();
}
else {
for(int i=k;i<=m;i++)
{
swap(list, i, k);//将当前元素list[k]固定到i的位置上
perm(list, k+1, m);
swap(list, i, k);//还原元素list[k]的位置
}
}
}
public static void swap(int list[],int a,int b) {
int temp=list[a];
list[a]=list[b];
list[b]=temp;
}
}
1.4 整数划分问题
将正整数n表示成一系列正整数之和,n=n1+n2+n3+…+n k _k k
n1>=n2>=n3…>=n k _k k
p(n)表示划分的种数
(1)当n=1时,不论m的值为多少(m>0),只有一种划分即{1};
(2)当m=1时,不论n的值为多少,只有一种划分即n个1,{1,1,1,…,1};
(3)当n=m时,根据划分中是否包含n,可以分为两种情况:
(a)划分中包含n的情况,只有一个即{n};
(b)划分中不包含n的情况,这时划分中最大的数字也一定比n小,即n的所有(n-1)划分。 因此 q(n,n) =1 + q(n,n-1);
(4)当n<m时,由于划分中不可能出现负数,因此就相当于q(n,n);
(5)但n>m时,根据划分中是否包含最大值m,可以分为两种情况:
(a)划分中包含m的情况,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和为n-m,因此这情况下为q(n-m,m)
(b)划分中不包含m的情况,则划分中所有值都比m小,即n的(m-1)划分,个数为q(n,m-1);
因此 q(n, m) = q(n-m, m)+q(n,m-1);
public class IntegerPartition {
static int num[]=new int[256];//保存划分结果
static int ans=0;//保存划分种数
public static void main(String[] args) {
System.out.println("p(6)="+q(6, 6));
dfs(0, 0, 6,6);
System.out.println("p(6)="+ans);
}
public static int q(int n,int m) {
if(n<1||m<1)
return 0;
if(n==1||m==1)
return 1;
if(n<m)
return q(n, n);
if(n==m)
return 1+q(n, m-1);
return q(n, m-1)+q(n-m, m);
}
/**
*
* @param sum 当前遍历的和
* @param depth 当前划分中元素的个数
* @param max 当前剩余的最大值
* @param n 待划分的值n
*/
public static void dfs(int sum,int depth,int max,int n) {
if(sum==n)
{
System.out.print(num[0]);
for(int i=1;i<depth;i++)
System.out.print("+"+num[i]);
System.out.println();
ans++;
}
else if(sum<n)
{
for(int i=max;i>=1;i--)
{
num[depth]=i;
dfs(sum+i, depth+1, i, n);
}
}
}
}
1.5 汉诺塔问题
public class HanoiDemo {
public static void main(String[] args) {
hanoi(3, 'a', 'b', 'c');
}
public static void hanoi(int n,char a,char b,char c) {
if(n>0)
{
hanoi(n-1, a, c, b);//将a上面n-1个圆盘从a-->c 以b作为辅助
move(n,a,b);//将圆盘n从a-->b(a、b在递归过程中不一定代表柱a和柱b)
hanoi(n-1, c, b, a);//将c上面n-1个圆盘从c-->b 以a作为辅助
}
}
public static void move(int n,char a,char b) {
System.out.println("圆盘"+n+" from "+a+" to "+b);//模拟移动过程
}
}
2.分治法的基本思想
- 分治法的基本思想是将一个规模为n的问题分解成k个规模较小的子问题,这些子问题互相独立且与原问题相同
2.1 二分搜索技术
二分搜索算法等待基本思想是将n个元素分成个数大致相同的两半,取a[n/2]与x进行比较,如果x=a[n/2]则找打x算法终止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x;如果x>a[n/2],则只要在数组a的右半部分继续搜索x
2.1.1 不带边界的二分搜索算法
public static int binary_search(int arr[],int target) {
int left=0,right=arr.length-1;
while(left<=right)
{
int mid=left+(right-left)/2;
if(arr[mid]==target)
return mid;
else if(arr[mid]>target)
right=mid-1;
else if(arr[mid]<target)
left=mid+1;
}
return -1;
}
2.1.2 查找左边界的二分搜索算法
public static int binary_search_leftBound(int arr[],int target) {
int left=0,right=arr.length;// [left,right) 左闭右开
while(left<right)
{
int mid=left+(right-left)/2;
if(arr[mid]==target)
right=mid;
else if(arr[mid]>target)
right=mid;//[left,mid)<==>[left,mid-1]
else if(arr[mid]<target)
left=mid+1;//[mid+1,right)
}
return arr[left]==target?left:-1;
}
2.1.3 查找右边界的二分搜索算法
public static int binary_search_rightBound(int arr[],int target) {
int left=0,right=arr.length;// [left,right) 左闭右开
while(left<right)
{
int mid=left+(right-left)/2;
if(arr[mid]==target)
left=mid+1;
else if(arr[mid]>target)
right=mid;
else if(arr[mid]<target)
left=mid+1;
}
return arr[left-1]==target?left-1:-1;
}
完整代码:
public class BinarySearch {
public static void main(String[] args) {
int arr[]= {1,2,3,3,3,4,5,6};
System.out.println(binary_search(arr, 3));
System.out.println(binary_search_leftBound(arr, 3));
System.out.println(binary_search_rightBound(arr, 3));
}
public static int binary_search(int arr[],int target) {
int left=0,right=arr.length-1;
while(left<=right)
{
int mid=left+(right-left)/2;
if(arr[mid]==target)
return mid;
else if(arr[mid]>target)
right=mid-1;
else if(arr[mid]<target)
left=mid+1;
}
return -1;
}
public static int binary_search_leftBound(int arr[],int target) {
int left=0,right=arr.length;// [left,right) 左闭右开
while(left<right)
{
int mid=left+(right-left)/2;
if(arr[mid]==target)
right=mid;
else if(arr[mid]>target)
right=mid;//[left,mid)<==>[left,mid-1]
else if(arr[mid]<target)
left=mid+1;//[mid+1,right)
}
return arr[left]==target?left:-1;
}
public static int binary_search_rightBound(int arr[],int target) {
int left=0,right=arr.length;// [left,right) 左闭右开
while(left<right)
{
int mid=left+(right-left)/2;
if(arr[mid]==target)
left=mid+1;
else if(arr[mid]>target)
right=mid;
else if(arr[mid]<target)
left=mid+1;
}
return arr[left-1]==target?left-1:-1;
}
}
2.2 棋盘覆盖
在一个2 k ^k k × \times × 2 k ^k k 方格组成的棋盘中,恰有一个方格与其他的方格不同,在棋盘覆盖问题中,要用四种L型骨牌覆盖住棋盘,且任意两个L型骨牌不能重合
特殊方格必定处于4个较小子棋盘之一,其余3个子棋盘中无特殊方格,可以用一个L型骨牌覆盖住这3个较小棋盘的会和处,从而将问题转化为4个较小规模的棋盘覆盖问题
如果某一个子棋盘里没有特殊方格,遵循如下规则:
- 左上角子棋盘填充右下角方格
- 右上角子棋盘填充左下角方格
- 左下角子棋盘填充右上角方格
- 右下角棋盘填充左上角方格
package chapter2;
public class ChessboardCoverage {
static int title=1;//L型骨牌初始编号为2 区别于棋盘中的特殊方格标志1
public static void main(String[] args) {
int board[][]= { {0,1,0,0},
{0,0,0,0},
{0,0,0,0},
{0,0,0,0}
};
chessBoard(board, 0, 0, 0, 1,4);
for(int i=0;i<board.length;i++)
{
for(int j=0;j<board[0].length;j++)
System.out.print(board[i][j]+" ");
System.out.println();
}
}
/**
*
* @param board 棋盘
* @param tr 棋盘左上角方格的行号
* @param tc 棋盘左上角方格的列号
* @param dr 特殊方格所在的行号
* @param dc 特殊方格所在的列号
* @param size 棋盘规格为2^k*2^k
*/
public static void chessBoard(int board[][],int tr,int tc,int dr,int dc,int size) {
if(size==1)//棋盘划分到只有一个格子了
return;
int t=++title;//L型骨牌号
int sz=size/2;//分割棋盘
//覆盖左上角子棋盘
if(dr<tr+sz&&dc<tc+sz)//此棋盘中有特殊方格
chessBoard(board, tr, tc, dr, dc, sz);//(tr,tc)是左上角子棋盘的开始点(左上角的点)
else {//此棋盘中无特殊方格
board[tr+sz-1][tc+sz-1]=t;//用t号L型骨牌覆盖右下角 左上子棋盘填充右下角
chessBoard(board, tr, tc, tr+sz-1, tc+sz-1, sz);//覆盖其余方格 (tr+sz-1, tc+sz-1)是上一步填充过的
}
//覆盖右上角子棋盘
if(dr<tr+sz&&dc>=tc+sz)
chessBoard(board, tr, tc+sz, dr, dc, sz);//(tr,tc+sz)是右上角子棋盘的开始点(左上角的点)
else {//此棋盘中无特殊方格
board[tr+sz-1][tc+sz]=t;//用t号L型骨牌覆盖左下角 右上子棋盘填充左下角
chessBoard(board, tr, tc+sz, tr+sz-1, tc+sz, sz);//覆盖其余方格
}
//覆盖左下角子棋盘
if(dr>=tr+sz&&dc<tc+sz)
chessBoard(board, tr+sz, tc, dr, dc, sz);//(tr,tc)是右下角子棋盘的开始点(左上角的点)
else {//此棋盘中无特殊方格
board[tr+sz][tc+sz-1]=t;//用t号L型骨牌覆盖右上角 左下子棋盘填充右上角
chessBoard(board, tr+sz, tc, tr+sz, tc+sz-1, sz);//覆盖其余方格
}
//覆盖右下角子棋盘
if(dr>=tr+sz&&dc>=tc+sz)
chessBoard(board, tr+sz, tc+sz, dr, dc, sz);//(tr,tc)是右下角子棋盘的开始点(左上角的点)
else {//此棋盘中无特殊方格
board[tr+sz][tc+sz]=t;//用t号L型骨牌覆盖右上角 右下子棋盘填充左上角
chessBoard(board, tr+sz, tc+sz, tr+sz, tc+sz, sz);//覆盖其余方格
}
}
}
//O(4^k)
2.3 归并排序
import java.util.Arrays;
public class MergeSortDemo {
public static void main(String[] args) {
int arr[]= {1,3,2,6,5,7,9,8,4};
int temp[]=new int[arr.length];
mergeSort(arr, temp, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void mergeSort(int arr[],int temp[],int lo,int hi) {
if(lo>=hi)
return;
int mid=lo+(hi-lo)/2;
mergeSort(arr, temp, lo, mid);
mergeSort(arr, temp, mid+1, hi);
merge(arr, temp, lo, mid, hi);
}
public static void merge(int arr[],int temp[],int lo,int mid,int hi) {
int i=lo;
int j=mid+1;
int t=0;
while(i<=mid&&j<=hi)
{
if(arr[i]<=arr[j])
temp[t++]=arr[i++];
else
temp[t++]=arr[j++];
}
while(i<=mid)
temp[t++]=arr[i++];
while(j<=hi)
temp[t++]=arr[j++];
t=0;
i=lo;
while(i<=hi)
arr[i++]=temp[t++];
}
}
2.4 快速排序
import java.util.Arrays;
import java.util.Random;
public class QuickSortDemo {
public static void main(String[] args) {
int arr[]= {1,3,2,6,5,7,9,8,4};
quickSort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int arr[],int lo,int hi) {
if(lo>=hi)
return;
int j=randomizedPartion(arr, lo, hi);
quickSort(arr, lo,j-1);
quickSort(arr, j+1, hi);
}
public static int partion(int arr[],int lo,int hi) {
int i=lo;
int j=hi+1;
int v=arr[lo];
while(true)
{
while(arr[++i]<v)
if(i==hi)
break;
while(arr[--j]>v)
if(j==lo)
break;
if(i>=j)
break;
swap(arr, i, j);
}
swap(arr, lo, j);
return j;
}
public static int randomizedPartion(int arr[],int lo,int hi) {//随机划分
int i=new Random().nextInt(hi-lo+1)+lo;//[lo,hi]之间的随机整数
swap(arr, i, lo);//将产生的随机数对应的元素放到首位
return partion(arr, lo, hi);
}
public static void swap(int arr[],int a,int b) {
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
}
2.5 循环赛程表问题
public class RoundRobinSchedule {
public static void main(String[] args) {
int a[][]=new int[9][9];
table(3,a);
for(int i=1;i<=8;i++)
{
for(int j=1;j<=8;j++)
System.out.print(a[i][j]+" ");
System.out.println();
}
}
public static void table(int k,int a[][]) {
int n=1;
for(int i=1;i<=k;i++)
n*=2;//n表示选手数量 2^k
for(int i=1;i<=n;i++)
a[1][i]=i;//初始化表格第一行 也就是1号选手的比赛日程
int m=1;//(m+1,m+1)是每一轮复制的开始位置
for(int s=1;s<=k;s++)//划分的次数 k=3时 8-4->2
{
n/=2;//选手划分
for(int t=1;t<=n;t++)//第一次需要复制4次 第二次需要复制2次 第三次需要复制1次(每一次复制涉及两个赋值操作)
{
for(int i=m+1;i<=2*m;i++)//控制行
{
for(int j=m+1;j<=2*m;j++)//控制列
{
a[i][j+(t-1)*m*2]=a[i-m][j+(t-1)*m*2-m];//将左上角的值复制到右下角
a[i][j+(t-1)*m*2-m]=a[i-m][j+(t-1)*m*2];//将右上角的值复制到左下角
}
}
}
m*=2;
}
}
}