complexity of mergesort with linked list

2019-02-27 12:06发布

问题:

i have code for mergesort using linked list,it works fine,my question what is complexity of this algorithm?is it O(nlog(n))?also is it stable?i am interested because as i know mergesort is stable,what about using linked list?if we have elements with some equal with each-other,does this code preserve orders of elements?thanks a lot

#include<stdio.h>
#include <stdlib.h>
struct node
{
    int number;
    struct node *next;

};
struct node *addnode(int number,struct node *next);
struct node*mergesort(struct node *head);
struct node *merge(struct node *one,struct node *two);

int main(void){
    struct node *head;
    struct node *current;
    struct node *next;
    int test[]={8,3,1,4,2,5,7,0,11,14,6};
    int n=sizeof(test)/sizeof(test[0]);
    int i;
    head=NULL;
     for (i=0;i<n;i++)
         head=addnode(test[i],head);
     i=0;
     head=mergesort(head);
    printf("before----after sort \n");
    for (current=head;current!=NULL;current=current->next)
        printf("%4d\t%4d\n",test[i++],current->number);

    /* free list */
    for (current=head;current!=NULL;current=current->next)
        next=current->next;free(current);
return 0;
}

struct node *addnode(int number,struct node* next){
    struct node *tnode;
    tnode=(struct node*)malloc(sizeof(*tnode));
    if(tnode!=NULL){
        tnode->number=number;
        tnode->next=next;
            }

     return tnode;
     }
struct node *mergesort(struct node *head){

    struct node *head_one;
    struct node *head_two;
    if((head==NULL) ||(head->next==NULL))
         return head;
    head_one=head;
    head_two=head->next;
    while( (head_two!=NULL) &&(head_two->next!=NULL)){
        head=head->next;
        head_two=head->next->next;
        }
    head_two=head->next;
    head->next=NULL;
    return merge(mergesort(head_one),mergesort(head_two));
    }
struct node *merge(struct node*head_one,struct node*head_two){

    struct node *head_three;
    if(head_one==NULL)
         return head_two;
    if(head_two==NULL)
         return head_one;
    if(head_one->number<head_two->number){

head_three=head_one;
head_three->next=merge(head_one->next,head_two);
    }
    else
    {

        head_three=head_two;
        head_three->next=merge(head_one,head_two->next);


    }

    return head_three;
    }

回答1:

You've got a typo in your code. With it corrected, it is indeed stable, and of O(n log n) complexity. Although to be sure, you really should reimplement your merge as a loop instead of recursion. C doesn't have tail call optimization (right?), so this can mess things up there:

struct node *mergesort(struct node *head){

    struct node *head_one;
    struct node *head_two;
    if((head==NULL) ||(head->next==NULL))
         return head;
    head_one=head;
    head_two=head->next;
    while( (head_two!=NULL) &&(head_two->next!=NULL)){
        head=head->next;
        // head_two=head->next->next;      // -- the typo, corrected:
        head_two=head_two->next->next;
        }
    head_two=head->next;
    head->next=NULL;
    return merge(mergesort(head_one),mergesort(head_two));
    }

And while we're at it, change your workflow from

    return merge(mergesort(head_one),mergesort(head_two));

to

    struct node *p1, *p2; 
    // ......
    p1 = mergesort(head_one);
    p2 = mergesort(head_two);
    return merge(p1,p2);

it'll be much easier on the stack this way (will use much less of it).

In general, this here is what's known as top-down mergesort. You could also do it in a bottom-up fashion, by initially sorting the consecutive chunks of two elements each, then merging them into (thus, now, sorted) chunks of 4 elements, then merging those pairwise, into chunks of 8 elements, etc., until only one chunk is left - the sorted list.

To get extra fancy (and efficient), instead of starting with the 2-chunks, start by splitting the list into monotonic runs, i.e. increasing sequences, and decreasing sequences - re-linking the latter ones in reverse as you go - thus segmenting the original list according to its innate order, so it's likely there will be fewer initial chunks to merge; then proceed merging those pairwise repeatedly, as before, until only one is left in the end.



回答2:

How not to implement mergesort for linked lists

  • do not recursively bisect the list - random access isn't free
  • do not divide into sublists of size 1 and n - 1, as explained by ruakh

How to implement mergesort for linked lists

Instead of using bisection, build the lists up by maintaining a stack of already sorted sublists. That is, start by pushing lists of size 1 to the stack and merge down until you reach a list of greater size; you don't actually need to store the list sizes if you can figure out the math behind that.

The sorting algorithm will be stable iff the merge function is. A stable version would build the merged list from scratch by always taking a single element from the lists, and using the first list in case of equality. An unstable, but better performing version would add to the merged list in chunks, avoiding unnecessary re-linking after each element.



回答3:

Mergesort means split&merge. The splitting in the fragment below is not perfect (it leads to quadratic behavior on equally distributed runlengths, see the comment from Christoph)), but it will do the trick:

#include <stdio.h>
#include <string.h>

struct llist {
        struct llist *next;
        char *payload;
        };

int llist_cmp(struct llist *l, struct llist *r);
struct llist * llist_split(struct llist **hnd
                        , int (*cmp)(struct llist *l, struct llist *r) );
struct llist * llist_merge(struct llist *one, struct llist *two
                        , int (*cmp)(struct llist *l, struct llist *r) );
struct llist * llist_sort(struct llist *ptr
                        , int (*cmp)(struct llist *l, struct llist *r) );

struct llist * llist_split(struct llist **hnd, int (*cmp)(struct llist *l, struct llist *r) )
{
struct llist *this, *save, **tail;

for (save=NULL, tail = &save; this = *hnd; ) {
        if (! this->next) break;
        if ( cmp( this, this->next) <= 0) { hnd = &this->next; continue; }
        *tail = this->next;
        this->next = this->next->next;
        tail = &(*tail)->next;
        *tail = NULL;
        }
return save;
}

struct llist * llist_merge(struct llist *one, struct llist *two, int (*cmp)(struct llist *l, struct llist *r) )
{
struct llist *result, **tail;

for (result=NULL, tail = &result; one && two; tail = &(*tail)->next ) {
        if (cmp(one,two) <=0) { *tail = one; one=one->next; }
        else { *tail = two; two=two->next; }
        }
*tail = one ? one: two;
return result;
}
struct llist * llist_sort(struct llist *ptr, int (*cmp)(struct llist *l, struct llist *r) )
{
struct llist *save;

save=llist_split(&ptr, cmp);
if (!save) return ptr;

save = llist_sort(save, cmp);

return llist_merge(ptr, save, cmp);
}

int llist_cmp(struct llist *l, struct llist *r)

{
if (!l) return 1;
if (!r) return -1;
return strcmp(l->payload,r->payload);
}


struct llist lists[] =
{{ lists+1, "one" }
,{ lists+2, "two" }
,{ lists+3, "three" }
,{ lists+4, "four" }
,{ lists+5, "five" }
,{ lists+6, "six" }
,{ lists+7, "seven" }
,{ lists+8, "eight" }
,{ lists+9, "nine" }
,{ NULL, "ten" }
        };

int main()
{
struct llist *root,*tmp;

root = lists;

fprintf(stdout, "## %s\n", "initial:" );
for (tmp=root; tmp; tmp=tmp->next) {
        fprintf(stdout, "%s\n", tmp->payload);
        }

fprintf(stdout, "## %s\n", "sorting..." );
root = llist_sort(root, llist_cmp);
for (tmp=root; tmp; tmp=tmp->next) {
        fprintf(stdout, "%s\n", tmp->payload);
        }
fprintf(stdout, "## %s\n", "done." );

return 0;
}