图文讲解Java中实现quickSort快速排序算法的方法
相对冒泡排序、选择排序等算法而言,快速排序的具体算法原理及实现有一定的难度。为了更好地理解快速排序,我们仍然以举例说明的形式来详细描述快速排序的算法原理。在前面的排序算法中,我们以5名运动员的身高排序问题为例进行讲解,为了更好地体现快速排序的特点,这里我们再额外添加3名运动员。实例中的8名运动员及其身高信息详细如下(F、G、H为新增的运动员): A(181)、B(169)、C(187)、D(172)、E(163)、F(191)、G(189)、H(182)
在前面的排序算法中,这些排序都是由教练主导完成了,现在运动员人数增加了,教练也想趁机去休息一下,于是教练叫来了两名助手,让这两名助手以快速排序法的排序方式来实现对8名运动员的身高从左到右、从低到高的排序。
按照快速排序法的算法原理,两名助手分别站在运动员排列的两侧,如下图所示:
首先,助手1从排列中选出一名运动员(一般选择左侧第1位运动员或最中间的运动员),这里选择运动员A(181)。由于排序是从左到右、从低到高,所以助手1需要一个身高比A(181)更小的运动员(选出来的A(181)作为比较的基准,本轮所有的比较,都是与最初选出来的运动员A(181)比较):
下面我们来继续参考快速排序第一轮的详细示意图。
当两名助手在排序寻找的过程中相遇后,就停止当前轮次的比较,并把最初选出来的运动员A(181)放在两名助手相遇的空位上:
在快速排序中,当两名助手相遇时,本轮排序就结束了。此时,我们发现,以两名运动员相遇的位置A(181)作为分割点,排列左边的都是身高比A(181)小的运动员,排列右侧的都是身高比A(181)大的运动员。这个时候,我们再把A(181)左侧和右侧的两个排序分开来看,如果我们继续使用上述两名助手的排序方法分别对两边的排列进行排序,那么经过多次排列后,最后就能够得到我们所需要的排序结果。
上面就是快速排序的整个排序实现过程。快速排序就是利用上述的排序规则,将一个排列分为两个排列,两个排列分为四个排列,直到无排列可分为止,最后就得到了我们所需要的排序结果。
现在,我们依然编程Java代码来实现使用快速排序对上述8名运动员的身高进行排序:
/** * 对指定的数组中索引从start到end之间的元素进行快速排序 * * @param array 指定的数组 * @param start 需要快速排序的数组索引起点 * @param end 需要快速排序的数组索引终点 */ public static final void quickSort(int[] array, int start, int end) { // i相当于助手1的位置,j相当于助手2的位置 int i = start, j = end; int pivot = array[i]; // 取第1个元素为基准元素 int emptyIndex = i; // 表示空位的位置索引,默认为被取出的基准元素的位置 // 如果需要排序的元素个数不止1个,就进入快速排序(只要i和j不同,就表明至少有2个数组元素需要排序) while (i < j) { // 助手2开始从右向左一个个地查找小于基准元素的元素 while (i < j && pivot <= array[j]) j--; if (i < j) { // 如果助手2在遇到助手1之前就找到了对应的元素,就将该元素给助手1的"空位",j成了空位 array[emptyIndex] = array[emptyIndex = j]; } // 助手1开始从左向右一个个地查找大于基准元素的元素 while (i < j && array[i] <= pivot) i++; if (i < j) { // 如果助手1在遇到助手2之前就找到了对应的元素,就将该元素给助手2的"空位",i成了空位 array[emptyIndex] = array[emptyIndex = i]; } } // 助手1和助手2相遇后会停止循环,将最初取出的基准值给最后的空位 array[emptyIndex] = pivot; // =====本轮快速排序完成===== // 如果分割点i左侧有2个以上的元素,递归调用继续对其进行快速排序 if (i - start > 1) { quickSort(array, 0, i - 1); } // 如果分割点j右侧有2个以上的元素,递归调用继续对其进行快速排序 if (end - j > 1) { quickSort(array, j + 1, end); } } //主方法 public static void main(String[] args) { // =====使用快速排序法对表示8名运动员身高的数组heights进行从低到高的排序===== // A(181)、B(169)、C(187)、D(172)、E(163)、F(191)、G(189)、H(182) int[] heights = { 181, 169, 187, 172, 163, 191, 189, 182 }; // 调用快速排序方法 quickSort(heights, 0, heights.length - 1); // 输出排序后的结果 for (int height : heights) { System.out.println(height); } }
以上Java代码运行结果输出如下:
163 169 172 181 182 187 189 191
注意:由于局部思维差异,上述快速排序的代码实现可能存在多种变体。不过,无论何种形式的变体,快速排序的核心思想并不会发生改变。
另一种实现:单向扫描
快速排序的数组切分还有另一种单向扫描的版本,具体步骤是选择数组中最后一个元素作为切分元素,同样设置两个指针,指针i指向数组中第一个元素前面一个位置,j则指向数组中第一个元素。j从前左右往右扫描,遇到小于等于切分元素时就把i加一,然后交换i和j指向的元素。最后把i+1位置的元素和切分元素交换即可完成一次数组划分。代码实现如下:
int partition(int[] a, int lo, int hi) { int i = lo - 1, j = lo; int v = a[hi]; while (j < hi) { if (a[j] <= v) { swap(a, ++i, j); } j++; } swap(a, i + 1, hi); return i + 1; }