算法第二章递归与分治策略小结

北战南征 提交于 2019-12-01 08:03:22

第2章 递归与分治策略

2.1.递归的概念

递归算法:直接或间接地调用自身的算法

递归函数:用函数自身给出定义的函数

!!!!递归函数的第一句一定是if语句作为边界条件,然后就是递归方程

如:阶乘函数的第一句就是if条件语句

1 int factorial(int n){
2     if( n ==0)
3         return 1;
4     return n*factorial(n-1);
5 }

※※※递归函数中比较著名的是Hanoi塔问题

Hanoi塔问题。
  设a,b,c是3个塔座。开始时,在塔座a上有一叠共n个圆盘,这些圆盘自下而上,由大到小地叠在一起。各圆盘从小到大编号为1,2,…,n,现要求将塔座a上的这一叠圆盘移到塔座c上,并仍按同样顺序叠置。在移动圆盘时应遵守以下移动规则:
  规则1:每次只能移动1个圆盘;
  规则2:任何时刻都不允许将较大的圆盘压在较小的圆盘之上;
  规则3:在满足规则1和2的前提下,可将圆盘移至a,b,c中任一塔座上。
hanoi塔问题题目描述
 1 #include<iostream>
 2 using namespace std;
 3 void move(char p1,char p2){
 4     cout<<p1<<"->"<<p2<<endl;
 5 }
 6 
 7 //将a上的n个盘子经b移动到c上
 8 void hanoi(int n,char a,char b,char c){
 9     if(n == 0) return;//当a上没有盘子的时候,直接返回不需要移动
10     if(n == 1) move(a,c);//当a上只有一个盘子的时候,直接将盘子从a上移动到c上
11     if(n>1){
12         hanoi(n-1,a,c,b);
13         move(a,b);
14         hanoi(n-1,c,b,a);
15     }
16 }
17 
18 int main(){
19     char x,y,z;
20     x = 'a';
21     y = 'b';
22     z = 'c';
23     hanoi(4,x,y,z);
24     return 0;
25 }
Hanoi塔

2.2分治法的基本思想

分治法的基本思想:将一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同

!!!在使用分治法设计算法的时候,最好使子问题的规模大致相同,即将一个问题分为大小相等的k个子问题,一般情况k取2.

※※※分治法中比较著名的是划分整数问题

1、整数划分问题
将一个正整数n表示为一系列正整数之和,
         n = n1 + n2 +…+nk
   其中n1≥n2≥…≥nk≥1, k≥1。

例如 p(6) = 11 ,即整数6的划分数为11种:
 6, 5+1, 4+2, 4+1+1,  3+3, 3+2+1, 3+1+1+1,
 2+2+2, 2+2+1+1, 2+1+1+1+1, 1+1+1+1+1+1
2、有时候,问题本身具有比较明显的递归关系,因而容易用递归函数直接求解。
    在本例中,如果设p(n)为正整数n的划分数,则难以直接找到递归关系。
    因此考虑增加一个自变量:在正整数的所有不同划分中,将最大加数n不大于m的划分个数记为q(n, m)
划分整数问题描述

递归关系:

 

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int q(int n,int m){
 5     if((n<1)||(m<1)) return 0;
 6     if((n==1)||(m==1)) return 1;
 7     if(n<m) return q(n,n);
 8     if(n == m) return 1+q(n,n-1);
 9     if(n>m) return q(n,m-1)+q(n-m,m);
10 }
11 
12 int main(){
13     int n,m;
14     cin >> n >> m;
15     cout << q(n,m);
16     return 0;
17 }
划分整数

2.3.二分搜索技术

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int bsearch(int x,int a[],int left,int right){
 5     if(left>right) return -1;
 6     int middle = (left+right)/2;
 7     if(x == a[middle])
 8         return middle;
 9     if(x < a[middle])
10         return bsearch(x,a,left,middle-1);
11     else
12         return bsearch(x,a,middle+1,right);
13 } 
14 
15 int main(){
16     int x;
17     cin >> x;//要找的特定元素
18     int n;
19     cin >> n; 
20     int a[n];
21     for(int i=0;i<n;i++){
22         cin >> a[i];
23     }
24     cout <<    bsearch(x,a,a[0],a[x-1]);
25     return 0;
26 }
二分搜索技术——递归算法

 时间复杂度分析:

1.前面两个if+int 一共三个语句 时间复杂度为3

2.后面两个if 时间复杂度是T(n/2)如果继续划分下去将会是T(n/4),T(n/8).....

T(n)= 3+ T(n/2) = 3+3+T(n/4) =........= 4log n + 4 =O(log n)

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int bsearch(int x,int a[],int left,int right){
 5     while(left <= right){
 6     int middle = (left+right)/2;
 7     if(x == a[middle])
 8         return middle;
 9     if(x < a[middle])
10         right = middle -1;
11     else
12         left = middle +1;
13     }
14     return -1;
15 } 
16 
17 int main(){
18     int a[10] = {1,2,3,4,5,6,7,8,9,10}; 
19     cout <<    bsearch(3,a,1,10);
20     return 0;
21 }
二分搜索算法——非递归写法

时间复杂度分析:

每执行一次算法的while循环,待搜索数组的大小减小一半。因此,在最坏情况下,while循环执行了O(log n)次。循环体内运算需要O(1)时间,因此整个算法在最坏情况下时间复杂度为O(log n)

※※※※分治法的时间复杂度

2.4大整数的乘法

题目:设X和Y都是n位的十进制整数。如果用常规的乘法计算乘积XY,其时间复杂性为O(n2)。

分治法的做法:

将X和Y都分成2段,即 X = A10n/2 + B, Y = C10n/2 + D

于是: XY = (A10n/2 + B)(C10n/2 + D) = AC10n +(AD+BC)10n/2 + BD

分析:

 

最终的时间复杂度跟按常规方法是一样的,因此这种方法不可行

※※※改进:减少乘法的运算次数

大整数的乘法的方法:

将XY改写成: XY = AC10n +((A–B)(D–C)+AC+BD)10n/2 + BD

仅需做3次n/2位整数的乘法(AC,BD,((A-B)(D-C))

T(n) = 3T(n/2) + O(n) = O(nlog23)  =  O(n1.59)

 2.5.Strassen矩阵乘法

常规方法:

两个n×n矩阵乘法的时间复杂性为 O(n3)

分治法:

 

 

※※※ 改进:

 

※※※※※※

分治法和大整数乘法,Strassen矩阵乘法的区别

分治法是将问题分为两个子问题通过递归来降低时间复杂度,而解决大整数乘法和Strassen矩阵乘法如果也用分治的思想的话就时间复杂度依旧很大,因此我们需要继续降低时间复杂度,方法是减少乘法的次数,因此我们把问题分为3,4..个子问题来减少乘法的次数,从而降低时间复杂度

2.6.棋盘覆盖(不是重点)

2.7.合并排序

基本思想:用分治策略实现对n个元素进行排序的算法,将待排序元素分成大小大致相同的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并成要求的排好序的集合

 参考:(1条消息)归并排序算法 C++ - summerlq的博客 - CSDN博客  https://blog.csdn.net/summerlq/article/details/81284928对合并排序进行逻辑分析

 

 

 1 #include<iostream>
 2 using namespace std;
 3 
 4 void merge(int a[],int l,int mid,int r) {
 5     int b[1000];
 6     int i = l;
 7     int j = mid+1;
 8     int k = 0;
 9     /*两段数组分别进行排序将排好序的数组放入数组b中*/ 
10     while(i <= mid && j<=r){
11         if(a[i]<a[j]){
12             b[k] = a[i];
13             i++;
14         }
15         else{
16             b[k] = a[j];
17             j++;
18         }
19         k++;
20     }
21     if(i<=mid){
22     //如果i<=m则证明第一段数组没有全部放入数组b中,即第二段数组已全部放入数组b中,而第一段数组已经排好序只需逐一放入数组b即可 ,else情况同理 
23         for(int p =i;p<=mid;p++){
24             b[k++] = a[p];
25         }
26     } else{
27         for(int p =j;p<=r;p++){
28             b[k++] = a[p];
29         }
30     }
31     for(int p =l;p<=r;p++){
32         a[p] = b[p-l];//将排好序的b数组复制到a数组中 
33     }
34 }
35 
36 /*将数组a分成两个部分,对每个部分递归调用mergesort进行排序,然后将两段排好序的数组进行合并到另一个数组b中*/
37 void mergesort(int a[],int l,int r){
38     if(r<=l) return;//如果数组中少于两个元素,则直接返回
39     int mid = (l+r)/2;
40     mergesort(a,l,mid);
41     mergesort(a,mid+1,r);
42     merge(a,l,mid,r);
43 }
44 
45 
46 int main(){
47     int a[10] = {10,4,9,33,88,34,78,66,56,43};
48     cout<<"原数组序列是:";
49     for(int i=0;i<10;i++){
50         cout << a[i]<<" ";
51     }
52     cout<<endl;
53     cout<<"排序后数组序列是:" ;
54     mergesort(a,0,9);
55     for(int i=0;i<10;i++){
56         cout << a[i]<<" ";
57     }
58     return 0;
59 } 
合并排序

时间复杂度分析:

 

2.8快速排序

2.9线性时间选择

2.10最接近点问题

2.11循环赛日程表

 (以上的8,9,10,11节都非本章重点)

---恢复内容结束---

第2章 递归与分治策略

目录

2.1.递归的概念

递归算法:直接或间接地调用自身的算法

递归函数:用函数自身给出定义的函数

!!!!递归函数的第一句一定是if语句作为边界条件,然后就是递归方程

如:阶乘函数的第一句就是if条件语句

1 int factorial(int n){
2     if( n ==0)
3         return 1;
4     return n*factorial(n-1);
5 }

※※※递归函数中比较著名的是Hanoi塔问题

Hanoi塔问题。
  设a,b,c是3个塔座。开始时,在塔座a上有一叠共n个圆盘,这些圆盘自下而上,由大到小地叠在一起。各圆盘从小到大编号为1,2,…,n,现要求将塔座a上的这一叠圆盘移到塔座c上,并仍按同样顺序叠置。在移动圆盘时应遵守以下移动规则:
  规则1:每次只能移动1个圆盘;
  规则2:任何时刻都不允许将较大的圆盘压在较小的圆盘之上;
  规则3:在满足规则1和2的前提下,可将圆盘移至a,b,c中任一塔座上。
hanoi塔问题题目描述
 1 #include<iostream>
 2 using namespace std;
 3 void move(char p1,char p2){
 4     cout<<p1<<"->"<<p2<<endl;
 5 }
 6 
 7 //将a上的n个盘子经b移动到c上
 8 void hanoi(int n,char a,char b,char c){
 9     if(n == 0) return;//当a上没有盘子的时候,直接返回不需要移动
10     if(n == 1) move(a,c);//当a上只有一个盘子的时候,直接将盘子从a上移动到c上
11     if(n>1){
12         hanoi(n-1,a,c,b);
13         move(a,b);
14         hanoi(n-1,c,b,a);
15     }
16 }
17 
18 int main(){
19     char x,y,z;
20     x = 'a';
21     y = 'b';
22     z = 'c';
23     hanoi(4,x,y,z);
24     return 0;
25 }
Hanoi塔

2.2分治法的基本思想

分治法的基本思想:将一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同

!!!在使用分治法设计算法的时候,最好使子问题的规模大致相同,即将一个问题分为大小相等的k个子问题,一般情况k取2.

※※※分治法中比较著名的是划分整数问题

1、整数划分问题
将一个正整数n表示为一系列正整数之和,
         n = n1 + n2 +…+nk
   其中n1≥n2≥…≥nk≥1, k≥1。

例如 p(6) = 11 ,即整数6的划分数为11种:
 6, 5+1, 4+2, 4+1+1,  3+3, 3+2+1, 3+1+1+1,
 2+2+2, 2+2+1+1, 2+1+1+1+1, 1+1+1+1+1+1
2、有时候,问题本身具有比较明显的递归关系,因而容易用递归函数直接求解。
    在本例中,如果设p(n)为正整数n的划分数,则难以直接找到递归关系。
    因此考虑增加一个自变量:在正整数的所有不同划分中,将最大加数n不大于m的划分个数记为q(n, m)
划分整数问题描述

递归关系:

 

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int q(int n,int m){
 5     if((n<1)||(m<1)) return 0;
 6     if((n==1)||(m==1)) return 1;
 7     if(n<m) return q(n,n);
 8     if(n == m) return 1+q(n,n-1);
 9     if(n>m) return q(n,m-1)+q(n-m,m);
10 }
11 
12 int main(){
13     int n,m;
14     cin >> n >> m;
15     cout << q(n,m);
16     return 0;
17 }
划分整数

2.3.二分搜索技术

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int bsearch(int x,int a[],int left,int right){
 5     if(left>right) return -1;
 6     int middle = (left+right)/2;
 7     if(x == a[middle])
 8         return middle;
 9     if(x < a[middle])
10         return bsearch(x,a,left,middle-1);
11     else
12         return bsearch(x,a,middle+1,right);
13 } 
14 
15 int main(){
16     int x;
17     cin >> x;//要找的特定元素
18     int n;
19     cin >> n; 
20     int a[n];
21     for(int i=0;i<n;i++){
22         cin >> a[i];
23     }
24     cout <<    bsearch(x,a,a[0],a[x-1]);
25     return 0;
26 }
二分搜索技术——递归算法

 时间复杂度分析:

1.前面两个if+int 一共三个语句 时间复杂度为3

2.后面两个if 时间复杂度是T(n/2)如果继续划分下去将会是T(n/4),T(n/8).....

T(n)= 3+ T(n/2) = 3+3+T(n/4) =........= 4log n + 4 =O(log n)

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int bsearch(int x,int a[],int left,int right){
 5     while(left <= right){
 6     int middle = (left+right)/2;
 7     if(x == a[middle])
 8         return middle;
 9     if(x < a[middle])
10         right = middle -1;
11     else
12         left = middle +1;
13     }
14     return -1;
15 } 
16 
17 int main(){
18     int a[10] = {1,2,3,4,5,6,7,8,9,10}; 
19     cout <<    bsearch(3,a,1,10);
20     return 0;
21 }
二分搜索算法——非递归写法

时间复杂度分析:

每执行一次算法的while循环,待搜索数组的大小减小一半。因此,在最坏情况下,while循环执行了O(log n)次。循环体内运算需要O(1)时间,因此整个算法在最坏情况下时间复杂度为O(log n)

※※※※分治法的时间复杂度

2.4大整数的乘法

题目:设X和Y都是n位的十进制整数。如果用常规的乘法计算乘积XY,其时间复杂性为O(n2)。

分治法的做法:

将X和Y都分成2段,即 X = A10n/2 + B, Y = C10n/2 + D

于是: XY = (A10n/2 + B)(C10n/2 + D) = AC10n +(AD+BC)10n/2 + BD

分析:

 

最终的时间复杂度跟按常规方法是一样的,因此这种方法不可行

※※※改进:减少乘法的运算次数

大整数的乘法的方法:

将XY改写成: XY = AC10n +((A–B)(D–C)+AC+BD)10n/2 + BD

仅需做3次n/2位整数的乘法(AC,BD,((A-B)(D-C))

T(n) = 3T(n/2) + O(n) = O(nlog23)  =  O(n1.59)

 2.5.Strassen矩阵乘法

常规方法:

两个n×n矩阵乘法的时间复杂性为 O(n3)

分治法:

 

 

※※※ 改进:

 

※※※※※※

分治法和大整数乘法,Strassen矩阵乘法的区别

分治法是将问题分为两个子问题通过递归来降低时间复杂度,而解决大整数乘法和Strassen矩阵乘法如果也用分治的思想的话就时间复杂度依旧很大,因此我们需要继续降低时间复杂度,方法是减少乘法的次数,因此我们把问题分为3,4..个子问题来减少乘法的次数,从而降低时间复杂度

2.6.棋盘覆盖(不是重点)

2.7.合并排序

基本思想:用分治策略实现对n个元素进行排序的算法,将待排序元素分成大小大致相同的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并成要求的排好序的集合

 参考:(1条消息)归并排序算法 C++ - summerlq的博客 - CSDN博客  https://blog.csdn.net/summerlq/article/details/81284928对合并排序进行逻辑分析

 

 

 1 #include<iostream>
 2 using namespace std;
 3 
 4 void merge(int a[],int l,int mid,int r) {
 5     int b[1000];
 6     int i = l;
 7     int j = mid+1;
 8     int k = 0;
 9     /*两段数组分别进行排序将排好序的数组放入数组b中*/ 
10     while(i <= mid && j<=r){
11         if(a[i]<a[j]){
12             b[k] = a[i];
13             i++;
14         }
15         else{
16             b[k] = a[j];
17             j++;
18         }
19         k++;
20     }
21     if(i<=mid){
22     //如果i<=m则证明第一段数组没有全部放入数组b中,即第二段数组已全部放入数组b中,而第一段数组已经排好序只需逐一放入数组b即可 ,else情况同理 
23         for(int p =i;p<=mid;p++){
24             b[k++] = a[p];
25         }
26     } else{
27         for(int p =j;p<=r;p++){
28             b[k++] = a[p];
29         }
30     }
31     for(int p =l;p<=r;p++){
32         a[p] = b[p-l];//将排好序的b数组复制到a数组中 
33     }
34 }
35 
36 /*将数组a分成两个部分,对每个部分递归调用mergesort进行排序,然后将两段排好序的数组进行合并到另一个数组b中*/
37 void mergesort(int a[],int l,int r){
38     if(r<=l) return;//如果数组中少于两个元素,则直接返回
39     int mid = (l+r)/2;
40     mergesort(a,l,mid);
41     mergesort(a,mid+1,r);
42     merge(a,l,mid,r);
43 }
44 
45 
46 int main(){
47     int a[10] = {10,4,9,33,88,34,78,66,56,43};
48     cout<<"原数组序列是:";
49     for(int i=0;i<10;i++){
50         cout << a[i]<<" ";
51     }
52     cout<<endl;
53     cout<<"排序后数组序列是:" ;
54     mergesort(a,0,9);
55     for(int i=0;i<10;i++){
56         cout << a[i]<<" ";
57     }
58     return 0;
59 } 
合并排序

时间复杂度分析:

 

2.8快速排序

参考文献:(1条消息)快速排序算法的C++实现 - xuezhu1的博客 - CSDN博客  https://blog.csdn.net/xuezhu1/article/details/81944875

(1条消息)算法之快速排序(C++实现) - lyl771857509的博客 - CSDN博客  https://blog.csdn.net/lyl771857509/article/details/78845221

思想:是基于分治策略的另一种排序算法。其基本思想是,对于输入的子数组a[p:r],按一下三个步骤进行排序:

①分解:以a[p]为基准元素将a[p:r]划分为3段a[p:q-1],a[q]和a[q+1:r],使a[p:q-1]中任何一个元素小于等于a[q],而a[q+1:r]中任何一个元素大于等于a[q]。下标q在划分过程中确定

②递归分解:通过递归调用快速排序算法,分别对a[p:q-1],a[q+1:r]进行排序

③合并:由于对a[p:q-1]和a[q+1:r]的排序是就地进行的,因此在a[p:q-1]和a[q+1:r]都排好序后,不需要执行任何算法,a[p:r]已经排好序

 1 #include<iostream>
 2 using namespace std;
 3 
 4 void Swap(int x,int y){
 5     int t;
 6     t = x;
 7     x = y;
 8     y = t;
 9 } 
10 
11 int Partition(int a[],int p,int r){
12     int i = p,j = r;
13     int x = a[p];// x 为基准数 
14     //将小于x的元素交换到左边区域,将大于x的元素交换到右边区域
15     while(true){
16         /*左边的数小于基准数,右边的数大于基准数的时候 不作处理*/
17         while(a[++i] < x && i<r); 
18         while(a[--j] > x && i<r);
19         if(i >= j)
20             break;
21         Swap(a[i],a[j]); 
22     } 
23     a[p] = a[j];
24     a[j] = x;
25     return j;
26 } 
27 
28 void QuickSort(int a[],int p,int r){
29     if(p<r){
30         int q =Partition(a,p,r);
31         QuickSort(a,p,q-1);//对左半段进行排序 
32         QuickSort(a,q+1,r);//对右半段进行排序 
33     }
34 } 
35 
36 int main(){
37     int a[5] = {1,2,9,7,0};
38     cout << "原序列是:"; 
39     for(int i=0;i<5;i++){
40         cout <<a[i]<<" ";
41     }
42     QuickSort(a,0,4);
43     cout << endl << "快排后的序列是:"; 
44     for(int i=0;i<5;i++){
45         cout << a[i]<<" ";
46     } 
47     return 0;
48 }
快速排序

2.9线性时间选择

2.10最接近点问题

2.11循环赛日程表

 (以上的9,10,11节都非本章重点)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!