目录
1、快速排序
快速排序是在冒泡排序的基础上改进而来的,冒泡排序每次只能交换相邻的两个元素,而快速排序是跳跃式的交换,交换的距离很大,因此总的比较和交换次数少了很多,速度也快了不少。
1.1、算法描述
采用“分治”的思想,对于一组数据,选择一个划分值(Num),通常选择第一个或最后一个元素,通过第一轮扫描,比Num小的元素都在划分值(Num)左边,比划分值(Num)大的元素都在划分值(Num)右边(这样的一次排序就是快速排序的子过程),再用同样的方法递归被划分值划分的这两个部分,直到序列中所有数据均有序为止。
1.2、快速排序的子过程
把数组的最后一个数作为划分值(Num),在数组两边各准备一个区域,一个小于划分值(Num)区域放在数组左边,一个大于划分值(Num)区域放在数组右边,通过对数组里的数字与划分值(Num)进行比较,如果小于划分值(Num)就将其放在小于区域,如果等于划分值(Num)则不动,比较下一个数字,如果大于划分值(Num)则将其放在大于区域。
{小于划分值(Num)的区域} [要进行操作的数组] {大于划分值(Num)的区域}
详细过程:
红色是小于划分值(Num)的区域,蓝色圈圈是大于划分值(Num)的区域,然后从L=0,arr[L]开始遍历
遍历的规则是:
1、当arr[ L ]小于划分值(Num)时,arr[ L ]与小于区域的下一个元素交换位置,然后小于区域向右移动一位,L++。
2、当arr[ L ]等于划分值(Num)时,L++。
3、当arr[ L ]大于划分值(Num)时,arr[ L ]与大于区域的上一个元素交换位置,然后大于区域向左移动一位,此时L不自增。最后再把大于区域的第一个数与大于区域的最后一个数交换一下位置,这里在图解说明中会说为什么。
2、图解说明
2.1、快速排序详细图解
(此详细图解使用最后一个数作为划分值)
此数组为[3, 5, 7, 1, 4, 3, 9, 8, 5] , i指向小于区域的最后一个数,j指向大于区域的第一个数,L指向要进行操作数组的第一个数,R指向划分值
第一次快速排序子过程partition()时,5作为划分值,详细过程:
这时候 3 (arr[L]) < 划分值 5 (arr[R])
执行规则1】3与小于区域的下一位元素交换位置,于是第一个3和第一个3交换位置。然后小于区域向右移动一位,L++
这时候 5 (arr[L]) = 划分值 5 (arr[R])
执行规则2】直接L++
这时候 7 (arr[L]) > 划分值 5 (arr[R])
执行规则3】7与大于区域的上一位元素交换位置,于是第一个7和第一个8交换位置。然后大于区域向左移动一位,L不变
这时候 8 (arr[L]) > 划分值 5 (arr[R])
执行规则3】8与大于区域的上一位元素交换位置,于是第一个8和第一个9交换位置。然后大于区域向左移动一位,L不变
这时候 9 (arr[L]) > 划分值 5 (arr[R])
执行规则3】9与大于区域的上一位元素交换位置,于是第一个9和第二个3交换位置。然后大于区域向左移动一位,L不变
这时候 3 (arr[L]) < 划分值 5 (arr[R])
执行规则1】3与小于区域的下一位元素交换位置,于是第一个3和第一个5交换位置。然后小于区域向右移动一位,L++
这时候 1 (arr[L]) < 划分值 5 (arr[R])
执行规则1】1与小于区域的下一位元素交换位置,于是第一个1和第一个5交换位置。然后小于区域向右移动一位,L++
这时候 4 (arr[L]) < 划分值 5 (arr[R])
执行规则1】4与小于区域的下一位元素交换位置,于是第一个4和第一个5交换位置。然后小于区域向右移动一位,L++
这时候因为L等于j了,L不再小于j了,所以结束了第一次子过程,但是看大于区域里下标为R的数是5,所以这个5应该与大于区域的第一个数交换一下位置
第一次子过程结束后的数组如下所示:
当第一次子过程结束后数组分成了三个区域,下标 [0] -- [i] 为小于划分值的区域,下标 [i+1] -- [j] 为等于划分值的区域,下标 [j+1] -- [R] 为大于划分值的区域,再用同样的方法递归被划分值划分的这两个部分。
第二次子过程
第三次子过程
后面的操作与前面一样的规则,由于篇幅问题就不继续展开了。
2.2、快速排序完整过程动图
(该动图使用第一个数作为划分值)
3、代码实现
3.1、快速排序
//快速排序
public static void quickSort(int[] arr, int L, int R){
if(L < R){
int[] p = partition(arr,L,R);
quickSort(arr,L,p[0]-1);
quickSort(arr,p[1]+1,R);
}
}
//快速排序的子过程partition()
public static int[] partition(int[] arr, int L, int R){
int less = L-1;
int more = R;
while (L < more){
if (arr[L] < arr[R]){
swap(arr,++less,L++);
} else if(arr[L] > arr[R]){
swap(arr,--more,L);
} else {
L++;
}
}
swap(arr,more,R);
return new int[] {less+1,more};
}
//swap方法用于交换arr[i]和arr[j]里面的数
public static void swap(int[] arr, int i, int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
3.2、快速排序(随机划分值)
因为快速排序的划分值一般是拿数组的第一个或最后一个数来作为划分值,所以当数组本身就比较有序时,快速排序就会是最坏情况了,快速排序在最坏情况下的时间复杂度和冒泡排序一样,是 O(n的2次方),实际上每次比较都需要交换
比如数组 [1, 2, 3, 4, 5, 6, 7, 8, 9] 用快速排序时的时间复杂度就是O(n的2次方)的
虽然这种情况并不常见,但是我们还是需要考虑到
所以我们可以加一条随机划分值的代码,从而使其的时间复杂度更偏向于O(nlongn)
随机划分值的代码:
swap(arr,L + (int)(Math.random()*(R-L+1)),R);
//快速排序
public static void quickSort(int[] arr, int L, int R){
if(L < R){
//从这一趟要排序的数字里获取随机的划分数
swap(arr,L + (int)(Math.random()*(R-L+1)),R);
int[] p = partition(arr,L,R);
quickSort(arr,L,p[0]-1);
quickSort(arr,p[1]+1,R);
}
}
//快速排序的子过程partition()
public static int[] partition(int[] arr, int L, int R){
int less = L-1;
int more = R;
while (L < more){
if (arr[L] < arr[R]){
swap(arr,++less,L++);
} else if(arr[L] > arr[R]){
swap(arr,--more,L);
} else {
L++;
}
}
swap(arr,more,R);
return new int[] {less+1,more};
}
//swap方法用于交换arr[i]和arr[j]里面的数
public static void swap(int[] arr, int i, int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
3.3、验证
写一个TextFast类,在里面创建两个数组,然后验证快速排序
public class TextFast {
public static void main(String[] args){
int[] arr1 = {52,6,5,12,8,6,7,32,55};
int[] arr2 = {6,12,5,47,2,5,96,45,3,8,2};
pint(arr1);
System.out.println();
pint(arr2);
}
public static void pint(int[] arr){
for (int i=0; i<arr.length; i++){
System.out.print(arr[i]+" ");
}
FastRost(arr);
System.out.println();
System.out.println("=============");
for (int i=0; i<arr.length; i++){
System.out.print(arr[i]+" ");
}
System.out.println();
}
public static void FastRost(int[] arr){
if(arr == null || arr.length < 2) return;
quickSort(arr,0,arr.length-1);
}
}
验证结果