【算法】分治初步

蒟蒻Ivan温暖的小窝 / 2023-08-22 / 原文

目录
  • 定义
  • 示例
    • 快速排序
      • 实现
      • 第k小
    • 三分法
    • 归并排序
      • 实现

定义

分治,字面上的解释是“分而治之”,就是把一个问题分成多个的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

示例

快速排序

把原数组分成左右两段,保证左 \(≤\) 右,再对左右分别排序。

实现

怎么才能让左不大于右呢?

基准值:左/右/中点/随机位置的值。 这么草率?

反正都一样。

\([l,r]\) 区间排序:

  1. i = l, j = r; 确定基准值x;
  2. i向后走,当 a[i]>x 时停下,j向前走,当 a[j]>x 时停下。
  3. if(i < j) swap(a[i], a[j]);
  4. 当i<j时,回到第二步。
  5. \([l, j]\)\([i, r]\) 分别递归,直到 l >= r
$\color{green}{\text{点击查看代码}}$
void quick_sort(int l, int r)
{
	int i = l, j = r;
	int x = a[(l+r)/2]; // 基准值(此处取中点,一般建议随机数)
	while(i<=j)
	{
		while(a[i] < x) i++;
		while(a[j] > x) j--;
		// i 表示小于基准值的下标,j 表示大于基准值的下标
		// 找到不满足以上的 a[i], a[j]
		if(i<=j) // 如果 i 在 j 左边,a[i] 和 a[j] 交换
		{
			swap(a[i], a[j]);
			i++,j--; // 防止越界 RE
		}
	}
	if(l<r) // 如果数列可以再分,递归
	{
		quick_sort(l,j);quick_sort(i,r);
	}
}

第k小

同快速排序。

但我们可以考虑一些神奇的方法。

\([l,r]\) 区间排序,找第 \(k\) 小:

qsort(l,r,k)
{
	选基准值,做交换。
	if(k <= j) return qsort(l,j,k);
	else if(k >= i) return qsort(i, r, k);
	else return x;
}
$\color{green}{\text{点击查看代码}}$
int quick_sort_mink(int l,int r,int k)
{
	int i = l, j = r;
	int x = a[(l+r)/2];
	while(i<=j)
	{
		while(a[i] < x) i++;
		while(a[j] > x) j--;
		if(i<=j)
		{
			swap(a[i],a[j]);
			i++,j--;
		}
	}
	if(k <= j) return quick_sort_mink(l,j,k);
	else if(k >= i) return quick_sort_mink(i,r,k);
	else return x;
}

三分法

例题:P1883 函数

归并排序

先对左右子区间排序,然后再把两个有序子区间合并成一个完整区间。

时间复杂度:稳定的 \(O(n \log n)\)

实现

当前区间 \([l,r]\)

  1. mid=(l+r)/2;
  2. 递归 \([l,mid]\) ;
  3. 递归 \([mid+1,r]\) ;
  4. 合并 \([l,mid],[mid+1,r]\) ;
$\color{green}{\text{点击查看代码}}$
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, j = mid+1; // 从两区间左端点开始
	int cnt = l-1;
	while(i <= mid && j <= r)
	{
		if(a[i] <= a[j]) b[++cnt] = a[i], i++;
		else b[++cnt] = a[j], j++;
		// 把a[i],a[j]更大的那个放进临时数组b[]
	}
	while(i <= mid) b[++cnt] = a[i], i++;
	while(j <= r) b[++cnt] = a[j], j++;
	// 把没放完的放进b[]
	for(int k = l;k <= r;k++) a[k] = b[k];
	// 把b[] 放回原区间
}