题
我需要排序双向链表。根据全能维基百科,归并是去该方式。
在递归算法的性能非常好,但我正在写一个通用的实现,性能可能是一个问题。
移植数组迭代版本将杀死性能重新扫描列表以将其分成子列表缓慢;对于任何有兴趣 - 这里的代码:
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;
}
我是什么在这里失踪?
解决方案 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->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的努力:我添加了一些票为
其他提示
你是否需要一个节点复制到列表的末尾?点击
什么是insert_before()
通话呢?
insert_before(node, list, NULL);
这会弄乱list->first
和node->prev
。
我现在已经运行你的代码,并得到了后,我注释掉线下方所示的工作。
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? */
/* ^^^^^^^^^^^^^^^^^^^ */
这是否对你的工作吗?
基于堆栈的方法
/* ... */
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
的末尾。
作为KRISS问它,这里的递归版本(一个标准的归并排序使用合并函数从其他exaples):
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);
}
不隶属于 StackOverflow