2016-08-01 33 views
2

我想實現快速排序算法來對不直接允許訪問其元素的列表進行排序。我應該僅使用兩種方法對list進行排序:swapcompare,而不使用僅用於調試目的而提供的toString方法。我選擇了子數組的中間元素作爲主鍵。使用函數調用期間傳遞的Comparator對象進行比較。實現快速排序來限制/不訪問其內容的列表排序

我跑了隨機生成的列表外面幾乎所有的越來越分類幾個JUnit測試(更新:運行幾個測試後,我發現更多的情況下,該算法失敗)。然而,當我嘗試partition 4元子陣列的順序排列的按鍵我的算法故障(其中情形之一):最小的,最大的,大的,小]

這裏的JUnitTest合格名單 - [0,3,2,1]:

private static final Comparator<Integer> INTEGER_COMPARATOR = new IntegerComparator(); 
@Test 
public void customTest() { 
    SwapList<Integer> customList; 
    AbstractSorter<Integer> customSorter; 
    customList = new ArrayBasedSwapList<Integer>(new Integer[] { 0, 3, 2, 1 }); 
    customSorter = new QuickSorter<Integer>(customList, 
      INTEGER_COMPARATOR); 
    SwapList<Integer> result = customSorter.sort(); 
    System.out.println("Result: " + result.toString()); 
    assertTrue(result.isSorted(INTEGER_COMPARATOR)); 
} 

和所使用的IntegerComparator類:

package comparators; 

import java.util.Comparator; 

/** 
* Comparator on two Integers in the usual order. 
* 
* @author liberato 
* 
*/ 
public class IntegerComparator implements Comparator<Integer> { 
    @Override 
    public int compare(Integer o1, Integer o2) { 
     return o1.compareTo(o2); 
    } 
} 

我把一些println語句和在代碼加入indent變量用於調試目的。下面是運行試驗後的輸出:

quicksort(0, 3) 
    Inside partition(0, 3) 
    pivotIndex = 1 
    Initially: [0, 3, 2, 1] 
    i = 1, pivotIndex = 1, j = 3 
    After 1st swap: [0, 1, 2, 3] 
    Pivot was swapped 
    i = 2, pivotIndex = 3, j = 2 
    After 2nd swap: [0, 1, 3, 2] 
    i = 2, pivotIndex = 3, j = 2 
    p = 2 
    quicksort(0, 1) 
    Inside partition(0, 1) 
    pivotIndex = 0 
    Initially: [0, 1, 3, 2] 
    i = 0, pivotIndex = 0, j = 0 
    After 2nd swap: [0, 1, 3, 2] 
    i = 0, pivotIndex = 0, j = 0 
    p = 0 
    quicksort(0, -1) 
    quicksort(1, 1) 
    quicksort(3, 3) 

結果:[0,1,3,2]

問題是內部partition(0, 3)其中第二交換語句反轉第一個交換的效果。有人可以幫助糾正我的快速排序算法嗎?我應該添加if語句,以便第二個交換僅在索引爲i>元素在pivotIndex時發生?

下面的代碼:

package sorters; 

import java.util.Comparator; 

import structures.SwapList; 

public class QuickSorter<T> extends AbstractSorter<T> { 
    //String indent = ""; 
    public QuickSorter(SwapList<T> list, Comparator<T> comparator) { 
     super(list, comparator); 
    } 

    @Override 
    public SwapList<T> sort() { 
     quicksort(0, list.size() - 1); 
     return list; 
    } 

    private void quicksort(int firstIndex, int lastIndex) { 
     //System.out.println(indent + "quicksort(" + firstIndex + ", " + lastIndex + ")"); 
     //indent+=" "; 
     if(firstIndex < lastIndex) { 
      int p = partition(firstIndex, lastIndex); 
      //System.out.println(indent + "p = " + p); 
      quicksort(firstIndex, p - 1); 
      quicksort(p + 1, lastIndex); 
     } 
     //indent = indent.substring(2); 
    } 

    private int partition(int firstIndex, int lastIndex) { 
     //System.out.println(indent + "Inside partition(" + firstIndex + ", " + lastIndex + ")"); 
     int pivotIndex = (firstIndex + lastIndex)/2; 
     //System.out.println(indent + "pivotIndex = " + pivotIndex); 
     int i = firstIndex; 
     int j = lastIndex; 
     while (i < j) { 
      while(list.compare(i, pivotIndex, comparator) < 0 && i < j) { 
       i++; 
      } 
      while(list.compare(j, pivotIndex, comparator) >= 0 && i < j) { 
       j--; 
      } 
      //System.out.println(indent + "Initially: " + list.toString()); 
      //System.out.println(indent + "i = " + i +", pivotIndex = " + pivotIndex + ", j = " + j); 
      if(i < j) { 
       list.swap(i, j); 
       //System.out.println(indent + "After 1st swap: " + list.toString()); 
       if(i == pivotIndex) { 
        pivotIndex = j; 
        //System.out.println(indent + "Pivot was swapped"); 
       } 
       else if(j == pivotIndex) { 
        pivotIndex = i; 
        //System.out.println(indent + "Pivot was swapped"); 
       } 
       i++; 
       j--; 
       //System.out.println(indent + "i = " + i +", pivotIndex = " + pivotIndex + ", j = " + j); 
      } 
     } 
     list.swap(pivotIndex, i); 
     //System.out.println(indent + "After 2nd swap: " + list.toString()); 
     //System.out.println(indent + "i = " + i +", pivotIndex = " + pivotIndex + ", j = " + j); 
     return i; 
    } 
} 

附加代碼:

正如在評論部分請求 -

的超AbstractSorter<T>

package sorters; 

import java.util.Comparator; 

import structures.SwapList; 

/** 
* An abstraction over the idea of a sorter. Concrete subclasses should sort the 
* list into ascending (smallest-first) order, using the provided Comparator. 
* 
* 
* @param <T> 
*/ 
public abstract class AbstractSorter<T> { 
    /** 
    * the list to be sorted 
    */ 
    protected final SwapList<T> list; 

    /** 
    * the comparator to be used 
    */ 
    protected final Comparator<T> comparator; 

    /** 
    * Constructs a new sorter, using the given list and comparator. 
    * @param list the list to be sorted 
    * @param comparator the comparator to use when sorting 
    * @throw IllegalStateException if the list has already been manipulated by a sorter 
    */ 
    public AbstractSorter(SwapList<T> list, Comparator<T> comparator) { 
     if ((list == null) || (comparator == null)) { 
      throw new NullPointerException(); 
     } 
     if (list.getComparisons() > 0 || list.getSwaps() > 0) { 
      throw new IllegalStateException(); 
     } 

     this.list = list; 
     this.comparator = comparator; 
    } 

    /** 
    * Sorts the associated list in-place, and returns a reference to it. 
    * 
    * @return a reference to the sorted list. 
    */ 
    public abstract SwapList<T> sort(); 
} 

接口SwapList<T>

package structures; 

import java.util.Comparator; 

/** 
* A list which supports the minimal operations necessary for most in-place 
* comparison-based sorts, along with two observers. 
* 
* Notably, it does not (directly) allow access to specific elements, though 
* though a toString() method is included in ArrayBasedSwapList for fans of caveman 
* debugging. 
* 
* 
* @param <T> 
*/ 
public interface SwapList<T> { 
    /** 
    * Return the result of comparator.compare() on the two elements of the list 
    * at the given indices. 
    * 
    * @param index1 
    * @param index2 
    * @param comparator 
    * @return the result of comparator.compare() on the values at the indices 
    */ 
    public int compare(int index1, int index2, Comparator<T> comparator); 

    /** 
    * Swaps the values contained in the indices of the list. 
    * @param index1 
    * @param index2 
    */ 
    public void swap(int index1, int index2); 

    /** 
    * 
    * @return the number of elements in the list 
    */ 
    public int size(); 

    /** 
    * 
    * @param comparator 
    * @return true iff the list is sorted according to the given comparator 
    */ 
    public boolean isSorted(Comparator<T> comparator); 

    /** 
    * 
    * @return the number of times swap() has been called on this list 
    */ 
    public int getSwaps(); 

    /** 
    * 
    * @return the number of times compare() has been called on this list 
    */ 
    public int getComparisons(); 
} 

和執行類ArrayBasedSwapList<T>

package structures; 

import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.Collection; 
import java.util.Comparator; 

public class ArrayBasedSwapList<T> implements SwapList<T> { 
    private final ArrayList<T> arrayList; 
    private int swaps = 0; 
    private int comparisons = 0; 

    public ArrayBasedSwapList(T[] array) { 
     arrayList = new ArrayList<T>(Arrays.asList(array)); 
    } 

    public ArrayBasedSwapList(Collection<T> coll) { 
     arrayList = new ArrayList<T>(coll); 
    } 

    @Override 
    public int compare(int index1, int index2, Comparator<T> comparator) { 
     comparisons++; 
     return comparator.compare(arrayList.get(index1), arrayList.get(index2)); 
    } 

    @Override 
    public void swap(int index1, int index2) { 
     swaps++; 
     T temp = arrayList.get(index1); 
     arrayList.set(index1, arrayList.get(index2)); 
     arrayList.set(index2, temp); 
    } 

    @Override 
    public int size() { 
     return arrayList.size(); 
    } 

    @Override 
    public boolean isSorted(Comparator<T> comparator) { 
     for (int i = 0; i < arrayList.size() - 1; i++) { 
      if (comparator.compare(arrayList.get(i), arrayList.get(i + 1)) > 0) { 
       return false; 
      } 
     } 
     return true; 
    } 

    public int getSwaps() { 
     return swaps; 
    } 

    public int getComparisons() { 
     return comparisons; 
    } 

    @Override 
    public String toString() { 
     return arrayList.toString(); 
    } 
} 

更新: 實現在@ ruakh的答案的建議,我能夠調試並找出問題。該故障發生在partition方法中。這裏的修正算法:

int partition(int firstIndex, int lastIndex) { 
    int pivotIndex = (firstIndex + lastIndex)/2; 
    int i = firstIndex; 
    int j = lastIndex; 
    while (i < j) { 
     while(i < lastIndex && list.compare(i, pivotIndex, comparator) <= 0 && i <= pivotIndex) { 
      i++; 
     } 
     if(i < pivotIndex) { 
      list.swap(i, pivotIndex); 
      pivotIndex = i; 
     } 

     while(firstIndex < j && list.compare(j, pivotIndex, comparator) >= 0 && pivotIndex <= j) { 
      j--; 
     } 

     if(j > pivotIndex) { 
      list.swap(j, pivotIndex); 
      pivotIndex = j; 
     } 
    } 
    return pivotIndex; 
} 
+2

使用調試器。 – Amit

+0

請將完整的代碼添加到您的問題 – JavaHopper

+0

我建議閱讀[Eric Lippert的「如何調試小程序」](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/)。你可以通過自己調試來發現錯誤,而不是通過在互聯網上找到陌生人來發現錯誤並告訴你。 – ruakh

回答

1

我看到partition方法三個錯誤或有爭議的錯誤:

  • 的方法是private,這意味着你沒有單元測試它,即使它是這是你得到的最複雜的代碼之一。
    • 您可以通過使其解決這個包私有的(一些API,如番石榴,提供了一個特殊的@VisibleForTesting註釋,你可以用弄清楚爲什麼你已經這樣做了),或者通過分解出來進入它自己的類QuickSorter代表。
  • 你的算法假定i <= pivotIndex && pivotIndex <= j在任何時候(因爲否則list.swap(i, j)無所作爲),但它只是確保i <= j
    • 我通過代碼檢查確定了這一點,但是一旦我看到了這個錯誤,我檢查了你的調試輸出,實際上這個問題確實出現在那裏:i = 2, pivotIndex = 3, j = 2。不幸的是,這是所有調試方法的侷限性:你可以看到問題的正確性,但是如果你不知道你在找什麼,你可能看不到它。一個好的策略是休息一下,然後用新鮮的眼睛回來。 (好吧,假設你已經給了自己足夠的時間了,那是可行的。)
    • 實際上,這實際上是兩個錯誤,而不僅僅是一個:至少有兩種完全不同的方式,i <= pivotIndex && pivotIndex <= j會變成錯誤的。我認爲你確實需要這個假設才能保持真實。 (也就是說,問題不在於你做出了假設,而是因爲你不能滿足它。)
    • 除了解決此問題,您可能會考慮使用斷言和驗證,以便您的代碼一旦進入不良狀態就會爆炸。 (也就是說,在一個複雜的代碼單元的中間可能很難考慮有用的斷言,找出正確的斷言並不比簡單地注意錯誤開始簡單得多,所以,不知道)
  • 接近尾聲的list.swap(pivotIndex, i)沒有很好的動機。相反,它看起來像是試圖修補一個錯誤?如果是這樣,那麼你應該總是爲了修復它們而引發錯誤,而不是在不瞭解發生了什麼的情況下嘗試解決它們。否則,您永遠無法確定您的解決方案是否完全解決了問題(並且根據我的經驗,您通常可以確定它不是不是)。

我想你可以看到,順便說一句,爲什麼我不分享Amit對調試器的喜愛。你的調試輸出顯示了你的錯誤,但你沒有看到它;你可能會得到與調試器相同的結果。 (如果有的話,一個調試器可能會讓它更難看清全貌。)也就是說,我認爲你應該給調試器一個嘗試;許多人確實發現它們很有用,而且在你的武器庫裏再也找不到武器。而當問題出在你沒有注意到問題時,誰知道以兩種不同的方式(在調試輸出和調試器中)看到相同的東西可能會增加一次你會注意到的機會?

+0

我完全錯過了使用JUnit測試的觀點,同時保持私有方法!今後我會記住這一點。 – ByteMan2021

+0

至於第三個錯誤,我現在確信不需要第二次交換。我想我對快速排序算法的不同變體感到困惑。我看到的方式是,一種方法是在循環結束時(我試過)將'pivot'元素與'i'(或'j')交換,而另一個方法是返回'我(或'j')。我認爲我犯的錯誤是用第一種方法,第一個(或最後一個)元素被選爲pivot **或** pivot(我的情況中的中間元素)被交換到其中一個末端。 – ByteMan2021

+0

關於你指出的第二個錯誤,我並不完全相信我的算法在假設'i <= pivotIndex && pivotIndex <= j'的情況下工作,或者我可能還沒有看到它;在這種情況下,我可以按照你的建議使用休息。此外,糾正我,如果我錯了,但假設的第一部分,即'我<= pivotIndex'是真的,這對我的算法來說是非常重要的。我目前的理解是,在給定的時間,('i'左邊的元素) = pivot元素(...繼續下一條評論) – ByteMan2021

1

除了@ruakh指出的之外,我觀察到在調用partition()之後,數據透視表可以保留在數組的任何部分。然而,因爲在樞軸索引處的元素是可靠分類的,甚至可以在list的極限末端;有條件地調用quicksort()應該可以解決你的問題。另外,請記住@ ruakh的回答中已經突出顯示的邊緣案例,以下代碼應該解決您的所有問題。我已經無限測試了這個例程,對於任何破案,迄今都找不到。

快速排序方法:

private void quicksort(int firstIndex, int lastIndex) { 
      //System.out.println(indent + "quicksort(" + firstIndex + ", " + lastIndex + ")"); 
      //indent+=" "; 
      if(firstIndex < lastIndex) { 
       int p = partition(firstIndex, lastIndex); 
       //System.out.println(indent + "p = " + p); 
       if (firstIndex < p - 1) 
        quicksort(firstIndex, p - 1); 
       if (p < lastIndex) 
        quicksort(p, lastIndex); 
      } 
//   indent = indent.substring(2); 
     } 

分區方法:

private int partition(int firstIndex, int lastIndex) { 
      //System.out.println(indent + "Inside partition(" + firstIndex + ", " + lastIndex + ")"); 
      int pivotIndex = (firstIndex + lastIndex)/2; 
      //System.out.println(indent + "pivotIndex = " + pivotIndex); 
      int i = firstIndex; 
      int j = lastIndex; 
      while (i <= j) { 
       while(list.compare(i, pivotIndex, comparator) < 0) { 
        i++; 
       } 
       while(list.compare(j, pivotIndex, comparator) > 0) { 
        j--; 
       } 
       //System.out.println(indent + "Initially: " + list.toString()); 
       //System.out.println(indent + "i = " + i +", pivotIndex = " + pivotIndex + ", j = " + j); 
       if(i <= j) { 
        list.swap(i, j); 
        //System.out.println(indent + "After 1st swap: " + list.toString()); 
        if(i == pivotIndex) { 
         pivotIndex = j; 
         //System.out.println(indent + "Pivot was swapped"); 
        } 
        else if(j == pivotIndex) { 
         pivotIndex = i; 
         //System.out.println(indent + "Pivot was swapped"); 
        } 
         i++; 
         j--; 
        //System.out.println(indent + "i = " + i +", pivotIndex = " + pivotIndex + ", j = " + j); 
       } 
      } 
//   list.swap(pivotIndex, i); 
      //System.out.println(indent + "After 2nd swap: " + list.toString()); 
      //System.out.println(indent + "i = " + i +", pivotIndex = " + pivotIndex + ", j = " + j); 
      return i; 
     } 

請注意,我並沒有改變你的代碼,這被註釋掉或任何其他的其它部分方法

測試儀等級:

package test; 

import java.util.Comparator; 
import java.util.Random; 

public class Test 
{ 

    private static final Comparator<Integer> INTEGER_COMPARATOR = new IntegerComparator(); 

    public static void main(String args[]) { 
     SwapList<Integer> customList; 
     AbstractSorter<Integer> customSorter; 
     do { 
      Random r = new Random(); 
      int length = r.nextInt(10); 
      Integer[] test = new Integer[length]; 
      for(int j = 0; j < length; j++) 
       test[j] = r.nextInt(10); 
      customList = new ArrayBasedSwapList<Integer>(test); 
      customSorter = new QuickSorter<Integer>(customList, 
        INTEGER_COMPARATOR); 
      SwapList<Integer> result = customSorter.sort(); 
      if(!result.isSorted(INTEGER_COMPARATOR)) { 
       System.out.println("Result: " + result.toString()); 
       System.out.println(result.isSorted(INTEGER_COMPARATOR)); 
      } 
     } while(true); 
    } 

} 

隨時評論任何失敗的測試用例,我會盡力解決這個問題。

+0

對不起,但我不認爲這是一個好的答案。 OP顯然參與了教育活動(可能是爲了一堂課);簡單地爲他/她修復他/她的代碼,甚至沒有完全解釋你在做什麼以及爲什麼,都沒有幫助。 – ruakh

+0

毫無疑問,您的代碼有效。儘管我同意@ruakh;我注意到你刪除了內部'while'循環中的兩個'i ByteMan2021