IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【算法】分治法详解 -> 正文阅读

[数据结构与算法]【算法】分治法详解

一、分治法介绍

分治法:

即分而治之,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
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){ //快排划分一次后,正好找到最小的k个数 
		return;
	}
	else if(k<m){ //快排划分一次后,最小的k个数都在前m个数当中,继续递归划分 
		partitionArray(arr, low, m-1, k);
	}
	else{ //快排划分一次后,还有k-m个数在右侧数组当中,继续递归划分 
		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;
}

// partition 函数和快速排序中相同,具体可参考快速排序相关的资料
// 代码参考 Sedgewick 的《算法4》
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){ //快排划分一次后,正好找到最小的k个数 
		return;
	}
	else if(k<m){ //快排划分一次后,最小的k个数都在前m个数当中,继续递归划分 
		partitionArray(arr, low, m-1, k);
	}
	else{ //快排划分一次后,还有k-m个数在右侧数组当中,继续递归划分 
		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);
}
//原文链接:https://blog.csdn.net/qq_40941722/article/details/94396010
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 02:14:52  更:2022-09-15 02:16:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 17:28:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码