迭代递归和分治
- 迭代就是自己的输出又成为自己的输入,环形结构。
- 递归就是自己调用自己,分治法和递归经常一起使用,树形结构。
- 分治法就是把一个问题分成一些规模较小的子问题,然后分别求解。
递归例子
- 阶乘函数
(分段函数表达式)
n!=
1 when n=0
n(n-1)! when n>0
(代码表示)
public static int jiecheng(int n){
if(n==0) return 1;
return n*jiecheng(n-1);
}
- 斐波那契数列
(分段函数表达式)
F(n)=
1 when n=0 or 1
F(n-1)+F(n-2) when n>1
(代码表示)
public static int feibonacci(int n){
if(n<=1) return 1;
return feibonacci(n-1)+feibonacci(n-2);
}
- Hanoi汉诺塔问题
a b c 3个柱子,把a柱子上面的盘子移动到b柱子上
(代码表示)
public static void hanoi(int n,int a,int b, int c){
if(n=1) nove(a,b); //直接把一个盘子从a移动到b
hanoi(n-1,a,c,b); //把n-1个盘子先移动到c上
move(a,b); //把最大的盘子移动到b上
hanoi(n-1,c,b,a); //把c上面的n-1个盘子移动到b上
}
迭代和递归的分析方法
- 分治法一般用递归方程进行分析
- 代换法(数学归纳)
- 递归树
- 主定理(计算机中lgn表示以2为底的对数)
- 迭代分析
- 级数求和
分治法的经典问题
二分查找
public static int binarySearch(int[] a, int x, int n){
// x:需要查找的元素,n:数组a的长度
int left = 0; int right = n-1;
while(left<=right){
int middle = (left+right)/2;
if(x == a[middle]) return middle;
if(x > a[middle]) left = middle+1;
else right = middle-1;
}
//表示没有找到
return -1;
}
归并排序(二分归并)
int main(){
int a[] = {
1,2,4,5,1,2,5,3};
int temp[a.length-1];
mergeSort(a, 0, a.length-1);
}
int mergeSort(int a[], int temp[], int left, int rgiht){
if(left < right){
// 表示至少2个元素
//中位数
int middle = (left+right)/2;
// 对2边子问题进行归并排序
mergeSort(a, left, middle);
mergeSort(a, middle+1, right);
// 合并划分后的元素,并且拷贝回原数组,归并排序需要多余的一个临时数组
merge_and_copy(a, temp, left, middle, right);
}
}
int merge_and_copy(int a, int temp, int left, int middle, int right){
int z = 0; // 临时数组的索引
// 把a数组划分成2部分 a[left, middle],a[middle+1, right],然后合并
int x = left;
int y = middle + 1;
while(x <= middle && y <= right){
//把2部分头一个最小的元素放入temp数组中,索引+1,注意a++和++a,先运算还是先+1。
if(a[x] < a[y]) temp[z++] = a[x++];
else temp[z++] = a[y++];
}
// 上面的循环结束后表示2部分中的一个数组已经遍历完了,接着查看另一个数组如果有剩余,则全部直接加在temp后面。
while(x <= middle){
temp[z++] = a[x++];
}
while(y <= right){
temp[z++] = a[y++];
}
// 表示把a[]中从left到right的已经排好序的元素复制回去
for(int i=0; i <= right-left+1, ++i){
a[left+i] = temp[i];
// 此处应为i有范围限制,所以不能left++
}
}
快速排序
public static void quickSort(int[] num, int left, int right) {
// left and right 表示从num数组的哪段元素排序
// 如果left等于right,即数组只有一个元素,直接返回
if(left>=right) {
return;
}
1. // 普通算法:每次都设置最左边的元素为基准值
int key=num[left];
2. // 随机化算法:先随机挑选一个值作为key,然后把该值和首元素交换位置。
Random rand = new Random();
int key_id = rand.nextInt( right-left+1 )+left;
int key = num[key_id];
num[key_id] = num[left];
num[left] = key;
// 数组中比key小的放在左边,比key大的放在右边,key值下标为i
int i = left;
int j = right;
while(i<j){
// j向左移,直到遇到比key小的值
while(num[j]>=key && i<j){
j--;
}
// i向右移,直到遇到比key大的值
while(num[i]<=key && i<j){
i++;
}
// 必须判断i和j的大小,然后i和j指向的元素交换
if(i<j){
int temp=num[i];
num[i]=num[j];
num[j]=temp;
}
}
// 此时i,j必相等,则此时把j或i的元素(比首元素小)和基准元素交换,然后此时成功分成2部分。
num[left]=num[i];
num[i] = key;
quickSort(num,left,i-1);
quickSort(num,i+1,right);
- 最好情况:T(n) = 2T(n/2) + n = O(nlgn)
- 一般情况:T(n) = T(n/10) + T(9n/10) + n = O(nlgn)
- 最坏情况:T(n) = 1 + T(n-1) + n = T(n-1) + n = O(n^2)
-
此时每次partition都划分成1, n-1两部分
-
所以需要用随机化快排使得期望值最小
-
- 上述代码没有把partition分开写,如果要分开需要让partition返回i或者j,这样才能qucikSort(a, left, i) and quickSort(a, i+1, right)
线性时间选择
- 从一个序列中选择第k大的数
- 类似快排,不过每次只操作一部分的元素,例如选择第k大的元素,先进行一次快排找出中间值,如果第k大的元素>中间值,那么只用考虑右边的元素。