C中的链接列表太慢了

时间:2018-04-07 01:33:33

标签: c performance data-structures linked-list

我正在使用C语言编写应用程序。此应用程序需要快速插入链接列表,但是,由于下面的示例代码,我只能获得16384或2 ^ 14 插入0.5秒。我需要每秒至少100000 - 1000000次插入。我想知道是否有任何方法可以提高链表的性能。我想我应该在链表中添加一个“索引”,就像数组索引一样,并从那里追加它。

注意:这是一个单链经典链表。

以下是代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>

#define NUM_OF_ELEMENTS 32768

struct Node {
    int data;
    struct Node* next;
};

struct Node* create_list(int genesis_data) {
    struct Node* list = (struct Node*)malloc(sizeof(struct Node));
    list->data = genesis_data;
    list->next = NULL;
    return list;
}

void append(struct Node* head, int _data_) {
    struct Node* new_block = (struct Node*)malloc(sizeof(struct Node));
    struct Node* current = (struct Node*)malloc(sizeof(struct Node));
    current = head;
    new_block->data = _data_;
    new_block->next = NULL;
    if(head->next == NULL) {
        head->next = new_block;
    }
    else {
        while(true) {
            if(current->next == NULL) {
                current->next = new_block;
                break;
            }
            else {
                current = current->next;
            }
        }
    }
}

void print_list_data(struct Node* head) {
    struct Node* current = (struct Node*)malloc(sizeof(struct Node));
    current = head;
    while(true) {
        if(current->next == NULL) {
            printf("Node: %d\n", current->data);
            break;
        }
        else {
            printf("Node: %d\n", current->data);
            current = current->next;
        }
    }
}

int main() {
    struct Node* list = create_list(15);
    printf("List created!\n");
    append(list, 5);
    printf("Single element appended to list!\n");
    printf("Appending %d items to the list...\n", NUM_OF_ELEMENTS);
    double t1 = clock();
    for(int x = 0; x < NUM_OF_ELEMENTS; x++) {
        append(list, 5);
    }
    double t2 = clock();
    printf("Time taken to append %d elements to the list: %f\n", NUM_OF_ELEMENTS, (t2-t1)/CLOCKS_PER_SEC);
    //print_list_data(list);    // to print data to the list
    return 0;
}

功能

  • 16 GB DDR4 RAM
  • Intel Core I7
  • 英特尔集成显卡

3 个答案:

答案 0 :(得分:3)

如果你想加快插入操作,那么你无法循环通过 每次附加某些内容时都会列出整个列表。您的append功能会添加新功能 列表末尾的元素,因此头节点可以有一个指针 列表的尾部:

struct Node {
    int data;
    struct Node *next;
    struct Node *tail;  // used only by the head node
};

struct Node* create_list(int genesis_data) {
    struct Node* list = malloc(sizeof *list);
    if(list == NULL)
        return NULL;

    list->data = genesis_data;
    list->next = NULL;
    list->tail = list;
    return list;
}

同时删除此行

struct Node* current = (struct Node*)malloc(sizeof(struct Node));

来自您appendprint_list_data函数,除了泄漏内存之外没有任何其他用途。和 不要使用以下划线开头的变量名,即变量名 以下划线开头是由语言/编译器保留的。

int append(struct Node* head, int data) {
    if(head == NULL)
        return 0;

    struct Node *node = malloc(sizeof *node);

    if(node == NULL)
        return 0;

    node->data = data;
    node->next = NULL;

    head->tail->next = node;
    head->tail = node; // updating tail of head

    return 1;
}

此功能在O(1)中。但它带来了一个代价:你创造了自己的节点 结构更大,需要更多空间。

当然,如果您删除节点并且碰巧移除了头部,请不要忘记 更新新头的tail成员。

如果你想要插入操作的速度,我可能会使用数组 而不是链接列表。

修改

正如Dave S在评论中指出的那样,这个解决方案需要付出代价 结构变得更大,你有很多浪费的空间,因为只有头部 使用tail成员。另一种解决方案是将链表包裹起来 包含指向尾部的指针的第二个结构:

struct Node {
    int data;
    struct Node *next;
};

struct List {
    struct Node *head;
    struct Node *tail;
}

struct Node* create_node(int genesis_data) {
    struct Node* list = malloc(sizeof *list);
    if(list == NULL)
        return NULL;

    list->data = genesis_data;
    list->next = NULL;
    return list;
}

struct List *create_list(int genesis_data)
{
    struct List *list = malloc(sizeof *list);
    if(list == NULL)
        return NULL;

    list->head = create_node(genesis_data);

    if(list->head == NULL)
    {
        free(list);
        return NULL;
    }

    list->tail = list->head;

    return list;
}

int append(struct List* list, int data) {
    if(list == NULL)
        return 0;

    struct Node *node = malloc(sizeof *node);

    if(node == NULL)
        return 0;

    node->data = data;
    node->next = NULL;

    list->tail->next = node;
    list->tail = node; // updating tail of head

    return 1;
}

void print_list_data(struct List* list) {
    if(list == NULL)
        return;

    struct Node *current = list->head;

    while(current)
    {
        printf("Node: %d\n", current->data);
        current = current->next;
    }
}

答案 1 :(得分:2)

使用链表的不同实现很容易加快。

不需要特殊的头节点。

指向next的最后一个节点的NULL成员,而不是指向链接列表的第一个节点。跟踪“尾部”以了解何时停止迭代列表。列表的“头部”很容易找到:tail->next

不是将“head”保存为“list”,而是保存“tail” - 这是单链经典链表的变体。

struct Node {
  int data;
  struct Node* next;
};

struct Node* create_list(int genesis_data) {
  struct Node* list = malloc(sizeof *list);
  list->data = genesis_data;
  list->next = list;
  return list;
}

// Add node to end of list
// pass in the address of the list
void append(struct Node** list, int _data_) {
  struct Node* tail = *list;
  struct Node* new_node = malloc(sizeof *new_node);
  new_node->data = _data_;
  new_node->next = tail->next;
  tail->next = new_node;
  *list = new_node;
}

// Add node to front of list
void prepend(struct Node* list, int _data_) {
  struct Node* tail = *list;
  struct Node* new_node = malloc(sizeof *new_node);
  new_node->data = _data_;
  new_node->next = tail->next;
  tail->next = new_node;
}

int list_head_value(const struct Node* list) {
  const struct Node* tail = list;
  return tail->next->data;
}

int list_tail_value(const struct Node* list) {
  const struct Node* tail = list;
  return tail->data;
}

void print_list_data(const struct Node* list) {
  const struct Node* tail = list;
  const struct Node* current = tail->next;
  do {
    printf("Node: %d\n", current->data);
    current = current->next;
  } while (current != tail);
}

希望这足以表达这个想法。

提供代码以将列表弹出到零节点所需的额外工作。

保留点是:
附加到列表末尾的是O(1)
前面列表的开头是O(1)
弹出列表的前面是O(1)
要报告列表的顶部是O(1)
报告列表的结尾是O(1) 要测试列表是否为空,请执行O(1)
弹出列表的末尾是O(N)
报告列表大小为O(N)

答案 2 :(得分:-2)

运行分析以查看您的计划花费最多时间的位置。 “过早优化是所有邪恶的根源”(唐纳德克努特)

在这种情况下,我猜想内存分配是罪魁祸首。如果剖析结果与我的猜测一致,则答案变得明显。你可以用malloc分配一个相当大的内存块,并将你的节点数据存储在其中。我必须警告你,虽然这并不简单。内存管理和conconrant访问将是你的敌人。这就是为什么它已经在许多图书馆中提供了。