当前位置: 首页 > news >正文

【算法笔记】快速排序算法

快速排序

  • 利用分治思想的实现排序的算法
  • 其基本思路是先将数组进行划分,划分成一侧是小于某个数的,一侧是大于某个数的,然后继续在这两侧进行划分,直到数组有序
  • 快速排序的最好时间复杂度为O(N*logN),最差时间负责度为O(N^2),空间复杂度为O(1)
    快速排序算法最重要的是如何分区的问题,我们可以利用荷兰国旗算法问题的思路来完成对数组的分区

1、快速排序的分区算法

1.1、荷兰国旗问题-二分区域

   将一个数组arr[L…R]按照指定位置的数进行划分,小于等于的到左边,大于的到右边,最后返回这个指定值的位置


思路:

  • 我们选定最右侧的一个数作为基准数据,先将arr[L…R-1]上的数与基准数arr[R]进行比较,小于等于的放到左侧,大于的放到右侧,再将大于区域最左边的数和arr[R]进行交换,这个位置下标就是返回值
  • 在划分的过程中,我们先假定一个小于等于的区域,最右侧边界的下标位置为L-1,即不包含任何区域上的数,从数组的L位置开始,到R-1位置进行和基准数字arr[R]比较,
  • 假设比较时当前位置为index,如果arr[index]<=arr[R],则将arr[index]与小于等于区域的下一个位置进行交换,小于等于区域右移一位,当前下标右移一位
  • 如果arr[index]>arr[R],则当前下标右移一位
  • 这样做的目的是小于等于区域包含了最右侧的下标位置,并且小于等于区域的下一位就是大于区域的第一个位置,最后将其和arr[R]位置交换,就能得到最后的结果


    过程:
  • 1、先假定arr[R]位置的数为比较的基准值,假定小于等于区域的最右侧边界为L-1
  • 2、从L开始到R-1位置,将当前值与arr[R]进行比较,
  • 3、如果当前值小于等于基准值,则将当前值与小于等于区域的下一个位置进行交换,小于等于区域右移一位,当前下标右移一位
  • 4、如果当前值大于基准值,则当前下标右移一位
  • 3、最后将小于等于区域的下一个位置的数和arr[R]位置交换,此时和arr[R]交换位置的下标就是二分区域的位置下标
    • 时间复杂度:O(N)
    • 空间复杂度:O(1)
/*** 荷兰国旗问题-二分区域* 将一个数组arr[L...R]按照指定位置的数进行划分,小于等于的到左边,大于的到右边,最后返回这个指定值的位置* 思路:* 我们选定最右侧的一个数作为基准数据,先将arr[L...R-1]上的数与基准数arr[R]进行比较,小于等于的放到左侧,大于的放到右侧,再将大于区域最左边的数和arr[R]进行交换,这个位置下标就是返回值* 在划分的过程中,我们先假定一个小于等于的区域,最右侧边界的下标位置为L-1,即不包含任何区域上的数,从数组的L位置开始,到R-1位置进行和基准数字arr[R]比较,* 假设比较时当前位置为index,如果arr[index]<=arr[R],则将arr[index]与小于等于区域的下一个位置进行交换,小于等于区域右移一位,当前下标右移一位* 如果arr[index]>arr[R],则当前下标右移一位* 这样做的目的是小于等于区域包含了最右侧的下标位置,并且小于等于区域的下一位就是大于区域的第一个位置,最后将其和arr[R]位置交换,就能得到最后的结果* 过程:* 1、先假定arr[R]位置的数为比较的基准值,假定小于等于区域的最右侧边界为L-1* 2、从L开始到R-1位置,将当前值与arr[R]进行比较,* 3、如果当前值小于等于基准值,则将当前值与小于等于区域的下一个位置进行交换,小于等于区域右移一位,当前下标右移一位* 4、如果当前值大于基准值,则当前下标右移一位* 3、最后将小于等于区域的下一个位置的数和arr[R]位置交换,此时和arr[R]交换位置的下标就是二分区域的位置下标* 时间复杂度:O(N)* 空间复杂度:O(1)*/public static int twoPartition(int[] arr, int L, int R) {if (L > R) {return -1;}if (L == R) {return L;}int pivot = arr[R];// 保存一个基准值int lessRight = L - 1;// 小于等于区域的最右侧边界int index = L;// 当前位置while (index < R) {if (arr[index] <= pivot) {// 小于等于,将当前值与小于等于区域的下一个位置进行交换,小于等于区域右移一位,当前下标右移一位swap(arr, index, ++lessRight);}index++;}// 最后将小于等于区域的下一个位置的数和arr[R]位置交换swap(arr, ++lessRight, R);return lessRight;}

1.2、荷兰国旗问题-三分区域

   将一个数组arr[L…R]按照指定位置的数进行划分,小于等于的到左边,等于的放到中间,大于的放到右边,最右返回等于区域的前后两个下标位置
鉴于java语言的特性,我们返回一个长度为2的数组,0位置为等于区域左边的下标,1位置为等于区域右边的下标。


思路:

  • 整体思路和二分区域的类似,先讲arr[R]位置的数作为基准值,将arr[L…R-1]上的数与基准数arr[R]进行比较,小于等于的放到左侧,等于的放中间,大于的放到右侧,最后将大于区域最左边的数和arr[R]进行交换
  • 在实际划分的过程中,我们要假定小于和大于两个区域,一开始小于区域最右侧下标为L-1(代表没有数据),大于区域最左侧下标为R(因为现在操作的是L…R-1,所以R位置代表没有数据)
  • 当前值从L开始,到最大边界左侧的位置(这里非常容易出错),将当前值与arr[R]进行比较,
  • 如果当前值和基准值相等,则当前下标右移一位
  • 如果当前值小于基准值,将当前值与小于区域右侧的下一个位置进行交换,小于区域右移一位,当前下标右移一位(这个过程可以将等于区域的值也右移)
  • 如果当前值大于基准值,将当前值与大于区域的前一个位置进行交换,大于区域左移一位,当前下标不变(这个过程可以将大于区域换过来的数据继续比较)
  • 最后将大于区域最左边位置的数和arr[R]位置交换,此时小于区域最右侧的下一位就是等于区域左边的下标,和arr[R]交换的位置就是等于区域右侧的下标


    过程:
  • 1、先假定arr[R]位置的数为比较的基准值,假定小于区域的最右侧边界为L-1,大于区域的最左侧边界为R
  • 2、从L开始到到最大边界左侧的位置(这里非常容易出错),将当前值与arr[R]进行比较,
  • 3、如果当前值和基准值相等,则当前下标右移一位
  • 4、如果当前值小于基准值,将当前值与小于区域右侧的下一个位置进行交换,小于区域右移一位,当前下标右移一位(这个过程可以将等于区域的值也右移)
  • 5、如果当前值大于基准值,将当前值与大于区域的前一个位置进行交换,大于区域左移一位,当前下标不变(这个过程可以将大于区域换过来的数据继续比较)
  • 6、最后将大于区域最左边位置的数和arr[R]位置交换,此时小于区域最右侧的下一位就是等于区域左边e下标,和arr[R]交换的位置就是等于区域右侧的下标
  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
/*** 荷兰国旗问题-三分区域* 将一个数组arr[L...R]按照指定位置的数进行划分,小于等于的到左边,等于的放到中间,大于的放到右边,最右返回等于区域的前后两个下标位置* 鉴于java语言的特性,我们返回一个长度为2的数组,0位置为等于区域左边的下标,1位置为等于区域右边的下标。* 思路:* 整体思路和二分区域的类似,先讲arr[R]位置的数作为基准值,将arr[L...R-1]上的数与基准数arr[R]进行比较,小于等于的放到左侧,等于的放中间,大于的放到右侧,最后将大于区域最左边的数和arr[R]进行交换* 在实际划分的过程中,我们要假定小于和大于两个区域,一开始小于区域最右侧下标为L-1(代表没有数据),大于区域最左侧下标为R(因为现在操作的是L...R-1,所以R位置代表没有数据)* 当前值从L开始,到最大边界左侧的位置(这里非常容易出错),将当前值与arr[R]进行比较,* 如果当前值和基准值相等,则当前下标右移一位* 如果当前值小于基准值,将当前值与小于区域右侧的下一个位置进行交换,小于区域右移一位,当前下标右移一位(这个过程可以将等于区域的值也右移)* 如果当前值大于基准值,将当前值与大于区域的前一个位置进行交换,大于区域左移一位,当前下标不变(这个过程可以将大于区域换过来的数据继续比较)* 最后将大于区域最左边位置的数和arr[R]位置交换,此时小于区域最右侧的下一位就是等于区域左边的下标,和arr[R]交换的位置就是等于区域右侧的下标* 过程:* 1、先假定arr[R]位置的数为比较的基准值,假定小于区域的最右侧边界为L-1,大于区域的最左侧边界为R* 2、从L开始到到最大边界左侧的位置(这里非常容易出错),将当前值与arr[R]进行比较,* 3、如果当前值和基准值相等,则当前下标右移一位* 4、如果当前值小于基准值,将当前值与小于区域右侧的下一个位置进行交换,小于区域右移一位,当前下标右移一位(这个过程可以将等于区域的值也右移)* 5、如果当前值大于基准值,将当前值与大于区域的前一个位置进行交换,大于区域左移一位,当前下标不变(这个过程可以将大于区域换过来的数据继续比较)* 6、最后将大于区域最左边位置的数和arr[R]位置交换,此时小于区域最右侧的下一位就是等于区域左边e下标,和arr[R]交换的位置就是等于区域右侧的下标* 时间复杂度:O(N)* 空间复杂度:O(1)*/public static int[] threePartition(int[] arr, int L, int R) {if (L > R) {return new int[]{-1, -1};}if (L == R) {return new int[]{L, L};}int pivot = arr[R];//保存一个基准值int lessRight = L - 1;//小于等于区域的最右侧边界,初始值为L-1(不包含数据)int moreLeft = R;//大于区域的最左侧边界,初始值为R(不包含数据)int index = L; // 当前位置while (index < moreLeft) { //当前位置从L出发,到最大边界左侧的位置(不是到R-1,这里非常容易出错)if (arr[index] == pivot) {// 当前位置和基准数据相等,当前位置右移一位index++;} else if (arr[index] < pivot) {//前值小于基准值,将当前值与小于区域右侧的下一个位置进行交换,小于区域右移一位,当前下标右移一位(这个过程可以将等于区域的值也右移)swap(arr, index, ++lessRight);index++;} else {// 当前值大于基准值,将当前值与大于区域的前一个位置进行交换,大于区域左移一位,当前下标不变(这个过程可以将大于区域换过来的数据继续比较)swap(arr, index, --moreLeft);}}//最后将大于区域最左边位置的数和arr[R]位置交换,此时小于区域最右侧的下一位就是等于区域左边e下标,和arr[R]交换的位置就是等于区域右侧的下标swap(arr, moreLeft, R);return new int[]{lessRight + 1, moreLeft};}

2、快速排序的实现

2.1、二分区域实现的快速排序

  • 利用二分区域的思想,将数组进行划分,划分成一侧是小于某个数的,一侧是大于某个数的,然后继续在这两侧进行划分,直到数组有序
  • 最好时间复杂度:O(N*logN)
  • 最差时间复杂度:O(N^2)
  • 空间复杂度:O(1)
    /*** 二分区域实现的快速排序* 利用二分区域的思想,将数组进行划分,划分成一侧是小于某个数的,一侧是大于某个数的,然后继续在这两侧进行划分,直到数组有序* 最好时间复杂度:O(N*logN)* 最差时间复杂度:O(N^2)* 空间复杂度:O(1)*/public static void twoPartitionQuickSort(int[] arr, int L, int R) {if (L >= R || arr == null || arr.length < 2) {return;}// 利用二分区域的方法进行划分区域,获得等于基准值的位置int M = twoPartition(arr, L, R);// 递归排序小于区域twoPartitionQuickSort(arr, L, M - 1);// 递归排序大于区域twoPartitionQuickSort(arr, M + 1, R);}

2.1、三分区域实现的快速排序

  • 利用三分区域的思想,将数组进行划分,划分成一侧是小于某个数的,一侧是大于某个数的,中间是等于某个数的,然后继续在这两侧进行划分,直到数组有序
  • 最好时间复杂度:O(N*logN)
  • 最差时间复杂度:O(N^2)
  • 空间复杂度:O(1)
    /*** 三分区域实现的快速排序* 利用三分区域的思想,将数组进行划分,划分成一侧是小于某个数的,一侧是大于某个数的,中间是等于某个数的,然后继续在这两侧进行划分,直到数组有序* 最好时间复杂度:O(N*logN)* 最差时间复杂度:O(N^2)* 空间复杂度:O(1)*/public static void threePartitionQuickSort(int[] arr, int L, int R) {if (L >= R || arr == null || arr.length < 2) {return;}// 利用三分区域的方法进行划分区域,获得等于基准值的位置int[] equalArea = threePartition(arr, L, R);// 递归排序小于区域threePartitionQuickSort(arr, L, equalArea[0] - 1);// 递归排序大于区域threePartitionQuickSort(arr, equalArea[1] + 1, R);}

2.1、改进后的三分区域实现的快速排序

  • 快速排序如果一开始数据就是有序的,那其就是最差的时间复杂度的情形,时间复杂度为O(N^2)
  • 这个时候,如果打乱其基准数据的选择,学术上通过证明,其时间复杂度就会收敛到O(N*logN)
  • 所以在进行分区的时候,可以先在L…R范围内随机选择一个位置和arr[R]位置交换,然后用交换后的arr[R]进行分区
    /*** 改进后的三分区域实现:* 快速排序如果一开始数据就是有序的,那其就是最差的时间复杂度的情形,时间复杂度为O(N^2)* 这个时候,如果打乱其基准数据的选择,学术上通过证明,其时间复杂度就会收敛到O(N*logN)* 所以在进行分区的时候,可以先在L...R范围内随机选择一个位置和arr[R]位置交换,然后用交换后的arr[R]进行分区*/public static void improvedThreePartition(int[] arr, int L, int R) {if (L > R || arr == null || arr.length < 2) {return;}// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, L + (int) (Math.random() * (R - L + 1)), R);// 利用三分区域的方法进行划分区域,获得等于基准值的位置int[] equalArea = threePartition(arr, L, R);// 递归排序小于区域threePartitionQuickSort(arr, L, equalArea[0] - 1);// 递归排序大于区域threePartitionQuickSort(arr, equalArea[1] + 1, R);}

2.1、利用栈结构来实现非递归的快速排序

思路:

  • 递归的本质就是程序调用栈记录了每次排序划分出来的中间位置M,然后递归程序让程序继续执行M左侧和右侧的数据,
  • 如果我们自定义一个栈,用来保存中间的结果,这样就能不用递归实现快速排序,


    过程:
  • 1、新建一个栈,用来保存每次生成的中间位置记录
  • 2、每次执行了划分区域的函数后,我们将要继续执行的数据的左右侧下标位置压入栈中,
  • 3、弹出栈中的数据,继续执行2的过程,直到栈为空
    /*** 利用栈结构来实现非递归的快速排序* 思路:* 递归的本质就是程序调用栈记录了每次排序划分出来的中间位置M,然后递归程序让程序继续执行M左侧和右侧的数据,* 如果我们自定义一个栈,用来保存中间的结果,这样就能不用递归实现快速排序,* 过程:* 1、新建一个栈,用来保存每次生成的中间位置记录* 2、每次执行了划分区域的函数后,我们将要继续执行的数据的左右侧下标位置压入栈中,* 3、弹出栈中的数据,继续执行2的过程,直到栈为空*/public static void quickSortUnrecursiveWithStack(int[] arr) {if (arr == null || arr.length < 2) {return;}// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, (int) (Math.random() * (arr.length + 1)), arr.length);// 利用三分区域的方法进行划分区域,获得等于基准值的位置int[] equalArea = threePartition(arr, 0, arr.length - 1);// 用来保存执行的结果Stack<QuickSortTempData> stack = new Stack<>();// 将新的划分区域压入堆栈中stack.push(new QuickSortTempData(0, equalArea[0] - 1));stack.push(new QuickSortTempData(equalArea[1] + 1, arr.length - 1));// 循环执行,直到栈为空while (!stack.isEmpty()) {// 弹出栈中的数据QuickSortTempData tempData = stack.pop();if (tempData.getLeft() < tempData.getRight()) { // 这一步判断一定要有,因为我们在压战的时候没有判断,所以这里要加,否则就会死循环// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, tempData.getLeft() + (int) (Math.random() * (tempData.getRight() - tempData.getLeft() + 1)), tempData.getRight());// 利用三分区域的方法进行划分区域,获得等于基准值的位置equalArea = threePartition(arr, tempData.getLeft(), tempData.getRight());// 将新的划分区域压入堆栈中stack.push(new QuickSortTempData(tempData.getLeft(), equalArea[0] - 1));stack.push(new QuickSortTempData(equalArea[1] + 1, tempData.getRight()));}}}
/*** 非递归实现快速排序时保存分区结果的类*/public static class QuickSortTempData {// 左侧区域的边界private final int left;// 右侧区域的边界private final int right;public QuickSortTempData(int left, int right) {this.left = left;this.right = right;}public int getLeft() {return left;}public int getRight() {return right;}}

2.1、利用队列结构来实现非递归的快速排序

思路:

  • 与利用栈结构实现非递归的快速排序类似,只是利用队列结构来实现非递归的快速排序,
  • 既能用队列实现,也能用栈实现的原因是快速排序对左右两边的排序子过程不关注顺序。
 /*** 利用队列结构来实现非递归的快速排序* 思路:* 与利用栈结构实现非递归的快速排序类似,只是利用队列结构来实现非递归的快速排序,* 既能用队列实现,也能用栈实现的原因是快速排序对左右两边的排序子过程不关注顺序。*/public static void quickSortUnrecursiveWithQueue(int[] arr) {if (arr == null || arr.length < 2) {return;}// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, (int) (Math.random() * (arr.length + 1)), arr.length);// 利用三分区域的方法进行划分区域,获得等于基准值的位置int[] equalArea = threePartition(arr, 0, arr.length - 1);Queue<QuickSortTempData> queue = new LinkedList<>();// 将新的划分区域压入队列中queue.offer(new QuickSortTempData(0, equalArea[0] - 1));queue.offer(new QuickSortTempData(equalArea[1] + 1, arr.length - 1));while (!queue.isEmpty()) {QuickSortTempData tempData = queue.poll();if (tempData.getLeft() < tempData.getRight()) {// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, tempData.getLeft() + (int) (Math.random() * (tempData.getRight() - tempData.getLeft() + 1)), tempData.getRight());// 利用三分区域的方法进行划分区域,获得等于基准值的位置equalArea = threePartition(arr, tempData.getLeft(), tempData.getRight());// 将新的划分区域压入队列中queue.offer(new QuickSortTempData(tempData.getLeft(), equalArea[0] - 1));queue.offer(new QuickSortTempData(equalArea[1] + 1, tempData.getRight()));}}}/*** 非递归实现快速排序时保存分区结果的类*/public static class QuickSortTempData {// 左侧区域的边界private final int left;// 右侧区域的边界private final int right;public QuickSortTempData(int left, int right) {this.left = left;this.right = right;}public int getLeft() {return left;}public int getRight() {return right;}}

3、完整代码和测试程序


import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;/*** 快速排序* 利用分治思想的实现排序的算法* 其基本思路是先将数组进行划分,划分成一侧是小于某个数的,一侧是大于某个数的,然后继续在这两侧进行划分,直到数组有序* 快速排序的最好时间复杂度为O(N*logN),最差时间负责度为O(N^2),空间复杂度为O(1)*/
public class QuickSort {/*** 荷兰国旗问题-二分区域* 将一个数组arr[L...R]按照指定位置的数进行划分,小于等于的到左边,大于的到右边,最后返回这个指定值的位置* 思路:* 我们选定最右侧的一个数作为基准数据,先将arr[L...R-1]上的数与基准数arr[R]进行比较,小于等于的放到左侧,大于的放到右侧,再将大于区域最左边的数和arr[R]进行交换,这个位置下标就是返回值* 在划分的过程中,我们先假定一个小于等于的区域,最右侧边界的下标位置为L-1,即不包含任何区域上的数,从数组的L位置开始,到R-1位置进行和基准数字arr[R]比较,* 假设比较时当前位置为index,如果arr[index]<=arr[R],则将arr[index]与小于等于区域的下一个位置进行交换,小于等于区域右移一位,当前下标右移一位* 如果arr[index]>arr[R],则当前下标右移一位* 这样做的目的是小于等于区域包含了最右侧的下标位置,并且小于等于区域的下一位就是大于区域的第一个位置,最后将其和arr[R]位置交换,就能得到最后的结果* 过程:* 1、先假定arr[R]位置的数为比较的基准值,假定小于等于区域的最右侧边界为L-1* 2、从L开始到R-1位置,将当前值与arr[R]进行比较,* 3、如果当前值小于等于基准值,则将当前值与小于等于区域的下一个位置进行交换,小于等于区域右移一位,当前下标右移一位* 4、如果当前值大于基准值,则当前下标右移一位* 3、最后将小于等于区域的下一个位置的数和arr[R]位置交换,此时和arr[R]交换位置的下标就是二分区域的位置下标* 时间复杂度:O(N)* 空间复杂度:O(1)*/public static int twoPartition(int[] arr, int L, int R) {if (L > R) {return -1;}if (L == R) {return L;}int pivot = arr[R];// 保存一个基准值int lessRight = L - 1;// 小于等于区域的最右侧边界int index = L;// 当前位置while (index < R) {if (arr[index] <= pivot) {// 小于等于,将当前值与小于等于区域的下一个位置进行交换,小于等于区域右移一位,当前下标右移一位swap(arr, index, ++lessRight);}index++;}// 最后将小于等于区域的下一个位置的数和arr[R]位置交换swap(arr, ++lessRight, R);return lessRight;}/*** 荷兰国旗问题-三分区域* 将一个数组arr[L...R]按照指定位置的数进行划分,小于等于的到左边,等于的放到中间,大于的放到右边,最右返回等于区域的前后两个下标位置* 鉴于java语言的特性,我们返回一个长度为2的数组,0位置为等于区域左边的下标,1位置为等于区域右边的下标。* 思路:* 整体思路和二分区域的类似,先讲arr[R]位置的数作为基准值,将arr[L...R-1]上的数与基准数arr[R]进行比较,小于等于的放到左侧,等于的放中间,大于的放到右侧,最后将大于区域最左边的数和arr[R]进行交换* 在实际划分的过程中,我们要假定小于和大于两个区域,一开始小于区域最右侧下标为L-1(代表没有数据),大于区域最左侧下标为R(因为现在操作的是L...R-1,所以R位置代表没有数据)* 当前值从L开始,到最大边界左侧的位置(这里非常容易出错),将当前值与arr[R]进行比较,* 如果当前值和基准值相等,则当前下标右移一位* 如果当前值小于基准值,将当前值与小于区域右侧的下一个位置进行交换,小于区域右移一位,当前下标右移一位(这个过程可以将等于区域的值也右移)* 如果当前值大于基准值,将当前值与大于区域的前一个位置进行交换,大于区域左移一位,当前下标不变(这个过程可以将大于区域换过来的数据继续比较)* 最后将大于区域最左边位置的数和arr[R]位置交换,此时小于区域最右侧的下一位就是等于区域左边的下标,和arr[R]交换的位置就是等于区域右侧的下标* 过程:* 1、先假定arr[R]位置的数为比较的基准值,假定小于区域的最右侧边界为L-1,大于区域的最左侧边界为R* 2、从L开始到到最大边界左侧的位置(这里非常容易出错),将当前值与arr[R]进行比较,* 3、如果当前值和基准值相等,则当前下标右移一位* 4、如果当前值小于基准值,将当前值与小于区域右侧的下一个位置进行交换,小于区域右移一位,当前下标右移一位(这个过程可以将等于区域的值也右移)* 5、如果当前值大于基准值,将当前值与大于区域的前一个位置进行交换,大于区域左移一位,当前下标不变(这个过程可以将大于区域换过来的数据继续比较)* 6、最后将大于区域最左边位置的数和arr[R]位置交换,此时小于区域最右侧的下一位就是等于区域左边e下标,和arr[R]交换的位置就是等于区域右侧的下标* 时间复杂度:O(N)* 空间复杂度:O(1)*/public static int[] threePartition(int[] arr, int L, int R) {if (L > R) {return new int[]{-1, -1};}if (L == R) {return new int[]{L, L};}int pivot = arr[R];//保存一个基准值int lessRight = L - 1;//小于等于区域的最右侧边界,初始值为L-1(不包含数据)int moreLeft = R;//大于区域的最左侧边界,初始值为R(不包含数据)int index = L; // 当前位置while (index < moreLeft) { //当前位置从L出发,到最大边界左侧的位置(不是到R-1,这里非常容易出错)if (arr[index] == pivot) {// 当前位置和基准数据相等,当前位置右移一位index++;} else if (arr[index] < pivot) {//前值小于基准值,将当前值与小于区域右侧的下一个位置进行交换,小于区域右移一位,当前下标右移一位(这个过程可以将等于区域的值也右移)swap(arr, index, ++lessRight);index++;} else {// 当前值大于基准值,将当前值与大于区域的前一个位置进行交换,大于区域左移一位,当前下标不变(这个过程可以将大于区域换过来的数据继续比较)swap(arr, index, --moreLeft);}}//最后将大于区域最左边位置的数和arr[R]位置交换,此时小于区域最右侧的下一位就是等于区域左边e下标,和arr[R]交换的位置就是等于区域右侧的下标swap(arr, moreLeft, R);return new int[]{lessRight + 1, moreLeft};}/*** 二分区域实现的快速排序* 利用二分区域的思想,将数组进行划分,划分成一侧是小于某个数的,一侧是大于某个数的,然后继续在这两侧进行划分,直到数组有序* 最好时间复杂度:O(N*logN)* 最差时间复杂度:O(N^2)* 空间复杂度:O(1)*/public static void twoPartitionQuickSort(int[] arr, int L, int R) {if (L >= R || arr == null || arr.length < 2) {return;}// 利用二分区域的方法进行划分区域,获得等于基准值的位置int M = twoPartition(arr, L, R);// 递归排序小于区域twoPartitionQuickSort(arr, L, M - 1);// 递归排序大于区域twoPartitionQuickSort(arr, M + 1, R);}/*** 三分区域实现的快速排序* 利用三分区域的思想,将数组进行划分,划分成一侧是小于某个数的,一侧是大于某个数的,中间是等于某个数的,然后继续在这两侧进行划分,直到数组有序* 最好时间复杂度:O(N*logN)* 最差时间复杂度:O(N^2)* 空间复杂度:O(1)*/public static void threePartitionQuickSort(int[] arr, int L, int R) {if (L >= R || arr == null || arr.length < 2) {return;}// 利用三分区域的方法进行划分区域,获得等于基准值的位置int[] equalArea = threePartition(arr, L, R);// 递归排序小于区域threePartitionQuickSort(arr, L, equalArea[0] - 1);// 递归排序大于区域threePartitionQuickSort(arr, equalArea[1] + 1, R);}/*** 改进后的三分区域实现:* 快速排序如果一开始数据就是有序的,那其就是最差的时间复杂度的情形,时间复杂度为O(N^2)* 这个时候,如果打乱其基准数据的选择,学术上通过证明,其时间复杂度就会收敛到O(N*logN)* 所以在进行分区的时候,可以先在L...R范围内随机选择一个位置和arr[R]位置交换,然后用交换后的arr[R]进行分区*/public static void improvedThreePartition(int[] arr, int L, int R) {if (L > R || arr == null || arr.length < 2) {return;}// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, L + (int) (Math.random() * (R - L + 1)), R);// 利用三分区域的方法进行划分区域,获得等于基准值的位置int[] equalArea = threePartition(arr, L, R);// 递归排序小于区域threePartitionQuickSort(arr, L, equalArea[0] - 1);// 递归排序大于区域threePartitionQuickSort(arr, equalArea[1] + 1, R);}/*** 非递归实现快速排序时保存分区结果的类*/public static class QuickSortTempData {// 左侧区域的边界private final int left;// 右侧区域的边界private final int right;public QuickSortTempData(int left, int right) {this.left = left;this.right = right;}public int getLeft() {return left;}public int getRight() {return right;}}/*** 利用栈结构来实现非递归的快速排序* 思路:* 递归的本质就是程序调用栈记录了每次排序划分出来的中间位置M,然后递归程序让程序继续执行M左侧和右侧的数据,* 如果我们自定义一个栈,用来保存中间的结果,这样就能不用递归实现快速排序,* 过程:* 1、新建一个栈,用来保存每次生成的中间位置记录* 2、每次执行了划分区域的函数后,我们将要继续执行的数据的左右侧下标位置压入栈中,* 3、弹出栈中的数据,继续执行2的过程,直到栈为空*/public static void quickSortUnrecursiveWithStack(int[] arr) {if (arr == null || arr.length < 2) {return;}// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, (int) (Math.random() * (arr.length + 1)), arr.length);// 利用三分区域的方法进行划分区域,获得等于基准值的位置int[] equalArea = threePartition(arr, 0, arr.length - 1);// 用来保存执行的结果Stack<QuickSortTempData> stack = new Stack<>();// 将新的划分区域压入堆栈中stack.push(new QuickSortTempData(0, equalArea[0] - 1));stack.push(new QuickSortTempData(equalArea[1] + 1, arr.length - 1));// 循环执行,直到栈为空while (!stack.isEmpty()) {// 弹出栈中的数据QuickSortTempData tempData = stack.pop();if (tempData.getLeft() < tempData.getRight()) { // 这一步判断一定要有,因为我们在压战的时候没有判断,所以这里要加,否则就会死循环// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, tempData.getLeft() + (int) (Math.random() * (tempData.getRight() - tempData.getLeft() + 1)), tempData.getRight());// 利用三分区域的方法进行划分区域,获得等于基准值的位置equalArea = threePartition(arr, tempData.getLeft(), tempData.getRight());// 将新的划分区域压入堆栈中stack.push(new QuickSortTempData(tempData.getLeft(), equalArea[0] - 1));stack.push(new QuickSortTempData(equalArea[1] + 1, tempData.getRight()));}}}/*** 利用队列结构来实现非递归的快速排序* 思路:* 与利用栈结构实现非递归的快速排序类似,只是利用队列结构来实现非递归的快速排序,* 既能用队列实现,也能用栈实现的原因是快速排序对左右两边的排序子过程不关注顺序。*/public static void quickSortUnrecursiveWithQueue(int[] arr) {if (arr == null || arr.length < 2) {return;}// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, (int) (Math.random() * (arr.length + 1)), arr.length);// 利用三分区域的方法进行划分区域,获得等于基准值的位置int[] equalArea = threePartition(arr, 0, arr.length - 1);Queue<QuickSortTempData> queue = new LinkedList<>();// 将新的划分区域压入队列中queue.offer(new QuickSortTempData(0, equalArea[0] - 1));queue.offer(new QuickSortTempData(equalArea[1] + 1, arr.length - 1));while (!queue.isEmpty()) {QuickSortTempData tempData = queue.poll();if (tempData.getLeft() < tempData.getRight()) {// 随机选一个数和R位置的数交换,让复杂度接近O(N*logN)swap(arr, tempData.getLeft() + (int) (Math.random() * (tempData.getRight() - tempData.getLeft() + 1)), tempData.getRight());// 利用三分区域的方法进行划分区域,获得等于基准值的位置equalArea = threePartition(arr, tempData.getLeft(), tempData.getRight());// 将新的划分区域压入队列中queue.offer(new QuickSortTempData(tempData.getLeft(), equalArea[0] - 1));queue.offer(new QuickSortTempData(equalArea[1] + 1, tempData.getRight()));}}}/*** 用对数器的方法测试*/public static void main(String[] args) {// 测试次数int testTime = 500000;// 数组最大长度int maxSize = 100;// 数组最大值int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {// 先生成一个随机的数组int[] arr = generateRandomArr(maxSize, maxValue);//printArray(arr);if (arr.length < 2) {continue;}// 复制一个数组,用来做对照int[] arrComparator = copyArr(arr);// 系统排序,比较器sortComparator(arrComparator);// 二分法实现的快速排序int[] arr1 = copyArr(arr);twoPartitionQuickSort(arr1, 0, arr.length - 1);if (!isEqual(arr1, arrComparator)) {succeed = false;System.out.println("原数组:");printArray(arr);System.out.println("系统排序后:");printArray(arrComparator);System.out.println("二分法实现快速排序后:");printArray(arr1);break;}// 三分法实现的快速排序int[] arr2 = copyArr(arr);threePartitionQuickSort(arr2, 0, arr.length - 1);if (!isEqual(arr2, arrComparator)) {succeed = false;System.out.println("原数组:");printArray(arr);System.out.println("系统排序后:");printArray(arrComparator);System.out.println("三分法实现快速排序后:");printArray(arr2);break;}// 改进后的三分法实现的快速排序int[] arr3 = copyArr(arr);improvedThreePartition(arr3, 0, arr.length - 1);if (!isEqual(arr3, arrComparator)) {succeed = false;System.out.println("原数组:");printArray(arr);System.out.println("系统排序后:");printArray(arrComparator);System.out.println("改进后的三分法实现快速排序后:");printArray(arr3);break;}// 利用栈实现的非递归的快速排序int[] arr4 = copyArr(arr);quickSortUnrecursiveWithStack(arr4);if (!isEqual(arr4, arrComparator)) {succeed = false;System.out.println("原数组:");printArray(arr);System.out.println("系统排序后:");printArray(arrComparator);System.out.println("利用栈实现的非递归的快速排序后:");printArray(arr4);break;}// 利用队列实现的非递归的快速排序int[] arr5 = copyArr(arr);quickSortUnrecursiveWithQueue(arr5);if (!isEqual(arr5, arrComparator)) {succeed = false;System.out.println("原数组:");printArray(arr);System.out.println("系统排序后:");printArray(arrComparator);System.out.println("利用队列实现的非递归的快速排序后:");printArray(arr5);break;}}System.out.println(succeed ? "successful!" : "error!");}/*** 生成随机数组* 长度是[0,maxSize]* 每个值是[-maxValue,maxValue]** @param maxSize  数组最大长度* @param maxValue 数组最大值* @return 随机数组*/public static int[] generateRandomArr(int maxSize, int maxValue) {// Math.random() -> [0,1) 所有的小数,等概率返回一个// Math.random() * N -> [0,N) 所有小数,等概率返回一个// (int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个// (int)(Math.random() * (maxSize + 1)) -> [0,maxSize] 所有的整数,等概率返回一个int size = (int) (Math.random() * (maxSize + 1));int[] arr = new int[size];for (int i = 0; i < arr.length; i++) {arr[i] = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * maxValue);}return arr;}/*** 复制数组** @param arr 原数组* @return 复制数组*/public static int[] copyArr(int[] arr) {if (arr == null) {return null;}int[] res = new int[arr.length];for (int i = 0; i < arr.length; i++) {res[i] = arr[i];}return res;}/*** 比较数组是否相等** @param arr1 数组1* @param arr2 数组2* @return 是否相等*/public static boolean isEqual(int[] arr1, int[] arr2) {if (arr1 == null && arr2 == null) {return true;}if (arr1 == null || arr2 == null) {return false;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;}public static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}/*** 获取的系统排序的对数器,用来验证自己的排序是不是正确的参照** @param arr 数组*/public static void sortComparator(int[] arr) {Arrays.sort(arr);}public static void swap(int[] arr, int i, int j) {if (i == j || i > arr.length - 1 || j > arr.length - 1) {return;}arr[i] = arr[i] ^ arr[j];arr[j] = arr[i] ^ arr[j];arr[i] = arr[i] ^ arr[j];}
}

后记
个人学习总结笔记,不能保证非常详细,轻喷


文章转载自:

http://yMDb6lMp.ypbdr.cn
http://cHKpMNvl.ypbdr.cn
http://i8pN5hSg.ypbdr.cn
http://79De289l.ypbdr.cn
http://wpgIBmw4.ypbdr.cn
http://HeVWofTk.ypbdr.cn
http://smch3v6U.ypbdr.cn
http://D5xnh7HQ.ypbdr.cn
http://OSbNE0JV.ypbdr.cn
http://S3aojvru.ypbdr.cn
http://yOAJXiyM.ypbdr.cn
http://xCp51q1c.ypbdr.cn
http://fOqlC9ut.ypbdr.cn
http://u2JiSjCH.ypbdr.cn
http://seBj6zQx.ypbdr.cn
http://u1IwCLQ5.ypbdr.cn
http://qUZMJW45.ypbdr.cn
http://wI58wvgg.ypbdr.cn
http://Dr7dUjQX.ypbdr.cn
http://OksTH4W4.ypbdr.cn
http://9CTAb0TN.ypbdr.cn
http://1lJADDGI.ypbdr.cn
http://4Sq1kNUY.ypbdr.cn
http://QoMYWTy6.ypbdr.cn
http://JjoTIuso.ypbdr.cn
http://trK6AAEk.ypbdr.cn
http://2waUJ9zf.ypbdr.cn
http://7HqcrpoI.ypbdr.cn
http://hSpzG3Yt.ypbdr.cn
http://afcaOvsM.ypbdr.cn
http://www.dtcms.com/a/382096.html

相关文章:

  • 数据结构——顺序表(c语言笔记)
  • Java 黑马程序员学习笔记(进阶篇6)
  • Day04 前缀和差分 1109. 航班预订统计 、304. 二维区域和检索 - 矩阵不可变
  • Java 类加载与对象内存分配机制详解
  • 【数据结构——图与邻接矩阵】
  • 再次深入学习深度学习|花书笔记1
  • 信息检索、推荐系统模型排序质量指标:AP@K和MAP@K
  • 详解 OpenCV 形态学操作:从基础到实战(腐蚀、膨胀、开运算、闭运算、梯度、顶帽与黑帽)
  • 《2025年AI产业发展十大趋势报告》五十五
  • 【面试题】RAG优化策略
  • 06 一些常用的概念及符号
  • Oracle事件10200与10201解析:数据库读一致性CR与Undo应用
  • 新手向:C语言、Java、Python 的选择与未来指南
  • 【人工智能通识专栏】第十四讲:语音交互
  • 3.RocketMQ核心源码解读
  • 微信小程序开发教程(十一)
  • [硬件电路-194]:NPN三极管、MOS-N, IGBT比较
  • 零基础学AI大模型之AI大模型常见概念
  • [Dify] 插件节点用法详解:如何将插件整合进工作流
  • 2025年数字公共治理专业重点学什么内容?(详细指南)
  • 如何在 Windows 系统中对硬盘 (SSD) 进行分区
  • 【深耕好论文】
  • Python快速入门专业版(二十八):函数参数进阶:默认参数与可变参数(*args/**kwargs)
  • 残差:从统计学到深度学习的核心概念
  • 华为体检转氨酶高能否入职
  • DeerFlow 实践:华为IPD流程的评审智能体设计
  • AI赋能金融研报自动化生成:智能体系统架构与实现
  • 一、Java 基础入门:从 0 到 1 认识 Java(详细笔记)
  • python123机器学习基础练习1
  • 微信小程序坐标位置使用整理(四)map组件