递归与分治策略
一,分治策略与递归
分治策略:是将规模比较大的问题分割成规模较小的相同问题,问题不变,规模变小。
递归:若一个函数直接的或间接的调用自己,则称则个函数是递归函数。
接下来比较下解决相同的问题,使用递归和分治算法各需要的时间复杂度和空间复杂度。
public static int digiu(int n){//递归运算 if (n<=1){ return 1; }else { return digiu(n-1)*n; } }
时间复杂度:O(n) 空间复杂度:S(n)
通过观察上述代码可知,随着变量n的值不断的变大,所需要递归调用的次数就越多,因此需要开辟的栈帧就越多,所以空间复杂度位S(n)。
递归调用与普通的函数调用一样,每当调用发生时,就要分配新的栈帧,而与普通的函数调用不同的是,由于递推的过程是一个逐层调用的过程,因此存在一个逐层连续的分配栈帧过程,直到遇到递归终止条件时,才开始回归,这时才逐层释放栈帧空间,返回到上一层,直到最后返回到主调函数。
在递归调用时,如果没有终止条件,考虑下函数会不会一直递归下去?答案是不会,因为递归调用时,会不断的往栈中压入栈帧,而栈内存默认大小只有10MB,当递归调用的过程中,栈内存一旦满就会报栈溢出的异常,这时候递归也就会停止。
public static int fun(int n){//分治策略算法 int sum=0; for (int i = 0; i <n; i++) { sum=sum*i; } return sum; }
时间复杂度:O(n) 空间复杂度:S(1)
使用分治策略时,在代码执行的过程中始终只调用了一个方法,因此只开辟了一个栈帧,所以空间复杂度就为S(1)。
1.实例
1.有一个整形数组,数值无序,使用循环和递归完成查询。
public class digiu { public static int fun(int[] arr,int val){ int pos=-1; for (int i = 0; i <arr.length; i++) { if (val==arr[i]){ pos=i; } } return pos; } public static int digiu(int[] arr,int val,int num){ if(num>=0&&arr[num]==val){ return num; } return digiu(arr,val,num-1);//注意先递归在打印的区别 } public static void main(String[] args) { int[] arr={1,8,3,9,0}; System.out.println(fun(arr,1)); System.out.println(digiu(arr,0,arr.length-1)); } }
2.二分查找。
public class partFind { public static int whilefind(int[] arr,int val){//5 6 7 10 int right=0; int left=arr.length-1; while (right<=left){ int mid=(right+left)/2;//当范围数值过大可以采用:mid=left+(right-left)/2 0.618 if (val==arr[mid]){ while (mid>0&&arr[mid-1]==val) --mid;//如果数组中存在相同元素,且要求返回的是最右边值的下标 return mid; } if (val<arr[mid]){ left=mid-1; } if (val>arr[mid]){ right=mid+1; } } return -1; } public static void main(String[] args) { int[] arr={12,12,23,23,23,34,45,56}; System.out.println(whilefind(arr,12)); } }
3.给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将被按顺序插入的位置。
(反复计算可知,如果该数组中存在该数,一般在mid处就可返回,如果不存在,则可以用left的值作为可插入下标返回)
public class digiu1_3 {
//为了方便判断返回的下标是该数在数组中的位置,还是没有找到该数返回的应插入的下标。
//所以就使用IndexNode类型,当返回的值的flag为true,表明找到该值,当flag为false,表明没有找到该值,返回的只是应插入的位置 static class IndexNode{ int value; boolean flag; }
private static IndexNode FindValue(int[] arr,int value) { int left=0; int right=arr.length-1; IndexNode indexNode=new IndexNode(); while (left<=right){ int mid=(right-left)/2+left;//(right+left)/2 if(value==arr[mid]){ indexNode.flag=true; indexNode.value=mid; return indexNode; }else if (value<arr[mid]){ right=mid-1; }else if(value>arr[mid]){ left=mid+1; } } indexNode.flag=false; indexNode.value=left; return indexNode; } public static void main(String[] args) { int[] arr=new int[]{12,23,34,45,56,67,78,85,92,100}; IndexNode indexNode=FindValue(arr,10); System.out.println(indexNode.flag+" "+indexNode.value); } }
4.贪吃的小明
小明的父母要出差N天,走之前给小明留下了M块奶糖,小明决定每天吃的奶糖数量不少于前一天吃的一半,但是他又不想在父母回来之前的某一天没有奶糖吃,请问他第一天最多可以吃多少块?
思路:1.因为要保证第一天吃的最多,所以就要保证后面每天吃的刚好为前一天的一半,但也要注意不能吃一半!!
public class digui1_4 { private static int EatMax(int day, int number) { int day1=(number+2)/2; //第一天要吃的糖的量, //第一天为什么是从对半开始??? int sum=day1;//累计吃的糖数 int eatday=sum;//每天要吃的量 while (true){ for (int i = 1; i <day; i++) { sum=sum+((eatday+1)/2);//加一是为解决每天吃半块糖的情况 eatday=eatday/2;//每经过一天,吃的糖数量就会减半 } if(sum<=number){ break; } if(sum>number){//表示给第一天分的太多 day1=day1-1; } } return day1; } public static void main(String[] args) { int day=2; int num=15; int day1=EatMax(day,num); System.out.println("第一天最多吃:"+day1); } }
5.旋转数组
寻找旋转数组排序中的最小值,然会返回它的下标,假如:数组{10,11,12,13,14,15,16,17}发生旋转变为{14,15,16,17,10,11,12,13};可以看出10,11,12,13部分发生了旋转,且10为旋转中的最小值,假设数组中不存在重复元素。
public class digui1_5 { public static int findMin(int[] arr){ int left=0; int right=arr.length-1; while (left<right-1){//因为如果有旋转发生,那么一定为最后两个数中右边的那一个 int mid=(right-left)/2+left; if(arr[left]>arr[mid]){//最左边如果大于中间值,说明旋转的范围在前半部分 right=mid; }else if (arr[right]<arr[mid]){//如果最右边值小于中间值,说明旋转的部分在后半部分 left=mid; }else{//如果最左边不大于中间值,且最右遍不小于中间值,说明此序列中没有发生旋转
return -1; } } return right; } public static void main(String[] args) { int[] arr=new int[]{14,15,16,17,10,11,12,13}; // 0 1 2 3 4 5 6 7 System.out.println(findMin(arr)); } }
6.计算两个数的最大公约数(1,遍历法 2,辗转相除法 3,直接使用库中函数)
public class digui1_6 { public static void main(String[] args) { int value1=15; int value2=15; //1.遍历法 fun1(value1,value2); //2.辗转相除法 System.out.println(fun2(value1,value2)); //3.直接使用库中的函数 fun3(value1,value2); } private static void fun1(int value1, int value2) { int a=value1;//无需区分大小,因为在do中进行一次之后,a就始终为大值,b始终为小值 int b=value2; int r=-1; do{ r=a; a=b; b=r%b; }while (b!=0); System.out.println("最大公约数为:"+a); } private static int fun2(int value1, int value2) { if(value1==value2){ return value1; } if (value1==0||value2==0){//当两个数中有一个数为0时,最大公约数为非0数 return value1==0?value2:value1; } int n=value1<value2?value1:value2;//找出较小的一个,使用穷举法 for (;n>0;--n) { if(value1%n==0&&value2%n==0){ break; } } return n; } private static void fun3(int value1, int value2) { BigInteger num1=new BigInteger(String.valueOf(value1)); BigInteger num2=new BigInteger(String.valueOf(value2)); BigInteger num3=num1.gcd(num2); System.out.println("最大公约数:"+num3); } }
7.将一个整数反向输出。如:12345,输出之后为54321
public class digui1_7 { public static void main(String[] args) { int value=12345; fun(value); } private static void fun(int value) { while (value!=0){ int n=value%10; value=value/10; System.out.print(n); } } } /* *下面是递归形式 */ public class digui1_7 { public static void main(String[] args) { int value=12345; fun(value); } private static void fun(int value) { if(value==0){ return; } int n=value%10; System.out.print(n); fun(value/10); } }