簡體   English   中英

使用鏈表進行分區快速排序

[英]quicksort with partition using linked list

我嘗試使用鏈表實現帶分區的快速排序。 我用常規數組做了幾次並且我很好地理解了這個想法,但是對於鏈表它真的不同。 我做了第一部分,然后卡住了。 這是我堅持的要點,假設我有一個數組{ 5, 3, 7, 1, 9, 8, 2, 5, 6 }所以 pivot 始終是第一個數字 - 5 我構建了一個新的 2 數組,一個數字小於 5(包括 5),另一個數字大於 5,然后像這樣重建主列表{ 3, 1, 2, 5, 5, 7, 9, 8, 6 } ;

到目前為止,我在 3 次運行后得到的最好結果是 1 -> 2 -> 3 -> 5 -> 5 -> 7 -> 9 -> 8 -> 6; 但是因為我一直調用 1 作為分區,所以我得到了相同的結果。

我有 2 function 分區和快速排序 - 快速排序需要遞歸 function 並且它是我實現它的主要問題。

如果數組(大/小)不為空,則標志 == 1

我的代碼:

void partition(list **lst, list **pivot, list **small, list **big) {
    list *temp = *lst;
    list *pv = *lst;
    *pivot = pv;
    int big_flag = 0;
    int small_flag = 0;

    *small = (list *)(malloc(sizeof(list)));
    list *n_small = *small;
    list *prev_small = NULL;

    *big = (list *)(malloc(sizeof(list)));
    list *n_big = *big;
    list *prev_big = NULL;

    int p = pv->data;
    pv = pv->next;
    while (pv) {
        if (pv->data <= p) {
            n_small->data = pv->data;
            prev_small = n_small;
            n_small->next = (list *)(malloc(sizeof(list)));
            n_small = n_small->next;
            small_flag = 1;
        } else {
            n_big->data = pv->data;
            prev_big = n_big;
            n_big->next = (list *)(malloc(sizeof(list)));
            n_big = n_big->next;
            big_flag = 1;
        }
        pv = pv->next;
    }
    pv = *lst; // Move the pointer back to the start;

    if (small_flag == 1) {
        n_small = prev_small;
        n_small->next = NULL;
        prev_small = *small;
        // Built the new lst by order 
        while (prev_small) {
            pv->data = prev_small->data;
            pv = pv->next;
            prev_small = prev_small->next;
        }
    }

    // add the pivot to the array 
    pv->data = p;
    pv = pv->next;

    if (big_flag == 1) {
        n_big = prev_big;
        n_big->next = NULL;
        prev_big = *big;
        while (prev_big) {
            pv->data = prev_big->data;
            pv = pv->next;
            prev_big = prev_big->next;
        }
    }
}

在這里我真的不確定我的停止條件是什么

void quickSortList(list **lst) {
    list *temp = *lst;
    list *big;
    list *small;
    list *pivot;
    while (temp->next != NULL) {
        partition(lst, &pivot, &small, &big);
        quickSortList(&small);
        quickSortList(&big);
    }
}

數組上的快速排序是這樣工作的:

  • 如果數組沒有或只有一個元素,則什么也不做。 否則:
  • 選擇一個 pivot 並將其移開;
  • 對數組進行分區:左邊小於pivot go的元素;
  • 將 pivot 放在兩個分區之間; 這是排序數組中的最后一個 position;
  • 對左右分區進行排序。

該算法不需要額外的memory,原地運行,所有移動都是元素交換。

鏈表上的快速排序是這樣工作的:

  • 如果數組沒有或只有一個元素,則什么也不做。 否則:
  • 選擇一個 pivot 並將其移開;
  • 通過將元素移動到兩個新列表之一來划分數組,具體取決於元素是否小於 pivot;
  • 對左右分區進行排序;
  • 連接列表:元素 less + pivot + 其他元素

這個算法不需要額外的memory。它工作到位,所有的動作都是對head pointers和next field的調整。 只有鏈接發生變化。 節點留在它們的位置。

這兩種變體之間的主要區別在於您對分區進行排序的時間。 數組元素的索引是position,所以排序前必須知道分區的position,所以我們把pivot放在最前面。

鏈表元素通過鏈接進行索引。 在排序之前必須知道頭節點和尾節點,所以我們必須先排序。 然后我們可以連接列表。

所以讓我們寫下我們的 function:

void quicksort(Node **head);

稍等一下:后面我們需要拼接鏈表,也就是說我們需要找到鏈表的尾部。 這是一個單鏈表,所以我們必須遍歷它。 我們可以通過返回排序列表的尾部來節省一些循環,所以:

Node *quicksort(Node **head);

好的,首先是基本情況:

    if (*head == NULL) return NULL;
    if ((*head)->next == NULL) return *head;

我們需要兩個列表:

    Node *lt = NULL;        // less than pivot
    Node *ge = NULL;        // greater than or equal to pivot

pivot 是第一個節點,所以是*head pivot是不會分區的,所以我們從之后的節點開始分區:

    Node *node = (*head)->next;

我們遍歷鏈表並刪除每個節點。 然后我們將該節點插入到兩個分區列表之一的頭部。 (在前面插入更容易和更快,但是這樣做會使算法“不穩定”,即不保留兩個相等元素的順序。我們暫時不要擔心。)

    while (node) {
        Node *next = node->next;

        if (node->data < (*head)->data) {
            node->next = lt;
            lt = node;
        } else {
            node->next = ge;
            ge = node;
        }

        node = next;
    }

我們必須保存在一個臨時的next ,因為我們正在覆蓋我們剛剛移動的節點的next字段。

現在對分區進行排序:

    Node *ltail = quicksort(&lt);
    Node *gtail = quicksort(&ge);

注意ltgt (因此ltailgtail )可能是NULL 我們有以下帶有{head, tail}的列表:

{lt, ltail} + {pivot, pivot} + {ge, gtail}

pivot*head 如果ltail不是NULLltail->next = pivot和 pivot- pivot->next = ge ,可能是NULL也可能不是。 最后, *head必須是整體的新頭:

    (*head)->next = ge;
    
    if (gtail == NULL) gtail = *head;
  
    if (lt) {
        ltail->next = *head;
        *head = lt;
    }

在這個小舞蹈之后, *head是列表的新頭部, gtail是新的尾部。

這是所有內容:

Node *quicksort(Node **head)
{
    // base cases: empty list and single node

    if (*head == NULL) return NULL;
    if ((*head)->next == NULL) return *head;
    
    // partition with *head as pivot

    Node *lt = NULL;        // less than pivot
    Node *ge = NULL;        // greater than or equal to pivot

    Node *node = (*head)->next;

    while (node) {
        Node *next = node->next;

        if (node->data < (*head)->data) {
            node->next = lt;
            lt = node;
        } else {
            node->next = ge;
            ge = node;
        }

        node = next;
    }
    
    // quick-sort recursively

    Node *ltail = quicksort(&lt);
    Node *gtail = quicksort(&ge);

    // rearrange lists: lt -> pivot -> ge

    *head = *head;
    (*head)->next = ge;
    
    if (gtail == NULL) gtail = *head;
  
    if (lt) {
        ltail->next = *head;
        *head = lt;
    }

    return gtail;
}

是一個完整的小例子。

最后兩點:您可以通過在分區列表的末尾插入來使算法穩定。 而且您不需要兩個分區列表:您還可以從原始列表中提取較少的節點並將其插入到le列表中。 reiaining 元素是另一個分區,如果你正確地提取, pivot->next已經有正確的值。 在記憶方面,只使用一個列表不會給你帶來任何好處。)

您的實施有多個問題:

  • 您的quickSortList() function 將永遠運行,因為您沒有在循環體中更新temp 您應該測試列表是否至少有 3 個元素並且不需要while循環。
  • 遞歸后不重新組合子列表,因此排序無法成功。
  • 分區 function 不應分配 memory,它應僅將節點分配到 3 個子列表中:一個數據較小的節點列表,一個數據等於樞軸的節點和一個數據較大的節點。 但是請注意,第一個和最后一個子列表可能為空。

這是執行穩定排序的完整實現,這對於鏈表是可行的,但通常不適用於 arrays,因為它的成本更高。

#include <stdio.h>
#include <stdlib.h>

typedef struct list {
    struct list *next;
    int data;
} list;

void partition(list **lst, list **pivot, list **small, list **big) {
    list *cur = *lst;
    list *pivot_head = cur, **pivot_link = &cur->next;
    list *small_head = NULL, **small_link = &small_head;
    list *big_head = NULL, **big_link = &big_head;
    int data = pivot_head->data;

    /* distribute the list into 3 sublists */
    while ((cur = cur->next) != NULL) {
        if (cur->data == data) {
            *pivot_link = cur;
            pivot_link = &cur->next;
        } else
        if (cur->data < data) {
            *small_link = cur;
            small_link = &cur->next;
        } else {
            *big_link = cur;
            big_link = &cur->next;
        }
    }
    /* close the sublists */
    *pivot_link = NULL;
    *small_link = NULL;
    *big_link = NULL;
    /* return the sublist heads */
    *pivot = pivot_head;
    *small = small_head;
    *big = big_head;
}

list *appendList(list *a, list *b) {
    if (a) {
        list *node = a;
        while (node->next)
            node = node->next;
        node->next = b;
        return a;
    } else {
        return b;
    }
}

void quickSortList(list **lst) {
    list *temp = *lst, *big, *small, *pivot;
    if (temp && temp->next) {
        partition(lst, &pivot, &small, &big);
        quickSortList(&small);
        quickSortList(&big);
        *lst = appendList(small, appendList(pivot, big));
    }
}

list *newList(int data) {
    list *node = malloc(sizeof(*node));
    node->data = data;
    node->next = NULL;
    return node;
}

void freeList(list **lst) {
    list *cur = *lst;
    *lst = NULL;
    while (cur) {
        list *next = cur->next;
        free(cur);
        cur = next;
    }
}

void printList(const list *node) {
    for (; node; node = node->next)
        printf(" %d", node->data);
    printf("\n");
}

int main() {
    list *head = NULL, *tail = NULL, *node;
    int p;

    while (scanf("%d", &p) == 1) {
        node = newList(p);
        if (head == NULL) {
            tail = head = node;
        } else {
            tail = tail->next = node;
        }
    }
    printList(head);
    quickSortList(&head);
    printList(head);
    freeList(&head);
    return 0;
}

我保留了您的 API 用於quickSortListpartition ,但跟蹤各處的尾節點以避免在appendList()中進行額外掃描會更有效。

如果您可以更改 API,這里有一個無需額外掃描的替代方案:

/* sort the list and return a pointer to the tail node next pointer */
list **quickSortList(list **lst) {
    list *cur = *lst;
    if (cur == NULL) {
        return NULL;
    } else
    if (!cur->next) {
        return &cur->next;
    } else {
        list *pivot = cur, **pivot_link = &cur->next;
        list *small = NULL, **small_link = &small;
        list *big = NULL, **big_link = &big;
        int data = pivot->data;

        /* distribute the list into 3 sublists */
        while ((cur = cur->next) != NULL) {
            if (cur->data == data) {
                *pivot_link = cur;
                pivot_link = &cur->next;
            } else
            if (cur->data < data) {
                *small_link = cur;
                small_link = &cur->next;
            } else {
                *big_link = cur;
                big_link = &cur->next;
            }
        }
        *small_link = NULL;
        if (small) {
            small_link = quickSortList(&small);
            *lst = small;
            *small_link = pivot;
        } else {
            *lst = pivot;
        }
        *big_link = NULL;
        if (big) {
            big_link = quickSortList(&big);
            *pivot_link = big;
            return big_link;
        } else {
            *pivot_link = NULL;
            return pivot_link;
        }
    }
}

暫無
暫無

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

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