快速排序算法的两种Java实现及图解
十大经典排序算法中,感觉“快速排序算法”不太容易理解,这里简单做个笔记,利用图示直观的展示了其排序原理。
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
快速排序实现的重点在于数组的拆分,通常我们将数组的第一个元素定义为比较元素,然后将数组中小于比较元素的数放到左边,将大于比较元素的放到右边,
这样我们就将数组拆分成了左右两部分:小于比较元素的数组;大于比较元素的数组。我们再对这两个数组进行同样的拆分,直到拆分到不能再拆分,数组就自然而然地以升序排列了。(如图可简明直观地展现quickSort的思想)。
拆分方法是整个快速排序中的核心,快速排序拥有非常多的拆分方式,这里介绍其中的两种:单指针遍历法与双指针遍历法(在下文中用英文单词split和partition称呼)[参考博客园 算法设计:两种快速排序代码实现]
1. split法
用单个指针。
首先将数组首元素设置为“基准值pivot”(即其他所有值都需要跟其比较大小),然后将第二个开始的元素依次与基准值比较,如果大于基准值pivot则跳过,如果小于pivot,则将其与前面较大的元素进行交换,将数组中所有元素交换完毕后,再将基准值pivot放到中间位置。
图解:
Java代码实现:
/**
* (6)-6.1 快速排序`方法
* 单指针方法-split
* @param arr
* @param low // start
* @param high // end
* @return
*/
public static void quickSort(int arr[], int low, int high)
{
if(low < high)
{
int i = split(arr, low, high); //划分并获取比较元素的位置
quickSort(arr, low, i-1); //对比较元素左边的数组进行排序
quickSort(arr, i+1, high); //对比较元素右边的数字进行排序
}
}
//划分数组
public static int split(int arr[], int low, int high)
{
int i = low; //i指向比较元素的期望位置
int x = arr[low]; //将该组的第一个元素作为比较元素
//从第二个元素开始,若当前元素大于比较元素,将其跳过
for(int j = low+1; j <= high; j++)
//若找到了小于比较元素的元素,将其与前面较大的元素进行交换
if(arr[j] <= x)
{
i++;
if(i != j)
swap(arr, i, j);
}
swap(arr, i, low); //将比较元素交换到正确的位置上
return i; //返回比较元素的位置
}
//交换方法
public static void swap(int arr[], int i, int j)
{
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
2. partition法
双指针实现。
使用头尾两个方向相反的指针进行遍历,先将数组第一个元素设置为“基准值”或者叫“比较元素”,头指针从左至右找到第一个大于比较元素的数,尾指针从右至左找到第一个小于比较元素的数,全部交换完毕后将比较元素放到中间位置。
图解:
partition图解完整流程:
Java代码实现
/**
* (6)-6.2 快速排序
* 双指针方法-partition
* @param arr
* @param low // start
* @param high // end
* @return
*/
public static void quicksort2(int arr[], int low, int high)
{
if(low < high)
{
int i = partition(arr, low, high); //划分数组并获取比较元素的位置
quicksort2(arr, low, i-1); //对比较元素左边进行排序
quicksort2(arr, i+1, high); //对比较元素右边进行排序
}
}
//划分数组
public static int partition(int arr[], int low, int high)
{
int x = arr[low]; //将该数组第一个元素设置为比较元素
int i=low;
int j=high;
while(i < j)
{
while(i<j && arr[j] >= x)
j--; //从右至左找到第一个小于比较元素的数
while(i<j && arr[i] <= x)
i++; //从左至右找到第一个大于比较元素的数
/*需要注意的是,这里的j--与i++的顺序不可以调换!
*如果调换了顺序,i会走过头,以至于将后面较大的元素交换到数组开头*/
//将大数与小数交换
if(i!=j)
swap(arr, i, j);
}
swap(arr, i, low); //将比较元素交换到期望位置
return i; //返回比较元素的位置
}
//交换函数
public static void swap(int arr[], int i, int j)
{
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
测试用例:
int data1[] = new int[] {3,5,38,15,36,26,27,2,4,19,46,48,47,50,44};
int[] data2 = {8,9,1,7,2,3,5,4,6,0};
int data3[] = {5,7,1,6,4,8,3,2};
测试结果:
[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8]
参考:
主要参考这两篇文章,侵权删。
https://www.cnblogs.com/sunriseblogs/p/10009890.html
https://www.jianshu.com/p/c1efd6a8bb95 (十大经典排序算法总结)