链式存储概述
线性表的链式存储(单向链表)解决了顺序存储的以下问题:
- 插入和删除效率低(顺序表需要移动大量元素)
- 动态存储问题(顺序表需要预先分配固定空间)
特点:
- 存储单元可以是连续的,也可以是不连续的。
- 每个节点(Node)包含:
- 数据域:存储数据元素
- 指针域:存储下一个节点的地址
- 通过指针链接各个节点,形成链式结构。
单向链表的基本操作(C语言实现)
头文件定义
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef int DATATYPE; // 数据类型可自定义
typedef struct LinkNode {
DATATYPE data; // 数据域
struct LinkNode *next; // 指针域
} LinkNode;
typedef struct LinkList {
LinkNode *head; // 头指针
int clen; // 当前链表长度
} LinkList;
创建链表
LinkList *CreateLinkList() {
LinkList *ll = (LinkList *)malloc(sizeof(LinkList));
if (ll == NULL) {
fprintf(stderr, "CreateLinkList malloc failed\n");
return NULL;
}
ll->head = NULL;
ll->clen = 0;
return ll;
}
头插法
int InsertLinkList(LinkList *ll, DATATYPE *data) {
if (ll == NULL || data == NULL) {
fprintf(stderr, "Invalid arguments\n");
return 1;
}
LinkNode *newnode = (LinkNode *)malloc(sizeof(LinkNode));
if (newnode == NULL) {
fprintf(stderr, "InsertLinkList malloc failed\n");
return 1;
}
memcpy(&newnode->data, data, sizeof(DATATYPE));
newnode->next = ll->head; // 新节点指向原头节点
ll->head = newnode; // 更新头指针
ll->clen++;
return 0;
}
判断链表是否为空
int IsEmptyLinkList(LinkList *ll) {
if (ll == NULL) {
fprintf(stderr, "Invalid argument\n");
return -1;
}
return (ll->head == NULL) ? 1 : 0;
}
显示链表
void ShowLinkList(LinkList *ll) {
if (ll == NULL || ll->head == NULL) {
printf("LinkList is empty\n");
return;
}
LinkNode *temp = ll->head;
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
查找节点
LinkNode *SearchLinkList(LinkList *ll, DATATYPE key) {
if (ll == NULL || ll->head == NULL) {
return NULL;
}
LinkNode *temp = ll->head;
while (temp != NULL) {
if (temp->data == key) {
return temp;
}
temp = temp->next;
}
return NULL;
}
删除节点
int DeleteLinkList(LinkList *ll, DATATYPE key) {
if (ll == NULL || ll->head == NULL) {
fprintf(stderr, "LinkList is empty\n");
return 1;
}
LinkNode *prev = NULL;
LinkNode *curr = ll->head;
while (curr != NULL) {
if (curr->data == key) {
if (prev == NULL) { // 删除头节点
ll->head = curr->next;
} else { // 删除中间或尾节点
prev->next = curr->next;
}
free(curr);
ll->clen--;
return 0;
}
prev = curr;
curr = curr->next;
}
fprintf(stderr, "Key not found\n");
return 1;
}
内存泄漏检测(Valgrind)
在Linux下可以使用 valgrind
检测内存泄漏:
sudo apt-get install valgrind # 安装
valgrind --leak-check=full ./your_program # 检测
总结
操作 | 时间复杂度 | 说明 |
---|---|---|
头插法 | O(1) | 直接在头部插入 |
尾插法 | O(n) | 需要遍历到链表末尾 |
查找 | O(n) | 最坏情况遍历整个链表 |
删除 | O(n) | 需要找到目标节点 |
优点:
- 动态分配内存,无需预先指定大小
- 插入和删除高效(O(1) 头插,O(n) 随机位置)
缺点:
- 访问元素需要遍历(O(n))
- 额外存储指针,占用更多内存