簡體   English   中英

QuickSort和Hoare分區

[英]QuickSort and Hoare Partition

我很難將QuickSort與Hoare分區轉換為C代碼,但無法找到原因。 我正在使用的代碼如下所示:

void QuickSort(int a[],int start,int end) {
    int q=HoarePartition(a,start,end);
    if (end<=start) return;
    QuickSort(a,q+1,end);
    QuickSort(a,start,q);
}

int HoarePartition (int a[],int p, int r) {
    int x=a[p],i=p-1,j=r;
    while (1) {
        do  j--; while (a[j] > x);
        do  i++; while (a[i] < x);

        if  (i < j)
            swap(&a[i],&a[j]);
        else
            return j;
    }
}

另外,我真的不明白為什么HoarePartition有效。 有人可以解釋它為什么有效,或者至少把我鏈接到一篇文章嗎?

我已經看到了分區算法的逐步完成,但我沒有直觀的感覺。 在我的代碼中,它似乎甚至沒有用。 例如,給定數組

13 19  9  5 12  8  7  4 11  2  6 21

它將使用數據透視表13,但最終會使用數組

 6  2  9  5 12  8  7  4 11 19 13 21 

並且將返回j ,即a[j] = 11 我認為從那個點開始並且前進的數組應該具有比樞軸更大的值,這應該是真的,但是這不是真的,因為11 <13。

這是Hoare分區的偽代碼(來自CLRS,第二版),如果這很有用:

Hoare-Partition (A, p, r)
    x ← A[p]
    i ← p − 1
    j ← r + 1
    while  TRUE
        repeat   j ←  j − 1
            until     A[j] ≤ x
        repeat   i ←  i + 1
            until     A[i] ≥ x
        if  i < j
            exchange  A[i] ↔ A[j]
        else  return   j 

謝謝!

編輯:

這個問題的正確C代碼將最終成為:

void QuickSort(int a[],int start,int end) {
    int q;
    if (end-start<2) return;
    q=HoarePartition(a,start,end);
    QuickSort(a,start,q);
    QuickSort(a,q,end);
}

int HoarePartition (int a[],int p, int r) {
    int x=a[p],i=p-1,j=r;
    while (1) {
        do  j--; while (a[j] > x);
        do  i++; while (a[i] < x);
        if  (i < j) 
            swap(&a[i],&a[j]);
        else 
            return j+1;
    }
}

回答“為什么Hoare分區工作?”的問題:

讓我們將數組中的值簡化為三種: L值(小於透視值的值), E值(等於透視值)和G值(大於透視值的值)。

我們還將為數組中的一個位置指定一個特殊名稱; 我們將這個位置稱為s ,它是程序結束時j指針所在的位置。 我們是否事先知道哪個位置s是? 不,但我們知道某個位置會符合該描述。

使用這些術語,我們可以用稍微不同的術語表達分區過程的目標:它是將單個數組拆分成兩個較小的子數組,這些子數組不會相互錯誤排序 如果滿足以下條件,則滿足“未錯誤排序”的要求:

  1. 從陣列的左端到包含s的“低”子陣列不包含G值。
  2. “高”子數組在s之后立即開始並繼續到右端,不包含L值。

這才是我們真正需要做的。 我們甚至不用擔心E值在任何給定的傳球中都會結束。 只要每次傳遞使子陣列相對於彼此正確,后來的傳遞將處理任何子陣列內存在的任何障礙。

所以,現在讓我們來解決從對方的問題:如何划分程序保證有在S或以它的左邊沒有G值,並且不使用LS的吧?

好吧,“ s右邊的值集合”與“ j指針到達s之前移動的單元格集”相同。 並且“包括s的左邊的值集合”與“在j到達s之前i指針移動的值的集合”相同。

這意味着在循環的某些迭代中,任何放錯位置的值將位於我們的兩個指針中。 (為方便起見,我們說這是第j指針在L值指向,雖然它的工作原理完全為指針在G值指向相同的。)在的指針會在哪里,當第j指針是一個錯位的價值? 我們知道它將是:

  1. 在“低”子陣列中的某個位置, L值可以沒有問題;
  2. 指向一個值為EG值的值,可以輕松替換j指針下的L值。 (如果它不是EG值,它就不會停在那里。)

請注意,有時ij指針實際上都會停止在E值上。 發生這種情況時,即使不需要,也會切換值。 但這並沒有造成任何傷害; 我們之前說過, E值的放置不會導致子陣列之間的錯誤排序。

總而言之,Hoare分區的工作原理是:

  1. 它將一個數組分成較小的子數組,這些子數組相對於彼此沒有錯誤排序;
  2. 如果你繼續這樣做並遞歸地對子數組進行排序,那么最終將沒有任何內容未被排序。

我相信這段代碼存在兩個問題。 對於初學者來說,在你的Quicksort功能中,我想你想重新排序

 int q=HoarePartition(a,start,end);
 if (end<=start) return;

所以你有這樣的:

 if (end<=start) return;
 int q=HoarePartition(a,start,end);

但是,你應該做的比這更多; 特別是這應該讀

 if (end - start < 2) return;
 int q=HoarePartition(a,start,end);

原因是如果您嘗試分區的范圍大小為零或一,則Hoare分區無法正常工作。 在我的CLRS版本中,這里沒有提到; 我不得不去書的勘誤頁找到這個。 這幾乎可以肯定是“訪問超出范圍”錯誤所遇到的問題的原因,因為在不變的情況下,您可以直接從陣列運行!

至於Hoare分區的分析,我建議首先手動追蹤它。 還有一個更詳細的分析在這里 直觀地說,它通過從范圍的兩端向另一端增長兩個范圍來工作 - 一個在左側包含小於樞軸的元素,一個在右側包含比樞軸大的元素。 這可以稍微修改以產生Bentley-McIlroy分區算法(在鏈接中引用),該算法可以很好地擴展以處理相等的密鑰。

希望這可以幫助!

你的最終代碼是錯誤的,因為j的初始值應該是r + 1而不是r 否則,您的分區函數始終忽略最后一個值。

實際上,HoarePartition是有效的,因為對於包含至少2個元素(即p < r )的任何數組A[p...r]A[p...j]每個元素都是<= A[j+1...r]每個元素A[j+1...r]終止時。 所以主算法重復出現的下兩個段是[start...q][q+1...end]

所以正確的C代碼如下:

void QuickSort(int a[],int start,int end) {
    if (end <= start) return;
    int q=HoarePartition(a,start,end);
    QuickSort(a,start,q);
    QuickSort(a,q + 1,end);
}

int HoarePartition (int a[],int p, int r) {
    int x=a[p],i=p-1,j=r+1;
    while (1) {
        do  j--; while (a[j] > x);
        do  i++; while (a[i] < x);
        if  (i < j) 
            swap(&a[i],&a[j]);
        else 
            return j;
    }
}

更多說明:

  1. 分區部分只是偽代碼的翻譯。 (注意返回值是j

  2. 對於遞歸部分,請注意基本情況檢查( end <= start而不是end <= start + 1否則您將跳過[2 1]子陣列)

你最后的C代碼是有效的。 但這並不直觀。 現在我幸運地正在學習CLRS。 在我看來,CLRS的偽代碼是錯誤的。(在2e)最后,我發現改變一個地方是正確的。

 Hoare-Partition (A, p, r)
 x ← A[p]
     i ← p − 1
     j ← r + 1
 while  TRUE
        repeat   j ←  j − 1
            until     A[j] ≤ x
    repeat   i ←  i + 1
            until     A[i] ≥ x
    if  i < j
              exchange  A[i] ↔ A[j]
    else  
              exchnage  A[r] ↔ A[i]  
              return   i

是的,添加交換A [r]↔A [i]可以使其有效。 為什么? 因為A [i]現在大於A [r] OR i == r。 所以我們必須交換以保證分區的功能。

  1. 將樞軸移至第一位。 (例如,使用三個中值。切換到小輸入大小的插入排序。)
  2. 划分,
    • 重復交換當前最左邊的1與當前最右邊的0。
      0 - cmp(val,pivot)== true,1 - cmp(val,pivot)== false。
      如果沒有離開<停止。
    • 之后,交換樞軸與最右邊的0。

首先,你誤解了Hoare的分區算法,可以從c中的翻譯代碼中看出,因為你認為樞軸是子陣列的最左邊元素。

我會解釋你將最左邊的元素視為樞軸。

int HoarePartition (int a[],int p, int r) 

這里p和r表示數組的下限和上限,它也可以是要分區的較大數組(子陣列)的一部分。

所以我們從最初指向數組終點之前和之后的指針(標記)開始(簡單地使用do while循環進行bcoz)。因此,

i=p-1,

j=r+1;    //here u made mistake

現在按照分區我們希望樞軸左邊的每個元素都小於或等於pivot,大於pivot的右側。

因此,我們將移動'i'標記,直到我們得到大於或等於樞軸的元素。 類似'j'標記,直到我們發現元素小於或等於pivot。

現在,如果我<j我們交換元素bcoz這兩個元素都在數組的錯誤部分。 所以代碼將是

do  j--; while (a[j] <= x);                 //look at inequality sign
do  i++; while (a[i] >= x);
if  (i < j) 
    swap(&a[i],&a[j]);

現在,如果'i'不小於'j',那意味着現在交換中沒有元素,所以我們返回'j'位置。

所以現在分區后半部分的數組是從'start to j'

上半部分是從'j + 1到結尾'

所以代碼看起來像

void QuickSort(int a[],int start,int end) {
    int q=HoarePartition(a,start,end);
    if (end<=start) return;
    QuickSort(a,start,q);
    QuickSort(a,q+1,end);
}

在Java中直接實現。

public class QuickSortWithHoarePartition {

    public static void sort(int[] array) {
        sortHelper(array, 0, array.length - 1);
    }

    private static void sortHelper(int[] array, int p, int r) {
        if (p < r) {
            int q = doHoarePartitioning(array, p, r);
            sortHelper(array, p, q);
            sortHelper(array, q + 1, r);
        }
    }

    private static int doHoarePartitioning(int[] array, int p, int r) {
        int pivot = array[p];
        int i = p - 1;
        int j = r + 1;

        while (true) {

            do {
                i++;
            }
            while (array[i] < pivot);

            do {
                j--;
            }
            while (array[j] > pivot);

            if (i < j) {
                swap(array, i, j);
            } else {
                return j;
            }
        }

    }

    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM