[英]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);
}
}
數組上的快速排序是這樣工作的:
該算法不需要額外的memory,原地運行,所有移動都是元素交換。
鏈表上的快速排序是這樣工作的:
這個算法不需要額外的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(<);
Node *gtail = quicksort(&ge);
注意lt
和gt
(因此ltail
和gtail
)可能是NULL
。 我們有以下帶有{head, tail}
的列表:
{lt, ltail} + {pivot, pivot} + {ge, gtail}
pivot
是*head
。 如果ltail
不是NULL
, ltail->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(<);
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
循環。這是執行穩定排序的完整實現,這對於鏈表是可行的,但通常不適用於 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 用於quickSortList
和partition
,但跟蹤各處的尾節點以避免在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.