(1)算法简介

       快速排序是一种高效的排序算法,由C.A.R. Hoare在1960年提出。它采用分治法(Divide and Conquer),通过递归地将未排序的部分分割为较小的子数组进行排序,再将其合并。快速排序的平均时间复杂度为 O(nlog⁡n),在大多数情况下比其他 O(nlog⁡n) 的算法,如归并排序,具有更好的性能。


       当然,我相信读者读完快速排序的简介之后,对于快速排序算法的认识还是没什么认识的,接下来让我们直接带着你边学如何实现排序算法边理解该算法的内核。


       (2)算法的原理与步骤

       快速排序的核心思想是选择一个“基准”(pivot),并通过一系列的比较和交换操作将数组分成两部分:一部分所有元素小于基准,另一部分所有元素大于基准。然后,对这两部分分别递归地应用快速排序。具体步骤如下:


选择基准: 从数组中选择一个元素作为基准。选择基准的方法有多种,如选择第一个元素、最后一个元素、随机选择或三数取中法。


分区 (Partitioning): 通过遍历数组,将所有小于基准的元素放在基准的左边,所有大于基准的元素放在右边。


递归排序: 对基准左边和右边的子数组递归地进行快速排序。


组合: 由于分区已经确保了基准左边的元素小于基准,右边的元素大于基准,因此当递归完成后,数组已经是有序的。


       ——看完了上述大体如何实现快速排序算法之后,让我们从代码的层面来实现一下快速排序算法。


       (3)Java代码实现

以下为Java中实现快速排序的大致代码:


public void quickSort(int[] array) {

   int end = array.length - 1;

   quickSort(array, 0, end);

}

private void quickSortHo(int[] array, int left, int right) {

   // 如果子数组长度为1或更小,则返回(即递归终止条件)

   if (left >= right) {

       return;

   }

   // 查找基准元素的位置,并将数组分成两部分

   int target = findTarget(array, left, right);

   

   // 对基准元素左边的子数组进行递归排序

   quickSort(array, left, target - 1);

   

   // 对基准元素右边的子数组进行递归排序

   quickSort(array, target + 1, right);

}

       从上述代码中我们可以看到我们在寻找基准元素位置的时候,使用了findTarget()方法,那么这个方法该如何实现呢?


       实现该方法的方式大致有三种,分别是:Hoare法、挖坑法和前后指针法。接下来我们一一进行讲解。


       【1】Hoare法

public int findTargetH(int[] array, int left, int right) {

   // 获取中间索引并将中间元素交换到数组的最左端

   int mid = middleIndex(array, left, right);

   swap(array, left, mid);

   

   // 将最左端的元素作为基准元素

   int temp = array[left];

   int keyi = left; // 保存基准元素的初始位置

   

   // 开始从数组的两端向中间扫描

   while (left < right) {

       // 从右向左扫描,找到第一个小于基准的元素

       while (left < right && array[right] >= temp) {

           right--;

       }

       // 从左向右扫描,找到第一个大于基准的元素

       while (left < right && array[left] <= temp) {

           left++;

       }

       // 交换左侧找到的大于基准的元素和右侧找到的小于基准的元素

       swap(array, left, right);

   }

   

   // 将基准元素放置到最终的位置,即左、右指针相遇的位置

   swap(array, keyi, left);

   

   // 返回基准元素的位置索引

   return left;

}

解释说明:


middleIndex(array, left, right):假设这是一个用于计算数组中间索引的方法,以获取中间元素的位置。


swap(array, left, mid):将中间元素和最左端元素交换,使得基准元素(最左端元素)在处理前放到适当的位置。


temp:基准元素,用于比较其他元素。


keyi:保存基准元素的初始索引位置,以便最终将基准元素放到正确的位置。


while (left < right):循环扫描数组,直到左右指针相遇。


swap(array, left, right):当找到左侧大于基准的元素和右侧小于基准的元素时,交换它们的位置。


最后一次swap:将基准元素放回到排序后应在的位置上,并返回该位置的索引。


       ——这就是Hoare实现查找基准元素位置。


       【2】挖坑法

public int findTargetW(int[] array, int left, int right) {

   // 将最左端的元素作为基准元素

   int temp = array[left];

   

   // 开始从数组的两端向中间扫描

   while (left < right) {

       // 从右向左扫描,找到第一个小于基准的元素

       while (left < right && array[right] >= temp) {

           right--;

       }

       // 将小于基准的元素移到左侧

       array[left] = array[right];

       

       // 从左向右扫描,找到第一个大于基准的元素

       while (left < right && array[left] <= temp) {

           left++;

       }

       // 将大于基准的元素移到右侧

       array[right] = array[left];

   }

   

   // 将基准元素放置到最终的位置,即左、右指针相遇的位置

   array[left] = temp;

   

   // 返回基准元素的位置索引

   return left;

}

解释说明:


temp:基准元素,取自数组的最左端,用于比较和调整其他元素的位置。


while (left < right):主循环,通过左右指针的移动将数组分为小于基准和大于基准的两部分,直到左右指针相遇。


array[left] = array[right]:将右侧扫描中找到的小于基准的元素移动到左侧。


array[right] = array[left]:将左侧扫描中找到的大于基准的元素移动到右侧。


最后一步:将基准元素放置在最终的正确位置,并返回该位置的索引,以供进一步的递归使用。