【问题标题】:Debugging mergesort调试归并排序
【发布时间】:2009-09-29 10:17:13
【问题描述】:

我需要对一个双向链表进行排序。根据全能的 wikipedia,mergesort 是实现这一目标的方法。

递归算法工作得相当好,但在我编写通用实现时,性能可能是个问题。

为数组移植迭代版本会降低性能,因为重新扫描列表以将其划分为子列表很慢;对于任何有兴趣的人 - 这是代码:

static void sort(struct linked_list *list,
    int (*cmp)(const void *, const void *))
{
    size_t slice_size = 1;
    for(; slice_size < list->size; slice_size *= 2)
    {
        struct node *tail = list->first;
        while(tail)
        {
            struct node *head = tail;

            size_t count = slice_size;
            while(tail && count--) // performance killer
                tail = tail->next;

            count = slice_size;
            while(head != tail && tail && count)
            {
                if(cmp(head->data, tail->data) <= 0)
                    head = head->next;
                else
                {
                    struct node *node = tail;
                    tail = tail->next;
                    remove_node(node, list);
                    insert_before(node, list, head);
                    --count;
                }
            }

            while(tail && count--) // performance killer
                tail = tail->next;
        }
    }
}

但还有另一个使用基于堆栈的方法的迭代版本:

struct slice
{
    struct node *head;
    size_t size;
};

static void sort(struct linked_list *list,
    int (*cmp)(const void *, const void *))
{
    if(list->size < 2) return;

    struct slice stack[32];
    size_t top = -1;
    struct node *current = list->first;

    for(; current; current = current->next)
    {
        stack[++top] = (struct slice){ current, 1 };
        for(; top && stack[top-1].size <= stack[top].size; --top)
            merge_down(list, cmp, stack + top);
    }

    for(; top; --top)
        merge_down(list, cmp, stack + top);
}

这会将大小为 1 的列表推入堆栈并向下合并,只要顶部列表的大小大于或等于其前一个列表的大小。

不幸的是,对于某些输入列表,某处存在错误,merge_down() 将无法通过完整性检查:

static void merge_down(struct linked_list *list,
    int (*cmp)(const void *, const void *), struct slice *top)
{
    struct node *right = top->head;
    size_t count = top->size;

    --top;

    struct node *left = top->head;
    top->size += count;

{
    // sanity check: count nodes in right list
    int i = count;
    struct node *node = right;
    for(; i--; node = node->next) if(!node)
    {
        puts("too few right nodes");
        exit(0);
    }
}

    // determine merged list's head
    if(cmp(left->data, right->data) <= 0)
    {
        top->head = left;
        left = left->next;
    }
    else
    {
        top->head = right;
        struct node *node = right;
        right = right->next;
        remove_node(node, list);
        insert_before(node, list, left);
        --count;
    }

    while(left != right && count)
    {
        if(cmp(left->data, right->data) <= 0)
            left = left->next;
        else
        {
            struct node *node = right;
            right = right->next;
            remove_node(node, list);
            insert_before(node, list, left);
            --count;
        }
    }
}

链表实现也可能是相关的:

struct node
{
    struct node *prev;
    struct node *next;
    long long data[]; // use `long long` for alignment
};

struct linked_list
{
    struct _list _list; // ignore
    size_t size;
    struct node *first;
    struct node *last;
};

static void insert_before(struct node *node, struct linked_list *list,
    struct node *ref_node)
{
    if(ref_node)
    {
        node->next = ref_node;
        node->prev = ref_node->prev;
        if(ref_node->prev) ref_node->prev->next = node;
        else list->first = node;
        ref_node->prev = node;
    }
    else // empty list
    {
        node->next = NULL;
        node->prev = NULL;
        list->first = node;
        list->last = node;
    }
    ++list->size;
}

static void remove_node(struct node *node, struct linked_list *list)
{
    if(node->prev) node->prev->next = node->next;
    else list->first = node->next;
    if(node->next) node->next->prev = node->prev;
    else list->last = node->prev;
    --list->size;
}

我在这里错过了什么?

【问题讨论】:

  • 你为什么不尝试直接反递归你的递归版本?它通常比尝试使用全新的迭代版本更有效(无论是调试还是代码性能)。反递归通常非常机械,不需要太多思考。如果您向我们展示您的递归版本,我们可以看到它是如何进行的。

标签: c debugging mergesort


【解决方案1】:

您是否需要将节点复制到列表末尾?
那么insert_before() 电话是什么?

insert_before(node, list, NULL);

那会搞砸list-&gt;firstnode-&gt;prev

【讨论】:

  • 我认为这样的调用不可能发生 - 我将添加一些调试输出到 insert_before() 以确保...
【解决方案2】:

我现在已经运行了你的代码,并在我注释掉下面指示的行后让它开始工作。

static void merge_down(struct linked_list *list,
    int (*cmp)(const void *, const void *), struct slice *top)
{
    struct node *right = top->head;
    size_t count = top->size;

    --top;

    struct node *left = top->head;
    top->size += count; /* possible bug? */
 /* ^^^^^^^^^^^^^^^^^^^ */

这对你也有用吗?

【讨论】:

  • 似乎有效 - 但我不明白为什么:当我合并前 2 个列表并丢弃右边的列表时,左边列表的大小不应该增加右边的列表跨度>
  • 不起作用:对于大输入大小,我得到一个无限循环;简单的解决方法是在循环条件中添加一个检查&amp;&amp; right,但我想了解什么不会出错
【解决方案3】:

我自己发现了错误:

for(; current; current = current->next)
{
    stack[++top] = (struct slice){ current, 1 };
    for(; top && stack[top-1].size <= stack[top].size; --top)
        merge_down(list, cmp, stack + top);
}

这里,current 的下一个值在调用merge_down() 之后确定,这可能会移动节点,即current-&gt;next 将不再指向正确的节点。

重新排列解决了问题:

while(current)
{
    stack[++top] = (struct slice){ current, 1 };
    current = current->next;
    for(; top && stack[top-1].size <= stack[top].size; --top)
        merge_down(list, cmp, stack + top);
}

感谢 pmg 的努力:我为此添加了一些投票。

【讨论】:

  • hm.. 我得等两天才能接受我自己的答案:(
  • 耶!很高兴您发现了问题!
【解决方案4】:

基于堆栈的方法

/* ... */
    struct slice stack[32];
    size_t top = -1;
    struct node *current = list->first;

    for(; current; current = current->next)
    {
        stack[++top] = (struct slice){ current, 1 };
        for(; top && stack[top-1].size <= stack[top].size; --top)
        /*    ^^^    */
            merge_down(list, cmp, stack + top);
    }
/* ... */

top 在第一次循环时总是为 0,对吧?
merge_down() 函数永远不会被调用。我没有尝试代码,但看起来不对。


编辑
stack 的 32 个元素是不够的...当列表按顺序包含超过 32 个元素时(可能经过几次传递),您会写到 stack 的末尾之外。

【讨论】:

  • afaik 这是正确的:如果堆栈上至少有两个列表,则只能向下合并,即第一次迭代时不合并
  • 检查top 没有超出限制:assert (top &lt; (sizeof stack / sizeof stack[0]) - 1);
【解决方案5】:

按照 kriss 的要求,这是递归版本(使用其他示例中的合并函数的标准合并排序):

static struct node *merge(struct linked_list *list,
    int (*cmp)(const void *, const void *),
    struct node *left, struct node *right, size_t right_count)
{
    struct node *head;
    if(cmp(left->data, right->data) <= 0)
    {
        head = left;
        left = left->next;
    }
    else
    {
        head = right;
        struct node *node = right;
        right = right->next;
        remove_node(node, list);
        insert_before(node, list, left);
        --right_count;
    }

    while(left != right && right_count)
    {
        if(cmp(left->data, right->data) <= 0)
            left = left->next;
        else
        {
            struct node *node = right;
            right = right->next;
            remove_node(node, list);
            insert_before(node, list, left);
            --right_count;
        }
    }

    return head;
}

static struct node *mergesort(struct linked_list *list,
    int (*cmp)(const void *, const void *), struct node *head, size_t size)
{
    if(size < 2) return head;
    size_t left_count = size / 2;
    size_t right_count = size - left_count;

    struct node *tail = head;
    size_t count = left_count;
    while(count--) tail = tail->next;

    return merge(list, cmp,
        mergesort(list, cmp, head, left_count),
        mergesort(list, cmp, tail, right_count),
        right_count);
}

static void sort(struct linked_list *list,
    int (*cmp)(const void *, const void *))
{
    mergesort(list, cmp, list->first, list->size);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-01
    • 2014-12-15
    • 2015-06-10
    • 2014-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多