【5】快速排序C++完整实现;LeetCode 215. 数组中的第k个最大元素;快速排序partition部分的几种C++实现代码;

0 文章目录

  1. 寻找第k大元素的实现思路;
  2. 快速排序算法分析;
  3. partition部分的两种C++实现代码;
  4. 可运行的完整C++代码(不是在leetcode里的,那里也已经有各种各样的参考答案了),可自定义输入输出进行测试。

1 寻找数组中第k个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

输入: (分别是数组长度、k值。这里与力扣题目要求略有不同,请自己根据需要酌情修改代码)
6 2
[3,2,1,5,6,4]

输出:
5

实现思路分析:
很容易想到的办法是,给未排序的数组排序,如果是升序排列,假设数组长度为n,那么所求第k个最大元素的值即下标为(n-k)对应的值
选择一个高效的排序算法,写好对应的输入输出函数即可。一般可采用堆排序、快速排序等时间复杂度、空间复杂度都比较低的算法,本文采用快速排序。

优化:
容易想到:冒泡排序中,第k大的元素在第k趟就已经排好了,可以直接提取,后面的(n-k)趟其实是不需要的操作,那么快速排序是否也存在这种“省力”的机会呢?

快速排序的思想是:选取一个pivot枢纽元素,比它大的放在右边,比它小的放在左边, 针对左边的一部分,重复上述操作递归。如果这个pivot经过第一轮递归后的位置是正好是(n-k),那么可以直接返回pivot的值即答案。如果(n-k)在pivot下标值的右边,那么只需要递归执行右边部分寻找即可,这样算法的时间复杂度可从 n log n 进一步降低到 n

2 快速排序算法分析

上面思路说起来简单,然而实现起来确实另一回事了。(哭泣)如果很久不看然后突然考到真的写不出来。难就难在,每一个变量的作用、变量在循环外还是循环内,如何实现递归、正确终止递归,以及最难的,在不开辟新数组空间的情况下,如何实现(与pivot比较后)大小数的交换。即 partition 部分算法的实现。

首先说递归部分,有两种常见的实现方式。一种是单独写partition部分,将其返回值作为递归数组的边界;另一种是只用一个函数搞定,直接在内部传递pivot下标值递归。
大致实现思路分别如下:

第一种:

int partition(int arr[], int left, int right)  //找基准数 划分
{
    
    
   ......
   //为了方便看清递归架构,此处具体实现省略,后面3再具体讲。
   ......
}

void quick_sort(int arr[], int left, int right)
{
    
    
    if (left > right)
        return;
    int j = partition(arr, left, right);
    quick_sort(arr, left, j - 1);
    quick_sort(arr, j + 1, right);
}

第二种:

void QuickSort(int array[], int start, int last)
{
    
    
    int i = start;
    int j = last;
    int pivot = array[i];
    if (i < j)
    {
    
    
        ......
        //此处依然省略partition部分的代码
        ......
        //把基准数放到i位置
        array[i] = pivot;
        //递归方法
        QuickSort(array, start, i - 1);
        QuickSort(array, i + 1, last);
    }
}

3 partition部分的两种C++实现代码

利用双指针的思想,从左右两头开始向中间走,边走边和pivot的值比较,左边应该比pivot小,如果遇到比pivot大的就停下来准备交换,右边同理,遇到比pivot小的就停止移动指针。对于具体如何实现交换,主要有两种实现思路:

第一种是:

  1. 先走right,右边找到小于pivot的值时,right指针停下;
    (一定要先走right,因为它遇到比pivot小的值停下,最后 left 和 right 遇到的时候,可以拿二者指向的值与pivot交换,先走right可以保证它一定小于pivot。)
  2. 再走left,左边找到大于pivot的值时,left指针停下;
  3. 此时如果 left < right,交换二者;
  4. 循环上述三步,直到 left 和right 相遇;
  5. 交换当前 left(right)指针指向的位置与初始first位置的数值。

代码如下:

int partition(int *nums, int left, int right)
{
    
    
    int pivot = nums[left];
    int first = left;
        while (left < right) {
    
    
            while (left < right & nums[right] >= pivot) {
    
    
                right--;
            }
            while (left < right & nums[left] <= pivot) {
    
    
                left++;
            }
            if(left < right){
    
    
                int tem = nums[right];
                nums[right] = nums[left];
                nums[left] = tem;
            };
        }
        nums[first] = nums[left];
        nums[left] = pivot;
        return left;
}

第二种是:

  1. 选取 left 指针指向的值为pivot(首个元素);
  2. 先走 right,遇到比pivot小的值,停下指针,将这个值赋给 left 指针指向的元素;(此时,left 的值已经赋值给了pivot,所以直接覆盖没问题)
  3. 再走 left,遇到比 pivot 大的值,就停下指针。将 left 中的值赋给第2步中 right 指针指向的元素;(第2步中,刚给 left 赋的值是一定小于pivot的)
  4. 循环执行2、3步,直至 left 和 right 指针相遇;
  5. 至此,2、3两步就完成了交换一大一小两个值,然后最开始 0 位置的值存在 pivot中,将 pivot 放到排序之后的枢纽位置,即 left 和 right 指针指向的位置。

代码如下:

int partition(vector<int> &nums, int left, int right) {
    
    
        int pivot = nums[left];
        while (left < right) {
    
    
            while (left < right & nums[right] >= pivot) {
    
    
                right--;
            }
            nums[left] = nums[right];
            while (left < right & nums[left] < pivot) {
    
    
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = pivot;
        return left;
}
4 可运行的完整C++代码

最后附上一份可以直接在编译器 / IDE中执行的C++代码:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
int partition(int *nums, int left, int right)
{
    
    
    int pivot = nums[left];
    int first = left;
        while (left < right) {
    
    
            while (left < right & nums[right] >= pivot) {
    
    
                right--;
            }
            while (left < right & nums[left] <= pivot) {
    
    
                left++;
            }
            if(left < right){
    
    
                int tem = nums[right];
                nums[right] = nums[left];
                nums[left] = tem;
            };
        }
        nums[first] = nums[left];
        nums[left] = pivot;
        return left;
}
int theKthNumber(int *nums, int n, int k){
    
    
    int left = 0;
    int right = n - 1;
    int target = n - k;
    while (true) {
    
    
        int p = partition(nums, left, right);
        if (p == target) {
    
    
            return nums[p];
        } else if (target < p) {
    
    
            right = p - 1;
        } else {
    
    
            left = p + 1;
        }
    }
}
int main(){
    
    
    int len,k;
    scanf("%d%d", &len, &k);
    int *N = (int *)malloc(len*(sizeof(int)));
    for (int i = 0; i <len ; ++i) {
    
    
        scanf("%d", &N[i]);
    }
    printf("%d\n",theKthNumber(N, len, k));
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_36161012/article/details/108799725