算法笔记:快速排序(Quick Sort)

一. 快排的基本思路

1. 给数组洗牌。(为什么要先让数组随机排列后续会讲)

2. 选择一个元素a[j]。如果数组是要从小到大排序,那么我们把所有小于a[j]的元素放到a[j]左侧,把所有大于a[j]的元素放到a[j]的右侧。

3. 对a[j]的左侧和右侧分别排序。

二. 具体实现

1. 如何实现上述第二项中的要求?

Repeat until i and j pointers cross.

    ・Scan i from left to right so long as (a[i] < a[lo]).

    ・Scan j from right to left so long as (a[j] > a[lo]).

    ・Exchange a[i] with a[j].

When pointers cross.

    ・Exchange a[lo] with a[j].

private static int partition(Comparable[] a, int lo, int hi)
{
 int i = lo, j = hi+1;
 while (true)
 {
 while (less(a[++i], a[lo]))
 if (i == hi) break;
 while (less(a[lo], a[--j]))
 if (j == lo) break;

 if (i >= j) break;
 exch(a, i, j);
 }
 exch(a, lo, j);
 return j;
} 

2. 完整实现代码

public class Quick
{
 private static int partition(Comparable[] a, int lo, int hi)
 { /* see previous slide */ }
 public static void sort(Comparable[] a)
 {
 StdRandom.shuffle(a);
 sort(a, 0, a.length - 1);
 }
 private static void sort(Comparable[] a, int lo, int hi)
 {
 if (hi <= lo) return;
 int j = partition(a, lo, hi);
 sort(a, lo, j-1);
 sort(a, j+1, hi);
 }
} 

三. 快速排序细节

1. 为什么要先对数组进行洗牌?

举一个worst case,

在上诉算法的实现过程中,我们默认使用的是数组的第一个元素作为分组的标志,假如数组已经是排好序的,我们会发现,我们每次选中的都是第一个元素,左侧没有元素,而右侧是剩余的全部元素。

2. 平均的算法复杂度

3. 快速排序是一种原地排序算法,但是不稳定。

三. 算法的提升

1. 提前排序

 private static void sort(Comparable[] a, int lo, int hi)
 {
 if (hi <= lo + CUTOFF - 1)
 {
 Insertion.sort(a, lo, hi);
 return;
 }
 int j = partition(a, lo, hi);
 sort(a, lo, j-1);
 sort(a, j+1, hi);
 }

和归并排序类似,我们也是在数组长度小到一定程度的时候直接使用插入排序,这样可以提高效率。

2. 取中位数

之前我们使用洗牌的方法确定分类标志,我们也可以通过取中位数的方式确定分类标志。

 private static void sort(Comparable[] a, int lo, int hi)
 {
 if (hi <= lo) return;
 int m = medianOf3(a, lo, lo + (hi - lo)/2, hi);
 swap(a, lo, m);
 int j = partition(a, lo, hi);
 sort(a, lo, j-1);
 sort(a, j+1, hi);
 }

四. 快速选择

我们可以在线性的时间复杂度内选择出第k大的元素吗?答案是肯定的。

借助快速排序的思路,我们可以通过选择分组标志,直到分组标志为k的时候结束。

public static Comparable select(Comparable[] a, int k)
{
 StdRandom.shuffle(a);
 int lo = 0, hi = a.length - 1;
 while (hi > lo)
 {
 int j = partition(a, lo, hi);
 if (j < k) lo = j + 1;
 else if (j > k) hi = j - 1;
 else return a[k];
 }
 return a[k];
}

五. 三路分组(3-way partitioning)

这种算法主要处理的是在很多项是重复的时候,如果在此时我们依然采用上述的算法,就可能落入n^2的算法复杂度,这个时候我们引入三路分组这个算法。

算法的具体细节可以看这篇博客 三分法 3-way partitioning。简单来说就是要把数组分为三部分,即<,=,>

private static void sort(Comparable[] a, int lo, int hi)
{
 if (hi <= lo) return;
 int lt = lo, gt = hi;
 Comparable v = a[lo];
 int i = lo;
 while (i <= gt)
 {
 int cmp = a[i].compareTo(v);
 if (cmp < 0) exch(a, lt++, i++);
 else if (cmp > 0) exch(a, i, gt--);
 else i++;
 }
 sort(a, lo, lt - 1);
 sort(a, gt + 1, hi);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值