視覺化的快速排序(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:不知是否繪畫函式插入在切分函式位置有所不妥,在某些情況下繪畫的排序過程會出現一兩個錯誤的條形資料,但排序後的資料是沒有錯誤的!(故可以排除排序演算法錯誤)只是繪畫過程有錯誤,希望有能夠有網友有指出其原因,萬分感謝。(錯誤情況如下)