
⚔️第三课:《分治算法挑战赛》⚔️
------ 分治魔法学院的最终试炼
一、🏰 故事开始:算法勇者大赛
1、经过前两节课的学习,
同学们已经掌握了:
🌟 归并排序
🌟 快速排序
🌟 分治思想
2、这一天,
汉克老师举办了一场:
⚔️"分治算法挑战赛"⚔️
3、只有真正理解:
🌈"拆问题"🌈
成功的同学,
4、才能成为:
👑 分治小勇者!
5、🌟 今天我们接下来要挑战:
🔍 二分查找
🏔️ 分治求最大值
💣 逆序对问题
🌳 汉诺塔问题
🧠 同学们真正理解"举一反三"
二、🌈 第一关:二分查找魔法
1、🏰 故事:猜数字王国
(1)数组中的数字我们都排好序了:
1 3 5 7 9 11 13
(2)汉克老师在纸上记了数组中一个数字9。
让同学们来猜
(3)怎样的方法,才能猜的快呢?
2、❌ 普通方法
一个一个找:
1 → 3 → 5 → 7 → 9
时间最多需要找n次,好像有些慢!
3、🌟 我们采用可以分治法!
每次:
🌟 砍掉一半!
4、🌈 第一步
看中间:
7
因为:
9 > 7
所以:
5、左边全部不用看了!
因为:
左边都更小
只剩:
9 11 13
6、🌈 第二步
再看中间:
11
因为:
9 < 11
所以:
右边不要了!
只剩:
9
找到!
7、🌟 这就是二分查找!
8、🌈 为什么这样查找快?
普通查找:
一个一个找
二分查找:
每次删掉一半!
9、🌟 二分查找完整程序
#include <iostream>
using namespace std;
int main()
{
int a[100];
int n, x;
cin >> n;
for(int i = 0; i < n; i++)
{
cin >> a[i];
}
cin >> x;
int l = 0;
int r = n - 1;
while(l <= r)
{
int mid = (l + r) / 2;
if(a[mid] == x)
{
cout << "找到了!位置是:" << mid;
return 0;
}
else if(a[mid] < x)
{
l = mid + 1;
}
else
{
r = mid - 1;
}
}
cout << "没找到";
return 0;
}
10、🌟 二分查找核心思想
🌈 看中间!
🌈 小了去右边
🌈 大了去左边
🌈 每次减少一半!
三、⚔️ 第二关:分治求最大值
1、🏰 故事:谁是力量最强的勇者?
现在:
3 9 2 7 5
2、汉克老师问:
👑 "谁最大?"
3、❌ 普通方法
我们一个个来比较。
4、🌟 我们也可以使用分治法
(1)先拆成两部分!
3 9 | 2 7 5
(2)左边最大:
9
(3)右边最大:
7
(4)最后:
9 和 7
比较。
(5)得到:
9
5、🌟 分治法求最大值程序
#include <iostream>
using namespace std;
int a[100];
int find_max(int l, int r)
{
// 只有一个数字
if(l == r)
{
return a[l];
}
int mid = (l + r) / 2;
// 左边最大
int left_max = find_max(l, mid);
// 右边最大
int right_max = find_max(mid + 1, r);
// 返回更大的
if(left_max > right_max)
return left_max;
else
return right_max;
}
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
{
cin >> a[i];
}
cout << find_max(0, n - 1);
return 0;
}
6、🌟 重点理解
真正关键:
(1)🌈 原来的大问题
整个数组最大值
(2)🌈 变成两个小问题:
求左边最大值
+
求右边最大值
(3)🌟 这就是:
"拆问题!"
四、⚔️ 第三关:逆序对大冒险(进阶)
1、🏰 什么是逆序对?
(1)例如:
5 2
(2)5在前面。
但:
5 > 2
(3)这就是:
🌟 逆序对
2、🌈 再看:
3 1 2
(1)逆序对有:
3 1
3 2
(2)共:
2个
3、🌟 如何快速统计?
可以使用我们学过的:
🌈归并排序!
4、🌟 逆序对完整程序
#include <iostream>
using namespace std;
int a[100], temp[100];
int ans = 0;
void merge_sort(int l, int r)
{
if(l >= r)
return;
int mid = (l + r) / 2;
merge_sort(l, mid);
merge_sort(mid + 1, r);
int i = l;
int j = mid + 1;
int k = l;
while(i <= mid && j <= r)
{
if(a[i] <= a[j])
{
temp[k++] = a[i++];
}
else
{
// 产生逆序对
ans += mid - i + 1;
temp[k++] = a[j++];
}
}
while(i <= mid)
{
temp[k++] = a[i++];
}
while(j <= r)
{
temp[k++] = a[j++];
}
for(int p = l; p <= r; p++)
{
a[p] = temp[p];
}
}
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
{
cin >> a[i];
}
merge_sort(0, n - 1);
cout << "逆序对数量:" << ans;
return 0;
}
5、🌟 最关键的一句代码
ans += mid - i + 1;
6、为什么?
(1)例如:
左边:
5 6 7
右边:
2
(2)发现:
5 > 2
(3)因为左边已经有序。
所以:
6 > 2
7 > 2
也一定成立!
(4)因此:
一次加:
3个逆序对
是不是特别省时间?😃
五、⚔️ 第四关:汉诺塔魔法塔
1、🏰 汉诺塔的故事
(1)有三根柱子:
A B C
(2)现在:
有很多盘子:
大盘
中盘
小盘
(3)规则:
🌟 小盘不能压大盘!
(4)目标:
把所有盘子:
A → C
2、🌟 汉诺塔真正思想
(1)不是:
"直接搬"
(2)而是:
🌈 先搬上面的小盘
🌈 再搬最大的盘
🌈 再搬小盘回来
(3)🌟 这也是分治的方法!
3、🌟 汉诺塔程序
#include <iostream>
using namespace std;
void hanoi(int n, char A, char B, char C)
{
// 只有一个盘子
if(n == 1)
{
cout << A << " -> " << C << endl;
return;
}
// 先移动上面的盘子
hanoi(n - 1, A, C, B);
// 移动最大的盘子
cout << A << " -> " << C << endl;
// 再移动上面的盘子
hanoi(n - 1, B, A, C);
}
int main()
{
int n;
cin >> n;
hanoi(n, 'A', 'B', 'C');
return 0;
}
4、🌳 汉诺塔递归树(重点)
(1)例如:
3个盘子
(2)会变成:
搬2个
搬最大
搬2个
(3)而:
搬2个
又会继续拆。
(4)🌟 这就是:
"把复杂的大问题,拆成简单的小问题!"
六、🌈 第五关:真正理解分治法,"举一反三"
1、现在同学们,开始明白:
🌟 分治不是一种题!
2、而是一种:
🧠 思想!
3、🌟 只要看到:
大问题难
能拆小
小问题能解决
最后能合并答案
🌟 就有可能用分治的方法!
七、🎯 课后总结
1、🌟 分治三步曲
① 分(Divide)
拆问题
② 治(Conquer)
解决小问题
③ 合(Combine)
合并答案
2、🌟 归并排序
拆数组再合并。
3、🌟 快速排序
按pivot分区域。
4、🌟 二分查找
每次砍掉一半。
5、🌟 分治求最大值
左右分别求最大。
6、🌟 汉诺塔
先解决小盘问题。
7、🌟 真正掌握分治方法的同学
不是只会背代码。
而是:
🌈"看到问题,会拆问题!"🌈
8、🏆 分治魔法学院今天毕业了!
同学们终于成为了:
👑 分治小勇者!
接下来,我们一起学习,更强大的算法魔法!