递归
- 找重复 : 找出子问题,并把原问不断分解成子问题
- 找变化 : 找出原问题和子问题之间有哪些变化
- 找边界 : 找出递归退出的点,不能无限循环
第一部分:递归基础
public class Hello {
public static int ff1(int n){
if(n==0)
return 1;
return n * ff1(n-1);
}
public static void main(String[] args) {
System.out.println(ff1(3));
}
}
public class Hello {
public static void ff1(int i,int j){
if(i==j)
return;
System.out.println(i);
ff1(i+1,j);
}
public static void main(String[] args) {
ff1(3,9);
}
}
public class Hello {
public static int ff1(int arr[], int i){
if (i==arr.length-1)
return arr[arr.length-1];
return arr[i] + ff1(arr,i+1);
}
public static void main(String[] args) {
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
System.out.println(ff1(arr, i));
}
}
public class Hello {
public static String ff1(String str, int j){
if (j==0){
return str.charAt(j)+"";
}
return str.charAt(j)+ff1(str,j-1);
}
public static void main(String[] args) {
String str1 = "abcdefg";
String str2 = "i love you";
System.out.println(ff1(str1,str1.length()-1));
System.out.println(ff1(str2,str2.length()-1));
}
}
- 斐波那契数列
- O(2^n) ,这种类似的还有3,8(八皇后问题)的n次方阶
- 为什么是这个时间复杂度呢?
- 因为每次递归,都分成了两个全新的部分,而全新的部分还都要进行计算,1、2、4、8…
public class Hello {
public static int ff1(int j){
if(j==2||j==1)
return 1;
return ff1(j-1)+ff1(j-2);
}
public static void main(String[] args) {
System.out.println(ff1(5));
}
}
- 最大公约数
- log(n)阶 (由数学公式推导出来,大概每两次,n削减一半)
- 为什么是这个时间复杂度?
m n
n m%n (根据数学推导 m%n < m/2)
m%n n%(m%n) (根据数学推导 n%(m%n) < n/2)
也就是说每隔两个数就衰减一半,
次数 规模
2次 n/2
4次 n/2^2
6次 n/2^3
...
k次 n/2^(k/2)
再取对数...
故与log(n)阶类似。
public class Hello {
public static int ff1(int m, int n){
if (m%n == 0)
return n;
else
return ff1(n, m%n);
}
public static void main(String[] args) {
System.out.println(ff1(4,3));
}
}
- 插入排序改递归
- o(n^2) : 想象一下,插入排序与冒泡大体类似,都是需要用一个元素,与剩下的元素逐一比较,而每次都用一个元素,这就相当于两个for循环了。
public class Hello {
public static void ff1(int arr[], int k){
if (k==0){
return;
}
ff1(arr,k-1);
int x = arr[k];
int index = k-1;
while (x<arr[index]){
arr[index+1] = arr[index];
index--;
if(index==-1)
break;
}
arr[index+1] = x;
}
public static void main(String[] args) {
int arr [] = {3,6,2,8,4,9};
ff1(arr, arr.length-1);
for(int i = 0;i<arr.length;i++){
System.out.print(arr[i]);
}
}
}
- 汉诺塔问题
- O(2^n)
- 发现每次调用函数,都是上次调用的两倍,与斐波那契数列类似。
public class Hello {
public static void hanno(int N, String A, String B, String C){
if (N==1){
System.out.println(N+"by"+A+"--->"+B);
}else{
hanno(N-1, A, C, B);
System.out.println(N+"by"+A+"--->"+B);
hanno(N-1,C, B, A);
}
}
public static void main(String[] args) {
hanno(6,"A", "B", "C");
}
}
- 二分查找
- log(n)阶
- 为什么是这个时间复杂度?
- 每次都找一半,理解为2^n(你查找的次数) = x(总次数),这样再取对数,即为log(n)阶
public class Hello {
public static int ff1(int arr[], int l, int r, int key) {
if (l > r)
return -1;
else if (key > arr[(l+r)/2])
return ff1(arr, (l + r) / 2 + 1, r, key);
else if(key < arr[(l+r)/2])
return ff1(arr, l, (l + r) / 2, key);
else
return (l+r)/2;
}
public static void main(String[] args) {
int array[] = {1,2,3,4,5,6};
System.out.println(ff1(array,0,5,3));
}
}
- 希尔排序
- nlog(n)~n^2之间
- 这里元素少与多是基于希尔排序的分组来定义的,并不是真实的元素数量。
- 如果排序的元素非常不好,也就是元素少的时候,有序,而元素多的时候,无序,导致
- 为什么快?:元素少的时候,无序,元素多的时候,已经基本有序。
public class Hello {
public static int[] ff1(int arr[]){
for(int interval = arr.length/2; interval>0 ;interval/=2){
for(int j = interval;j< arr.length; j++){
int num = arr[j];
int index = j-interval;
while(j>-1&&num<arr[index]){
arr[index+interval] = arr[index];
index -= interval;
if(index<0){
break;
}
}
arr[index+interval] = num;
}
}
return arr;
}
public static void main(String[] args) {
int array[] = {3,7,2,9,4,1};
array = ff1(array);
for(int i = 0;i< array.length;i++){
System.out.println(array[i]);
}
}
}
- 希尔排序最不喜欢的状态
- 这个导致希尔排序的时间复杂度为O ( n ^ 2 ),因为后面很大一部分元素要进行插入,加上前面的几次遍历,时间复杂度为O ( n ^ 2)
- 希尔排序最喜欢的状态
- 当然这里代表一部分情况,只要是大体有序都可以达到这种水平(大体有序指大的数字靠右,且这里假设每次最多交换一次,即每次都为O(n ^ 2),层数是由元素个数/2决定的,是log (n),二者相乘即为最小的时间复杂度。
第二部分、算法复杂度及稳定性
第三部分、递归例题
- 题1:小白上楼梯(递归设计)
- 小白正在上楼梯, 楼梯有n阶台阶, 小白一次可以上1阶, 2阶或者3阶 , 实现一个方法,计算小白有多少中走完楼梯的方式。
- 思路:想象一下走完全程,倒退一阶台阶,二阶,三阶,在这三阶台阶上,都可以一步走完全程,故得到递推式 :f(n) = f(n-1) + f(n-2) + f(n-3) ,这里指的是方法数相加 ,是方法数的递推(即走完楼梯的方式),与斐波那契数列非常相似,这里的算法时间复杂度是 O(n^3),而且注意控制出口,即n=1,n=2,n=3的时候都要控制。
- 代码如下:
import java.util.Scanner;
public class Hello {
public static int ff1(int n){
if (n==0) return 1;
else if (n==1) return 1;
else if (n==2) return 2;
else
return ff1(n-1) + ff1(n-2) + ff1(n-3);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while(true) {
int num = sc.nextInt();
int req = ff1(num);
System.out.println(req);
}
}
}
- 题2:旋转数组的最小数字(改造二分法)
- 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
- 思路 : 我们发现旋转数组中的最小元素是在将数组进行二分切割后无序的那一半(看有序还是无序只要比较数组开头和结尾的元素的大小即可),而最小的那个元素又总是在最大的元素的右边,即当只剩下两元素的时候,右边那个就是最小的元素
- 注意这种方法的弊端很大,比如旋转数组10111测试出错误答案等等…emm
- 代码如下:
import java.util.Scanner;
public class Hello {
public static void ff1(int arr[]){
int begin = 0;
int end = arr.length - 1;
if (arr[begin]<arr[end]) System.out.println(arr[begin]);;
while (true){
int mid = (begin + end) / 2;
if (arr[begin]<arr[mid]){
begin = mid;
}else if(arr[mid]<arr[end]){
end = mid;
}else if(begin+1==end){
System.out.println(arr[end]);
break;
}
}
}
public static void main(String[] args) {
int arr[] = {2,3,4,0,1};
ff1(arr);
}
}