您的位置:首页 > 产品设计 > UI/UE

快速排序(Quick Sort)介绍

2016-05-04 14:47 423 查看

原理介绍

快速排序是分治法(Divide and Conquer)的典型应用,基本思想是在数组中适当的轴心(partition),然后将数组一分为二,再分别对左边和右边数组进行排序,而影响快速排序法效率的正是轴心的选择。
快速排序根据分割点的不同,有三种版本:
选择最左端的数为比较对象,低指针从左边开始向右移动,高指针从右边开始向左移动;
选择中间的数为比较对象,低指针从左边开始向右移动,高指针从右边开始向左移动;
选择最右端的数为比较对象,低指针和高指针都从左端开始移动。

快排的三种版本

下面分别介绍三种快速排序的版本。
第一种版本:
(1)将最左边的数设定为轴,其值为val
循环:
(2)令索引i 从数组左方往右方找,直到找到大于 val 的数
(3)令索引 j 从数组右方往左方找,直到找到小于 val 的数
(4)如果 i >= j,则离开循环,否则,交换索引 i 与 j 两处的值
(5)将左侧的轴与 j 进行交换
(6)对轴左边(j-1)的数组进行递归处理
(7)对轴右边(j+1)的数组进行递归处理







public static void QuickSort(int[] a, int lo, int hi)
{
if (lo >= hi) return;
int i = lo;
int j = hi + 1;

while (true)
{
while ((a[++i] < a[lo]))
if (i == hi) break;
while ((a[--j] > a[lo]))
if (j == lo) break;
if (i >= j) break;
exch(a, i, j);
}
exch(a, lo, j);

QuickSort(a, lo, j-1);
QuickSort(a, j+1, hi);
}


第二种版本:
(1)将中间的数val设为轴
循环:
(2)令索引i 从数组左方往右方找,直到找到大于 val 的数
(3)令索引 j 从数组右方往左方找,直到找到小于 val 的数
(4)如果 i >= j ,则离开循环,否则,交换索引 i 与 j 两处的值
(5)对轴左边(i-1)的数组进行递归处理
(6)对轴右边(j+1)的数组进行递归处理
边界正确性证明:
(1)若 i == j ,则 i-1 和 j+1 正好是某个数两边的界限
(2)若 i > j ,则不难知道 i-1 == j (即 i == j+1),则 i-1 与 j+1 正好是两个子数组的边界

public static void QuickSort (int[] a, int lo, int hi)
{
if (lo >= hi) return;
int i = lo-1, j = hi+1;
int mid = (lo + hi) / 2;
int val = a[mid];

while (true)
{
while (a[++i] < val)
if (i == hi) break;
while (a[--j] > val)
if (j == lo) break;
if (i >= j) break;
exch(a, i, j);
}

QuickSort(a, lo, j);
QuickSort(a, j+1, hi);
}


第三种版本:
(1)将最右边的数 val 设为轴
循环:

(2)令索引 j 从数组左方开始往右方找,直到找到小于 val的数
(3)若 j 小于高位hi ,则交换 i 和 j 位置的元素,然后 i++
(4)若j
大于或等于高位hi ,则退出循环



(5)交换 i位置的数和最右边的数



(6)对 i-1 左边的数组进行递归排序
(7)对i+1 右边的数组进行递归排序



public static void QuickSort (int[] a, int lo, int hi)
{
if (lo >= hi) return;
int i = lo, j = lo-1;
int val = a[hi];

while (true)
{
while (a[++j] >= val)
if (j == hi) break;
if (j >= hi) break;
exch(a, i++, j);
}
exch(a, i, hi);

QuickSort(a, lo, i-1);
QuickSort(a, i+1, hi);
}


含有重复键(duplicate key)的快排程序

若序列中含有大量重复键,若使用前面三种方法进行排序,会使得重复键参与到下一次排序中,极大地降低了排序算法的效率。Robert Sedgewick 提出了一种方法,使得重复键不参与下一次的排序中,提升了算法的效率。不过键交换的次数会增多。
算法的步骤如下:
(1)设定三个指针分别为lt,i,gt。其中,在指针lt左边的数组值小于val,在lt到i之间的数组值等于val,在gt右边的数组值大于val,在i到gt之间的值是未处理的。







循环(i <= gt时):
(2)当索引i处的值小于val时,交换i 和lt的值
(3)当索引i处的值大于val时,交换i 和gt的值
(4)否则(当索引i处的值等于val时),i自增
(5)对lt左边的数组进行排序
(6)对gt右边的数组进行排序

public static void QuickSort_DuplicateKeys(int[] a, int lo, int hi)
{
if (lo >= hi) return;
int lt = lo, gt = hi;
int i = lt;
int val = a[lo];

while (i <= gt)
{
if (a[i] < val)
exch(a, i++, lt++);
else if (a[i] > val)
exch(a, i, gt--);
else i++;
}

QuickSort_DuplicateKeys(a, lo, lt-1);
QuickSort_DuplicateKeys(a, gt+1, hi);
}


快排的特性

1.快排是一种内排序算法
2.快排时间复杂度的最好情况是~NlgN,最坏情况是~(1/2)*N^2,平均情况是~1.39NlgN
3.快排的空间复杂度:对于Partitioning:需要常数级的额外空间;对于递归的深度:需要对数级的额外空间

快排的优化

1.在JDK API的数组排序算法中,会先对数组的长度进行判断。若是长度较短的数组,则直接使用插入排序。对于较长的数组才使用快速排序。
2.Robert Sedgewick认为在快速排序之前,应先对数组进行Shuffle操作,以提高快速排序的性能。因为在最坏的情况下,快排的时间复杂度是~(1/2)*N^2。而平均情况下,快排的时间复杂度是~1.39NlgN。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: