前段时间在学习数据结构的时候,对单链表操作有了一定的认识,于是写了一个简易的单链表来练手。写完之后,感觉对链表的增删改查逻辑一下子清晰不少,特地记录下来,希望能帮到正在学习链表的朋友们。
下面先给大家看一下我写的单链表代码,仓库放在 gitee 上,感兴趣的话可以直接拉取代码自己试试。
一、为什么选择单链表?
单链表(Singly Linked List)是一种动态数据结构,每个节点包含数据和指向下一个节点的指针。相比于顺序表,单链表的优势在于:
-
动态内存管理:不需要预先分配固定大小的内存,插入和删除操作更灵活。
-
插入删除高效:在已知节点位置的情况下,插入和删除的时间复杂度为O(1)。
当然,单链表也有缺点,比如不支持随机访问,查找元素需要遍历链表,时间复杂度为O(n)。
二、代码结构与核心设计
我的代码分为三个文件:
-
SList.h:定义结构体、宏和函数声明。
-
SList.c:函数的具体实现。
-
test.c:测试用例。
1. 结构体设计
typedef struct SListNode {
SLTDataType Data; // 节点数据
struct SListNode* next; // 指向下一个节点的指针
} SLTNode;
-
节点结构:每个节点包含数据域和指针域,指针域指向下一个节点。
-
动态创建节点:通过
malloc
动态分配内存,避免静态数组的固定大小限制。
三、核心功能实现
1. 创建节点
SLTNode* BuyNode(SLTDataType x) {
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL) {
perror("malloc fail");
return NULL;
}
newnode->Data = x;
newnode->next = NULL;
return newnode;
}
-
动态分配内存:每次插入新节点时,调用
BuyNode
创建节点。 -
错误处理:如果
malloc
失败,输出错误信息并返回NULL
2. 尾插与头插
-
尾插:在链表末尾插入新节点。
void SLTPushBack(SLTNode** pphead, SLTDataType x) { assert(pphead != NULL); SLTNode* NewNode = BuyNode(x); if (*pphead == NULL) { *pphead = NewNode; // 链表为空时,新节点为头节点 return; } SLTNode* cur = *pphead; while (cur->next != NULL) { cur = cur->next; } cur->next = NewNode; }
-
头插:在链表头部插入新节点。
void SLTPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead != NULL); SLTNode* NewNode = BuyNode(x); NewNode->next = *pphead; // 新节点指向原头节点 *pphead = NewNode; // 更新头节点 }
3. 尾删与头删
-
尾删:删除链表末尾的节点。
void SLTPopBack(SLTNode** pphead) { assert(*pphead && pphead); if ((*pphead)->next == NULL) { free(*pphead); // 只有一个节点时直接删除 *pphead = NULL; return; } SLTNode* cur = *pphead; while (cur->next->next != NULL) { cur = cur->next; // 找到倒数第二个节点 } free(cur->next); // 释放最后一个节点 cur->next = NULL; // 将倒数第二个节点的next置空 }
-
头删:删除链表头部的节点。
void SLTPopfront(SLTNode** pphead) { assert(pphead); if (*pphead == NULL) { return; // 链表为空时直接返回 } SLTNode* first = *pphead; *pphead = first->next; // 更新头节点 free(first); // 释放原头节点 first = NULL; }
4. 查找与插入删除
-
查找:根据值查找节点。
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) { SLTNode* cur = phead; while (cur) { if (cur->Data == x) { return cur; // 找到匹配节点 } cur = cur->next; } return NULL; // 未找到 }
-
插入与删除:在指定位置插入或删除节点。
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { assert(pos && pphead); if (pos == *pphead) { SLTPushFront(pphead, x); // 在头部插入 return; } SLTNode* cur = *pphead; while (cur->next != pos) { cur = cur->next; // 找到pos的前一个节点 } SLTNode* newnode = BuyNode(x); newnode->next = pos; // 新节点指向pos cur->next = newnode; // 前一个节点指向新节点 }
四、测试与效果
通过test1
函数测试链表的基本操作:
void test1() {
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPushFront(&plist, 0);
SLTPushFront(&plist, -1); // -1 0 1 2 3 4 5
SLTPopBack(&plist);
SLTPopBack(&plist);
SLTPopBack(&plist); // -1 0 1 2
SLTPopfront(&plist); // 0 1 2
SLTNode* ret = SLTFind(plist, 2);
ret->Data *= 2; // 0 1 4
SLTNode* NewRet1 = SLTFind(plist, 4);
SLTInsert(&plist, NewRet1, 3); // 0 1 3 4
SLTNode* NewRet2 = SLTFind(plist, 3);
SLTErase(&plist, NewRet2); // 0 1 4
SLTPrint(plist); // 输出:0->1->4->NULL
}