快速排序算法

[size=small]排序是数据处理领域一种最常用的运算,排序的目的主要是为了快速查找。
常用的算法有:选择排序、快速排序、希尔排序、堆排序、冒泡排序、插入排序、归并排序。
其中选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。
其中这些排序算法中,以快速排序和二路归并排序效率较高,并且在面试时稍微深入算法面试官都会问到的。本次博文讲解快速排序。

[b]快速排序[/b]
快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治法也确实实用,因此很多软件公司的笔试面试,包括像腾讯,微软等知名IT公司都喜欢考这个,还有大大小的程序方面的考试如软考,考研中也常常出现快速排序的身影。
总的说来,要直接默写出快速排序还是有一定难度的,因为本人就自己的理解对快速排序作了下白话解释,希望对大家理解有帮助,达到快速排序,快速搞定。



快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

该方法的基本思想是:

1.先从数列中取出一个数作为基准数。

2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。

3.再对左右区间重复第二步,直到各区间只有一个数。



虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排序的全部步骤。因此我的对快速排序作了进一步的说明:挖坑填数+分治法:

举例来说,现在有一个数据集{85, 24, 63, 45, 17, 31, 96, 50},怎么对其排序呢?
第一步,选择中间的元素45作为"基准"。(基准值可以任意选择,但是选择中间的值比较容易理解。)
[img]http://dl2.iteye.com/upload/attachment/0119/4302/c6bdc989-b3e3-35e4-945a-b258a1f22baf.png[/img]
第二步,按照顺序,将每个元素与"基准"进行比较,形成两个子集,一个"小于45",另一个"大于等于45"。
[img]http://dl2.iteye.com/upload/attachment/0119/4304/0ef995ee-ae9c-3b0a-8ab7-2a2dd7675c1c.png[/img]
第三步,对两个子集不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
[img]http://dl2.iteye.com/upload/attachment/0119/4306/8131e3fa-587a-37f0-bdf7-a0619643195e.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0119/4308/bd50dc8f-f843-366c-adb9-94a688005b8d.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0119/4310/29248705-886c-31a0-9a9d-c62206843a52.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0119/4312/ac3da9b3-04ac-3d51-b891-70beaabe7d3c.png[/img]

参考网上和书上的资料,得出如下代码:
[b]注意:我这里代码中为了简单起见,让第一个元素值为基准值。[/b]
代码方法:

/**
* 快速排序算法,完成一次区间划分,返回中间位置j
* 时间复杂度O(Nlog₂N)其中N为数组长度
* @param a
* @param s
* @param t
* @return
*/
public static int partition(Object[] a, int s, int t) {
// 1、给i,j赋初值
int i = s;
int j = t;
// 2、将基准元素的值赋给临时变量,并让i++
Object x = a[i++];
// 3、i<=j时,依次检查需要进行交换位置的元素
while(i <= j) {
// 从前向后进行顺序查找一个大于基准值的元素,该元素需要与后面的区间中的某一个元素交换位置
while(i <= j && ((Comparable)a[i]).compareTo(x) <= 0) {
i++;
}
// 从后向前进行顺序查找一个小于基准值的元素,该元素需要与前面的区间中的某一个元素交换位置
while(j >= i && ((Comparable)a[j]).compareTo(x) >= 0) {
j--;
}
// 当条件成立时,交换a[i],a[j]的位置
if (i < j) {
Object temp = a[i];
a[i] = a[j];
a[j] = temp;
i++;
j--;
}
}
// 交换a[s]与a[j]的值
a[s] = a[j];
a[j] = x;
return j;
}

/**
* 递归调用快速排序算法进行排序,本质上是分治的思想
* @param a
* @param s
* @param t
*/
public static void quickRecursion(Object[] a, int s, int t) {
// 进行第一次快速排序
int j = partition(a, s, t);
// 对左区间进行快速排序
if(s < j - 1) {
quickRecursion(a, s, j-1);
}
// 对右区间进行快速排序
if(j + 1 < t) {
quickRecursion(a, j+1, t);
}
}

/**
* 主函数测试
* @param args
*/
public static void main(String[] args) {
Object[] a1 = {8,9,4,3,5,7,2,1};
System.out.println("初始数组:" + Arrays.toString(a1));
quickRecursion(a1, 0, a1.length-1);
System.out.println(Arrays.toString(a1));
}


[b]运行结果:[/b]
[i]初始数组:[8, 9, 4, 3, 5, 7, 2, 1]
经过排序后:[1, 2, 3, 4, 5, 7, 8, 9][/i]

在快速排序中,若把基准元素看做根结点,若把划分得到的左区间和右区间看做根结点的左子树和右子树,那么整个排序过程就对应着一棵具有n个元素的二叉搜索树。在快速排序中,记录的移动次数通常小于记录的比较次数。时间复杂度为[O(N*logN),O(n²)],空间复杂度为[O(log₂N),O(n)]。
大量的实践证明:当n较大时,它是目前为止平均情况下速度最快的一种排序算法。平均情况下,快速排序的比较次数为理想情况的2ln2≈1.39倍,所以平均的时间复杂度仍然为
O(log₂N)。

[b]注意:快速排序不适用与基本有序的无序表。
当然,为了避免这种情况发生,也可以修改快速排序算法,每次划分前比较当前区间的第一个元素,中间元素和最后一个元素的值,取中间值为基准元素,这样就可以避免快速排序沦为简单排序了[/b]

参考:
《数据结构使用教程Java语言描述》徐孝凯
[url]http://www.ruanyifeng.com/blog/2011/04/quicksort_in_javascript.html[/url]
[url]http://blog.csdn.net/morewindows/article/details/6684558[/url]

[/size]
发布了151 篇原创文章 · 获赞 2 · 访问量 1万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章