递归与分治(Recursive and Divide)
2.1递归
递归是一种算法实现机制或技术。直接或间接调用自身。递归不一定是分治,如Ackman函数
简单来说也就是从初值出发反复进行某一运算得到所需结果(从已知到位置,从小到大),再从所需结果出发不断回溯前一运算直到初值再递推得到所需结果。
2.1.1阶乘
看如下代码:
int func(int n){
return (n==1)?1:n*func(n-1);
}
首先将大问题分解为小问题,找到递归出口,再通过小问题的解,计算大问题
例如求5!
2.1.2数组排序
对于给定的含有n个元素的数组a,采用简单选择排序和冒泡排序,基于递归排序
简单选择排序
void SelectSort(int a[],int i,int n){ //i初值为1
int j,k;
if(i==n)return;//递归出口
else{
k=i; //k记录a[i../n]中最小元素的下标
for(j=i+1;j<=n;j++){ //在a[i...n]中找最小元素
if(a[j]<a[k])k=j;
}
if(k!=i)swap(a[i],a[k]); //若最小元素不是a[i],a[i]与a[k]交换
}
SelectSort(a,i+1,n);
}
冒泡排序
void BubbelSort(int a[],int i,int n){ //i初值为1
int j;
bool exchange;
if(i==n)return; //满足递归出口条件
else{
exchange=false; //置exchange为false
for(j=1;j<=n-1;j++){
if(a[j]>a[j+1]){ //当相邻元素反序时
swap(&a[j],&a[j+1]);
exchange=true; //发生交换置exchange为true
}
}
if(exchange=false)return; //未发生交换时直接返回
else BubbleSort(a,i+1,n); //发生交换时继续递归调用
}
}
2.1.3求n个元素的全排列
例如求1234的全排列
有1234 1243 1324...等
大问题即求数组a[k..n]的全排列
小问题是求a[k+1...n]的全排列
void perm(int list[],int k,int n){
if(k==n){
for(int i=1;i<=5;i++)printf("%d ",a[i]);
printf("\n");
}
else{
for(int i=k;i<=n;i++){
swap(&list[k],&list[i]);
perm(list,k+1,n);
swap(&list[k],&list[i]);
}
}
}
2.2递归算法分析
2.2.1替换法
根据递归规律,将递归公式通过方程展开,反复代换子问题的规模变量,从而得到递归方程的解
例如:
若问题太复杂,则一点都不好算,故本人不太爱用
2.2.2递归树法
步骤:根据递归方程,构造对应的递归树,把每一层的时间进行求和,得到算法的时间复杂度
直接上题:
2.2.3主方法
很好理解,不多说了就
2.3分治
基本思想:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
适用于分治法的问题特点:
1.原问题可以分解为若干较小的相同问题,即该问题具有最优子结构性质。
2.子问题缩小到一定的程度就可以容易的解决
3.子问题的解可以合并为原问题的解
4.各子问题相互独立,即子问题之间不包含公共子问题
分治法步骤:
1.分解:将待解决的问题划分为足够小的问题
2.求解:求解小问题
3.合并:将小问题的解合并为原问题的解
在数据结构中学习的许多算法都用到了分治法,比如:二路归并、折半查找、快速排序
2.3.1排序
快速排序
基本思想:
int Partition(int a[],int low,int high){
int i=low,j=high;
int povit=a[low];
while(i<j){
while(i<j&&a[j]>=povit)j--;
a[i]=a[j];
while(i<j&&a[i]<=povit)i++;
a[j]=a[i];
}
a[i]=povit;
return i;
}
int Solution(int a[],int n){
int low=1,high=n;
bool flag=true;
while(flag){
int i=Partition(a,low,high);
if(i==n/2)
flag=false;
else if(i<n/2)
low=i+1;
else high=i-1;
}
int s1=0,s2=0;
for(int i=1;i<=2;i++)s1+=a[i];
for(int j=n/2+1;j<=2;j++)s2+=a[i];
return s2-s1;
}
归并排序
归并排序有自顶向下和自底向上两种方法
自顶向下代码:
void merge(int a[],int low,int mid,int high)//a[low..mid],a[mid+1..high]分别有序,将其归并为新的有序序列 并存入a[low,..high]
{
i=low,j=mid+1,k=1;
tmp=(int *)malloc((high-low+2)*sizeof(int));
while (i<=mid && j<=high)
if (a[i]<=a[j]) {
tmp[k]=a[i];
i++;
k++;
}
else {
tmp[k]=a[j];
j++;
k++;
}
while (i<=mid) {
tmp[k]=a[i];
i++;
k++;
}
while (j<=high) {
tmp[k]=a[j];
j++;
k++;
}
for (k=1,i=low; i<=high; k++,i++) a[i]=tmp[k];
free(tmp);
}
void mergeSort(int a[],int low,int high){
if(low<high){
mid=(low+high)/2;
mergeSort(a,low,mid);
mergeSort(a,mid+1,high);
merge(a,low,mid,high);
}
}
2.3.2查找
n个数中求出最大最小值
void maxMin(int *a,int i,int j,int *max,int *min) {
int mid;
int lmax,lmin,rmax,rmin;
if(j==i) {
*max=a[i];
*min=a[i];
return;
} else if(j-i==1) {
if(a[i]>a[j]) {
*max=a[i];
*min=a[j];
return;
} else {
*max=a[j];
*min=a[i];
return;
}
} else {
mid=i+(j-i)/2;
maxMin(a,i,mid,&lmax,&lmin);
maxMin(a,mid+1,j,&rmax,&rmin);
if(lmax>rmax) *max=lmax;
else *max=rmax;
if(lmin<rmin) *min=lmin;
else *min=rmin;
}
}
查找第k小元素
int quickSort(int a[],int s,int t,int k) { //在a[s..t]序列中找第k小的元素
int i,j,tmp;
if(s==t && s==k) return a[s];
if(s>t) return -1;
tmp=a[s];
i=s;
j=t;
while(i<j) { //从区间两端交替向中间扫描,直至i=j为止
while (i<j && a[j]>=tmp) j--;
if(i<j) {
a[i]=a[j]; //将a[j]前移到a[i]的位置
i++;
}
while (i<j && a[i]<=tmp) i++;
if(i<j) {
a[j]=a[i]; //将a[i]后移到a[j]的位置
j--;
}
}
a[i]=tmp;
if (i-s+1 == k) return a[i];
else if (k < i-s+1) return quickSort(a, s, i-1, k); //在左区间中递归查找
else return quickSort(a, i+1, t, k-(i-s+1)); //在右区间中递归查找
}
寻找两个等长有序序列的中位数
用分治法求含有n个有序元素的序列a、b的中位数的过程如下
若n=1,则较小者为中位数
n>1时,分别求出a[s1...t1],b[s2...t2]的中位数a[m1],b[m2]
1.若a[m1]=b[m2],结果为a[m1]或b[m2]
2.若a[m1]<b[m2],在a[m1...t1]和b[s2...m2]内查找
3.若a[m1]>b[m2],将2调换位置即可
int FindMidNumber(int *arr1,int *arr2,int s1,int t1,int s2,int t2)
{
if(t1==0)return arr1[t1]>arr2[t1]?arr2[t1]:arr1[t1];
int m1=(s1+t1)/2;
int m2=(s2+t2)/2;
if(t1-s1==1){
int arr[4]={arr1[s1],arr1[t1],arr2[s2],arr2[t2]};
sort(arr,4);
return arr[1];
}
if(arr1[m1]==arr2[m2])return arr1[m1];
else if(arr1[m1]<arr2[m2])
{
if((t1-s1+1)%2==0)
{
return FindMidNumber(arr1,arr2,m1+1,t1,s2,m2);
}
else
{
return FindMidNumber(arr1,arr2,m1,t1,s2,m2);
}
}
else
{
if((t1-s1+1)%2==0)
{
return FindMidNumber(arr2,arr1,m2+1,t2,s1,m1);
}
else
{
return FindMidNumber(arr2,arr1,m2,t2,s1,m1);
}
}
}
2.3.3组合
最大连续子序列和
问题描述:给定一个有n个整数的序列,求出其中最大连续 子序列的和。例如{-2,11,-4,13,-5,-2}的最大连续子序列和为20{11,-4,13}。规定一个序列最大连续子序列和至 少是0如果小于0,其结果为0。
问题分析:
该子序列只可能出现在三个地方
1.左半部分a[1...mid]
2.右半部分a[mid+1,n]
3.横跨左右两个部分
int MaxContinuous(int *arr,int left,int right)
{
if(left==right)return arr[left]>0?arr[left]:0;
int mid=(left+right)/2;
int maxLeftSum,maxRightSum,maxLeftBorderSum,maxRightBorderSum=0,tmaxLeftBorderSum=0,tmaxRightBorderSum=0,maxBorderSum=0;
maxLeftSum=MaxContinuous(arr,left,mid);
maxRightSum=MaxContinuous(arr,mid+1,right);
maxLeftBorderSum=arr[mid];
tmaxLeftBorderSum=0;
for(int i=mid;i>=left;i--){
tmaxLeftBorderSum+=arr[i];
if(tmaxLeftBorderSum>maxLeftBorderSum)
maxLeftBorderSum=tmaxLeftBorderSum;
}
maxRightBorderSum=arr[mid+1];
tmaxRightBorderSum=0;
for(int i=mid+1;i<=right;i++){
tmaxRightBorderSum+=arr[i];
if(tmaxRightBorderSum>maxRightBorderSum)
maxRightBorderSum=tmaxRightBorderSum;
}
maxBorderSum=maxRightBorderSum+maxLeftBorderSum;
return max(maxLeftSum,maxRightSum,maxBorderSum);
}
2.4总结
多敲多敲多敲!