快速排序进阶之三路快排——学习笔记

原理

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

使用了分冶法。

分冶法是把一个规模为N的问题分成两个或多个较小的与原问题类型相同的子问题,通过对子问题的求解,并把子问题的解合并起来从而构成整个问题的解,即对问题各个击破,分而治之。如果子问题的的规模仍然相当大,仍不足以很容易的求得它的解,这时可以对此子问题重复的应用分冶策略。

复杂度

时间复杂度

最坏情况:最坏情况发生在每次划分过程产生的两个区间分别包含n-1个元素和1个元素的时候。O(n2),退化成冒泡排序,即每次都排好一个元素的顺序。

最好得情况:每次划分过程产生的区间大小都为n/2,则快速排序法运行就快得多了。O(nlogn)

平均情况:O(nlogn)

空间复杂度

最优的情况下空间复杂度为:O(logn)  :每一次都平分数组的情况。

最差的情况下空间复杂度为:O( n ) :退化为冒泡排序的情况。

代码

老版本(没有减小重复元素的影响,可以看作不完全的三路快排)

public static void main(String[] args) {
  int[] n = { 2, 1, 7, 4, 8, 5 };
  quickSort(n, 0, n.length - 1);
  for (int i : n) {
    System.out.print(i + " ");
  }
}

public static void quickSort(int[] list, int left, int right) {
  if (left < right) {
    int middle = getMiddle(list, left, right); // 将list数组进行一分为二
    quickSort(list, left, middle - 1); // 对低字表进行递归排序
    quickSort(list, middle + 1, right); // 对高字表进行递归排序
  }
}

public static int getMiddle(int[] list, int left, int right) {
  int tmp = list[left]; // 数组的第一个作为中轴
  while (left < right) {
    while (left < right && list[right] >= tmp) {
      right--;
    }
    list[left] = list[right]; // 比中轴小的记录移到低端
    while (left < right && list[left] <= tmp) {
      left++;
    }
    list[right] = list[left]; // 比中轴大的记录移到高端
  }
  list[left] = tmp; // 中轴记录到尾
  return left; // 返回中轴的位置

}

二路快排(简单,最常用)

public static void quickSort2(int[] n, int left, int right) {
        //[left,slow) 是小于key的数据集; (slow,right]是大于key的数据集
        if (n == null || n.length < 1 || left >= right) {
            return;
        }
        //int[] n = { 7, 1, 2, 8, 7, 2, 12, 7 };
        int key = n[left];//比较的基础值(可优化:见优化点)
        int slow = left;//基础值的下标(最后基础值也会放在这个下标点上)
        int fast = left + 1;//遍历数组的指针,从基础值的下一个元素开始
        while (fast <= right) {
            if (n[fast] < key) {
                //大于等于key不动,小于key跟 n[slow + 1]交换
                swap(n, ++slow, fast);
            }
            fast++;
        }
        swap(n, left, slow);//基础值还在left位置,交换一下,最后放在最右边的小于基础值的下标上
        //System.out.println(JSON.toJSONString(n) + " " + slow);
        quickSort2(n, left, slow - 1);
        quickSort2(n, slow + 1, right);
    }

   public static void swap(int[] n, int left, int right) {
       if (n[left] == n[right]) {
           return;
       }
       n[left] = n[left] ^ n[right];
       n[right] = n[left] ^ n[right];
       n[left] = n[left] ^ n[right];
   }

三路快排(有效避免重复元素的反复排序)

public static void quickSort3(int[] n, int left, int right) {
        //[left,slow)是小于key的数据集; (largeIndex,right]是大于key的数据集; [slow,largeIndex]是等于key的数据集
        if (n == null || n.length < 1 || left >= right) {
            return;
        }
        //int[] n = { 7, 1, 2, 8, 7, 2, 12, 7 };
        int key = n[left];//比较的基础值(可优化:见优化点)
        int slow = left;//基础值的下标(如果基础值有重复元素,此为最左下标)
        int fast = left + 1;//遍历数组的指针,从基础值的下一个元素开始
        int largeIndex = right;//由于大于基础值的会被交换到后面,所以最后此值为基础值下标(如果基础值有重复元素,此为最右下标)
        while (fast <= largeIndex) {
            if (n[fast] < key) {
                swap(n, fast++, slow++);
            } else if (n[fast] > key) {
                swap(n, fast, largeIndex--);
            } else {
                fast++;
            }
        }
        //System.out.println(JSON.toJSONString(n) + " " + slow + " " + largeIndex);
        quickSort3(n, left, slow - 1);
        quickSort3(n, largeIndex + 1, right);
    }

 

优化点

中间值(解决基础值问题)

与一般的快速排序方法不同,它并不是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进对于原来的快速排序算法来说,主要有两点优势:
(1) 首先,它使得最坏情况发生的几率减小了。
(2) 其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。

分区间隔小时可使用插入排序

快速排序的优化可考虑当分区间隔小的的时候转而使用插入排序。

五分钟学算法  双路、三路快排(对于有很多重复值的数据集很有效)

三路快排(解决重复元素的反复排序问题)

见代码

posted on 2019-03-15 22:02  反光的小鱼儿  阅读(135)  评论(0编辑  收藏  举报