深入理解分治算法(附上详例及代码)

分治算法
分治算法,根据字面意思解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治策略:对于一个规模为n的 问题,若该问题可以容易解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归的解决这些子问题,然后将各子问题的解合并得到原问题的解。
使用场景:
·该问题的规模缩小到一定的程度就可以容易的解决。
·该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
·利用该问题分解出的子问题的解可以合并为该问题的解。
·该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
步骤:
·分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题。
·求解:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题。
·合并:将各个子问题的解合并为原问题的解。

伪代码:
在这里插入图片描述

其中|p|表示问题p的规模,n0为一阈值,表示当问题p的规模不超过n0时,问题已容易直接解出,不必再继续分解。ADHOC(P)是该分治法的基本子算法,用于直接解小规模的问题p。因此,当p的规模不超过n0时直接用算法ADHOC(p)求解。算法MERGE(y1,y2,……,yk)是该分治法中的合并子算法,用于将p的子问题p1,p2……pk的相应的解y1,y2,……yk合并为p的解。
例题:二分查找
二分查找是典型的分治算法的应用。需要注意的是,二分查找的前提是查找的数列是有序的。
算法流程:
·选择一个标志i将集合分为两个子集合。
·判断标志L(i)是否能与要查找的值des相等,相等则直接返回。
·否则判断L(i)与des的大小
·基于判断的结果决定下步是向左查找还是向右查找。
·递归继续上面的步骤。
通过二分查找的流程可以看出,二分查找是将原有序数列划分为左右两个子序列,然后在对两个子序列中的其中一个在进行划分,知道查找成功。
代码:
其中|p|表示问题p的规模,n0为一阈值,表示当问题p的规模不超过n0时,问题已容易直接解出,不必再继续分解。ADHOC(P)是该分治法的基本子算法,用于直接解小规模的问题p。因此,当p的规模不超过n0时直接用算法ADHOC(p)求解。算法MERGE(y1,y2,……,yk)是该分治法中的合并子算法,用于将p的子问题p1,p2……pk的相应的解y1,y2,……yk合并为p的解。
例题:二分查找
二分查找是典型的分治算法的应用。需要注意的是,二分查找的前提是查找的数列是有序的。
算法流程:
·选择一个标志i将集合分为两个子集合。
·判断标志L(i)是否能与要查找的值des相等,相等则直接返回。
·否则判断L(i)与des的大小
·基于判断的结果决定下步是向左查找还是向右查找。
·递归继续上面的步骤。
通过二分查找的流程可以看出,二分查找是将原有序数列划分为左右两个子序列,然后在对两个子序列中的其中一个在进行划分,知道查找成功。
代码:

#include<string.h>
#include<stdio.h>
int k;
int binarysearch(int a[], int x, int low, int high)//a表示需要二分的有序数组(升序),x表示需要查找的数字,low,high表示高低位
{
    
    
	if (low > high)
	{
    
    
		return -1;//没有找到
	}
	int mid = (low + high) / 2;
	if (x == a[mid])//找到x
	{
    
    
		k = mid;
		return x;
	}
	else if (x > a[mid]) //x在后半部分
	{
    
    
		binarysearch(a, x, mid + 1, high);//在后半部分继续二分查找
	}
	else//x在前半部分
	{
    
    
		binarysearch(a, x, low, mid - 1);
	}
}

int main()
{
    
    
	int a[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	printf("请输入需要查找的正数字:\n");
	int x;
	scanf_s("%d", &x);
	int r = binarysearch(a, x, 0, 9);
	if (r == -1)
	{
    
    
		printf("没有查到\n");
	}
	else
	{
    
    
		printf("查到了,在数列的第%d个位置上\n", k + 1);
	}
	return 0;
}

例:快速排序
快排的基本思想:当前待排序的无序区为A[low……high],利用分治法可将快速排序的基本思想描述为:
·分解:
在A[low……high]中任选一个记录为基准(pivot),以此基准将当前无序区划分为左,右两个较小的子区间R[low……pivotpos-1]和R[pivotpos+1……high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.Key,而基准记录pivot则位于正确的位置(pivotpos)上,它无需参与后续的排序。
·求解:
通过递归调用快速排序对左,右子区间R[low……pivotpos-1]和R[pivotpos+1……high]快速排序。
·合并:
因为当“求解”步骤中的两个递归调用结束时,其左,右两个子区间已有序。对快速排序而言,“组合”步骤无须做什么,可看做是空操作。
代码:

#include<iostream>
using namespace std;
void quickSort(int a[], int m, int n);
int partion(int a[], int m, int n);
int main()
{
    
    
	int a[] = {
    
     6,1,2,7,9,3,4,5,10,8 };
	int m = 0;
	int n = (sizeof(a) / 4) - 1;
	quickSort(a, m, n);
	for (int i = 0; i < 10; i++)
	{
    
    
		cout << a[i] << " ";
	}
}

void quickSort(int a[], int m, int n)
{
    
    
	if (m < n)
	{
    
    
		int q = partion(a, m, n);
		quickSort(a, m, q);
		quickSort(a, q + 1, n);
	}
}
int partion(int a[], int m, int n)
{
    
    
	int key = m;
	int j = n, i = m;
	int temp1, temp2;
	while (i != j)
	{
    
    
		while (a[j] > a[key] && i < j)
		{
    
    
			--j;
		}

		while ((a[i] < a[key]) && (i < j))
		{
    
    
			++i;
		}if (i < j)
		{
    
    
			temp1 = a[j];
			a[j] = a[i];
			a[i] = temp1;
		}
	}
	temp2 = a[key];
	a[key] = a[i];
	a[i] = temp2;
	return i;
}

本文参考:https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247485228&idx=1&sn=9f48aee51dcb2b98b56b1827cc658439&chksm=fa0e68adcd79e1bbcd0183ef30a79ede4e46c5835ce05ee6644169c3cc9454073019ccd85d3d&scene=21#wechat_redirect

猜你喜欢

转载自blog.csdn.net/qq_52269550/article/details/121396400