计算机算法设计与分析第二章递归与分治知识总结

 递归

递归概念

    直接或间接地调用自身的算法成为递归算法。用函数自身给出定义的函数成为递归函数

递归实例

  1. 阶乘函数
  2. 斐波那契数列
  3. Ackerman函数
  4. 排列问题
  5. 证书划分问题
  6. 汉诺塔问题

分治

分治法的基本思想

    将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立而且与原问题相同,递归地求解后,将各个子问题合并得到原问题的解。

分治法的特征

  1. 该问题所分解出的各个子问题是相互独立的,子问题之间不包含公共的子问题
  2. 利用该问题分解出的子问题的解可以合并为该问题的解
  3. 该问题的规模缩小到一定的程度就可以容易地解决
  4. 该问题可以分解为若干个规模较小的相同问题

主定理计算时间复杂度

用T(n)表示该分治法解规模为|p|=n的问题所需的计算时间,

T(n)=\left\{\begin{matrix}O(1) \\ kT(n/m)+f(n) \end{matrix}\right.   

f(n)\in \Theta (n^{^{d}}) d>=0 ,则T(n)=\left\{\begin{matrix}\Theta (n^{d})\, \; \: k<m^{d} & \\ \Theta (n^{d}logn)\; \: k=m^{d} & \\ \Theta (n^{log{_{m}}^{k}})\: \; k>m^{d} & \end{matrix}\right.

分治法实例

1.二分搜索

        基本思想

    将n个元素分成个数大致相同的两半,取a[n/2]与x比较,如果x<a[n/2],在数组a的左半部分继续搜索;如果X>a[n/2],在数组a的右半部分继续搜索,如果x==a[n/2],找到x程序终止。

        代码实现

#include<iostream>
using namespace std;
int BinarySearch(int a[], int aSize, int key)  
{  
    if ( aSize == 0 )  
        return -1;  
    int low = 0;  
    int high = aSize - 1;  
    int mid = 0;  
  
    while ( low <= high )  
    {  
        mid = (low + high )/2;  
          
        if ( a[mid] < key)  
            low = mid + 1;  
        else if ( a[mid] > key )     
            high = mid - 1;  
        else  
            return mid;  
    }  
    return -1;  
}  
int main()  
{  
    int a[10];  
    for (int i=0; i<10; i++)  
        a[i] = i;  
  
 
    cout<<BinarySearch(a, 10, 6)<<endl;  
      
  
    return 0;  
}  

       时间复杂度

时间复杂度为O(logn) 

2.大整数乘法

        基本思想

    将n位整数X和Y分为2段,每段为n/2位,X=A*10^(n/2)+B,Y=C*10^(n/2)+D;XY=AC*10^n+((A-B)(D-C)+AC+BD)*10^n+BD

        代码实现

 #include<iostream>
#include<cmath>
using namespace std;
long long mul(long long a,long long b,int len){
	if(len==0)return 0;
	else if(len==1){
		return a*b;
	}
	else{
		long long A=a/(int)(pow(10,len/2));
		long long B=a%(int)(pow(10,len/2));
		long long C=b/(int)(pow(10,len/2));
		long long D=b%(int)(pow(10,len/2));
		long long AC=mul(A,C,len/2);
		long long BD=mul(B,D,len/2);
		long long ABCD=mul((A-B),(D-C),len/2)+AC+BD;
		return pow(10,len/2+len/2)*AC+ABCD*pow(10,len/2)+BD;
	}
}
int main(){
	long long a,b,temp;
	cin>>a>>b;
	temp=a;
	int len=0;
	while(temp){
		temp/=10;
		len++;
	}
	long long c=mul(a,b,len);
	cout<<c;
}

        时间复杂度

3.Strassen矩阵乘法

        基本思想

        时间复杂度

4.合并排序

        基本思想

     递归实现:使用分治法,将待排序的数组分为两份,分别递归排序,然后合并两个各自有序的数组。

     非递归实现:先将数组中的相邻两元素两两配对,再用合并算法将它们排序,构成n/2组长度为2的排好序的子数组段,再将它们排序成长度为4的排好序的字数组段。如此下去,直至整个数组排好序。

        代码实现

递归

#include<iostream>
using namespace std;
void merge(int a[],int l,int m,int h){
	int i=l,j=m+1,k=0;
	int b[h-l+1];
	while(i<=m&&j<=h){
		if(a[i]>a[j]){
			b[k++]=a[j++];
		}
		else b[k++]=a[i++];
	}
	while(i<=m){
		b[k++]=a[i++];
	}
	while(j<=h){
		b[k++]=a[j++];
	}
	for(i=l,k=0;i<=h;i++,k++){
		a[i]=b[k];
	}
}
void merge_sort(int a[],int low,int high){
	if(low<high){
		int mid=(low+high)/2;
		merge_sort(a,low,mid);
		merge_sort(a,mid+1,high);
		merge(a,low,mid,high);
	}
	
}
int main (){
	int n;
	cin>>n;
	int a[n];
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	merge_sort(a,0,n-1);
	cout<<count;
}

非递归

#include<iostream>
using namespace std;
void merge_sort(int a[],int l,int r,int n){
	int gap=1;
	int temp[n];
	while(gap<n){
		for(int i=0;i<=r;i=i+2*gap){
			int begin1=i,end1=i+gap-1,mid=i+gap-1;
			int begin2=mid+1,end2=i+2*gap-1;
			if(end2>r)end2=r;
			if(begin2>r)break;
			int j=i;
			while(begin1<=end1&&begin2<=end2){
				if(a[begin1]>a[begin2]){
					temp[j++]=a[begin2++];
				}
				else temp[j++]=a[begin1++];
			}
			while(begin1<=end1)temp[j++]=a[begin1++];
			while(begin2<=end2)temp[j++]=a[begin2++];
			for(j=i;j<=end2;j++)a[j]=temp[j];
			}
			gap*=2;
	}
	
}
int main(){
	int n;
	cin>>n;
	int a[n];
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	merge_sort(a,0,n-1,n);
	for(int i=0;i<n;i++)cout<<a[i]<<' ';
}

        时间复杂度

    时间复杂度为O(nlogn)

5.棋盘覆盖

        基本思想

当k>0时,将2 k ∗ 2 k 2^k*2^k2 k∗2 k 棋盘分割为4个2 k − 1 ∗ 2 k − 1 2^{k-1}*2^{k-1}2 k−1∗2 k−1子棋盘。特殊方格必位于4个较小子棋盘之一中,其余3个子棋盘无特殊方格。为了将这3个无特殊方格的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,这3个子棋盘上被L型骨牌覆盖的方格就成为该棋盘上的特殊方格,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种分割,直至棋盘简化为1 ∗ 1 1*11∗1棋盘。

        时间复杂度

时间复杂度为O(4^k)
 

6.快速排序

        基本思想

基本思想:使用分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

        代码实现

递归代码

#include<iostream>
using namespace std;
void quick_sort(int a[],int l,int r){
	if(l>=r)return;
	int i=l-1,j=r+1,x=a[(l+r)/2];
	while(i<j){
		do i++;while(a[i]<x);
		do j--;while(a[j]>x);
		if(i<j)swap(a[i],a[j]);
	}
	quick_sort(a,l,j);
	quick_sort(a,j+1,r);
}
int main(){
	int n;
	cin>>n;
	int a[n];
	for(int i=0;i<n;i++)cin>>a[i];
	quick_sort(a,0,n-1);
	for(int i=0;i<n;i++)cout<<a[i]<<' ';
}

随机代码

#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
void quick_sort(int a[],int l,int r){
	srand(unsigned(time(NULL)));
	int y=rand()%(r-l+1)+l;
	swap(a[(l+r)/2],a[y]);
	if(l>=r)return;
	int i=l-1,j=r+1,x=a[(l+r)/2];
	while(i<j){
		do i++; while(a[i]<x);
		do j--;while(a[j]>x);
		if(i<j)swap(a[i],a[j]);
		
	}
	quick_sort(a,l,j);
	quick_sort(a,j+1,r);
}
int main(){
	int n;
	cin>>n;
	int a[n];
	for(int i=0;i<n;i++)cin>>a[i];
	quick_sort(a,0,n-1);
	for(int i=0;i<n;i++)cout<<a[i]<<' ';
}

        时间复杂度

    最坏情况下,标志点一侧没有元素,时间复杂度为O(n^2),最好情况下,两侧元素数相同,时间复杂度为O(nlogn),快速排序不稳定。

7.最近点对问题

        基本思想

   将所给的平面上n个点的集合S分为两个子集S1和S2,每个子集中约有n/2个点,然后在每个子集中递归地求其最接近的点对。最近点对可能单纯在S1或S2中,也可能分别在S1和S2中。取两个子集递归求解最小值为d,第三种情况只会发生在 ( mid - d , mid + d ) 内,这个范围,mid左边p1,mid右边p2,p1中每个点最多在p2中存在6个点会更新答案,即按照y坐标排序后,p1每点最多只要检查p2中排好序的相继6个点。

        时间复杂度

 时间复杂度为 O(nlogn)

8.求第k小问题

        基本思想

快排改进算法:在快速排序的过程中,基准点在经历一次排序后会排在应在的位置上,这时只需判断第k个点与该基准点位置的距离,递归求解即可。

线性时间选择法:找到一个划分基准,使得这个基准的划分的两个子数组的长度都至少是原来的ε 倍(0 < ε &&ε < 1),那么最坏情况也能O( n )解决问题。

随机选择算法:在快排改进算法的基础上改进,随机取基准点,将快排的不稳定性降低为概率事件

        代码实现

快排改进算法

#include<iostream>
#include<algorithm>
using namespace std;
const int N=10000000;
int x[N],y[N];
int k_sort(int a[],int l,int r,int k){
    if(l>=r)return a[l];
    int i=l-1,j=r+1,x=a[(l+r)/2];
    while(i<j){
        do i++;while(a[i]<x);
        do j--;while(a[j]>x);
        if(i<j)swap(a[i],a[j]);
    }
    if(k<=j-l+1)return k_sort(a,l,j,k);//k在左侧
    else return k_sort(a,j+1,r,k-(j-l+1));//k在右侧,k变成右侧的第k-(j-l+1)小
}
int main(){
    int n;cin>>n;
    for(int i=0;i<n;i++){
        scanf("%d %d",&x[i],&y[i]);
    }
    cout<<"("<<k_sort(x,0,n-1,(n+1)/2)<<","<<k_sort(y,0,n-1,(n+1)/2)<<")";
}

线性时间选择

#include <cstdio>
#include <cstdlib>

int num[2000001];

int select(int low, int high, int top);
int partition(int low, int high, int median);
void selectSort(int low, int high);
void swap(int &a, int &b);

int main()
{
	int n, m, i;

	while (~scanf("%d%d", &n, &m))
	{
		for (i = 0; i < n; i++)
			scanf("%d", &num[i]);

		printf("%d\n", select(0, n - 1, m - 1));

		/*
		for (i = 0; i < n; i++)
			printf("%d%c", num[i], i < n - 1 ? ' ' : '\n');
		*/
	}
	return 0;
}
// 中位数法线性时间选择
int select(int low, int high, int top)
{
	// 小于75个数据随便用一个排序方法
	if (high - low < 74)
	{
		selectSort(low, high);				// 选择排序
		return num[low + top];				// 排完序直接返回第low + top的数
	}

	int groupNum = (high - low - 4) / 5;		// 每组5个数, 计算多少个组, 从0开始计数

	for (int i = 0; i <= groupNum; i++)
	{
		int start = low + 5 * i;				// 每组的起始位置
		int end = start + 4;					// 每组的结束位置
		for (int j = 0; j < 3; j++)				// 从小到大冒3个泡
			for (int k = start; k < end - j; k++)
				if (num[k] > num[k + 1])
					swap(num[k], num[k+1]);
		swap(num[low + i], num[start + 2]);		// 每组的中位数交换到前面第low + i的位置
	}
	// 上面排完后, 数组low + 0 到 low + groupNum都是每一组的中位数
	int median = select(low, low + groupNum, (groupNum + 1) / 2);	// 找中位数的中位数
	int p = partition(low, high, median);			// 将数组分为两段, 左边的小于中位数的中位数, 右边的大于中位数的中位数
	int n = p - low;			// 计算p到low之间有多少个数, 后面得减掉

	if (n == top)
		return num[p];			// 如果运气好, 刚好要找的就是中位数
	if (n > top)
		return select(low, p - 1, top);				// n比top大就在左边找
	if (n < top)
		return select(p + 1, high, top - n - 1);	// n比top小就在右边找, 并且top要减去已经大的个数
}
// 以中位数进行分割, 分成两半
int partition(int low, int high, int median)
{
	int p;
	for (int i = low; i <= high; i++)
		if (num[i] == median)
		{
			p = i;
			break;
		}
	// 将中位数交换到最前面
	swap(num[p], num[low]);
	// 记下最前面的数
	int key = num[low];
	// 把小于key的放前面, 大于key的放后面
	while (low < high)
	{
		while (num[high] >= key && low < high)
			high--;
		if (low < high)
			num[low] = num[high];
		while (num[low] <= key && low < high)
			low++;
		if (low < high)
			num[high] = num[low];
	}
	// 分别从两头开始, 找到中间时, 把key存回
	num[low] = key;
	return low;
}
// 选择排序
void selectSort(int low, int high)
{
	for (int i = low; i <= high; i++)
	{
		int MIN = i;
		for (int j = i + 1; j <= high; j++)
			if (num[MIN] > num[j])
				MIN = j;
		swap(num[i], num[MIN]);
	}
}
// 交换两个元素
void swap(int &a, int &b)
{
	int temp = a;
	a = b;
	b = temp;
}

        时间复杂度

快排改进:最坏O(n^2),时间复杂度为O (N*K)

随机选择:最坏O(n^2)

线性时间:O(n)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值