分治法

分治法的概念:

作为五大算法之一的分治法,可算是最早接触的一种算法。分治法,与其说是一种算法,不如将其称为策略来的更贴切一些。算法的思想就是将大问题分成小问题,并解决小问题之后合并起来生成大问题的解。

分治法的精髓:

分–将问题分解为规模更小的子问题;

治–将这些规模更小的子问题逐个击破;

合–将已解决的子问题合并,最终得出“母”问题的解;

分治法的作用,自然是让程序更加快速地处理问题。比如一个n的问题分解成两个n/2的问题,并由两个人来完成,效率就会快一些。当然单线程的程序的分治法,就是把n的问题剔除掉可以省略的步骤,从而提高程序运行的速度。

图解分治法:

在这里插入图片描述
二分法:

二分法针对于有序集合的处理来说显得比按序遍历来得更快一些。比如:有一个城市到另一个城市之间的电杆不通电了,该如何排查?作为常用思维大概就是逐个去查了,如果能确定两地间的电杆是串联的节点,那么二分法显然是比较有效率的,因为它跳过了很多不必要排查的电杆。

我们一般使用二分法去寻找一个流中的特定的数。比如查找有序数列中是否存在一个数,或者使用二分法求一个数的根号。

找数字:

假设我们需要在1-10000里面找一个数200,使用逐个搜索的方法,我们会消耗200步。如果使用二分法呢?

第一步我们找到1-10000中间的那个数:5000。它大于200,所以200应该在1-4999这个区间内,这样我们就丢掉了后5000个数。

第二步我们找到2500,也比200要大,200在1-2500这个区间内。

第三步找到1250这个数,也比200大。

第四步找到750。

第五步找到375。

第六步找到167,它比200要小了,说明200在167-375之间。

第七步找到271,它在167-271之间。

第八步找到219,它在167-219之间。

第九步找到193,它在193-219之间。

第十步找到206,它在193-206之间。

第十一步找到199,它在199-206之间。

第十二步找到202,它在199-202之间。

第十三步找到200。

二分法查找函数:
代码示例1:

/*
写一个函数BinarySearch,在包含size个元素的、从小到大排序的int数组a里查找元素p,
如果找到,则返回元素下标,如果找不到,则返回-1。要求复杂度o(log(n)) 
 */
int BinarySearch(int a[], int size, int p)
{
	int L = 0;
	int R = size - 1;
	while (L <= R)
	{
		int mind = L + (R - L) / 2;
		if (p == a[mid])
		{
			return mid;
		}
		else if (p > a[mid])
		{
			L = mid + 1;
		}
		else
		{
			R = mid - 1;
		}
	}
	return -1;
}

代码示例2:

/*
写一个函数LowerBound,在包含size个元素的、从小到大排序的int数组a里查找比给定元素p,
小的,如果找到,则返回元素下标,如果找不到,则返回-1。要求复杂度o(log(n)) 
*/
int LowerBound(int a[], int size, int p)
{
	int L = 0;
	int R = size - 1;
	int LastPos = - 1;
	while(L <= R)
	{
		int mid = L + (R - L) / 2;
		if (a[mid] >= p)
		{
			R = mid - 1;
		}
		else
		{
			LastPos = mid;
			L = mid - 1;
		}
	}
	return LastPos
} 

二分法求方程的根:

/*
f(x) = x^3 - 5x^2 + 10x - 80
解法:f'(x) > 0,f(x)是单调递增的,故可用二分法在区间[0,100]
中寻找根。 
*/
#include <iostream>
#include <cmath>
using namespace std;
double EPS = 1e-6; 
double f(double x)
{
	return x * x * x - 5 * x * x + 10 * x - 80; 
}
int main()
{
	double root, x1 = 0, x2 = 100, y;
	root = x1 + (x2 - x1) / 2;
	y = f(root);
	while (fabs(y) >= EPS)
	{
		if (y > 0)
		{
			x2 = root;
		}
		else
		{
			x1 = root;
		}
		root = x1 + (x2 - x1) / 2;
		y = f(root);
	}
	printf("%.5f\n", root);
	return 0;
}
/*
通用技巧
1. 判定条件为start+1小于end,start=0, end=size-1 

2. mid=start+((end-start)>>1) 

3. 如果data[mid]满足条件直接返回,如果满足条件的数据在mid的右边则将start=mid,如果满足条件的数据在mid左边则将end=mid 

4. 根据条件再次判定start,end两个元素是否满足条件 

5. 其实整个过程就不断缩小范围的过程,最后一步就是将整个数组的范围缩小到start
end两个元素而已。 

这里第3步特别注意的地方就是不管什么情况下都应该单独判data[mid]大于等于和小于的情况,而不应该合并,因为会比较容易出错。
*/

归并排序:

将两个的有序数列合并成一个有序数列,我们称之为"归并"。

归并排序(Merge Sort)就是利用归并思想对数列进行排序。根据具体的实现,归并排序包括"从上往下"和"从下往上"2种方式。

  1. 从下往上的归并排序:将待排序的数列分成若干个长度为1的子数列,然后将这些数列两两合并;得到若干个长度为2的有序数列,再将这些数列两两合并;得到若干个长度为4的有序数列,再将它们两两合并;直接合并成一个数列为止。这样就得到了我们想要的排序结果。

  2. 从上往下的归并排序:它与"从下往上"在排序上是反方向的。它基本包括3步:

    ① 分解 – 将当前区间一分为二,即求分裂点 mid = (low + high)/2;

    ② 求解 – 递归地对两个子区间a[low…mid] 和 a[mid+1…high]进行归并排序。递归的终结条件是子区间长度为1。

    ③ 合并 – 将已排序的两个子区间a[low…mid]和 a[mid+1…high]归并为一个有序的区间a[low…high]

归并排序图解:
在这里插入图片描述
从上往下归并排序:
在这里插入图片描述
从下往上归并排序:
在这里插入图片描述
代码示例:

#include <iostream>
using namespace std;
int a[20], b[20];
void mergesort(int L, int R)
{
	if (L >= R)
	{
		return;
	}
	int mid = L + R;
	mid /= 2;
	mergesort(L, mid);
	mergesort(mid + 1, R);
	int i = L, k = L;
	int j = mid + 1;
	while (i <= mid && j <= R)
	{
		if (a[i] <= a[j])
		{
			b[k++] = a[i++];
		}
		else
		{
			b[k++] = a[j++];
		}
	}
	while (i <= mid)//将imid-i和R-j两组有序序列,归并在一个有序序列中
	{
		b[k++] = a[i++];//将mid-i剩余的数放在最后
	}
	while (j <= R)
	{
		b[k++] = a[j++];//将r-j剩余的数放在最后
	}
	for(k = L;k <= R; k++)//将b数组中的数据拷贝到原数组中
	{
		a[k] = b[k];
	}
}
int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	mergesort(0, n - 1);
	for (int i = 0; i < n; i++)
	{
		cout << a[i] << " ";
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值