期末了,通过写博客的方式复习一下算法,把自己知道的全部写出来
分治:分而治之,把一个复杂的问题分解成很多规模较小的子问题,然后解决这些子问题,把解决的子问题合并起来,大问题就解决了
但是我们应该在什么时候用分治呢?这个问题也困扰了我很久,做题的时候就不知道用什么算法
能用分治法的基本特征:
1.问题缩小到一定规模容易解决
2.分解成的子问题是相同种类的子问题,即该问题具有最优子结构性质
3.分解而成的小问题在解决之后要可以合并
4.子问题是相互独立的,即子问题之间没有公共的子问题
第一条大多数问题都可以满足
第二条的大多数问题也可以满足,反应的是递归的思想
第三条:这个是能分治的关键,解决子问题之后如果不能合并从而解决大问题的话,那凉凉,如果满足一,二,不满足三,即具有最优子结构的话,可以考虑贪心或者dp
第四条:如果不满足第四条的话,也可以用分治,但是在分治的过程中,有大量的重复子问题被多次的计算,拖慢了算法效率,这样的问题可以考虑dp(大量重复子问题)
了解了什么问题可以采用分治,那么分治到达怎么用?步骤是什么呢
三个步骤:
1.分解成很多子问题
2.解决这些子问题
3.将解决的子问题合并从而解决整个大问题
化成一颗问题树的话,最底下的就是很多小问题,最上面的就是要解决的大问题,自底向上的方式求解问题
说的再多不如看经典的样例,更好的体会分治的思想
样例1:二分查找
条件:数组有序,假设是升序数组
虽然二分很容易,但是我还是要具体从算法思想分治的方向分析一下
现在我们要在一个有序的升序数组里面查找一个数x有没有
暴力的做法就是拿跟数组里面每个数比较一下,有的话就返回下标,这个是大问题
仔细想一下,就知道这个大问题是由很多小问题组成的,小问题:在数组的一部分里面找x
那么我们可以把数组分成很多部分,在很多部分里面找x,如果在这些部分里面没有找到x,那么把这些子问题合并起来,就是大数组里面没有x,否则就是有x
这个真的很好的反应了分治的思想,先分解成很多小问题,解决这些小问题,把解决的小问题合并起来,大问题就解决了,二分具体的做法我就不多说了,都知道,贴个代码
#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("%d",&x); int r=binarysearch(a,x,0,9); if(r==-1) { printf("没有查到\n"); } else { printf("查到了,在数列的第%d个位置上\n",k+1); } return 0; }
经典样例二:全排列问题
有1,2,3,4个数,问你有多少种排列方法,输出来
仔细想想,采用分治的话,我们就要把大问题分解成很多的子问题,大问题是所有的排列方法
那么我们分解得到的小问题就是以1开头的排列,以2开头的排列,以3开头的排列,以4开头的排列
现在这些问题有能继续分解,比如以1开头的排列中,只确定了1的位置,没有确定2,3,4的位置,把2
3,4三个又看成大问题继续分解,2做第二个,3做第二个,或者4做第二个
一直分解下去,直到分解成的子问题只有一个数字的时候,不再分解
因为1个数字肯定只有一种排列方式啊,现在我们分解成了很多的小问题,解决一个小问题就合并,合并成
一个大点的问题,合并之后这个大点的问题也解决了,再将这些大点的问题合并成一个更大的问题,那么这
个更大点的问题也解决了,直到最大的问题解决为止
这个就是用分治的思想解决全排列问题,我主要想分析的是分治的思想者全排列问题上是怎么用的,不想分析具体全排列的做法,因为我觉得思想比方法更重要,在解题的时候深有体会,因为又的时候没有题是你做过的原题,全排列问题的具体做法参考我的这篇博客:https://www.cnblogs.com/yinbiao/p/8684313.html,也贴一下代码
#include<string.h> #include<stdio.h> int k=0; char a[100]; long long count=0;//全排列个数的计数 void s(char a[],int i,int k)//将第i个字符和第k个字符交换 { char t=a[i]; a[i]=a[k]; a[k]=t; } void f(char a[],int k,int n) { if(k==n-1)//深度控制,此时框里面只有一个字符了,所以只有一种情况,所以输出 { puts(a); count++; } int i; for(i=k;i<n;i++) { s(a,i,k); f(a,k+1,n); s(a,i,k);