前言
本专栏上一篇文章学习了位运算以及位运算的深入了解
今天我们来学习新的算法知识——分治算法
其实也不算是新的知识,因为我们在数据结构专栏把排序这一块的知识都实现过
今天我们用分治来完成一些算法题吧
分治算法
分治算法(Divide and Conquer)是一种将复杂问题分解为更小、更易解决的子问题的策略,其核心思想是通过递归实现「分解-解决-合并」的流程
定义
分治算法将一个规模为 n 的问题分解为 k个规模较小的子问题(通常为 n/m),这些子问题与原问题性质相同且相互独立。通过递归解决子问题后,将子问题的解合并为原问题的解。
核心步骤
分解:将原问题划分为若干个规模较小的子问题。例如,将16个硬币分成两组,通过比较重量缩小问题范围
解决:递归地求解每个子问题。当子问题规模足够小时(如单个硬币),直接判断是否为伪币
合并:将子问题的解合并为原问题的解。例如,归并排序中将已排序的子数组合并为完整有序数组
其实分治算法就是分为:归并排序和快速排序,将问题分成一个一个子区间进行处理
我们在数据结构专栏的排序算法第二幕和第三幕都已经详细了解过了
来看看以前板子复习复习
void QuickSort(int* arr, int left, int right) // 三路划分 快速排序
{
if (left >= right)
{
return;
}
int begin = left;
int end = right;
int randi = left + (rand() % (right - left + 1)); // 取随机数值作为基准值
swap(arr[randi], arr[left]); // 把基准值放在最左边
int key = arr[left]; // 定义key值
int cur = left + 1; // 这里类似于前后指针法 但是做了一些优化
while (cur <= right) // 左右同时往中间推
{
// 解除了中间数据相同影响性能的问题
if (arr[cur] < key) // 遇到比key小的数值 交换数值 left++,cur++
{
swap(arr[cur], arr[left]);
left++;
cur++;
}
else if (arr[cur] > key) // 遇到比key大的数据 不管right此时为什么 直接交换
{
swap(arr[cur], arr[right]);
right--;
}
else
{
cur++;
}
} // 每次都看cur指定的值 如果小于key就放左边 大于right就放右边 等于就继续走
// left-right区间都是相同的值 不用进一步递归
QuickSort(arr, begin, left - 1); // 左区间
QuickSort(arr, right + 1, end); // 右区间
}
void _MergeSort(int* arr, int left, int right, int* tmp) // 把大区间分配数个小空间
{
// 两个小空间 排序成一个空间 用tmp接受 返回赋值给原数组
if (left >= right) // 递归出口
{
return;
}
int mid = left + (right - left) / 2;// 分成两个区间 采用二分
_MergeSort(arr, left, mid, tmp); // 左区间
_MergeSort(arr, mid + 1, right, tmp);// 右区间
// 递归处理之后 现在就是合并两个子区间 使他们有序
int begin1 = left, end1 = mid; // 第一个区间的begin和end
int begin2 = mid + 1, end2 = right; // 第二个区间的 begin和end
int index = begin1; // 新的下标 对应tmp数组
while (begin1 <= end1 && begin2 <= end2) // 合并两个数组的流程 不多赘述啦
{
if (arr[begin1] < arr[begin2])
{
tmp[index++] = arr[begin1++];
}
else
{
tmp[index++] = arr[begin2++];
}
}
while (begin1 <= end1) // 有数组没有全部传给tmp的情况
{
tmp[index++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = arr[begin2++];
}
for (int i = left; i <= right; i++) // 赋值返回原数组
{
arr[i] = tmp[i];
}
}
void MergeSort(int* arr, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n); // 我们这里传一个新开的tmp数组空间进去,辅助合并两个子区间
_MergeSort(arr, 0, n - 1, tmp);
free(tmp);
}
归并和快排虽然都是排序,但归并在有些场景用起来会很舒服,本篇文章主要是
快速排序
,话不多说,刷题!!!
颜色分类
颜色分类
思路:
只有 0 ,1, 2 三个元素,然后分区间,其实我们可以直接用三指针,遇到 0 往前放,遇到 1 继续,遇到 2往后放
其实这种也类似于快速排序的划分,可以用快速排序的递归板子, 也可以用三指针一个一个遍历排序
看看代码吧
// 三指针 直接遍历一遍
class Solution
{
public:
void sortColors(vector<int>& nums)
{
int n = nums.size();
int left = 0, cur = 0, right = n - 1; // 定义初始left right
while(cur <= right)
{
if(nums[cur] == 0) // 遇到 0 就放前面
{
swap(nums[++left], nums[cur++]);
}
else if(nums[cur] == 2) // 遇到 2 就放后面
{
swap(nums[right--], nums[cur]);
}
else // 遇到 1 放中间
cur++;
}
}
};
有关递归的方法这里就不多赘述啦
// 递归快速排序板子 三路划分
class Solution
{
public:
void sortColors(vector<int>& nums)
{
quicksort(nums, 0, nums.size() - 1);
}
void quicksort(vector<int>& nums, int left, int right)
{
if(left >= right)
return;
int begin = left, end = right;
int randi = left + (rand()%(right - left + 1));
swap(nums[left],nums[randi]);
int key = nums[left];
int cur = left + 1;
while(cur <= right)
{
if(nums[cur] < key)
{
swap(nums[left++], nums[cur++]);
}
else if(nums[cur] > key)
{
swap(nums[cur], nums[right--]);
}
else
cur++;
}
quicksort(nums, begin, left - 1);
quicksort(nums, right + 1, end);
}
};
排序数组
排序数组
思路:
上快排吧,其实也可以直接sort,因为sort的底层是内省排序,应对什么数据基本上都可以 哈哈哈哈
没什么好说的了,直接把板子一套就OK了
其实这题也可以试试希尔排序,堆排序,时间复杂度都接近 n*logn
class Solution
{
public:
void QuickSort(vector<int>& nums, int left, int right)
{
if(left >= right)
return;
int begin = left, end = right;
int randi = left + (rand()%(right - left + 1));
swap(nums[left], nums[randi]);
int key = nums[left];
int cur = left + 1;
while(cur <= right)
{
if(nums[cur] < key)
{
swap(nums[cur], nums[left]);
left++;
cur++;
}
else if(nums[cur] > key)
{
swap(nums[cur], nums[right]);
right--;
}
else
cur++;
}
QuickSort(nums, begin, left - 1);
QuickSort(nums, right + 1, end);
}
vector<int> sortArray(vector<int>& nums)
{
int n = nums.size();
QuickSort(nums, 0, n - 1);
return nums;
}
};
数组中的第k个最大元素
数组中的第k个最大元素
思路:
和排序有关,来吧来吧,快排快排,排序之后,倒数k个就好了

// 快速排序
class Solution
{
public:
void QuickSort1(vector<int>& nums, int left, int right)
{
if(left >= right)
return;
int begin = left, end = right;
int randi = left + (rand()%(right - left + 1));
swap(nums[left], nums[randi]);
int key = nums[left];
int cur = left + 1;
while(cur <= right)
{
if(nums[cur] < key)
{
swap(nums[cur], nums[left]);
left++;
cur++;
}
else if(nums[cur] > key)
{
swap(nums[cur], nums[right]);
right--;
}
else
cur++;
}
QuickSort1(nums, begin, left - 1);
QuickSort1(nums, right + 1, end);
}
int findKthLargest(vector<int>& nums, int k)
{
int n = nums.size();
//ShellSort(nums,n);
QuickSort1(nums, 0, n - 1);
int ret = 0;
for(int i = n-1;i>=0;i--)
{
k--;
if(k == 0)
{
ret = nums[i];
break;
}
}
return ret;
}
};
希尔排序
class Solution
{
public:
void ShellSort(vector<int>& arr, int n)
{
int gap = n;
while(gap > 1)
{
gap = gap / 3 + 1;
for(int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = arr[end + gap];
while(end >= 0)
{
if(arr[end] > tmp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
break;
}
arr[end + gap] = tmp;
}
}
}
int findKthLargest(vector<int>& nums, int k)
{
int n = nums.size();
ShellSort(nums, n);
int ret = 0;
for(int i = n - 1; i >= 0; i--)
{
k--;
if(k == 0)
{
ret = nums[i];
break;
}
}
return ret;
}
};
其实还有一种非常简单的方法,用优先级队列,默认是大堆,直接pop k - 1个就好了
class Solution
{
public:
int findKthLargest(vector<int>& nums, int k)
{
priority_queue<int> pq; // 定义优先级队列
for(auto x : nums)
{
pq.push(x); // 插入优先级队列 自动排序
}
k--;
while(k--) // 弹出k-1个数据
{
pq.pop();
}
return pq.top(); //第k个大的元素
}
};
针对数据排序,三路划分和归并排序,堆排序,都是优选,时间复杂度度比较稳定
总结
总结
分治算法通过「分解-解决-合并」的递归策略,将复杂问题拆解为独立子问题,最终高效求解。其核心在于子问题与原问题的同构性及合并逻辑的合理性
。在排序领域,归并排序通过二分递归与归并操作实现稳定排序,时间复杂度为O(nlogn)
快速排序利用三路划分优化,通过随机基准值降低最坏时间复杂度风险,平均性能更优。实际应用中,分治思想可灵活应对多场景:如颜色分类通过快速排序的划分逻辑优化空间复杂度,第k大元素可通过排序或堆结构快速定位。分治算法的优势在于其普适性与可扩展性,尤其适合处理大规模数据或需递归优化的场景,但需注意递归深度与合并操作的效率平衡。
今天的内容就到这里啦,不要走开,小编持续更新中~~~~~