在c中对双向链表进行排序

时间:2012-02-15 22:50:18

标签: c performance algorithm sorting linked-list

我希望在插入元素时按照排序顺序保留链表(列表中大约200000个元素),您可以推荐哪种算法?我使用插入排序做了一个简单的实现,但它的性能非常糟糕(很多CPU使用率)。

感谢您的帮助。

我在合并排序和插入排序之间进行了一些比较,但似乎插入排序具有更好的性能,我对这个结果有点困惑。你能告诉我什么是错的,是否有更好的算法?

我的代码(为简单起见,我省略了节点结构中的prev节点):

struct node {
    int number;
    struct node *next;
};

插入排序:

void insert_node(int value) {
    struct node *new_node = NULL;
    struct node *cur_node = NULL;
    struct node *last_node = NULL;
    int found; /* 1 means found a place to insert the new node in, 0 means not*/


    new_node = (struct node *)malloc(sizeof(struct node *));
    if(new_node == NULL) {
        printf("memory problem\n");
    }
    new_node->number = value;
    /* If the first element */
    if (head == NULL) {
        new_node->next = NULL;
        head = new_node;
    } 

    else if (new_node->number < head->number) {
        new_node->next = head;
        head = new_node;    
    } 

    else {
        cur_node = head;
        found = 0;
        while (( cur_node != NULL ) && ( found == 0 )) {
            if( new_node->number < cur_node->number )
            {
                found = 1;
            }
            else
            {
                last_node = cur_node;
                cur_node = cur_node->next;
            }
        }
    /* We got the right place to insert our node */
    if( found == 1 )
    {
        new_node->next = cur_node; 
    }
    /* Insert at the tail of the list */
    else
    {
        last_node->next = new_node;
        new_node->next = NULL;
    }           
}

合并排序:

/* add a node to the linked list */
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;
}

/* perform merge sort on the linked list */
struct node *merge_sort(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(merge_sort(head_one), merge_sort(head_two));
}

/* merge the lists.. */
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;
}

9 个答案:

答案 0 :(得分:6)

要在链表中插入元素,它排序的前提条件无济于事! 没有算法可以提供帮助。

您可能需要考虑元素的其他结构。根据您的情况,简单的堆或二进制搜索树可能会很好地为您服务。

如果要将大量元素插入到大型有序链表中,可以对它们进行排序,然后进行非常快速的合并O(N)。

答案 1 :(得分:2)

对于在线解决方案(在项目到达时插入项目),平衡二叉树可能是一个不错的选择。它允许在时间O(Log(N))中插入(以及删除)。

否则,MergeSort可以应用于完整列表。

在这两种情况下,O(N.Log(N))总计比较。

答案 2 :(得分:1)

您未正确实现合并排序,该排序基于递归地将列表分为两部分,对它们进行排序并合并结果。但是在你的代码中,你并没有把列表分成两半。

请注意以下行:

while((head_two != NULL) && (head_two->next != NULL)) {
    head = head->next;
    head_two = head->next->next;
}
head_two = head->next;
head->next = NULL;

当head_two到达列表末尾时退出while循环:例如,如果您在循环中到达head_two->next == NULL,则使用head->next->next == NULL退出它。当您运行head_two = head->next;时,您会得到head_twohead_two->next == NULL这是列表中的最后一项。

这意味着您基本上是在进行插入排序而不是合并排序。

因此,尝试通过向函数length添加参数merge_sort来跟踪列表的长度,以便能够将其拆分为2.以下是对算法的一个很好的解释。 wikipedia

答案 3 :(得分:0)

链接列表必须要求O(N)步骤移动到列表中的任意位置。所以,听起来它不适合你。

您可以尝试使用C ++中的有序集合 - std :: set或者SortedSet&lt;&gt;在C#。

如果排序集抽象不适合您的问题,可能与排序链表类似的最简单数据结构是跳过列表:http://igoro.com/archive/skip-lists-are-fascinating/。更复杂的选择是AVL树和红黑树。

答案 4 :(得分:0)

Priority queues(或Skip Lists具体而言)是您正在寻找的。对于简单的链表,它们允许使用对数插入而不是 O(n)

这是skip list implementation in C on GitHub

答案 5 :(得分:0)

如果您需要将m元素插入到大小为n的排序列表中,您可以直接插入复杂度m·n,或预先分配元素(我建议)合并排序,并将它们合并到原始列表中,原始列表具有复杂性m·ln(m) + (n + m)

基本上,您将使用插入和合并排序的专用版本,这两种版本都可以作为在线算法实现,因此非常适合链接列表。请记住,合并排序的“教科书”实现不会执行良好,因为它在将列表划分为子列表时不必要地迭代列表:您需要稍微复杂的基于堆栈的版本... < / p>

答案 6 :(得分:0)

实际上你不想对列表进行排序。 合并排序用于排序未排序的列表。 (有些人已经指出了这一点。)

要保持列表排序,您必须在正确的位置插入每个元素。 所以基本上你必须:

  1. 搜索插入新条目的地点
  2. 插入
  3. 这里的复杂性是搜索算法。

    所以二叉树甚至更好的AVL树(http://en.wikipedia.org/wiki/AVL_tree)都会非常有效。

    另一种选择是使用二进制搜索(http://en.wikipedia.org/wiki/Binary_search_algorithm)。但同样这只对跳过列表有效。

    因此,无论是更改数据结构还是使用简单的双链表,O(N)复杂性都是您可以实现的最佳选择。

答案 7 :(得分:0)

我已对您的代码进行了必要的更改。我测试过它,它的工作原理非常好。您现在应该可以关闭查询了。

struct node *insert_node( struct node *head, int *value )
{
        struct node *new_node = NULL;
        struct node *cur_node = NULL;
        struct node *last_node = NULL;
        int found; /* 1 means found a place to insert the new node in, 0 means not*/
        new_node = (struct node *)malloc(sizeof(struct node *));
        if(new_node == NULL)
        {
                printf("memory problem\n");
        }
        new_node->number = *value;
        /* If the first element */
        if (head == NULL)
        {
                new_node->prev = new_node->next = NULL;
                head = new_node;
        }
        else if (new_node->number < head->number)
        {
                new_node->next = head;
                head = new_node;
        }
        else
        {
                cur_node = head;
                found = 0;
                while (( cur_node != NULL ) && ( found == 0 ))
                {
                        if( new_node->number < cur_node->number )
                                found = 1;
                        else
                        {
                                last_node = cur_node;
                                cur_node = cur_node->next;
                        }
                }
                /* We got the right place to insert our node */
                if( found == 1 )
                {
                        new_node->next = cur_node;
                        new_node->prev = last_node;
                        last_node->next = new_node;
                        cur_node->prev = new_node;
                }
                /* Insert at the tail of the list */
                else
                {
                        last_node->next = new_node;
                        new_node->next = NULL;
                        new_node->prev = last_node;
                }
        }
        return head;
}

答案 8 :(得分:0)

我喜欢这样的事情。插入和查找是O(log(n)),遍历是O(n)。 'heapness'属性确保数据始终排序。你可以向前和向后遍历,这样你就可以获得双向链表的优势。