代码之家  ›  专栏  ›  技术社区  ›  Christoph

调试合并排序

  •  0
  • Christoph  · 技术社区  · 16 年前

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

    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;
    }
    

    我在这里错过了什么?

    5 回复  |  直到 16 年前
        1
  •  1
  •   pmg    16 年前

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

    insert_before(node, list, NULL);
    

    那会搞砸的 list->first node->prev .

        2
  •  1
  •   pmg    16 年前

    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? */
     /* ^^^^^^^^^^^^^^^^^^^ */
    

        3
  •  1
  •   Christoph    16 年前

    我自己发现了错误:

    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->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的努力:我为此增加了一些选票。

        4
  •  0
  •   pmg    16 年前

    基于堆栈的方法

    /* ... */
        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() 函数永远不会被调用。我没有尝试代码,但看起来不对。


    编辑
    32个元素 stack 这还不够。..当列表中有32个以上的元素按顺序排列时(可能在几次遍历后),您可以在末尾之后写入 堆栈 .

        5
  •  0
  •   Christoph    16 年前

    正如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);
    }