(算法很美)查找和排序学习第一部分
2.1 什么是递归
一、目标:
1、对递归,建立起感觉
2、学会评估算法性能
3、能大致预估程序的执行时间
二、递归设计经验
1、找重复(更小规模的子问题)(最重要,父问题转化为子问题)
2、找重复中的变化量->参数(如果感觉递归无法进行下去,很有可能是缺参数了)
3、找边界->设计出口
三、练习策略
1、循环改递归
2、经典递归
3、大量练习,总结规律,掌握套路
4、找到感觉,挑战高难度
四、三种思维方式
1、切蛋糕思维(划分方法):
求阶乘、打印i到j、数组求和、翻转字符串(直接量+小规模子问题)
2、划不开:没有递推公式?没有等价转换?
斐波那契数列 f(n)=f(n-1)+f(n-2 )
最大公约数 f(m,n)=f(n,m%n)
3、插入排序改递归:
(1)对前n-1个数排序(子问题)
(2)将第n个数插入到前面n-1个数中
4、汉诺塔问题(将1到N从A移动到B,C为辅助,移动过程中不能出现逆序)
(1)1到N-1移动到C,B作为辅助
(2)把N从A移动到B
(3)把1-N从C移动到B,A作为辅助
//关于递归
public class SearchRank1 {
public static void main(String[] args){
part8(5,"A","B","C");
}
//求阶乘
public static int part1(int n){
if(n==1){
return 1;
}
return n*part1(n-1);
}
//其打印出i到j
static void part2(int i,int j){
System.out.println(i);
if(i<j){
part2(i+1,j);
}
}
//求数组中所有元素的和
static int part3(int[] arr,int i){
if(i<arr.length){
return arr[i]+part3(arr,i+1);
}else{
return 0;
}
}
//翻转字符串 从end或者从start都可
static String part4(String str,int start){
if(start==str.length()-1){
return str.charAt(start)+"";
}else{
return part4(str,start+1)+str.charAt(start);//直接量+小规模子问题
}
}
//斐波那契数列
static int part5(int n){
if(n==2||n==1){
return 1;
}
return part5(n-1)+part5(n-2);//多个小规模子问题相加,现将第一项全部计算完毕后,再去计算第二项
}
//辗转相除法求最大公约数(默认m>=n)
static int part6(int m,int n){
if(n==0){
return m;
}
return part6(n,m%n);
}
//插入排序改递归*****在重复中找变化
static void part7(int[] arr,int k){
if(k==0){
return;
}
//对前k-1个元素排序
part7(arr,k-1);
//将第k个元素插入到前面的部分
int target=arr[k];
int index=k-1;
while(index>=0&&target<arr[index]){
arr[index+1]=arr[index];
index--;
}
arr[index+1]=target;
}
//汉诺塔问题
static void part8(int N,String from,String to,String help){
if(N==1){
System.out.println("move"+N+"from"+from+"to"+to);
}else{
part8(N-1,from,help,to);
System.out.println("move"+N+"from"+from+"to"+to);//将第N个移动到目标位置
part8(N-1,help,to,from);//将N-1个移动到第N个上面
}
}
}
2.2 二分查找
1、全范围内的二分查找等价于三个子问题:左边找(递归)、中间比、右边找(递归) 左边查找和右边查找只选一个,复杂度低
//二分查找的递归算法(两个分支)
public static int part1(int[] arr,int low,int high,int key){
//出口条件
if(low>high){
return -1;//没有找到
}
int mid=low+((high-low)>>1);//找到中间位置,移位能够防止溢出而且更高效
int midVal=arr[mid];
if(midVal<key){
return part1(arr,mid+1,high,key);
}else if(midVal>key){
return part1(arr,low,mid-1,key);
}else{
return mid;//找到了
}
}
2.3 希尔排序
1、一趟一个增量,用增量来分组,组内进行插入排序
//希尔排序
public static void part2(int[] arr){
//不断缩小增量,从总长度的一半开始,每次变为原来的一半
for(int interval=arr.length/2;interval>0;interval=interval/2){
//增量为interval的插入排序
for(int i=interval;i<arr.length;i++){
int target=arr[i];//保存要插入的这个值
int j=i-interval;
while(j>-1&&target<arr[j]){
arr[j+interval]=arr[j];//重要 如果小于target的换就向后移
j-=interval;
}
arr[j+interval]=target;
}
}
}
2.3 算法性能分析
1、评估算法性能:问题的输入规模n与元素的访问次数f(n)的关系
2、大O符号:
(1)忽略非主体部分,如常数项、低阶项
(2)O(g(n))表示这个算法是有一个渐进上界的,这个渐进上界为g(n),算法的运行时间f(n)趋近并小于等于这个g(n)
3、存在常数阶的算法
4、n!的弱上界是n^n,因此增长速度非常快,这意味着单位时间内可求解的问题很小,换言之,超慢
5、2^n这样的指数函数增长速度非常快,算超慢
6、O(n2)和O(n3)增长很快,算法很慢,至少要优化到nlgn,O(n2)的有冒泡排序,直接插入排序,选择排序
7、nlgn可以认为是及格算法,一般分治法可以缩小层数为lgn,而每层的复杂度一般为O(n),例如:归并排序算法,快速排序算法
8、O(n)叫做线性算法,这种算法比较优秀,或者问题本身比较简单
9、lgn就是很优秀的算法了,比如二分查找,但这种算法一般对输入数据格式有要求,比如二分查找要求输入的数据有序
10、还有一种是常量,无论规模怎么扩大,复杂度都是常量
2^10=1024 220=106 227=108 (含约等)
Java中的Arrays.sort()用的是快速排序
11、经典算法分析
(1)顺序查找:O(n)
(2)二分查找:O(lgn)
12、递归算法时间复杂度分析
(1)子问题的规模下降
(2)子问题的答案处理消耗时间
写出式子:T(n)=T(n-1)+O(1)
13、之前提到的递归算法的时间复杂度
(1)求阶乘、打印i到j、数组求和、翻转字符串都是O(n)
(2)菲波那切数列:T(n)=T(n-1)+T(n-2)+O(1)可以看做T(n)=2T(n-1)+O(1)
最后的时间复杂度为O(2n)
(3)汉诺塔问题:类似于菲波那切数列也是O(2n)
(4)求最大公因式:m%n<m/2 每进行两次折半,时间复杂度为O(lgn)(2lgn)
若每三次折半的话就是3lgn,以此类推……
2.4 希尔排序的性能分析
1、如果原始数据的的部分元素已经排序,那么插入排序的速度很快
2、快的原因:无序的时候,元素少;元素多的时候,已经基本有序
3、最好:nlgn 最坏:n2
2.5 排序算法的稳定性
1、若a=b,排序前a在b的前面,排序后a还在b的前面,则稳定
2.6 题1:小白上楼梯(递归设计)
小白正在上楼梯,楼梯有n阶台阶,小白一次可以上1阶,2阶或者3阶,实现一个方法,计算小白有多少种走完楼梯的方式。
1、f(n)=f(n-1)+f(n-2)+f(n-3) 走到最后一个台阶最后一步有三种情况,而走到这三种情况的方法又可以用f表示,从而形成递归
//上楼梯问题
public static int f(int n){
if(n==0)
return 1;//这种特殊的情况如果在一开始确定不了就在后面试一试正常的情况
if(n==1)
return 1;
if(n==2)
return 2;
return f(n-1)+f(n-2)+f(n-3);
}
2.7 旋转数组的最小数字(改造二分法)
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1
旋转后的数组总是一边有序一边无序,最小值往往在无序的一侧
2.8 在有空字符串的有序字符串数组中查找
有个排序后的字符串数组,其中散布着一些空字符串,编写一个方法,找出给定字符串(肯定不是空字符串)的索引。
{字符串中的compareTo方法:从第一位开始比较,如果遇到不同的字符,则马上返回这两个字符的ASCII值得差值,返回类型是int型。可以用来比较两个字符串的大小。}
//二分法变体:旋转数组的最小数字(改造二分法) 最小的数字往出现在无序的一侧*****
public static int part3(int[] arr){
int low=0;
int high=arr.length-1;
int mid=0;
//旋转零个元素的情况
if(arr[low]<arr[high]){
return arr[low];
}
//一般情况
while(low+1<high){
mid=(low+high)/2;
if(arr[mid]>arr[low]){//左侧有序
low=mid;
}else{//右侧有序
high=mid;
}
}
return high;
}
//二分法变体:在有空字符串的有序字符串数组中查找
public static int part4(String[] arr,String key){
int begin =0;
int end=arr.length-1;
int mid=0;
while(begin<=end){//停止标志
mid=(begin+end)/2;
//如果是空字符串的话就向右移动
//因为空字符比不了大小,空字符是随机分布在字符串中的
while(arr[mid].equals("")){
mid++;
if(mid>end){
return -1;//避免死循环
}
}
if(arr[mid].compareTo(key)>0){
end=mid-1;
}else if(arr[mid].compareTo(key)<0){
begin=mid+1;
}else{
return mid;
}
}
return -1;
}
//设计一个高效的求a的n次幂的算法,高效
public static int part5(int a,int n){
if(n==0) return 1;//边界处理
int ex=1;
int res=a;
while((ex<<1)<n){
res*=res;
ex=ex<<1;
}
return res*part5(a,n-ex);//利用递归
}
2.9 最长连续递增子序列(部分有序)
(1,9,2,5,7,3,4,6,8,0)中最长的递增子序列为(3,4,6,8),有些数组是锯齿形的
//最长递增子序列的长度
//n是数组A的长度
public static void findLongest(int A[],int n){
int m=0;
int i=0;
int p=1;
//b数组的下标是递增子序列的起始位置,值是递增长度
int[] b=new int[n];
while(p<n){
i=m;
b[i]=1;
while(p<n&&A[m]<A[p]){
b[i]++;
m++;
p++;
}
m++;
p++;
}
p=0;
for(int j=0;j<n;j++){
if(b[j]>p) p=b[j];
}
System.out.println(p);//打印出递增子序列的长度
}
{java中实现格式化输出:
(1)System.out.printf("%f",d);//“f"表示格式化输出浮点数。
(2)System.out.printf(”%9.2f",d);//“9.2"中的9表示输出的长度,2表示小数点后的位数。
(3)System.out.printf(”%+9.2f",d);//"+“表示输出的数带正负号。
(4)System.out.printf(”%-9.4f",d);//"-“表示输出的数左对齐(默认为右对齐)
(5)System.out.printf(”%±9.3f",d);//“±"表示输出的数带正负号且左对齐
(6)System.out.printf(”%d",i);//“d"表示输出十进制整数
(7)System.out.printf(”%o",i);//“o"表示输出八进制整数
(8)System.out.printf(”%x",i);//“x"表示输出十六进制整数
(9)System.out.printf(”%#x",i);//" #x “表示输出带有十六进制标志的整数
(10)System.out.printf(”%s",s);//"d"表示输出字符串
(11)System.out.printf(“输出一个浮点数:%f,一个整数:%d,一个字符串:%s”,d,i,s);
(12)System.out.printf("字符串:%2
s
,
s,%1
s,d的十六进制数:%1KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲x",i,s);//"X"表示第几个变量。}