算法与数据结构(快速排序)

快速排序 Quick Sort

快速排序是对冒泡排序的一种改进
通过一趟排序将序列分为两个部分,其中一个部分的所有数据比另一个部分的所有数据都小,然后再分别对两个部分进行类似操作(递归),直到整个序列有序

基本思路:

随机找出一个数,或是取第一个或最后一个数为基准。然后将所有的数和基准比较,比基准小,就放左边,比基准大就放右边,这样就把一个序列分成了两个子序列。然后按照同样的方法对子序列排序
在这里插入图片描述
在这里插入图片描述
最终,基准数左边的数都比它小,右边的数都比他大

实现:

首先写出在每一层递归内需要完成的基本操作:

		// 左下标
		int l = left;
		// 右下标
		int r = right;
		// 基准数
		int pivot = arr[left]; // 保存基准数的数值大小
		int temp; // 临时变量,用来辅助完成交换操作
		
		while(l < r) {
			// 这个 while 循环结束时找出比基准数小或相等的 r
			while(arr[r]>pivot && l<r) {
				r--;
			}
			// 这个 while 循环找出比基准数大的 l
			while(arr[l] <=pivot && l<r) {
				l++;
			}
      //交换 l 和 r
			if (l < r) {
				temp  = arr[r];
				arr[r] = arr[l];
				arr[l] = temp;
			}
		}
		// 如果最终基准数和i,j不重合,将基准数和i,j所指的数交换
		if (left != l) {
			arr[left] = arr[l];
			arr[l] = pivot;
		}

然后考虑递归的终止情况,我们会序列不断细分,当序列只有最后一个数字时,递归将终止。此时,我们输入的 l e f t left left r i g h t right right 应该满足条件: l e f t ≥ r i g h t left \geq right leftright

因此,最终的代码为:

public static void quickSort(int[] arr, int left, int right) {
		// 左下标
		int l = left;
		// 右下标
		int r = right;
		// 终止条件
		if (left >= right) {
			return;
		}
		// 中轴值
		int pivot = arr[left];
		int temp;
		
		while(l < r) {
			// 这个 while 循环结束时找出比基准数小或相等的 r
			while(arr[r]>pivot && l<r) {
				r--;
			}
			// 这个 while 循环找出比基准数大的 l
			while(arr[l] <=pivot && l<r) {
				l++;
			}
			if (l < r) {
				temp  = arr[r];
				arr[r] = arr[l];
				arr[l] = temp;
			}
		}
		// 将基准数和i,j所指的数交换
		if (left != l) {
			arr[left] = arr[l];
			arr[l] = pivot;
		}
		quickSort(arr, left, l-1);
		quickSort(arr, l+1, right);
	}

时间复杂度:

  • 平均时间复杂度:

快速排序涉及递归调用,因此该算法的复杂度和递归的时间复杂度有关

递归的时间复杂度公式为: T ( n ) = a T ( n / b ) + f ( n ) T(n) = aT(n/b) + f(n) T(n)=aT(n/b)+f(n) ,表示一个规模为 n 的问题被分成规模为 n/b 的 a 个子问题(在这里,a = 2, b = 2), f ( n ) f(n) f(n) 表示平分这个序列所花的时间,而平分序列需要从头到尾遍历,因此 f ( n ) = O ( n ) f(n) = O(n) f(n)=O(n)

下面开始推导:

第一次递归

T ( n ) = 2 T ( n / 2 ) + n T(n) = 2T(n/2) + n T(n)=2T(n/2)+n

第二次递归,令 n = n/2

T ( n ) = 2 ( 2 T ( n / 4 ) + n ) + n = 2 2 T ( n / 4 ) + 3 n T(n) = 2(2T(n/4) + n) + n\\ = 2^2T(n/4)+3n T(n)=2(2T(n/4)+n)+n=22T(n/4)+3n

第 m 次递归,令 n = n/(2^{(m-1)})

T ( n ) = 2 m T ( n / 2 m ) + m n T(n) = 2^{m}T(n/2^m) + mn T(n)=2mT(n/2m)+mn

而到最后得到 T(1) 时,说明公式已经递归到了最后一步

T ( n / 2 m − 1 ) = T ( 1 ) n = 2 m → m = l o g ( n ) T ( n ) = 2 l o g ( n ) T ( 1 ) + n l o g ( n ) = n + n l o g ( n ) = O ( n l o g n ) T(n/2^{m-1}) = T(1) \\ n = 2^m \rightarrow m = log(n)\\ T(n) = 2^{log(n)}T(1) + nlog(n) = n + nlog(n) = O(nlogn) T(n/2m1)=T(1)n=2mm=log(n)T(n)=2log(n)T(1)+nlog(n)=n+nlog(n)=O(nlogn)

  • 最坏时间复杂度:

当基准数就是最大或最小数时,每一次都需要从同一边遍历数组,这时就相当于一个冒泡排序,所以最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2)

空间复杂度:

在每一次递归中,空间复杂度都是 O ( 1 ) O(1) O(1)

在最优情况下,递归 logn 次(每一次正好平分),因此为 O ( l o g n ) O(logn) O(logn)

在最坏情况下,递归 n 次(每一次都是 n-1 和 1 这样分),因此为 O ( n ) O(n) O(n)

稳定性:

因为快速排序为打乱数字间的顺序,所以快速排序是不稳定的


相关章节
第一节 简述
第二节 稀疏数组 Sparse Array
第三节 队列 Queue
第四节 单链表 Single Linked List
第五节 双向链表 Double Linked List
第六节 单向环形链表 Circular Linked List
第七节 栈 Stack
第八节 递归 Recursion
第九节 时间复杂度 Time Complexity
第十节 排序算法 Sort Algorithm
第十一节 冒泡排序 Bubble Sort
第十二节 选择排序 Select Sort
第十三节 插入排序 Insertion Sort
第十四节 冒泡排序,选择排序和插入排序的总结
第十五节 希尔排序 Shell’s Sort
第十六节 快速排序 Quick Sort
第十七节 归并排序 Merge Sort

代码实现

/**
 * 分治法解决快排问题:
 * 1. 分解:将数组A[p..r]划分为两个子数组A[p..q-1]和A[q+1..r],
 * 使得A[p..q-1]中的每个元素都小于A[q],而A[q+1..r]中的每个元素都大于A[q]。
 * 2. 解决:递归调用快速排序,对子数组进行快排。
 * 3. 合并:由于快排是原地排序,因此不需要合并步骤。
 */

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

/**
 * @brief (单向移动)分解函数,将数组划分为A[p..q-1]和A[q+1..r],
 * 使得A[p..q-1]中的每个元素都小于A[q],而A[q+1..r]中的每个元素都大于A[q]。
 * 
 * @param nums 
 * @param p 
 * @param r 
 */
int partition(vector<int>& nums, int p, int r) {
    int value = nums[r]; // 将最后一个元素作为主元
    int i = p-1; // 设置i的初始值,i会始终指向有序序列中最后一个比主元小的数
    for (int j=p;j<r;j++) { // 注意此处j!=r,因为r是主元
        if (nums[j] <= value) {
            swap(nums[++i], nums[j]); 
        }
    }
    swap(nums[++i], nums[r]); // 最后将主元放到正确的位置,即nums[i+1]
    return i;
}

void quickSort(vector<int>& nums, int p, int r) {
    if (p < r) {
        int q = partition(nums, p, r);
        quickSort(nums, p, q-1);
        quickSort(nums, q+1, r);
    }
}

// test
int main(int argc, char const *argv[])
{
    vector<int> nums = {1,2,6,7,8,9,3,4,5};
    quickSort(nums, 0, nums.size()-1);
    for (auto num : nums) {
        cout << num << endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值