Skip to content

快速排序(Quick Sort)

介绍

快速排序(Quick Sort)是一种高效的排序算法,它通过选取一个“基准”元素,将数组分为两部分: 比基准小的元素和比基准大的元素,然后递归地对这两部分进行排序,从而实现对整个数组的排序。 快速排序由英国计算机科学家托尼·霍尔(Tony Hoare)于1960年提出,因此也被称为霍尔排序。

所谓最差时间复杂度

最优时间复杂度:O(n log n);
最差时间复杂度:O(n²);
平均时间复杂度:O(n log n);

看上去最差有点差(和冒泡一样),实际上就没见过最差情况;

动画模拟

工作原理

快速排序的工作原理基于分治策略,具体步骤如下:

  • 选取基准: 从待排序序列中选取一个元素作为基准值(pivot)。基准值的选择可以是任意的,但常见的选择方法包括选取序列的第一个元素、最后一个元素、中间元素或随机选取一个元素。
  • 分区操作: 重新排列序列,使得所有比基准值小的元素都移动到基准值的左边,所有比基准值大的元素都移动到基准值的右边。在这个分区操作结束时, 该基准值就处于序列的中间位置,这个基准值的位置就已经被确定,然后可以将基准值位置左边的子序列和右边的子序列独立排序。
  • 递归排序: 递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。递归的最底部情形是序列的大小是零或一,也就是已经排序好了。

在快速排序的具体实现中,分区操作通常使用双指针技术来实现。一个指针从数组的开头开始向右移动,另一个指针从数组的末尾开始向左移动。当左指针指向的元素小于等于基准元素, 且右指针指向的元素大于等于基准元素时,交换这两个元素的位置。当左指针移动到右指针的位置时,整个数组就被划分为了两个子数组。

算法特性

  1. 时间复杂度:
  • 平均时间复杂度:O(n log n)。在实际应用中,由于快速排序的随机性,其平均时间复杂度为O(n log n),因此具有很高的效率。
  • 最坏时间复杂度:O(n²)。当每次选取的基准元素都是当前数组中的最小或最大元素时,会导致每次划分得到的子数组大小相差很大,从而使得递归树的深度很大,排序效率降低。然而,在实际应用中,这种情况很少出现。
  • 最优时间复杂度:O(n log n)。当每次划分都能将数组均匀地分成两部分时,递归树的深度最小,排序效率最高。
  1. 空间复杂度:* 快速排序是一种原地排序算法,只需要常数级别的额外空间,因此在处理大规模数据时具有很大的优势。 然而,递归调用栈会占用一定的空间,其空间复杂度为O(log n)(在最优情况下)到O(n)(在最坏情况下,但这种情况很少见)。
  2. 稳定性: 快速排序是一种不稳定的排序算法。这意味着在排序过程中,相等的元素可能会改变它们的相对顺序。这通常不会影响到排序结果的正确性, 但在某些特定的应用场景下,如需要保持元素原始顺序的排序,就需要选择其他稳定的排序算法。
  3. 分治策略: 快速排序采用分治策略来解决问题。它将一个大问题分解成若干个小问题来处理,然后将处理结果合并起来得到最终答案。这种策略使得快速排序在处理大规模数据时具有很高的效率。
  4. 原地排序: 快速排序只需要常量级的额外空间来存放辅助信息(如递归调用栈和基准值),因此被称为原地排序算法。这使得它在处理大规模数据时具有很大的优势。

源码

vue
<script setup lang="ts">
import SortBarChart from "../comp/sort-bar-chart.vue";
import { ref } from "vue";
import { genRandArray, sortExchangeReact } from "../sort.util";
import { sleep } from "../../../utils/cmm.util";

const data = ref<number[]>(genRandArray(1, 100, 100));

const refreshData = () => {
  data.value = genRandArray(1, 100, 100);
};

const stepInterval = ref<number>(20);
const actives = ref<number[]>([]);

/**
 * 快速排序入口
 * [start, end] 分治处理范围
 * @param start 开始位置
 * @param end 结束位置
 */
const sort = async (start: number = 0, end: number = data.value.length - 1) => {
  let left = start;
  let right = end;
  if (start >= end) return;
  let p = left;
  while (left < right) {
    actives.value = [left, right];
    await sleep(stepInterval.value);
    if (data.value[left] > data.value[right]) {
      sortExchangeReact(data.value, left, right);
      p = p == left ? right : left;
    }
    if (p == left) right--;
    else left++;
  }
  await sort(start, p - 1);
  await sort(p + 1, end);
};
</script>

<template>
  <div class="">
    <a-space direction="horizontal">
      <a-input-number v-model="stepInterval" :step="50" :min="5" style="width: 200px">
        <template #suffix>ms</template>
        <template #prefix> 步进间隔 </template>
      </a-input-number>
      <a-button @click="sort()">开始排序</a-button>
      <a-button @click="refreshData">刷新数据</a-button>
    </a-space>
    <div class="mt-sm" style="height: 300px">
      <sort-bar-chart :data="data" :actives="actives" />
    </div>
  </div>
</template>

<style scoped lang="less"></style>

MIT Licensed | fangjc1986@qq.com