第一次写博客,就写到了算法,我必须承认,算法并不是我的强项,大学时代学了很多算法,当时用的是c语言指针实现的,老师讲的糊里糊涂,作为学生的我也是听得云里雾里,不知道指针为何物,更别提如何实现算法了。学生时代,看着课本会根据讲解理清算法的思路,但是真正实现起来,会发现又有很长的一段路要走。尤其是递归这种让人感觉离奇又神奇的东西,不禁让人望而却步。
然而真正的成长必须从敲代码开始,只有自己真正实现的东西才是自己的。俗话说,一千个读者一千个哈姆雷特,算法的思路只有一个,但是实现起来却可以有无数个版本。当然有的版本容易理解,有的版本不容易理解。有句话又说离理性太近,离灵魂越远,用在计算机方面也恰到好处,比如说,一个算法太接近人的思维,那么可能就偏离计算机的运算,造成效率低下;一个算法很符合计算机的运算,但是人们就不太容易理解它。于是人们就提用互联网的思维看问题。作为编程人员也是如此,要用计算机的思维看待问题。
以前总有这种心理,觉得别人写得总是最好的,因为别人早早就写好了,但是慢慢的下去,发现自己从未有过长进,我于是又换了一种思路,我实现的算法并非最好,但是够用就行了。于是我也变得大胆起来。别人的终究是别人的。今天我就要用我的思路来讲解一下快速排序递归算法。
首先需要说明什么是快速排序:它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。(百度百科)。定义永远都只是定义,并不能那么直观。下面就看一下快排的过程演示。
比如一个数组{ 6, 1, 2, 7, 9, 3, 4, 5, 10, 8}。首先将该数组的第一位作为基位,即6 。
接下来从数组的最右边向左遍历,直到找到第一个比基位6小的数,即5,
然后从数组的最左边向右遍历,直到找到第一个比基位6大的数,即7。此时将7 和5 交换。
{ 6, 1, 2,7, 9, 3, 4,5, 10, 8}
{ 6, 1, 2, 5, 9, 3,4,7, 10, 8}
接下来继续刚才位置向左遍历 找到4(4<6),向右遍历找到9(9>6)。然后交换。
{ 6, 1, 2, 5, 4, 3,9, 7, 10, 8}
接下来,继续遍历,在3处相遇。此时将基位6 和3 交换,至此实现了基位6的归位,即6已经排好了序。
{ 3, 1, 2, 5, 4,6, 9, 7, 10, 8}
接下来以6 为分割点,将数组分为两部分,继续分别排序。这时就用到了递归的思想。
{ 3, 1, 2, 5, 4 } --->{ 2,1,3,5,4} --->{2,1} ,3,{5,4} --> {1,2,3,4,5}
{9, 7, 10, 8}--->{9,7,8,10}--->{8,7},9,10}--->{7,8,9,10}
整体--->{1,2,3,4,5,6,7,8,9,10}
整体过程如下:
6.1.2.5.9.3.4.7.10.8.
6.1.2.5.4.3.9.7.10.8.
3.1.2.5.4.6.9.7.10.8.
2.1.3.5.4.6.9.7.10.8.
1.2.3.5.4.6.9.7.10.8.
1.2.3.4.5.6.9.7.10.8.
1.2.3.4.5.6.9.7.8.10.
1.2.3.4.5.6.8.7.9.10.
1.2.3.4.5.6.7.8.9.10.
下边是代码:
package QuickSort;
public class QuickSort {
public static void quickSort(int[] sortArray, int low, int high) {
int i = low;//一般从零位作为基位,数组的第一位
int j = high;//数组的最后一位
while (i < j) { // i ,j 左右两边相向而行的哨兵
while (sortArray[j] >= sortArray[low] && i < j) {//从数组的最高位向左移动,遇到比基位小的数即停止
j--;
}
while (sortArray[i] <= sortArray[low] && i < j) {//从数组的最低位向右移动,遇到比基位大的数即停止
i++;
}
if (i < j) {//交换从左右两边分别找到的两个数
int temp = sortArray[i];
sortArray[i] = sortArray[j];
sortArray[j] = temp;
for (int a : sortArray) {//遍历输出整个数组
System.out.print(a + ".");
}
System.out.println();
} else if (i == j) {//当左右两边的哨兵相遇时,说明已经遍历完数组,将基位归位即正确排序的的位置,即数组的第一位已经排好序。交换后实现了该书左边的数
//都小于该数,该数右边的数都大于该数。
int temp = sortArray[low];
sortArray[low] = sortArray[j];
sortArray[j] = temp;
for (int a : sortArray) {//遍历输出整个数组
System.out.print(a + ".");
}
System.out.println();
}
}
if (low < i){
quickSort(sortArray, low, i - 1);//将归位数的左边的数组继续递归排序 此时i-1 作为最高位
}
if (j < high){
quickSort(sortArray, j + 1, high);//将已经归位数右边的数组继续递归排序 此时j+1作为最低位
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] sortArray= { 6, 1, 2, 7, 9, 3, 4, 5, 10, 8 };
quickSort(sortArray, 0, sortArray.length - 1);//程序入口
System.out.println("快速排序完成!");
}
}
看了其它很多的快速排序算法,发现人们普遍会问这样一个问题,为什么要先从右边开始遍历呢?
在这里我想要详细说明一下,把左边的第一个数作为基数,然后从右向左,再从左向右分别向中间遍历数组。右边的j的任务是找到比基数小的数,
左边的i的任务是找到比基数大的数,然后两者交换。也就是说,交换的目的是为了把大数放到后面,小数放到前面。i和j相遇的位置即是基数的正确
位置,此时需要将基数和该位置的值交换,这里的值一定小于基数。如果从左边开始遍历会造成这个位置的值大于基数。举个例子来说明
{1,2,3,4,5} 1 作为基数,先从左边遍历遇到2停止,从右边遍历到2停止(i
抽象的来讲,我们的遍历的目的是从右边找到较小的数,从左边找到较大的数,然后二者交换,直到左右相遇后,将相遇位置的数和基数交换,此数必然比基数小。如果开始先从左边遍历,就会造成相遇在较大的数。因为谁先开始谁掌握着左右相遇位置的主动权,于是从右开始会相遇在较小的数(<=),从左开始会相遇在较大的数,而这个数只有为较小时,和基位交换时才能正确排序,否则排序出错。所以如果以第一个数作为基数时,就应该先从右边开始遍历。
最后说明一下快速排序数据分治递归算法,即把整体分割,递归处理。同时它也是不稳定的排序算法,不能保证相同的数排序后的前后顺序和之前的一致。