分治算法讲解
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第二条件:每个小规模都具有最佳子结构(在变量范围内,可以用来表示)
第三条件:每个小问题可以通过合并在一起形成大问题的解
第四条件:每个小问题相互独立
专题一:分治算法之二分查找
思考题:找假币:
有一堆个数为32的硬币,和一个天平,已知其中有一个假币,且假币比真硬币轻,找出这个假币
1.普通方法:两两比较,轻的那个是假币,最多比较16次
四:基本步骤
(1)划分:把问题的实例划分成子问题
(2)求解:若是子问题比较简单,就直接解决,否则递归求解子问题
(3)合并:合并子问题的解得到原问题的解
课前引导:
一:分段函数
例如:高中数学中的分段函数也是类似分治思想的体现,如看图求y关于x的表达式。一个分段函数,反映的是x与y的关系,简单来说,就是在R的范围内将y的表达式表示出来,那么这时候利用分治的思想,将R区间划分为小区间,然后分别求出各个小区间的表达式,最后合并起来,完成y关于x的表达式的求解
【思路】
按分治的实现过程,可以先找到上面所示日程表的规律,即对角线相等,那么所要完成的操作就是对角线填充。
实现过程:
(1)用一个for循环输出日程表的第一行for(int i=1;i<=N;i++) a[1][i] = i
(2)然后定义一个m值,m初始化为1,m用来控制每一次填充表格时i(i表示行)和j(j表示列)的起始填充位置。
}
int main()
{
int a[100];
int n,key,i;
cin>>n;
for(i=0;i<n;i++)
cin>>a[i];
cin>>key;
erfen(a,n,key);
return 0;
}
二分递归:
#include <iostream>
using namespace std;
int search(int a[],int left,int right,int key)
(4)重复上述过程,直到在数组中找到相同的数字。
(5)若在数组中找不到这个数据,则显示查找不成功
III. 算法框架:
按照分治算法三步骤,将二分算法作如下介绍:
(1)二分算法代码设计模式:
//arr[]表示要进行二分查找的顺序排列对象数组,low表示数组下标的最小值,high表示数组下表的最大值,key表示要查找的元素
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
总的比较次数为:3+4+4=11;
归并基本算法:
输入两个整数,作为两个数组的长度,输入两个按升序排列好的数组,将两个已排序的数组合并后存放在另一个数组中,且合并后的数组也是有序排列(要求不能合并后再排序),再输出合并后的数组。
cin>>a[i];
for(j=0;j<n;j++)
cin>>b[j];
i=0;j=0;
while(i<m&&j<n)//当数组a和数组b都没有完全赋值到数组c中时
{
if(a[i]<b[j])//如果a数组里的元素比b数组的小
{
c[k]=a[i];//就把数组a的元素赋给数组c
i++;k++;//且将数组a和数组c的下标都往后移一位
{
right=middle-1;
return search(a,left,right,key);
}
else
{
left=middle+1;
return search(a,left,right,key);
}ቤተ መጻሕፍቲ ባይዱ
}
}
int main()
{
int a[100],n,x,left,right,i;
cin>>n;
for(i=0;i<n;i++)
普通写法:
#include<iostream>
using namespace std;
int main()
{
int i,s[100],n;
cin>>n;
for(i=0;i<n;i++)
cin>>s[i];
int x;
cin>>x;
for(i=0;i<n;i++)
{
if(s[i]==x)
{
cout<<i<<endl;
cin>>a[i];
left=0;
right=n-1;
cin>>x;
cout<<"这个数的下标是:"<<search(a,left,right,x)<<endl;
}
专题二:分治算法之归并排序
归并排序是分治算法的一个非常典型的应用。
归并排序原理:
归并排序具体工作原理如下(假设序列共有n个元素):
将序列每相邻两个数字进行归并操作(merge),形成floor(n/2)个序列,排序后每个序列包含两个元素
二:基本思想
分治设计思想:将一个大的问题,分解成一个个小的,相同类型的问题,然后逐个击破各个小问题,最后将小问题逐步合并,得到最终的解。(可能会用到递归,大问题里包含小问题,找到规律然后解决)
分治基本策略:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
j++;k++;
}
}
else//当数组b已经被完全赋值到数组c中
{
while(i<m)//当数组a还没有完全赋值
{
c[k]=a[i];//此时,只需要把a数组中剩余的数全部赋值到数组c接下去的位置上
i++;k++;
}
}
for(i=0;i<m+n;i++)
cout<<c[i]<<" ";
}
归并函数:所涉及知识过多,目前只需要了解思想
分治算法
一:基本概念(分而治之)
分治就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。比如:二分查找,归并排序,快速排序,树的遍历等等
任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。
}
else//要是数组b的元素比数组a的元素大时
{
c[k]=b[j];//就把数组b的元素赋值到数组c中
j++;k++;//且将数组b和数组c的下标往后移一位
}
}
if(i==m)//当数组a已经被完全赋值到数组c中
{
while(j<n)//当数组b还没有完全赋值
{
c[k]=b[j];//此时,只需要把b数组中剩余的数全部赋值到数组c接下去的位置上
三:分治使用情况
1)该问题的规模缩小到一定的程度就可以容易地解决
2)该问题可以分解为相同类型的小问题(前提)
3)利用该问题分解出的子问题的解可以合并为该问题的解;(关键)
4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
{
if(left>right)
{
cout<<"Not found!"<<endl;
exit(0);
}
else
{
int middle=(left+right)/2;
if (a[middle]==key)
{
return middle;
}
else if(key<a[middle]) //这里key是和a[middle]比较,而非middle;
while(low<=high)
{
mid=(low+high)/2;
if(a[mid]==key)
{
cout<<"这个数的下标是:"<<mid<<endl;
break;
}
else if(a[mid]>key)
high=mid-1;
else
low=mid+1;
}
if(low>high)
cout<<"Not Found!"<<endl;
II. 思路分析:
二分查找的基本思想是:
(1)先确定一组顺序排列的数据存储到数组中,输入要查找的数据
(2)将数组元素的中值与查找的数据相比较,如果两者相等,则子函数返回相应的结果后终止;
(3)否则利用中间位置缩小数据查找的范围。如果中间位置的数组元素大于查找数值,则进一步查找中值之前的数组元素,否则进一步查找中值之后的数组元素。
同理,对第二部分(即三四行),划分为两部分,第三部分同理。
(5)最后,根据以上for循环对整体的划分和分治法的思想,进行每一个单元格的填充。填充原则是:对角线填充
for(inti=m+1;i<=2*m;i++) //i控制行
(3)用一个for循环将问题分成几部分,对于k=3,n=8,将问题分成3大部分,第一部分为,根据已经填充的第一行,填写第二行,第二部分为,根据已经填充好的第一部分,填写第三四行,第三部分为,根据已经填充好的前四行,填写最后四行。for (ints=1;s<=k;s++) N/=2;
(4)用一个for循环对③中提到的每一部分进行划分for(intt=1;t<=N;t++)对于第一部分,将其划分为四个小的单元,即对第二行进行如下划分
二:大整数乘法
123 345 678 * 3 = 370 037 034
在这里我们可以这样写:
123 * 3 = 369 345 * 3 = 1035 678 * 3=2034
组合在一起是 369 1035 2034。
对比发现,当使用千进制的时候结果变成了370037034
首先他满足:
第一条件:分解到一定小规模的时候可以解决
将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
重复步骤2,直到所有元素排序完毕
归并操作:
归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。
如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
int erfen(int arr[],int low,int high,int key){
如果数组下标的最小值大于最大值
则返回结果为-1至主函数;//表明在数组中不存在要查找的元素
确定数组的中间位置mid
若查找的元素等于数组的中间元素,则进行相应的步骤
④若查找的元素大于数组(指定范围内)的中间元素
break;
}
}
if(i==n)
cout<<"Not found"<<endl;
return 0;
}
二分写法:
#include<iostream>
using namespace std;
void erfen(int a[],int n,int key)
{
int low=0,high=n-1,mid;
【例题三】设有n=2^k个运动员要进行网球循环赛。现要设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能参赛一次;
(3)循环赛在n-1天内结束
请按此要求将比赛日程表设计成有n行和n-1列的一个表。在表中的第i行,第j列处填入第i个选手在第j天所遇到的选手。其中1≤i≤n,1≤j≤n-1。假设有8位参赛选手,8个选手的比赛日程表如下图:
2.二分法:将硬币分为两份,假币在轻的那份中,然后继续分,直到找出假币,最多用5次
哪种方法好?
课题:二分查找(折半查找)
知识目标:理解二分查找算法的概念以及执行过程。
重点:掌握二分查找算法的常规写法以及递归写法。
1.边界错误造成的问题
2.死循环
3.溢出
I. 算法介绍:
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
则将查找范围缩小至数组(指定范围内)中间元素右边;
⑤若查找的元素小于数组指定范围内的中间元素
则将查找范围缩小至数组(指定范围内)中间元素左边;
}
例题1:输入一个整数n,然后按升序输入n个整数,将它们存入数组a中,再输入一个数x,然后在数组中查找x,如果找到,输出相应的最小下标,否则,输出“Not Found”.
【样例输入】
4 5
1 2 3 4
5 6 7 8 9
【样例输出】
1 2 3 4 5 6 7 8 9
代码:
#include<iostream>
using namespace std;
int main()
{
int m,n,i,j,k=0,a[100],b[100],c[200];
cin>>m>>n;
for(i=0;i<m;i++)
第三条件:每个小问题可以通过合并在一起形成大问题的解
第四条件:每个小问题相互独立
专题一:分治算法之二分查找
思考题:找假币:
有一堆个数为32的硬币,和一个天平,已知其中有一个假币,且假币比真硬币轻,找出这个假币
1.普通方法:两两比较,轻的那个是假币,最多比较16次
四:基本步骤
(1)划分:把问题的实例划分成子问题
(2)求解:若是子问题比较简单,就直接解决,否则递归求解子问题
(3)合并:合并子问题的解得到原问题的解
课前引导:
一:分段函数
例如:高中数学中的分段函数也是类似分治思想的体现,如看图求y关于x的表达式。一个分段函数,反映的是x与y的关系,简单来说,就是在R的范围内将y的表达式表示出来,那么这时候利用分治的思想,将R区间划分为小区间,然后分别求出各个小区间的表达式,最后合并起来,完成y关于x的表达式的求解
【思路】
按分治的实现过程,可以先找到上面所示日程表的规律,即对角线相等,那么所要完成的操作就是对角线填充。
实现过程:
(1)用一个for循环输出日程表的第一行for(int i=1;i<=N;i++) a[1][i] = i
(2)然后定义一个m值,m初始化为1,m用来控制每一次填充表格时i(i表示行)和j(j表示列)的起始填充位置。
}
int main()
{
int a[100];
int n,key,i;
cin>>n;
for(i=0;i<n;i++)
cin>>a[i];
cin>>key;
erfen(a,n,key);
return 0;
}
二分递归:
#include <iostream>
using namespace std;
int search(int a[],int left,int right,int key)
(4)重复上述过程,直到在数组中找到相同的数字。
(5)若在数组中找不到这个数据,则显示查找不成功
III. 算法框架:
按照分治算法三步骤,将二分算法作如下介绍:
(1)二分算法代码设计模式:
//arr[]表示要进行二分查找的顺序排列对象数组,low表示数组下标的最小值,high表示数组下表的最大值,key表示要查找的元素
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
总的比较次数为:3+4+4=11;
归并基本算法:
输入两个整数,作为两个数组的长度,输入两个按升序排列好的数组,将两个已排序的数组合并后存放在另一个数组中,且合并后的数组也是有序排列(要求不能合并后再排序),再输出合并后的数组。
cin>>a[i];
for(j=0;j<n;j++)
cin>>b[j];
i=0;j=0;
while(i<m&&j<n)//当数组a和数组b都没有完全赋值到数组c中时
{
if(a[i]<b[j])//如果a数组里的元素比b数组的小
{
c[k]=a[i];//就把数组a的元素赋给数组c
i++;k++;//且将数组a和数组c的下标都往后移一位
{
right=middle-1;
return search(a,left,right,key);
}
else
{
left=middle+1;
return search(a,left,right,key);
}ቤተ መጻሕፍቲ ባይዱ
}
}
int main()
{
int a[100],n,x,left,right,i;
cin>>n;
for(i=0;i<n;i++)
普通写法:
#include<iostream>
using namespace std;
int main()
{
int i,s[100],n;
cin>>n;
for(i=0;i<n;i++)
cin>>s[i];
int x;
cin>>x;
for(i=0;i<n;i++)
{
if(s[i]==x)
{
cout<<i<<endl;
cin>>a[i];
left=0;
right=n-1;
cin>>x;
cout<<"这个数的下标是:"<<search(a,left,right,x)<<endl;
}
专题二:分治算法之归并排序
归并排序是分治算法的一个非常典型的应用。
归并排序原理:
归并排序具体工作原理如下(假设序列共有n个元素):
将序列每相邻两个数字进行归并操作(merge),形成floor(n/2)个序列,排序后每个序列包含两个元素
二:基本思想
分治设计思想:将一个大的问题,分解成一个个小的,相同类型的问题,然后逐个击破各个小问题,最后将小问题逐步合并,得到最终的解。(可能会用到递归,大问题里包含小问题,找到规律然后解决)
分治基本策略:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
j++;k++;
}
}
else//当数组b已经被完全赋值到数组c中
{
while(i<m)//当数组a还没有完全赋值
{
c[k]=a[i];//此时,只需要把a数组中剩余的数全部赋值到数组c接下去的位置上
i++;k++;
}
}
for(i=0;i<m+n;i++)
cout<<c[i]<<" ";
}
归并函数:所涉及知识过多,目前只需要了解思想
分治算法
一:基本概念(分而治之)
分治就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。比如:二分查找,归并排序,快速排序,树的遍历等等
任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。
}
else//要是数组b的元素比数组a的元素大时
{
c[k]=b[j];//就把数组b的元素赋值到数组c中
j++;k++;//且将数组b和数组c的下标往后移一位
}
}
if(i==m)//当数组a已经被完全赋值到数组c中
{
while(j<n)//当数组b还没有完全赋值
{
c[k]=b[j];//此时,只需要把b数组中剩余的数全部赋值到数组c接下去的位置上
三:分治使用情况
1)该问题的规模缩小到一定的程度就可以容易地解决
2)该问题可以分解为相同类型的小问题(前提)
3)利用该问题分解出的子问题的解可以合并为该问题的解;(关键)
4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
{
if(left>right)
{
cout<<"Not found!"<<endl;
exit(0);
}
else
{
int middle=(left+right)/2;
if (a[middle]==key)
{
return middle;
}
else if(key<a[middle]) //这里key是和a[middle]比较,而非middle;
while(low<=high)
{
mid=(low+high)/2;
if(a[mid]==key)
{
cout<<"这个数的下标是:"<<mid<<endl;
break;
}
else if(a[mid]>key)
high=mid-1;
else
low=mid+1;
}
if(low>high)
cout<<"Not Found!"<<endl;
II. 思路分析:
二分查找的基本思想是:
(1)先确定一组顺序排列的数据存储到数组中,输入要查找的数据
(2)将数组元素的中值与查找的数据相比较,如果两者相等,则子函数返回相应的结果后终止;
(3)否则利用中间位置缩小数据查找的范围。如果中间位置的数组元素大于查找数值,则进一步查找中值之前的数组元素,否则进一步查找中值之后的数组元素。
同理,对第二部分(即三四行),划分为两部分,第三部分同理。
(5)最后,根据以上for循环对整体的划分和分治法的思想,进行每一个单元格的填充。填充原则是:对角线填充
for(inti=m+1;i<=2*m;i++) //i控制行
(3)用一个for循环将问题分成几部分,对于k=3,n=8,将问题分成3大部分,第一部分为,根据已经填充的第一行,填写第二行,第二部分为,根据已经填充好的第一部分,填写第三四行,第三部分为,根据已经填充好的前四行,填写最后四行。for (ints=1;s<=k;s++) N/=2;
(4)用一个for循环对③中提到的每一部分进行划分for(intt=1;t<=N;t++)对于第一部分,将其划分为四个小的单元,即对第二行进行如下划分
二:大整数乘法
123 345 678 * 3 = 370 037 034
在这里我们可以这样写:
123 * 3 = 369 345 * 3 = 1035 678 * 3=2034
组合在一起是 369 1035 2034。
对比发现,当使用千进制的时候结果变成了370037034
首先他满足:
第一条件:分解到一定小规模的时候可以解决
将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
重复步骤2,直到所有元素排序完毕
归并操作:
归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。
如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
int erfen(int arr[],int low,int high,int key){
如果数组下标的最小值大于最大值
则返回结果为-1至主函数;//表明在数组中不存在要查找的元素
确定数组的中间位置mid
若查找的元素等于数组的中间元素,则进行相应的步骤
④若查找的元素大于数组(指定范围内)的中间元素
break;
}
}
if(i==n)
cout<<"Not found"<<endl;
return 0;
}
二分写法:
#include<iostream>
using namespace std;
void erfen(int a[],int n,int key)
{
int low=0,high=n-1,mid;
【例题三】设有n=2^k个运动员要进行网球循环赛。现要设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能参赛一次;
(3)循环赛在n-1天内结束
请按此要求将比赛日程表设计成有n行和n-1列的一个表。在表中的第i行,第j列处填入第i个选手在第j天所遇到的选手。其中1≤i≤n,1≤j≤n-1。假设有8位参赛选手,8个选手的比赛日程表如下图:
2.二分法:将硬币分为两份,假币在轻的那份中,然后继续分,直到找出假币,最多用5次
哪种方法好?
课题:二分查找(折半查找)
知识目标:理解二分查找算法的概念以及执行过程。
重点:掌握二分查找算法的常规写法以及递归写法。
1.边界错误造成的问题
2.死循环
3.溢出
I. 算法介绍:
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
则将查找范围缩小至数组(指定范围内)中间元素右边;
⑤若查找的元素小于数组指定范围内的中间元素
则将查找范围缩小至数组(指定范围内)中间元素左边;
}
例题1:输入一个整数n,然后按升序输入n个整数,将它们存入数组a中,再输入一个数x,然后在数组中查找x,如果找到,输出相应的最小下标,否则,输出“Not Found”.
【样例输入】
4 5
1 2 3 4
5 6 7 8 9
【样例输出】
1 2 3 4 5 6 7 8 9
代码:
#include<iostream>
using namespace std;
int main()
{
int m,n,i,j,k=0,a[100],b[100],c[200];
cin>>m>>n;
for(i=0;i<m;i++)