一、分治法介绍
分治法:
即分而治之,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。 eg:快速排序
动态规划:(参考:https://blog.csdn.net/qq_36935691/article/details/113888868)
通常用于求解最优解问题,与分治法类似,其基本思想也是将待求解问题分解成若干子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是,适合用动态规划求解的问题,经分解得到的子问题往往不是相互独立的。 若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题会被重复计算多次。 而动态规划的做法是将已解决子问题的答案保存下来,在需要子问题答案的时候便可直接获得,而不需要重复计算,这样就可以避免大量的重复计算,提高效率。(记忆化搜索(MEMO)、递归化递推)
算法范式(算法设计的设计模式):
二、例题介绍:
Question1:寻找多数
思路: 根据多数元素的定义可知多数元素必定在数组中出现次数最多,而若将原数组分成2部分,则在其中一部分出现次数最多的必定是多数元素,证明如下: (参考:https://blog.csdn.net/liudehuatw/article/details/116832113)
假定数组长度为n,多数元素为a,出现次数为f(a),已知:fn(a) > n/2 将数组分成两部分,长度分别为l,r,其中l+r = n 若假设fl(a) <= l/2, fr(a) <= r/2 则fn(a) <= n/2,与已知条件相矛盾,故假设不成立 故将原数组分成2部分,则在其中一部分出现次数最多的必定是多数元素
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int countNumber(int nums[], int num, int left, int right){
int count = 0;
for(int i=left; i<=right; i++){
if(nums[i]==num) count++;
}
return count;
}
int majorityElement(int nums[], int left, int right){
if(left==right) return nums[left];
int mid = (right-left)/2 + left;
int left_majority = majorityElement(nums, left, mid);
int right_majority = majorityElement(nums, mid+1, right);
if(left_majority==right_majority) return left_majority;
else{
int left_count = countNumber(nums, left_majority, left, mid);
int right_count = countNumber(nums, right_majority, mid+1, right);
return left_count > right_count ? left_majority : right_majority;
}
}
int main(){
int n;
cin>>n;
vector<int> nums;
int num;
while (cin>>num) {
nums.push_back(num);
if (cin.get() == '\n') break;
}
int arr[nums.size()];
for(int i=0;i<nums.size();i++){
arr[i] = nums[i];
}
cout<<majorityElement(arr, 0, nums.size()-1);
return 0;
}
方法二:sort排序+输出
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main()
{
int n;
cin>>n;
vector<int> nums;
int num;
while (cin>>num) {
nums.push_back(num);
if (cin.get() == '\n') break;
}
sort(nums.begin(), nums.end());
cout<<nums[n/2]<<endl;
return 0;
}
Question2:找到 k 个最小数
思路:(参考:https://blog.csdn.net/qq_15026111/article/details/105356456) 使用sort函数对整个数组进行排序,然后取前k个最小的数字。目前最高效的排序算法是快速排序,其采用了分治法的思想。这里打算手撕快速排序,而不用algorithm头文件中的sort()函数,借以巩固分治法的基础。
优化点: 题目要求取前 K 个数字,所以快排流程不需要完全执行完,只要判断枢纽元素的位置与 K 的关系即可。
一次快速排序的划分:
int partition(int arr[], int low, int high){
int i = low;
int j = high+1;
int pivot = arr[low];
while(true){
while(arr[++i]<pivot){
if(i==high) break;
}
while(arr[--j]>pivot){
if(j==low) break;
}
if(i>=j) break;
swap(arr, i, j);
}
swap(arr, low, j);
return j;
}
一次划分后 得到的数组大小与 k 的关系:k与m
if(k==m){
return;
}
else if(k<m){
partitionArray(arr, low, m-1, k);
}
else{
partitionArray(arr, m+1, high, k);
}
partitionArray(arr, m+1, high, k); 细节: 当 m < k 时,我们需要进行第二种类型的划分,即在剩余数组中寻找剩余的 k-m 个最小数,那么你是否有疑问,既然已经找到了 m个最小数,还差 k-m 个,那么是不是最后一个参数也要传 k-m? 其实这里的 k 的含义并不是剩余需要寻找的数字的个数,而是一个相对于整个数组而言,所需的前 K小的数字的个数,无论你要在数组的哪个范围内进行划分,这个标准不能变。 还有一种解释方法是,因为首指针没有从 0 开始计算,而是 m+1 ,如果首指针从 0 开始计算的话,那么最后一个参数可以为 k-m,但是这样不方便进行递归操作。
完整代码:
#include<iostream>
#include<algorithm>
using namespace std;
void swap(int arr[], int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
int partition(int arr[], int low, int high){
int i = low;
int j = high+1;
int pivot = arr[low];
while(true){
while(arr[++i]<pivot){
if(i==high) break;
}
while(arr[--j]>pivot){
if(j==low) break;
}
if(i>=j) break;
swap(arr, i, j);
}
swap(arr, low, j);
return j;
}
void partitionArray(int arr[], int low, int high, int k){
int m = partition(arr, low, high);
if(k==m){
return;
}
else if(k<m){
partitionArray(arr, low, m-1, k);
}
else{
partitionArray(arr, m+1, high, k);
}
}
void printArr(int arr[], int k){
sort(arr, arr+k);
for(int i=0; i<k; i++)
{
cout<<arr[i]<<" ";
}
}
int main(){
int len, k;
cin>>len>>k;
int num;
int nums[len];
for(int i=0; i<len; i++){
cin>>nums[i];
}
if(k==0) {
cout<<"0";
return 0;
}else if (len <= k) {
printArr(nums, k);
return 0;
}
partitionArray(nums, 0, len-1, k);
int res[k] = {0};
for(int i=0; i<k; i++){
res[i] = nums[i];
}
printArr(nums, k);
return 0;
}
方法二:sort排序+输出
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main(){
int len, k;
cin>>len>>k;
int num;
vector<int> nums;
while(cin>>num){
nums.push_back(num);
if(cin.get()=='\n') break;
}
sort(nums.begin(),nums.end());
for(int i=0; i<k-1; i++){
cout<<nums[i]<<" ";
}
cout<<nums[k-1];
return 0;
}
附: 快速排序算法
void Quick_Sort(int *arr, int begin, int end){
if(begin > end)
return;
int tmp = arr[begin];
int i = begin;
int j = end;
while(i != j){
while(arr[j] >= tmp && j > i)
j--;
while(arr[i] <= tmp && j > i)
i++;
if(j > i){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
arr[begin] = arr[i];
arr[i] = tmp;
Quick_Sort(arr, begin, i-1);
Quick_Sort(arr, i+1, end);
}
|