Algorithm 链表合并排序中分步的时间复杂度

Algorithm 链表合并排序中分步的时间复杂度,algorithm,linked-list,mergesort,Algorithm,Linked List,Mergesort,我一直在研究合并排序在链表中的应用。我看过的一些文章鼓吹合并排序是对链表排序的最佳算法。这对于分而治之策略中的“征服”部分是有意义的,在该部分中,您合并了两个已排序的链表,从而节省了所需的内存(与数组相比)。但是,我不明白的是算法中除法步骤的时间复杂性 对于一个数组,这一步是通过利用随机访问并将数组分割成更小的块来实现的恒定时间。但是,对于链表来说,这不需要额外的O(n)吗?我见过Floyd的算法(龟兔算法)用于找到链表的中点并将问题分成更小的块。我对分割步骤做了一些分析。假设链表的大小为n,则

我一直在研究合并排序在链表中的应用。我看过的一些文章鼓吹合并排序是对链表排序的最佳算法。这对于分而治之策略中的“征服”部分是有意义的,在该部分中,您合并了两个已排序的链表,从而节省了所需的内存(与数组相比)。但是,我不明白的是算法中除法步骤的时间复杂性

对于一个数组,这一步是通过利用随机访问并将数组分割成更小的块来实现的恒定时间。但是,对于链表来说,这不需要额外的O(n)吗?我见过Floyd的算法(龟兔算法)用于找到链表的中点并将问题分成更小的块。我对分割步骤做了一些分析。假设链表的大小为n,则仅划分问题所涉及的操作数如下:

n/2+n/4*2+n/8*4+…=n/2*日志(n)

从上面可以看出,与数组情况相比,Floyd算法增加了一个因子“n”。因此,最终的时间复杂度是O(n^2*log(n))。有人能解释一下这种差异吗

编辑:根据@Yves的评论,我发现了错误

当需要添加排序的块时,我在从下到上合并排序的块的同时将其乘以。因此,净时间为:nlogn/2+nlogn=O(nlogn)


这可能是对上述问题最有效的答案;其他答案有点间接/不提供任何解释

您的问题是,扫描每一级递归的半个子列表所增加的O(n/2)时间复杂度转化为O的总体时间复杂度((0.5 n log(n)+1.0(n log(n))=O(1.5 n log(n)),而不是O(n^2(log(n)),以及O(1.5(n log(n)))转换为O(n log(n)),因为时间复杂度忽略了低阶项或常数。然而,在我对具有分散节点的大型列表的实际测试中,大多数节点访问都会导致缓存丢失,我的基准测试显示递归与迭代的相对时间复杂度为O(1.4 n log(n)),使用基于计数的扫描来分割列表,而不是采用乌龟兔方法

<递归>版本,使用龟兔路径是相对缓慢的,并且可以通过使用节点计数来改进,如果链表容器不保持节点计数(例如C++ STD::list:sie()),则需要对N个节点进行一次扫描。通过链表运行

C++ C/C++代码:


但是,在这种情况下(大列表、分散节点),将列表中的数据复制到数组中,对数组进行排序,然后从排序后的数组中创建新的排序列表会更快。这是因为数组中的元素是按顺序合并的(而不是通过随机链表下一个指针),这是缓存友好的。

在自顶向下的合并排序中,查找拆分列表的位置所需的时间与要拆分的列表的长度成正比。在每个递归级别上,这些操作的总时间与所有子列表的长度成正比,即:与原始列表的长度成正比。递归级别的数量为log2(长度(L))因此,拆分列表所涉及的总体时间复杂度为O(长度(L)*log2(长度(L))与合并阶段的复杂度相同

在自底向上的合并排序中,列表一次拆分一个元素,该单个元素要么存储在子列表数组的第一个元素中,要么与其合并以存储在第二个元素中,等等。因此拆分列表只会增加线性时间,这解释了为什么自底向上合并通常比自顶向下合并快。其他影响因素实现效率的关键是缓存友好性,这通常在自底向上和平衡长度方面更好,而在自底向上的合并排序中更难实现

下面是列表自底向上合并排序的简单示例:

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct list {
    struct list *next;
    int value;
} list;

list *list_merge(list *a, list *b) {
    list *head, **tailp = &head;
    while (a && b) {
        if (a->value <= b->value) {
            *tailp = a;
            a = a->next;
        } else {
            *tailp = b;
            b = b->next;
        }
        tailp = &(*tailp)->next;
    }
    *tailp = a ? a : b;
    return head;
}

list *list_mergesort(list *p) {
    list *a[sizeof(size_t) * CHAR_BIT];
    list *e;
    size_t i, top = 0;

    while (p) {
        e = p;
        p = p->next;
        e->next = NULL;
        for (i = 0;; i++) {
            if (i == top) {
                a[top++] = e;
                break;
            }
            if (a[i] == NULL) {
                a[i] = e;
                break;
            }
            e = list_merge(a[i], e);
            a[i] = NULL;
        }
    }
    e = NULL;
    for (i = 0; i < top; i++) {
        e = list_merge(a[i], e);
    }
    return e;
}

list *list_append(list **headp, int value) {
    list *e = malloc(sizeof *e);
    if (e) {
        e->next = NULL;
        e->value = value;
        while (*headp)
            headp = &(*headp)->next;
        *headp = e;
    }
    return e;
}

void list_print(list *e) {
    while (e) {
        printf("%d%c", e->value, " \n"[!e->next]);
        e = e->next;
    }
}

void list_free(list *head) {
    while (head) {
        list *e = head;
        head = head->next;
        free(e);
    }
}

int main() {
    list *head = NULL;
    for (int i = 0; i < 10; i++)
        list_append(&head, rand());
    head = list_mergesort(head);
    list_print(head);
    list_free(head);
    return 0;
}
#包括
#包括
#包括
类型定义结构列表{
结构列表*下一步;
int值;
}名单;
列表*列表\合并(列表*a,列表*b){
列表*头部,**尾部=&头部;
while(a&b){
如果(a->value){
*tailp=a;
a=a->next;
}否则{
*tailp=b;
b=b->next;
}
tailp=&(*tailp)->下一步;
}
*tailp=a?a:b;
回流头;
}
列表*list\u合并排序(列表*p){
列表*a[sizeof(size\u t)*字符位];
清单*e;
尺寸i,顶部=0;
while(p){
e=p;
p=p->next;
e->next=NULL;
对于(i=0;i++){
如果(i==顶部){
a[top++]=e;
打破
}
如果(a[i]==NULL){
a[i]=e;
打破
}
e=列表合并(a[i],e);
a[i]=NULL;
}
}
e=零;
对于(i=0;inext=NULL;
e->value=value;
while(*headp)
headp=&(*headp)->下一步;
*headp=e;
}
返回e;
}
作废清单打印(清单*e){
while(e){
printf(“%d%c”,e->value,“\n”[!e->next]);
e=e->next;
}
}
无效列表(列表*标题){
while(head){
列表*e=头;
头部=头部->下一步;
免费(e);
}
}
int main(){
列表*head=NULL;
对于(int i=0;i<10;i++)
列表_追加(&head,rand());
head=列表合并排序(head);
列表打印(头);
自由列表(标题);
返回0;
}

你能详细说明一下吗?我不确定我是否理解“时间复杂度是一样的,但这忽略了常数因素”,和什么一样?@SahilGupta-I