1. 程式人生 > >視覺化的快速排序(JAVA)

視覺化的快速排序(JAVA)

視覺化的快速排序(JAVA)

看了《演算法第四版》後,想要熟練下自己對排序演算法的理解,就選擇用快速排序來練手,並運用了《演算法第四版》提供的API,以實現排序演算法的視覺化。
在眾多排序演算法中,快速排序是其中有很高效率的一種,同時也是較為複雜的排序演算法。

快速排序的思想:

快速排序是一種分治的排序演算法。其思想是將一段無序的資料進行切分後,在分別對切分後的兩段資料進行排序,對這兩段資料都進行再一次的切分後分別排序,不斷迴圈至完全排序完成,這個過程可通過遞迴實現。

基礎函式程式設計:

先實現較為基礎的函式方法(比較大小,交換資料,是否排序成功):

/*比較大小*/
public boolean less(Comparable x,Comparable y){
		return x.compareTo(y)<0;
}

/*資料交換*/
public void change(Comparable[] a,int i,int j){
		Comparable temp = a[i];
		a[i] = a[j];
		a[j] = temp;
}

/*判斷是否完成排序*/
public boolean isSort(Comparable[] a){
	for(int i=0;i<a.length-1;i++){
		if(less(a[i+1],a[i]))
			return false;
	}
	return true;
}

切分函式程式設計及分析

基礎函式寫好後,進行核心程式碼程式設計。由快速排序演算法的性質可知,需要有一個切分方法對資料進行切分。(思路:既然要進行切分,就需要找一個進行比較的資料,這裡我用每段資料的最後一個數據作為比較的臨時資料,切分的主要目的是將比該臨時資料小的資料放置其前面,將比該臨時資料大的資料放置其後面,因此需要迴圈來實現。該切分方法還需要返回該臨時資料的位置數字,才能將其傳給下一次切分作為其切分的臨界點)

public int partition(Comparable[] a,int lo, int hi){
		int i=lo-1,j=hi;
		Comparable temp = a[hi];
		while(true){
			//從左到右遍歷
			while(less(a[++i],temp))//當大於或等於時都退出迴圈
				if(i==hi) break;//防止越界
			//右到左
			while(less(temp,a[--j]))
				if(j==lo) break;
			if(i>=j) break;
			change(a,i,j);
		}
		change(a,hi,i);//i位置的值比較大,temp值是在最後,故將i位置的值交換至最後位置
		return i;
	}

切分( partition)方法有幾個需要注意的點,都在註釋中提醒了,由於快速排序的主要缺點是非常脆弱,在實現時要非常小心才能避免低劣的效能。
1.
在從左往右遍歷資料是,當大於或等於臨時資料時都要將其進行位置交換操作,因為這樣可以避免演算法的執行時間變為平方級別(在含有大量重複值時,切分效率及其低效。PS:《演算法第四版》練習2.3.11),從右往左遍歷同理。

while(less(a[++i],temp))

2.
在遍歷的過程中,要注意終止條件,如用判斷語句:

if(i==hi)

判斷是否越界,防止訪問到該資料段以外的資料,導致排序出錯。
3.


判斷左右遍歷是否已經有交匯處以終止迴圈,使用判斷語句:

f(i>=j)

至此,排序演算法的核心程式碼已經分析完畢。

排序程式碼:

public void sort(Comparable[] a){
		/*打亂原有資料*/
		StdRandom.shuffle(a);
		sort(a,0,a.length-1);
		if(isSort(a))
			System.out.println("success!");
	}
	
	public void sort(Comparable[] a,int lo,int hi){
		if(lo>=hi)
			return;
		int j=partition(a,lo,hi);//切開
		sort(a,lo,j-1);
		sort(a,j+1,hi);
	}

排序(sort)程式碼運用了遞迴的思想以實現不斷地切分,並通過切分(partition)函式返回的值傳遞給下一次排序函式。由於快速排序存在一個潛在的缺點:在切分不平衡時可能會非常低效。例如,如果第一次從最小元素切分,第二次從第二小元素切分,如此這般,每次呼叫只會移除一個元素,這會導致一個大陣列需要切分很多次。因此需要在排序前將資料打亂以防止這種可能性。

StdRandom.shuffle(a);

(改程式碼使用了《演算法第四版》提供的API)

排序過程視覺化

為了將排序的過程視覺化,使用了《演算法第四版》API寫出將資料轉化為條形統計圖的方法。(思路:一個將畫板指定條形資料清空的方法(toWhite)和一個將指定條形資料重新畫在畫板上的方法(toBlack))

public static void toWhite(Comparable[] a,int i,int j){
	StdDraw.setPenColor(StdDraw.WHITE);
	double x1 = 1.0*i/a.length;
	double y1 = (double)a[i]/2.0;

	double x2 = 1.0*j/a.length;
	double y2 = (double)a[j]/2.0;
	
	double rw = 0.5/a.length;
	StdDraw.filledRectangle(x1, y1, rw, 1);/*通過畫白色的新條形體將原有條形體掩蓋*/
	StdDraw.filledRectangle(x2, y2, rw, 1);
	
	/*延時*/
	try {
		Thread.sleep(200);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}


public static void toBlack(Comparable[] a,int i,int j){
	StdDraw.setPenColor(StdDraw.BLACK);
	double x1 = 1.0*i/a.length;
	double y1 = (double)a[i]/2.0;
	double rh1 = (double)a[i]/2.0;
	
	double x2 = 1.0*j/a.length;
	double y2 = (double)a[j]/2.0;
	double rh2 = (double)a[j]/2.0;
	double rw = 0.5/a.length;
	StdDraw.filledRectangle(x1, y1, rw, rh1);
	StdDraw.filledRectangle(x2, y2, rw, rh2);
	
	try {
		Thread.sleep(200);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

由於需要在排序過程中一邊改變條形圖,故需要在快速排序程式碼中進行繪圖程式碼的插入,主要是在切分函式進行修改:

public int partition(Comparable[] a,int lo, int hi){
		int i=lo-1,j=hi;
		Comparable temp = a[hi];
		while(true){
			while(less(a[++i],temp))/
				if(i==hi) break;
			while(less(temp,a[--j]))
				if(j==lo) break;
			if(i>=j) break;
			Draw.toWhite(a, i, j);/*資料交換前清除對於的條形資料*/
			change(a,i,j);
			Draw.toBlack(a, i, j);/*畫上交換後的條形資料*/
		}
		Draw.toWhite(a, i, hi);/*資料交換前清除對於的條形資料*/
		change(a,hi,i);
		Draw.toBlack(a, i, hi);/*畫上交換後的條形資料*/
		return i;
	}

生成隨機資料方法:

public class Random {
	private Comparable[] num;
	
	public Comparable[] output(int n){
		num = new Comparable[n];
		for(int i=0;i<n;i++){
			num[i] = StdRandom.random();/*運用到演算法API*/
		}
		return num;
	}

主函式程式碼編寫:

public static void main(String[] args) {
		int N=50;
		Random r = new Random();
		Comparable[] num = r.output(N);
		
		for(int i=0;i<num.length;i++){
			double x=1.0*i/N;
			double y = (double)num[i]/2.0;
			double rw = 0.5/N;
			double rh = (double)num[i]/2.0;
			StdDraw.filledRectangle(x, y, rw, rh);
		}
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//排序
		Quick q = new Quick();
		q.sort(num);
		
	}

至此,所有程式碼已經變現完成,執行main函式檢視其效果。
在這裡插入圖片描述

PS:不知是否繪畫函式插入在切分函式位置有所不妥,在某些情況下繪畫的排序過程會出現一兩個錯誤的條形資料,但排序後的資料是沒有錯誤的!(故可以排除排序演算法錯誤)只是繪畫過程有錯誤,希望有能夠有網友有指出其原因,萬分感謝。(錯誤情況如下)
在這裡插入圖片描述