Home > Enterprise >  quicksort with partition using linked list
quicksort with partition using linked list

Time:05-07

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(&lt);
    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(&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;
}

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 update temp in the body of the loop. You should test if the list has at least 3 elements and there is no need for a while 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;
        }
    }
}
  • Related