Sort_2_quickSort_快速排序

  1. 快速排序
  2. 912.排序数组
  3. 215.数组中的第K个最大的元素
  4. Reference

快速排序

1.快速排序, 是一种基于 divide-and-conquer 思想下的排序算法, 核心实现是借助于 partition 算法
2.我们已知 partition 算法可以支持对数组中的任意一个元素, 放到属于它在排序之后正确的位置之上; 延续 partition 的这种功能, 快速排序递归地对安排好当前位置的元素的两边递归地进行 partition, 直到所有的元素都被 partition 完毕, 数组最终得到排序之后的结果, 快速排序的框架如下:

void quickSort(vector<int>& nums, int low, int high) {
  if (low >= high) {
    return;
  }
  int pos = partition(nums, low, high);
  quickSort(nums, low, pos-1);
  quickSort(nums, pos+1, high);
  return;
}

3.对于随机分布的数据而言, 快速排序略快于 mergeSort 和 heapSort; 平均的时间复杂度为 O(nlogn), 最坏情况下 O(n^2)
4.快速排序的优化
(i). 切入到插入排序: 在排序数组长度比较小的情况下, 用插入排序更快, 通常长度为 5-15 这样一个排序长度下, 插入排序更快
(ii). 三向切分排序: 在实际应用中, 我们待排序的数组通常都大概率有很多重复元素, 例如按照男女性别排序; 如果处理这种存在大量重复元素的情况, 一个元素重复的部分我们不要去排序, 一个典型的思路是采用三分的思路, 将数组切分成 < target, == target, > target 的三部分, 然后只对 < target 和 > target 这两部分进行递归, 思路如下:

void quickSort(vector<int>& nums, int l, int r) {
  if (l >= r) {
    return;
  }
  int pivotPos = partition(nums, l, r);
  int lPivot = pivotPos - 1;
  int rPivot = pivotPos + 1;
  while (lPivot >= l && nums[lPivot] == nums[pivotPos]) {
    --lPivot;
  }
  while (rPivot <= r && nums[rPivot] == nums[pivotPos]) {
    ++rPivot;
  }
  quickSort(nums, l, lPivot);
  quickSort(nums, rPivot, r); 
}

5.我们写一个双指针 partition 且交换版本

912.排序数组

class Solution {
 public:
  int partition(vector<int>& arr, int left, int right) {
    int pivotIdx = left;
    int l = left + 1;
    int r = right;
    while (l <= r) {
      while (l <= r && arr[l] < arr[pivotIdx]) {
        ++l;
      }
      while (l <= r && arr[r] > arr[pivotIdx]) {
        --r;
      }
      if (l <= r) {
        swap(arr[l], arr[r]);
        ++l;
        --r;
      }
    }
    swap(arr[pivotIdx], arr[r]);
    return r;
  }
  void quickSort(vector<int>& nums, int l, int r){
    if (l < r) {
      swap(nums[l + rand() % (r - l + 1)], nums[l]);
      int mid = partition(nums, l, r);
      quickSort(nums, l, mid-1);
      quickSort(nums, mid+1, r);
    }
  }
  vector<int> sortArray(vector<int>& nums) {
    quickSort(nums, 0, nums.size()-1);
    return nums;
  }
};

再补充一个双指针 partition 覆盖实现版本

class Solution {
 public:
  int partition(vector<int>& nums, int left, int right) {
    int randIdx = left + rand() % (right - left + 1);
    swap(nums[randIdx], nums[right]);
    int pivot = nums[right];
    int l = left;
    int r = right;
    while (l < r) {
      while (l < r && nums[l] <= pivot) {
        ++l;
      }
      nums[r] = nums[l];
      while (l < r && nums[r] >= pivot) {
        --r;
      }
      nums[l] = nums[r];

    }
    nums[l] = pivot;
    return l;
  }
  void quickSort(vector<int>& nums, int l, int r) {
    if (l >= r) {
      return;
    }
    int pivotPos = partition(nums, l, r);
    int lPivot = pivotPos - 1;
    int rPivot = pivotPos + 1;
    while (lPivot >= l && nums[lPivot] == nums[pivotPos]) {
      --lPivot;
    }
    while (rPivot <= r && nums[rPivot] == nums[pivotPos]) {
      ++rPivot;
    }
    quickSort(nums, l, lPivot);
    quickSort(nums, rPivot, r); 
  }
  vector<int> sortArray(vector<int>& nums) {
    quickSort(nums, 0, nums.size()-1);
    return nums;
  }
};

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

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1: 输入: [3,2,1,5,6,4], k = 2 输出: 5

示例 2: 输入: [3,2,3,1,2,4,5,5,6], k = 4 输出: 4

分析:
1.原始数组长度为 len, 找第 k 大的元素, 也就是找排序后 index = len-k 位置的元素
2.我们对数组采用 partition 算法, 可以实现检索 len - k 位置元素的二分思想, 因为 partition 算法每次可以使得左边一半小于右边一半元素, 然后实现只在一半元素里面搜索
3.对我们随机找的第 1 个枢轴, 例如最左侧端点位置, 还原位置为 pos; 然后判断 pos 和 len-k 的关系
(i). pos == len - k, 那么就是 pos 元素就是我们要的数
(ii). pos < len - k, 那么就去以 pos 作为分界点的右侧区间找, l = pos + 1
(iii). pos > len - k, 那么就去以 pos 作为分界点的左侧区间找, r = pos - 1
4.另外我们可以引入 threeWayPartition 的思想, 额外处理一下相同的元素不进入 partition, 时间复杂度搞到线性
(i). pos < len - k, l 可以持续向右比较, 如果当前元素和左侧相邻元素相同, 那么 ++l
(ii). pos > len - k, r 可以持续向左比较, 如果当前元素和右侧相邻元素相同, 那么 —r

class Solution {
 public:
  int partition(int l, int r, vector<int>& nums) {
    int pivot = nums[l];
    while (l < r) {
      while (l < r && nums[r] >= pivot) {
        --r;
      }
      nums[l] = nums[r];
      while (l < r && nums[l] <= pivot) {
        ++l;
      }
      nums[r] = nums[l];
    }
    nums[l] = pivot;
    return l;
  }
  int findKthLargest(vector<int>& nums, int k) {
    int len = nums.size();
    int l = 0;
    int r = len - 1;
    int pos;
    k = len - k;
    while (l <= r) {
      pos = partition(l, r, nums);
      if (pos == k) {
        return nums[pos];
      } else if (pos < k) {
        l = pos + 1;
        while (l < k && nums[l] == nums[l-1]) {
          ++l;
        }
      } else if (pos > k) {
        r = pos - 1;
        while (r > k && nums[r] == nums[r+1]) {
          --r;
        }
      }
    }
    return 0;
  }
};

Reference

[1]. 算法 第四版.


转载请注明来源, from goldandrabbit.github.io

💰

×

Help us with donation