I try to implement quicksort with partition using linked list. I did it few times with regular array and I got the idea well. but with linked list it is really different. I did the first part and than I got stuck. This is the point I stick let say I have an array { 5, 3, 7, 1, 9, 8, 2, 5, 6 } so the pivot is always the first number - 5; I build new 2 array one with numbers lower than five (include 5) and another with number bigger than 5 and after that rebuild the main list like that {3,1,2,5,5,7,9,8,6};
the best result I got until now after 3 runs is 1 -> 2 -> 3 -> 5 -> 5 -> 7 -> 9 -> 8 -> 6; but because I keeping calling 1 as the partition I get the same result.
I have 2 function partition and quicksort - quicksort need to be recursive function and it my main problem to implement it.
the flag == 1 if array (big/small) isn't empty
my code:
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;
}
}
}
Here I'm really not sure what need to be my stop condition
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);
}
}
CodePudding user response:
Quicksort on an array works like this:
- do nothing if the array has no or only one element. Otherwise:
- pick a pivot and move it out of the way;
- partition the array: elements that are less than the pivot go to the left;
- put the pivot between the two partitions; this is its final position in the sorted array;
- sort the left and right partitions.
This algorithm does not need extra memory. It works in place and all movements are element swaps.
Quicksort on a linked list works like this:
- do nothing if the array has no or only one element. Otherwise:
- pick a pivot and move it out of the way;
- partition the array by moving the elements to one of two new lists, depending on whether the element is less than the pivot or not;
- sort the left and right partitions;
- concatenate the lists: elements less pivot other elements
This algorithm does not need extra memory. It works in place and all movements are adjustments to the head pointers and next
field. Only the links change. The nodes stay in their place.
The major difference between these two variants is when you sort the partitions. Array elements are indexed by position, so the position of the partition must be known before sorting, so we place the pivot first.
Linked-list elements are indexed by following the links. The head and tail nodes must be known before sorting, so we must sort first. Then we can concatenate the lists.
So let's write our function:
void quicksort(Node **head);
Wait a moment: Later we need to concatenate linked lists, which means that we need to find the tail of the list. It's a singly linked list, so we must traverse it. We can save some cycles by returning the tail of the sorted list, so:
Node *quicksort(Node **head);
Okay, the base cases first:
if (*head == NULL) return NULL;
if ((*head)->next == NULL) return *head;
We need two lists:
Node *lt = NULL; // less than pivot
Node *ge = NULL; // greater than or equal to pivot
The pivot is the first node, so the *head
. The pivot will not be partitioned, so we start partitioning at the node after that:
Node *node = (*head)->next;
We walk the linked list and remove each node. Then we insert that node at the head of one of the two partition lists. (It's easier and faster to insert at the front, but doing so will make the algorithm "unstable", that is the order of two equal elements is not retained. Let's not worry about that right now.)
while (node) {
Node *next = node->next;
if (node->data < (*head)->data) {
node->next = lt;
lt = node;
} else {
node->next = ge;
ge = node;
}
node = next;
}
We must save next
to a temporary, because we are overwriting the next
field of the node we have just moved.
Now sort the partitions:
Node *ltail = quicksort(<);
Node *gtail = quicksort(&ge);
Nothe that both lt
and gt
(and therefore ltail
and gtail
) may be NULL
. We have the following lists with {head, tail}
:
{lt, ltail} {pivot, pivot} {ge, gtail}
The pivot
is *head
. If ltail
is not NULL
, ltail->next = pivot
and pivot->next = ge
, which may be NULL
or not. Finally, *head
must be the overall new head:
(*head)->next = ge;
if (gtail == NULL) gtail = *head;
if (lt) {
ltail->next = *head;
*head = lt;
}
After this little dance, *head
is the new head of the list and gtail
is the new tail.
Here's everything put together:
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;
}
And here is a complete small example.
Two final remarks: You can make the algorithm stable by inserting at the end of the partition lists. And you don't need two partition lists: You could also extract the nodes that are less from the original list and insert it to the le
list. The reiaining elements are the other partition and if you do your extraction right, pivot->next
has already the correct value. Memory-wise using just one list won't buy you anything.)
CodePudding user response:
Your implementation has multiple problems:
- your
quickSortList()
function would run forever because you do not updatetemp
in the body of the loop. You should test if the list has at least 3 elements and there is no need for awhile
loop. - you do not recombine the sublists after recursing, so the sort cannot succeed.
- the partition function should not allocate memory, it should merely distribute the nodes into 3 sublists: a list of nodes with smaller data, one with nodes with data equal to the pivot's and one with larger data. Note however that the first and last sublists may be empty.
Here is a full implementation that performs stable sorting, which is possible for linked lists but usually not for arrays as it is more costly.
#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;
}
I kept your API for quickSortList
and partition
but it would be more efficient to keep track of tail nodes everywhere to avoid the extra scan in appendList()
.
If you can change the API, here is an alternative without extra scans:
/* 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;
}
}
}