快速排序和快速选择算法

快速排序

快速排序算法的实现思路是:

  • 从待排序序列中任选一个元素(假设为 pivot)作为中间元素,将所有比 pivot 小的元素移动到它的左边,所有比 pivot 大的元素移动到它的右边;(这一步被称为「划分 partition」)
  • pivot 左右两边的子序列看作是两个待排序序列,各自重复执行第一步。直至所有的子序列都不可再分(仅包含 1 个元素或者不包含任何元素),整个序列就变成了一个有序序列。
void QuickSort(int* a, int left, int right)
{
    if (left >= right)//如果区间只剩一个数或没有数就不进行操作
        return;
    int key = Partition(a, left, right);//调用单趟排序函数,取key的位置
    QuickSort(a, left, key - 1);//递归调用,对左区间进行排序
    QuickSort(a, key + 1, right);//递归调用,对右区间进行排序
}

「划分 partition」的方法大概有三种,参考:https://blog.csdn.net/LiangXiay/article/details/121421920

记住Hoare方法就好:

right从区间的最右边向左走,找到比key小的元素就停下。left从最左边向右走,找到比key大的元素就停下。然后交换right和left所指向的元素。

重复上面的过程,直到right、left相遇,交换key和right-left相遇位置的元素。

可以看出,经过right、left的不断交换,比key小的值换到了左边,比key大的值换到了右边。最终将key换至中间就完成了单趟排序。

注意:若key取的是最左边的元素,则必须先让right先走。

int Partition(int* a, int left, int right)
{
    int key = left;//取最左边的元素做key
    while (left < right)//当左右没有相遇
    {
        while (left < right && a[right] >= a[key])//如果右比key小就退出循环
            right--;
        while (left < right && a[left] <= a[key])//如果左比key大就退出循环
            left++;
        swap(a[left], a[right]);//交换左右
    }
    swap(a[key], a[left]);//交换key和相遇位置的元素
    return left;//返回key的位置
}

最坏情况下,快速排序算法的时间复杂度为O(n2),理想状态对应的时间复杂度为O(nlogn)

实例:

 

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 #define ARRAY_SIZE 6
 7 
 8 int Partition(int *a, int left, int right)
 9 {
10     int key = left;      //取最左边的元素做key
11     while (left < right) //当左右没有相遇
12     {
13         while (left < right && a[right] >= a[key]) //如果右比key小就退出循环
14             right--;
15         while (left < right && a[left] <= a[key]) //如果左比key大就退出循环
16             left++;
17         swap(a[left], a[right]); //交换左右
18     }
19     swap(a[key], a[left]); //交换key和相遇位置的元素
20     return left;           //返回key的位置
21 }
22 
23 void QuickSort(int *a, int left, int right)
24 {
25     if (left >= right) //如果区间只剩一个数或没有数就不进行操作
26         return;
27     int key = Partition(a, left, right); //调用单趟排序函数,取key的位置
28     QuickSort(a, left, key - 1);         //递归调用,对左区间进行排序
29     QuickSort(a, key + 1, right);        //递归调用,对右区间进行排序
30 }
31 
32 main()
33 {
34     int array[ARRAY_SIZE]{2, 3, 10, 1, 3, 8}; // 数组
35 
36     int *p = array;
37     cout << "print original array:" << endl;
38     for (int i = 0; i < ARRAY_SIZE; ++i)
39     {
40         cout << *(p + i) << " ";
41     }
42     cout << endl;
43 
44     QuickSort(array, 0, 5);
45     cout << "print sorted array:" << endl;
46     for (int i = 0; i < ARRAY_SIZE; ++i)
47     {
48         cout << *(p + i) << " ";
49     }
50 }

 

 

 

 

快速选择

寻找数组中第k大的元素,可以用快速选择。

快速选择,其实就是快排中轴值计算的过程。

「划分 partition」 过程是:从子数组 a[l⋯r] 中选择任意一个元素 x 作为主元(pivot),调整子数组的元素使得左边的元素都小于等于它,右边的元素都大于等于它, x 的最终位置就是 q。

所以只要某次划分的 q为倒数第 k 个下标的时候,我们就已经找到了答案。 我们只关心这一点,至于 a[l⋯q−1] 和 a[q+1⋯r] 是否是有序的,我们不关心。 

 leetcode 215. 数组中的第K个最大元素

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int left = 0;
        int right = nums.size() - 1;
        int target = nums.size()- k;
        while (left < right) 
        {
            int mid = quickSelection(nums, left, right);
            if (mid == target) 
            {
                return nums[mid];
            }
            if (mid < target)
            {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return nums[left];
    }

    // 返回Pivot(key)的位置
    int quickSelection(vector<int>& a, int left, int right) {
        int key = left;      //取最左边的元素做key
        while (left < right) //当左右没有相遇
        {
            while (left < right && a[right] >= a[key]) //如果右比key小就退出循环
                right--;
            while (left < right && a[left] <= a[key]) //如果左比key大就退出循环
                left++;
            swap(a[left], a[right]); //交换左右
        }
        swap(a[key], a[left]); //交换key和相遇位置的元素
        return left;           //返回key的位置
    }
};

 

posted @ 2023-03-19 23:06  blogzzt  阅读(253)  评论(0编辑  收藏  举报