【发布时间】: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;
}
我在这里错过了什么?
【问题讨论】:
-
你为什么不尝试直接反递归你的递归版本?它通常比尝试使用全新的迭代版本更有效(无论是调试还是代码性能)。反递归通常非常机械,不需要太多思考。如果您向我们展示您的递归版本,我们可以看到它是如何进行的。