算法——快速排序

本文介绍了快速排序算法中的分区过程,包括如何选择基准元素,以及两种不同的基准选择策略。通过C和Java代码示例展示了如何实现中位数选择和元素交换,以达到优化排序效率的目的。
摘要由CSDN通过智能技术生成

一、思想

1 总体思想

对数组进行 分区,选定一个元素作为 基准元素,然后将 小于基准元素的元素 放在 基准元素的左边,将 大于基准元素的元素 放在 基准元素的右边,分区完成之后,再对 基准元素左边的元素 和 其右边的元素 分别进行分区,直到不可再分。该算法采用了 分治 的思想,使排序速度加快。

2 分区 ( 分治 )

  1. 首先,选取 区间中的某个元素 作为 基准元素,并将其放到 区间的第一个元素的位置
  2. 然后,使用两个指针 (此处的指针其实是数组元素的索引) 寻找不同元素,在寻找的过程中,左指针不能超过右指针
    • 右指针 从区间尾部向区间头部(逆序) 寻找第一个 小于 基准元素 的元素
    • 左指针 从区间头部向区间尾部(顺序) 寻找第一个 大于 基准元素 的元素
  3. 接着,交换 左、右指针 指向的元素。
  4. 重复步骤二和步骤三,直到左指针超过右指针,此时就完成了 将小于基准元素的元素放到一起、将大于基准元素的元素放到一起 的操作,区间按这种形式排序:[基准元素, 小于基准的元素们, 大于基准的元素们]
  5. 最后,交换 基准元素 与 最后一个小于基准元素的元素 的位置。从而将区间按这种形式排序:[小于基准的元素们, 基准元素, 大于基准的元素们]

3 选取基准元素

实际上,选取基准元素 也不是一个简单的事,有以下两种策略:

策略一

直接将 待排序区间的第一个元素 作为基准。但这样简单的实现有一个缺点:如果待排序区间的值从大到小排列,则每次分区的左区间都为空,右区间是 除已有的基准元素们外的 所有剩余元素,这样就会使快速排序降级为冒泡排序,极大地降低了排序速度。

策略二

找 待排序区间中 第一个元素、最后一个元素 和 中间元素 的中位数。这样就避免了 基准元素极度不均衡 的问题,就算这三个数分别是 区间的最大值、第二大值、第三大值,也能选出一个第二大值来,从而左区间就不可能为空了。寻找中位数可以使用 异或 这个逻辑运算符,可以看看这篇文章:寻找中位数

二、举例

由于快速排序比较难理解,所以这里使用 第一种选取基准元素的策略 举一个例子,对于待排序数组 arr = [3, 4, 2, 9, 8, 7, 6],快速排序的流程如下:

第一轮分区:待排序区间为0~606 都是 arr 的下标),即 [3, 4, 2, 9, 8, 7, 6]

  • 第一步:选取 3 为基准元素
  • 第四步完成后,区间为 [3, 2, 4, 9, 8, 7, 6]
  • 第五步交换 基准元素3 与 元素2 的位置,区间为 [2, 3, 4, 9, 8, 7, 6]

此时 arr = [2, 3, 4, 9, 8, 7, 6]

第二轮分区:待排序区间为 0~02~6,即 [2][4, 9, 8, 7, 6]

  1. 对于区间 0~0,即 [2]
    • 第一步:选取 2 为基准元素
    • 第四步完成后,区间为 [2]
    • 第五步交换 基准元素2 与 元素2 的位置,区间为 [2]
  2. 对于区间 2~6,即 [4, 9, 8, 7, 6]
    • 第一步:选取 4 为基准元素
    • 第四步完成后,区间为 [4, 9, 8, 7, 6]
    • 第五步交换 基准元素4 与 元素4 的位置,区间为 [4, 9, 8, 7, 6]

此时 arr = [2, 3, 4, 9, 8, 7, 6]

第三轮分区:待排序区间为 0~-11~02~13~6,前三个区间都为空,只有最后一个区间有元素,元素为 [9, 8, 7, 6]

  • 第一步:选取 9 为基准元素
  • 第四步完成后,区间为 [9, 8, 7, 6]
  • 第五步交换 基准元素9 与 元素6 的位置,区间为 [6, 8, 7, 9]

此时 arr = [2, 3, 4, 6, 8, 7, 9]

第四轮分区:待排序区间为 3~57~6,第二个区间为空,第一个区间的元素为 [6, 8, 7]

  • 第一步:选取 6 为基准元素
  • 第四步完成后,区间为 [6, 8, 7]
  • 第五步交换 基准元素6 与 元素6 的位置,区间为 [6, 8, 7]

此时 arr = [2, 3, 4, 6, 8, 7, 9]

第五轮分区:待排序区间为 3~24~5,第一个区间为空,第二个区间的元素为 [8, 7]

  • 第一步:选取 8 为基准元素
  • 第四步完成后,区间为 [8, 7]
  • 第五步交换 基准元素8 与 元素7 的位置,区间为 [7, 8]

此时 arr = [2, 3, 4, 6, 7, 8, 9]

第六轮分区:待排序区间为 4~46~5,第二个区间为空,第一个区间的元素为 [7]

  • 第一步:选取 7 为基准元素
  • 第四步完成后,区间为 [7]
  • 第五步交换 基准元素7 与 元素7 的位置,区间为 [7]

此时 arr = [2, 3, 4, 6, 7, 8, 9]

第七轮分区:待排序区间为 4~35~4,两个区间都为空,排序完成,最终 arr = [2, 3, 4, 6, 7, 8, 9]

三、代码实现

1 使用 C 语言实现

// 寻找中位数
int medianThree(int arr[], int left, int mid, int right) {
	int a = arr[left], b = arr[mid], c = arr[right];
    if ((a < b) ^ (a < c)) {
        return left;
    } else if ((b < a) ^ (b < c)) {
        return mid;
    } else {
        return right;
    }
}
// 交换元素
void swap(int arr[], int i, int j) {
	int t = arr[i];
	arr[i] = arr[j];
	arr[j] = t;
}
// 分区
void partition(int arr[], int left, int right) {
	if (left >= right) { // 如果子区间长度为0或为负数,则退出递归
		return;
	}
	
	int p = medianThree(arr, left, (left + right) >> 1, right); // 寻找基准的索引
	swap(arr, left, p); // 让基准成为区间左端点
	
	int i = left + 1, j = right;
	while (i < j) {
		while (arr[j] >= arr[left] && i < j) { // 寻找小于基准的元素
			j--;
		}
		while (arr[i] <= arr[left] && i < j) { // 寻找大于基准的元素
			i++;
		}
		swap(arr, i, j); // 交换这两个元素
	}
	swap(arr, left, i); // 让基准成为区间的分界线,i指向最后一个小于基准的元素
	partition(arr, left, i - 1); // 对左子区间进行分区
	partition(arr, i + 1, right); // 对右子区间进行分区
}
// 快速排序
void quickSort(int arr[], int len) {
	partition(arr, 0, len - 1);
}

2 使用 Java 语言实现

class Sort {
	public static void sort(int[] arr) {
		partition(arr, 0, arr.length - 1);
	}
	private static void partition(int[] arr, int left, int right) {
		if (left >= right) { // 如果子区间长度为0或为负数,则退出递归
			return;
		}
		
		int p = medianThree(arr, left, (left + right) >> 1, right); // 寻找基准的索引
		swap(arr, left, p); // 让基准成为区间左端点
		
		int i = left + 1, j = right;
		while (i < j) {
			while (arr[j] >= arr[left] && i < j) { // 寻找小于基准的元素
				j--;
			}
			while (arr[i] <= arr[left] && i < j) { // 寻找大于基准的元素
				i++;
			}
			swap(arr, i, j); // 交换这两个元素
		}
		swap(arr, left, i); // 让基准成为区间的分界线,i指向最后一个小于基准的元素
		partition(arr, left, i - 1); // 对左子区间进行分区
		partition(arr, i + 1, right); // 对右子区间进行分区
	}
	private static void swap(int[] arr, int i, int j) {
		int t = arr[i];
		arr[i] = arr[j];
		arr[j] = t;
	}
	private static int medianThree(int[] arr, int left, int mid, int right) {
		int a = arr[left], b = arr[mid], c = arr[right];
	    if ((a < b) ^ (a < c)) {
	        return left;
	    } else if ((b < a) ^ (b < c)) {
	        return mid;
	    } else {
	        return right;
	    }
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值