我正在使用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;
}
功能:
答案 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));
来自您append
和print_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访问将是你的敌人。这就是为什么它已经在许多图书馆中提供了。