在双向链表的实现中,我使用的是典型结构:
struct node
{
void *data;
struct node *prev;
struct node *next;
};
我还将在O(1)时间的列表末尾插入,因此我还有另一个struct
存储head
和和tail
:
struct linklist
{
struct node *head;
struct node *tail;
size_t size;
};
该程序对所有插入和删除操作均按预期方式工作,但是我对sort函数有问题,我使用的是merge-sort算法,据我所知,它是对列表进行排序最有效或最有效的方法之一,算法效果很好:
static struct node *split(struct node *head)
{
struct node *fast = head;
struct node *slow = head;
while ((fast->next != NULL) && (fast->next->next != NULL))
{
fast = fast->next->next;
slow = slow->next;
}
struct node *temp = slow->next;
slow->next = NULL;
return temp;
}
static struct node *merge(struct node *first, struct node *second, int (*comp)(const void *, const void *))
{
if (first == NULL)
{
return second;
}
if (second == NULL)
{
return first;
}
if (comp(first->data, second->data) < 0)
{
first->next = merge(first->next, second, comp);
first->next->prev = first;
first->prev = NULL;
return first;
}
else
{
second->next = merge(first, second->next, comp);
second->next->prev = second;
second->prev = NULL;
return second;
}
}
static struct node *merge_sort(struct node *head, int (*comp)(const void *, const void *))
{
if ((head == NULL) || (head->next == NULL))
{
return head;
}
struct node *second = split(head);
head = merge_sort(head, comp);
second = merge_sort(second, comp);
return merge(head, second, comp);
}
但我不知道如何保持list->tail
更新地址:
void linklist_sort(struct linklist *list, int (*comp)(const void *, const void *))
{
list->head = merge_sort(list->head, comp);
// list->tail is no longer valid at this point
}
当然,订购后我可以遍历整个列表,并list->tail
通过蛮力进行更新,但是我想知道是否有更完美的方法。
我设法使用循环列表解决了该问题,但我想避免更改程序的结构。
您的算法通过对merge
函数的每一步进行递归来使用O(N)堆栈空间。使用这种方法,跟踪tail
节点将非常麻烦。您只需扫描列表即可找到它并更新中的list
结构linklist_sort
。这个额外的步骤不会改变分类操作的复杂性。您可以通过从:的当前值开始节省一些时间,link->tail
如果列表已经排序,循环将立即停止。
这是修改后的版本:
void linklist_sort(struct linklist *list, int (*comp)(const void *, const void *)) {
list->head = merge_sort(list->head, comp);
if (list->tail) {
struct node *tail = list->tail;
while (tail->next)
tail = tail->next;
list->tail = tail;
}
}
使用合并排序对链表进行排序仅应使用O(log(N))空间和O(N log(N))时间。
以下是一些改进此算法的想法:
由于您知道列表的长度,因此无需扫描整个列表进行拆分。您可以将长度与列表指针一起传递,并使用它来确定在何处进行拆分,并仅扫描列表的一半。
如果转换merge
为非递归版本,则可以跟踪合并阶段中的最后一个节点,并更新struct node **tailp
作为参数传递的指针以指向该最后一个节点。这将保存上一次扫描,并且删除递归将降低空间复杂度。基准测试可以证明这是否会提高效率尚不清楚。
根据经验,使用指向列表节点的N个指针的辅助数组,可以更有效地实现对链接列表的单独排序和双重链接的排序。您将对该数组进行排序,然后根据排序后的数组的顺序重新链接节点。额外的要求是O(N)大小。
这是使用列表长度和非递归的修改版本merge
:
struct node {
void *data;
struct node *prev;
struct node *next;
};
struct linklist {
struct node *head;
struct node *tail;
size_t size;
};
static struct node *split(struct node *head, size_t pos) {
struct node *slow = head;
while (pos-- > 1) {
slow = slow->next;
}
struct node *temp = slow->next;
slow->next = NULL;
return temp;
}
static struct node *merge(struct node *first, struct node *second,
int (*comp)(const void *, const void *))
{
struct node *head = NULL;
struct node *prev = NULL;
struct node **linkp = &head;
for (;;) {
if (first == NULL) {
second->prev = prev;
*linkp = second;
break;
}
if (second == NULL) {
first->prev = prev;
*linkp = first;
break;
}
if (comp(first->data, second->data)) <= 0 {
first->prev = prev;
prev = *linkp = first;
linkp = &first->next;
} else {
second->prev = prev;
prev = *linkp = second;
linkp = &second->next;
}
}
return head;
}
static struct node *merge_sort(struct node *head, size_t size,
int (*comp)(const void *, const void *))
{
if (size < 2) {
return head;
}
struct node *second = split(head, size / 2);
head = merge_sort(head, size / 2, comp);
second = merge_sort(second, size - size / 2, comp);
return merge(head, second, comp);
}
void linklist_sort(struct linklist *list, int (*comp)(const void *, const void *)) {
list->head = merge_sort(list->head, comp, list->size);
if (list->tail) {
struct node *tail = list->tail;
while (tail->next)
tail = tail->next;
list->tail = tail;
}
}
请注意,您还可以简化merge
功能,并且在排序期间不更新后退指针,因为您可以在上一次扫描期间重新链接整个列表。最后一次扫描将更长且对缓存的友好程度较低,但仍应更高效且更不易出错。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句