算法详细讲解- 快速排序与归并排序

快速排序

讲解

快速排序是基于分治来做的。

那么问题来了,分治又是什么呢?

分治,简单来说,就是"分而治之"的意思。基本思路是把一个大问题拆分成几个小问题,如果这些小问题还比较大,就继续拆,直到这些问题变得足够简单,可以直接解决。解决了所有的小问题之后,再把这些解决方案合并起来,就能解决原来的大问题了。

那分治又是怎么工作的?

基本来看大致是这么个流程:选择基准 -> 分割 -> 递归排序

  • 选择分界点:从数列中挑出一个元素作为分界点。
  • 分割:以分界点为界将序列一分为二。所有比基准小的元素放到基准前面,所有比基准大的元素放到基准后面(相同的数可以放到任一边)。
  • 递归排序:对两个子序列(一个是基准左边的序列,另一个是基准右边的序列)重复上述步骤,直到每个序列里只剩下一个元素或者为空为止。

通过这种方式,一个复杂的问题(给一组数排序)被分解成了一些简单的子问题(每次只关注于排序一个小的子序列),这正是分治思想的核心。然后,通过解决这些子问题,并将它们的结果合并起来,我们就能够解决原始的问题。

那分治就这么随意吗?当然不是,在实际情况会复杂许多。

首先就是确定分界点:首先我们可以想象一个数列,最左边界索引 为l,最右边界索引 为r。那么分界点就会有以下四种情况:q[l],q[r],q[(l+r)/2],随机

注意分界点不是一个数值,而是数组的索引。

所以就可以根据以上步骤总结出模板了。

暴力的模板

  1. 新开两个数组a与b,用于存储一分为二之后的子序列
  2. 将要分治操作的序列看作是新开的数组q
  3. 在数组q上确定分界点,也就是索引,并确定索引上的值
  4. 然后递归处理数组q,小于等于的数值放进数组a,大于等于的数值放进数组b
  5. 最后将a,b两个数组拼起来,放进数组q

这么做需要开辟一个额外空间。就不用演示了,没有技术含量。

做题模板

  1. 定义指针i指向数组最左边界l,定义指针j指向数组最右边界r,并定义分界点
  2. i指针一直往分界点走,直到遇见比分界点处的数大的数,停止,那这时候就应该把i指针所指的数移到分界点的右侧
  3. 那么这个时候i指针不动,移动j指针,j指针一直往分界点走,停止,直到遇见比分界点处小的数,那这时候就应该把j指针所指的数移到分界点的左侧
  4. 那么两个指针都停下来了,而且所指的数都是错位的,那么swap进行交换就可以,让错位的数对号入座
  5. 一直递归直到i,j指针相遇,就完成了

模板题

785. 快速排序 - AcWing题库

解法

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N  = 100005;

int n;
int q[N];

void quick_sort(int q[], int l, int r) {
    // 1. 判断是否是有效区间:没有数或者是只有一个数
	if (l >= r) return; 
    // 两个指针需要先移动一次再去做判断(与下面的dowhile对应)
    // 所以需要有预先的偏移量(l-1与j+1)
    // 2. 确定指针与分界点
	int i = l - 1, j = r + 1, x = q[l + r >> 1]; 
    // 3. 使用dowhile指针正常移动,swap递归交换
	while (i < j) {
		do i++; while (q[i] < x);
		do j--; while (q[j] > x);
		if (i < j) swap(q[i],q[j]);
	}
    // 4. 传入左右两段,进行递归处理
	quick_sort(q, l, j);
	quick_sort(q, j+1, r);
    // quick_sort(q, l, i - 1);
	// quick_sort(q, i, r);
}

int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) scanf("%d", &q[i]);
	quick_sort(q, 0, n - 1); // 注意这里传入的是索引,所以要n - 1
	for (int i = 0; i < n; i++) printf("%d ", q[i]);
	return 0;
}

基本步骤

  1. 判断是否是有效区间:没有数或者是只有一个数

  2. 确定指针与分界点

  3. 使用dowhile进行指针的移动与比较,swap递归交换

  4. 传入左右两段,进行递归处理

注意事项

  • 因为两个指针需要先移动一次再去做判断(与下面的dowhile对应,所以需要有预先的偏移量(l-1与j+1)
  • do(i++); while (q[i] <= x);do(j--); while (q[j] >= x);这是错误的写法。

举个例子:传入的数组为[2, 2, 2, 2, 2],i 会一直增加,直到越界;j 会一直减少,直到越界,那么ij 永远不会停下来,或者最终 i 超出数组范围。

练习题

0实现快速排序 - 蓝桥云课

0卡java排序 - 蓝桥云课

归并排序

讲解

归并排序也是一种分治算法。它的基本思想是:

  1. :将原数组分成两个子数组;
  2. :递归地对子数组分别进行归并排序;
  3. :将两个有序子数组合并成一个有序数组。

模板题

787. 归并排序 - AcWing题库

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 100005;
int n, q[N], tmp[N];

void merge_sort(int q[], int l ,int r) {
    // 1.判断是否是有效区间:一个数或者没有数
	if (l >= r) return;
	// 2.确定分界点:mid = (l+r)/2
	int mid = l + r >> 1;
	// 3.从分界点开始,将原数组分成两个子数组
	merge_sort(q, l, mid);
	merge_sort(q, mid + 1, r);
	// 4.定义两个数组分别对应的初始双指针与临时数组tmp的计数k
	int k = 0, i = l, j = mid + 1;
	// 5.双指针 i 和 j 分别遍历左右两个子数组,按顺序把较小的元素放入tmp
	while (i <= mid && j <= r) {
		if (q[i] <= q[j]) {
			tmp[k++] = q[i++];
		} else {
			tmp[k++] = q[j++];
		}
	}
	// 6.处理剩余元素(其中一个子数组可能还有未处理的元素)
	while (i <= mid) {
		tmp[k++] = q[i++];
	}
	while (j <= r) {
		tmp[k++] = q[j++];
	}
	// 7.将临时数组复制回原数组
	for (i = l, j = 0; i <= r; i++, j++) {
		q[i] = tmp[j];
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", &q[i]);
	}
	merge_sort(q, 0, n - 1);
	for (int i = 0; i < n; i++) {
		printf("%d ", q[i]);
	}
	return 0;
}

基本步骤

1.判断是否是有效区间:一个数或者没有数

2.确定分界点:mid = (l+r)/2

3.从分界点开始,将原数组分成两个子数组

4.定义两个数组分别对应的初始双指针,以及临时数组的计数k

5.双指针遍历两个数组,按顺序把较小数放入临时数组

6.处理剩余元素

7.临时数组复制到原数组

注意事项

练习题

6.实现归并排序 - 蓝桥云课

快速排序与归并排序的区别

序算法 思想
快速排序 选择一个基准元素,将数组划分为两个子数组:左边小于等于基准,右边大于等于基准,然后递归地对子数组排序。
归并排序 将数组分为两个子数组,分别排序后合并(先递归排序,后合并)。
排序算法 划分方式
快速排序 原地划分不需要额外空间,通过交换元素完成划分。
归并排序 非原地划分需要一个临时数组来合并两个有序子数组。
排序算法 稳定性
快速排序 不稳定(交换可能破坏相同元素的相对顺序)
归并排序 稳定(合并时优先左边数组元素)
快速排序 归并排序
使用双指针 i 和 j 从两端向中间扫描,找到不符合条件的元素进行交换 拆分到最小单位后,再合并两个有序数组
不需要额外空间(原地排序) 需要一个临时数组 tmp[] 来合并数据
特性 快速排序 归并排序
时间复杂度 平均 O(n log n),最坏 O(n²) 始终 O(n log n)
空间复杂度 O(log n) O(n)
稳定性 不稳定 稳定
是否原地排序
适用场景 内排序、速度快 外排序、稳定排序
相关推荐
zh_xuan1 小时前
LeeCode 40.组合总和II
c语言·数据结构·算法
wangluoqi2 小时前
c++ 数据结构-并查集、ST表 小总结
数据结构·c++
小马学嵌入式~11 小时前
数据结构:队列 二叉树
c语言·开发语言·数据结构·算法
省四收割者12 小时前
Go语言入门(10)-数组
数据结构·经验分享·笔记·vscode·算法·golang
月盈缺14 小时前
学习嵌入式的第二十四天——数据结构——队列和树
数据结构·学习
SunnyKriSmile19 小时前
指针实现数组的逆序存放并输出
c语言·算法·排序算法·数组逆序存放
Y40900119 小时前
Java算法之排序
java·数据结构·笔记·算法
艾莉丝努力练剑21 小时前
【C语言16天强化训练】从基础入门到进阶:Day 6
c语言·数据结构·学习·算法
快去睡觉~21 小时前
力扣1005:k次取反后最大化的数组和
数据结构·算法·leetcode
想不明白的过度思考者1 天前
初识数据结构——Map和Set:哈希表与二叉搜索树的魔法对决
数据结构·散列表