快速排序

  這次來複習排序,講一講快速排序。

  要說應用最廣的排序大概就是快速排序了,因爲它有着許多優點。實現簡單,需要的輔助空間少,需要的時間比其他的排序少。所以,快速排序是一個必須要了解的排序算法。但是它也有着一些缺點,快速排序算法非常的脆弱,這一點下面也會提到。

  快速排序的基本思路就是將排序的元素分爲兩組,在將兩組分別再次排序,兩個子數組排序完成後整個數組的排序也就完成了。大體的思路非常簡單,和歸併排序有點相反,歸併排序是將原本的數組分爲兩個數組,在分別排序子數組後,歸併後再用歸併排序。這個排序以後有機會再講(感覺這句話自己打了很多遍)。快速排序相反,將數組分爲兩個數組,分別將子數組排序後,快速排序就認爲數組已經完成了排序。這就是兩者的區別。下面講講具體的思路。

  快速排序的具體思路如下:先是取出數組中的一個元素(切分元素),將切分元素放入原數組正確的位置,將大於切分元素的元素放到元素的右邊,小於的則放到左邊。在利用遞歸分別將左邊數組和右邊數組排序。排序完成就代表整體排序完成。整體的算法不難想到,同時也可以發現算法的最主要部分就是找到一個合適的切分元素。如果切分元素一直是一個有序數組的頭一個元素,就會發現快排的性能還不如插入,這也是前面說到的快速排序的算法非常脆弱。在經過人們的改進後,發現將數組隨機打亂後,取數組第一個元素會保證排序算法的穩定性。(非常搞笑,我第一次看到也是這樣想的)找到合適的切分元素後,將數組分割和排序也就比較簡單了。下面就粘貼代碼,同時也對於算法的具體進行解釋。

package sf;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class demo {
	/*
	 * 此方法是爲了和其他的方法保持一致,和本算法無關
	 */
	public static void sort(Comparable[] a)
	{
		//快速排序
		//將原數組隨機打亂
		List s = Arrays.asList(a);
		Collections.shuffle(s);
		Comparable[] b = (Comparable[]) s.toArray();//將數組轉換爲list,並將其打亂隨機,最後在轉換爲數組
		//調用快速排序的算法,傳入的是數組b
		sort(b, 0, a.length-1);
		//打印排序的結果,這裏還是打印數組b
		for (int i = 0; i < a.length; i++) {
			System.out.print(b[i] + " ");
		}
	}
	public 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);//切點右邊排序
		
	}
	/*
	 * 快速排序的切分方法
	 */
	public static int partition(Comparable[] a,int lo,int hi)
	{
		int i=lo,j=hi+1;
		Comparable v=a[lo];//將隨機的數組的第一個元素作爲切分元素,將切分元素的值給v,方便後面的比較。
		//這裏還是不清楚Comparable的用法
		/*
		 * 該接口定義類的自然順序,實現該接口的類就可以按這種方式排序.一般要求:e1.equals((Object)e2)和e1.compareTo(
		 * (Object)e2)==0具有相同的值,這樣的話我們就稱自然順序就和equals一致. 這個接口有什麼用呢?
		 * 如果數據或者List中的元素實現了該接口的話,我們就可以調用Collections.sort或者Arrays方法給他們排序.
		 * 如果自然順序和equals不一致的話,如果出現在Sorted Map和Set裏面,
		 * 就會出現預想不到的邏輯錯誤,可能你調用add的時候添加不了,而集合裏面確沒有這個元素.具體的討論要接口哈希表的應用.
		 */
		while (true) 
		{
			//1.注意++i和--j,先+。2.兩個比較的先後順序要注意
			/*
			 * 算法的主體是找到兩個分別大於和小於的位置,如果只有一個,另一個還是會找到,不過不在特定邊
			 */
			while(less(a[++i], v))  if (i==hi) break;  //直到找到比v大的。
			while(less(v, a[--j]))  if (j==lo) break; //直到找到比v小的
			if (i>=j) break;//檢查掃描是否完成
			exch(a, i, j);//將大於v的和小於v的交換。
		}
		exch(a, lo, j);//交換,將切分元素放到切分元素合適的位置
		return j;
		
	}

	private static boolean less(Comparable v, Comparable w) {
		// 比較用來排序的兩個參數。根據第一個參數小於、等於或大於第二個參數分別返回負整數、零或正整數。
		// 如果v大於w,返回false,相等也是false
		return v.compareTo(w) < 0;

	}

	private static void exch(Comparable[] a, int i, int j) {
		// 交換位置
		Comparable t = a[i];
		a[i] = a[j];
		a[j] = t;
	}

	public static void main(String args[]) {
		// 1 2 4 6 7 7 8 23 55 56 90 
		Comparable[] a = {1,4,2,6,7,55,8,23,7,90,56};
		/*
		 * 這裏不清楚隨機算法爲什麼不能放在sort函數中,有一些小bug
		 * 還是傳入的參數問題,穿進去的數組a不是主函數的數組a
		 */
		sort(a); 

	}

}

  很難受,現在才發現,自己寫的代碼自己看不懂了。快速排序的主體算法比較簡單,沒什麼難懂的,就是將數組隨機化的時候發現傳參有一點問題,後來發現參數和原本的數組不是同一個數組,傳參的改變不會影響原本數組的變化。所以這一點要注意。難點就是將切分元素放到合適的位置,這一段代碼看了大概一個小時,現在也不懂我當時怎麼想的了。主要有一點一直不明白,兩個交換的意義和特別情況下的切分元素的移動問題。具體的解釋都在代碼中了,這裏不再累述。下面講講自己的一些看法。

  快速排序還是有一些缺點的,之前說的不穩定。還有就是當數組中有大量相等元素時,這個快速排序也是比較乏力的,有一個三向切分的快速排序,不再將原數組分爲兩個數組,而是將原數組分爲三個子數組,與切分元素相等的元素在第二個子數組。這種快速排序對於數組中存在大量重複的元素的排序要比上面的好的多。剩下就沒有什麼問題了,切分方法還是要多看看,弄清楚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章