算法基础--分治

分治就是将问题的一个实例划分为两个或更多个较小的实例(这些较小的实例通常也是原问题的实例),一直持续划分实例,直至问题规模小到可以轻松获得答案为止。实际上这也可以说是一种递归的思想。

二分查找(所查找的序列必须有序,查找时间复杂度O(logn) )

在包含size个元素的、从小到大排序的int数组a里查找元素p,如果找到,则返回元素下标,如果找不到,则返回-1。

二分查找函数非递归版本:

int BinarySearch(int a[],int size,int p)
{
	int L=0;
	int R=size-1;
	int mid;
	while(L<=R)
	{
		mid=L+(R-L)/2;
		if(p==a[mid]) return mid;
		else if(p<a[mid])
		{
			R=mid-1;
		}
		else
		{
			L=mid+1;
		}
	}
	return -1;//若没找到则返回-1
}

二分查找函数递归版本:

int rBinarySearch(int a[],int l,int r,int x)
{
	if(l>r) return -1;
	else
	{
		int mid=l+(r-l)/2;
		if(x==a[mid]) return mid;
		else if(x<a[mid]) return rBinarySearch(a,l,mid-1,x);
		else return rBinarySearch(a,mid+1,r,x);
	}
} 

二分查找例题
寻找指定和的整数对
输入n ( n<= 100,000)个整数, 找出其中的两个数,它们之和等于整数m(假定
肯定有解)。 题中所有整数都能用 int 表示
解法1:

  1. 将数组排序,复杂度是O(n×log(n))
  2. 对数组中的每个元素a[i],在数组中二分查找m-a[i],看能否找到。复杂度log(n),最
    坏要查找n-2次,所以查找这部分的复杂度也是O(n×log(n))
    这种解法总的复杂度是O(n×log(n))的。

解法2:

  1. 将数组排序,复杂度是O(n×log(n))
  2. 查找的时候,设置两个变量i和j,i初值是0,j初值是n-1.看a[i]+a[j],如果大于m,就让j
    减1,如果小于m,就让i加1,直至a[i]+a[j]=m。
    这种解法总的复杂度是O(n×log(n))的。

归并排序

数组排序任务可以如下完成:

  1. 把前一半排序
  2. 把后一半排序
  3. 把两半归并到一个新的有序数组,然后再拷贝回原数组,排序完成。
#include <iostream>
using namespace std;
void Merge(int a[],int s,int m, int e)//将两部分合并 
{
	int p1=s,p2=m+1;
	int pb=0;
	int *tmp=new int[e-s+1];
	while(p1<=m && p2<=e)
	{
		if(a[p1]<a[p2]) tmp[pb++]=a[p1++];
		else tmp[pb++]=a[p2++];
	}
	while(p1<=m) tmp[pb++]=a[p1++];
	while(p2<=e) tmp[pb++]=a[p2++];
	for(int i = 0;i < e-s+1; ++i) a[s+i] = tmp[i];
	delete [] tmp;
}
void MergeSort(int a[],int s,int e)//递归实现合并排序 
{
	if( s < e) 
	{
		int m = s + (e-s)/2;
		MergeSort(a,s,m);
		MergeSort(a,m+1,e);
		Merge(a,s,m,e);
	}
}

快速排序

数组排序任务可以如下完成:

  1. 设k=a[0], 将k挪到适当位置,使得比k小的元素都在k左边,比k大的元素都在k右边,和k相等的在k左右出现均可 ( O( n)时间完成)
  2. 把k左边的部分快速排序
  3. 把k右边的部分快速排序
#include <iostream>
using namespace std;
void swap(int & a,int & b) //交换变量a,b值
{
	int tmp = a;
	a = b;
	b = tmp;
}
void QuickSort(int a[],int s,int e)
{
	if( s >= e) return;
	int k = a[s];
	int i = s,j = e;
	while( i != j ) 
	{
		while( j > i && a[j] >= k ) --j;
		swap(a[i],a[j]);
		while( i < j && a[i] <= k ) ++i;
		swap(a[i],a[j]);
	} //处理完后, a[i] = k
	QuickSort(a,s,i-1);
	QuickSort(a,i+1,e);
}

例题:输出前m大的数
描述:给定一个数组包含n个元素,统计前m大的数并且把这m个数从大到小输出。
输入
第一行包含一个整数n,表示数组的大小。 n < 100000。
第二行包含n个整数,表示数组的元素,整数之间以一个空格分开。每个整数的绝对值不超过100000000。
第三行包含一个整数m。 m < n。
输出
从大到小输出前m大的数,每个数一行。

解法一:排序后再输出,复杂度 O(nlogn)

解法二:用分治处理: 复杂度 O(n+mlogm)
思路:把前m大的都弄到数组最右边,然后对这最右边m个元素排序,再输出
关键 : O(n)时间内实现把前m大的都弄到数组最右边
引入操作 arrangeRight(k): 把数组(或数组的一部分)前k大的都弄到最右边

如何将前k大的都弄到最右边
1)设key=a[0], 将key挪到适当位置,使得比key小的元素都在
key左边,比key大的元素都在key右边(线性时间完成)
2) 选择数组的前部或后部再进行 arrangeRight操作

例题:快速幂
非递归实现:

int Pow(int a,int b)
{ //快速求a^b ,复杂度 log(b)
 	int result = 1;
	int base = a;
	while(b) 
	{
			if( b & 1)
				result *= base;
			base *= base;
			b >>= 1;
	}
	return result;
}

递归实现:

int Pow(int a,int b)
{ //快速求a^b ,复杂度 log(b)
	if(b == 0)
	return 1;
	if(b & 1)  //b是奇数
	{
		return a * Pow(a,b-1);
	}
	else 
	{
		int t = Pow(a,b/2);
		return t * t;
	}
}

不应使用分治方法的情况:

  1. 一个规模为n的实例被划分成两个或多个实例,而每个实例的复杂度仍然几乎为n
  2. 一个规模为n的实例被划分为差不多n个规模为n/c的实例(c为常数)

比如很多书都会举的斐波那契数列,如使用分治(递归)的方法将其分为f(n-1)与f(n-2)就会导致大量的重复计算,上述两种情况的本质就是如此,只是导致的复杂度规模有所不同,此处不详谈。

而解决此类问题,一般会使用循环或者动态规划。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值