【算法】二、递归与分治

递归与分治(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总结

多敲多敲多敲!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

噶炜123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值