C++少儿编程(15)分治算法

分治算法概述ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

 

分治算法(Divide and Conquer)是一种重要的算法设计策略。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

核心思想(基本步骤):ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

1、分(Divide)- 分解:ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

将原问题分解为若干个规模较小的同类型子问题;ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

2、治(Conquer)- 解决:ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

递归求解子问题(如果子问题规模足够小,则直接求解);ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

3、合(Combine)- 合并:ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

将子问题的解合并得到原问题的解。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库
 ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

分治算法经典案例ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

【案例一】汉诺塔问题

【描述】汉诺塔Hanoi Tower),又称河内塔,源于印度的一个古老传说。大梵天创造世界的时候做了3根金刚石柱子,在一根柱子上从下往上按照从大到小的顺序摞着64片黄金圆盘。大梵天命人把圆盘从下往上按照从大到小的顺序重新摆放到另一根柱子上。并且规定,任何时候,在小盘上都不能放大盘,且在3根柱子之前一次只能移动一个圆盘。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库
 ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

【输入】圆盘的数量n(0<n<10)。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

【输出】移动圆盘的过程,每一步占一行。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

【分析过程】ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

为了便于描述,给三根柱子分别命名A、B、C,刚开始的时候,圆盘都在A柱上,假设C柱是目标柱子,B柱是辅助柱子。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

情形1:如果只有1个圆盘,直接从A柱移到C柱,只需1步。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

情形2:如果有2个圆盘,需要把第1个圆盘从A柱移到B柱,再将第2个圆盘从A柱移到C柱,最后将第1个圆盘从B柱移到C柱,总共需要3步。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

情形3:如果有3个圆盘,将最上面的2个圆盘看作一个整体,整体部分与最下方圆盘构成情形2。先将整体部分从A柱移到B柱,再将最下方圆盘从A柱移到C柱,最后再将整体部分从B柱移动C柱,总体来看,也需要3步。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

由于每次只能移动一个圆盘,所以将整体部分(最上面两个圆盘)从A柱移到B柱,参照情形2,需要3步;再将最下方圆盘从A柱移到C柱,需要1步;最后将整体部分(最上面两个圆盘)从B柱移到C柱,再次参照情形2,需要3步。所以,移动3个圆盘总共需要7步(3+1+3)。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

情形4:如果有64个圆盘,将最上面的63个圆盘看作一个整体,整体部分与最下方圆盘构成情形2。将整体从A柱移到B柱,再将最下方圆盘从A柱移到C柱,最后将整体从B柱移到C柱。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

如何将整体(最上面的63个圆盘)从A柱移到B柱呢?ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

继续分而治之,将最上面的62个圆盘看作一个整体,与倒数第二个(第63个)圆盘构成情形2。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

62个圆盘还是没法移动,那就继续分而治之,直到圆盘的数量足够少,可以移动为止。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

以上就是分治算法的解题思路,可以使用递归函数实现。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

【示例程序】ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库
ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库
 ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

  1. # include <bits/stdc++.h> 
  2. using namespace std; 
  3. void hanoi(int n, char a, char b, char c) { 
  4.     // 情形 1 
  5.     if (n == 1) { 
  6.         printf("%d: %c -> %c\n", n, a, c);  // 将n号圆盘从a柱移到c柱 
  7.         return
  8.     } 
  9.     // 情形 2 和 其它情形 
  10.     else { 
  11.         hanoi(n-1, a, c, b);  // 将整体部分从a柱借助c柱移到b柱 
  12.         printf("%d: %c -> %c\n", n, a, c);  // 将n号圆盘从a柱移到c柱 
  13.         hanoi(n-1, b, a, c);  // 再将整体部分从b柱借助a柱移到c柱 
  14.     } 
  15. int main() { 
  16.     int n; 
  17.     cin >> n; 
  18.     hanoi(n, 'A''B''C'); 
  19.     return 0; 

注意:ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

大家在测试的时候,圆盘的数量不要太多,最好不要超过10个圆盘,否则递归函数频繁调用自己,不断地压栈,会导致计算机内存溢出。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

通过观察规律,发现移动n个圆盘需要的步数:f(n) = 2^n - 1。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

可见,移动次数随着圆盘个数的增加呈指数级增长,千万不要轻易尝试64个圆盘。感兴趣的同学可以计算一下,移动64个圆盘的步数。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

 ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

【案例二】快速排序ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

快速排序(Quick Sort)是对冒泡排序的一种改进。
基本思想(分治思想):通过一遍排序将待排序数据分割成独立的两个部分,其中一部分的所有数据都比另一部分的所有数据都小;然后再按此方法对这两部分数据分别再次分割成更小的部分。整个排序过程可以递归进行,最终实现排序效果。(默认为升序排序)
快速排序流程如下:
1、首先设定一个分界值(通常使用第一个或最后一个元素作为分界值)。
2、将小于等于分界值的元素集中到左边,大于分界值的元素集中到右边。
3、接着,对左、右两部分元素独立排序。
(1)对于左边部分,取一个分界值,将该部分再分成左、右两小部分,同样左边放置较小元素,右边放置较大元素;
(2)对于右边部分,取一个分界值,将该部分再分成左、右两小部分,同样左边放置较小元素,右边放置较大元素。
4、递归执行上述过程,将左边排好序后,再将右边排序;递归直到左、右两部元素少于2个的时候,停止排序。
快速排序具体步骤如下:(a[]为待排序数组)
1、选取第一个元素作为分界值(key),左索引(L)为第一个元素的索引,右索引(R)为最后一个元素的索引;
2、当左索引等于右索引的时候,说明只有1个元素,此时停止排序;
3、先从后往前遍历(从R到L),如果找到比key小的元素,将此时右索引对应的元素存入左索引中;
4、再从前往后遍历(从L到R),如果找到大于等于key的元素,将此时左索引对应的元素存入右索引中;
5、只要左索引小于右索引,就重复执行第3、4两步;
6、第5步结束之后,左索引和右索引相等,左边部分都不超过key,右边部分都比key大,将分界值(key)填入此时的右索引中,此轮排序结束;
7、递归调用自身,对左边部分独立排序:本次的左索引为上次的左索引,本次的右索引为上次结束时的右索引-1;
8、递归调用自身,对右边部分独立排序:本次的左索引为上次结束时的右索引+1,本次的右索引为上次的右索引。

【例题】快速排序ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

让用户输入的n个整数,使用递归函数实现快速排序(升序排序),请输出第m小的元素。(1<m<n<100)ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

【示例程序】快速排序ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

  1. # include <bits/stdc++.h> 
  2. using namespace std; 
  3. // 在函数之前声明的变量,可供全局使用 
  4. int a[101]; 
  5. // 创建递归函数,实现快速排序 
  6. void qucik_sort(int L, int R) { 
  7.     int key = a[L];   // 将开始元素作为分界值 
  8.     int l = L;          // 存储左索引 
  9.     int r = R;          // 存储右索引 
  10.     if (l >= r) return// 递归终止条件(元素少于2个) 
  11.     while (l < r) {     // 本轮排序执行条件 
  12.         // 1、先从右向左查找小于等于key的元素 
  13.         while (a[r] > key && r > l) r--; 
  14.         a[l] = a[r];    // 将小元素移到左边 
  15.         // 2、再从左向右查找大于key的元素 
  16.         while (a[l] <= key && l < r) l++; 
  17.         a[r] = a[l];    // 将大元素移到右边 
  18.     } 
  19.     a[r] = key;     // 将分界值存入“中间”位置 
  20.     qucik_sort(L, r-1);     // 独立排序左边部分 
  21.     qucik_sort(r+1, R);     // 独立排序右边部分 
  22. int main() { 
  23.     int n, m; 
  24.     cin >> n >> m; 
  25.     for (int i = 1; i <= n; i++) { 
  26.         cin >> a[i];    // 输入元素 
  27.     } 
  28.     qucik_sort(1, n);   //  调用快速排序函数排序 
  29.     cout << a[m];       // 输出排序后的指定元素 
  30.     return 0; 

课后习题ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

 

让用户输入的n个整数,查找并输出第m小的元素。(1<m<n<100)ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

要求:只需找到第m小的元素,无需全部排序。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

提示:每轮排序后,左边部分都不超过key,右边元素都大于key,只需要针对目标元素所在的区间排序即可,其它区间无需排序。ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

【参考代码】区间排序(快速查找)ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

  1. # include <bits/stdc++.h> 
  2. using namespace std; 
  3. // 在函数之前声明的变量,可供全局使用 
  4. int a[101]; 
  5. // 创建递归函数,实现快速查找(区间排序) 
  6. int qucik_find(int L, int R, int m) { 
  7.     int key = a[L];   // 将开始元素作为分界值 
  8.     int l = L;          // 存储左索引 
  9.     int r = R;          // 存储右索引 
  10.     while (l < r) {     // 本轮排序执行条件 
  11.         // 1、先从右向左查找小于等于key的元素 
  12.         while (a[r] > key && r > l) r--; 
  13.         a[l] = a[r];    // 将小元素移到左边 
  14.         // 2、再从左向右查找大于key的元素 
  15.         while (a[l] <= key && l < r) l++; 
  16.         a[r] = a[l];    // 将大元素移到右边 
  17.     } 
  18.     a[r] = key;     // 将分界值存入“中间”位置 
  19.     // 创建变量ans存储查找结果 
  20.     int ans; 
  21.     // 如果m和中间索引相等,这是最理想的情况,直接将key赋值给ans 
  22.     if (m == r) ans = key; 
  23.     // 如果m小于中间索引,说明目标在左边部分,递归查找左边部分,将结果存入ans 
  24.     else if (m < r) ans = qucik_find(L, r-1, m); 
  25.     // 如果m大于中间索引,说明目标在右边部分,递归查找右边部分,将结果存入ans 
  26.     else ans = qucik_find(r+1, R, m); 
  27.     return ans; // 返回查找结果 
  28. int main() { 
  29.     int n, m; 
  30.     cin >> n >> m; 
  31.     for (int i = 1; i <= n; i++) { 
  32.         cin >> a[i];    // 输入元素 
  33.     } 
  34.     //  调用快速查找函数,并输出查找结果 
  35.     cout << qucik_find(1, n, m) << endl; 
  36.     return 0; 

ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库
 ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

 ahu100150满分答卷(100150.com)-青少年编程等级考试及竞赛题库

关 键 词

分治算法

相关教程

提示声明

  • 免责声明:本站资源均来自网络或者用户投稿,仅供用于学习和交流:如有侵权联系删除!

猜你喜欢