一、链表简介
链表是一种物理存储结构上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表在结构上的分类:
![](https://i-blog.csdnimg.cn/blog_migrate/838d868dd8badf4c0dfe5db83eb3d98e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/eddf3461a7c1e3cf781ad8b3b2f67c8c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ec57a8aa9b801cb181fc16d55f409466.png)
虽然链表有多种结构类型,但是我么在实际开发中常用的只有两种结构:
- 无头单向非循环链表:结构简单,通常不单独使用,而是作为其他数据结构的子结构,如哈希桶、图的邻接表……
- 带头双向循环链表:结构最复杂,功能最全面,使用效率高
下例代码是无头单向非循环链表的实现,设计思路:
- 每个ListNode节点都包含一个数据和一个next指针,next指针指向下一个节点
- 当pList == NULL 的时候,代表这个链表为空,没有任何数据
- 链表最后一个节点的next指针一定是NULL
- 当函数涉及数据增删时,传入的参数为二级指针 ListNode** ppList
二、SingleList.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
typedef int DataType;
typedef struct ListNode
{
DataType data;
struct ListNode* next;
}ListNode;
bool Empty(ListNode* plist)
{
return plist == NULL;
}
void Print(ListNode* plist)
{
ListNode* cur = plist;
while (cur != NULL)
{
printf("%2d -> ", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
// 动态申请一个节点
ListNode* BuyNode(DataType x)
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->data = x;
node->next = NULL;
return node;
}
// 尾插
void PushBack(ListNode** pplist, DataType x)
{
assert(pplist);
ListNode* node = BuyNode(x);
// 插入链表的第一个节点
if (*pplist == NULL)
{
*pplist = node;
return;
}
// cur指针通过循环遍历找到链表的尾结点
ListNode* cur = *pplist;
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = node;
}
// 头插
void PushFront(ListNode** pplist, DataType x)
{
assert(pplist);
ListNode* node = BuyNode(x);
if (*pplist == NULL)
{
*pplist = node;
return;
}
node->next = *pplist;
*pplist = node;
}
// 尾删
void PopBack(ListNode** pplist)
{
if (Empty(*pplist))
{
printf("链表为空,尾删失败\n");
return;
}
ListNode* cur = *pplist;
ListNode* prev = cur;
while (cur->next != NULL)
{
prev = cur;
cur = cur->next;
}
free(cur);
cur = NULL;
prev->next = NULL;
}
// 头删
void PopFront(ListNode** pplist)
{
if (Empty(*pplist))
{
printf("链表为空,尾删失败\n");
return;
}
ListNode* cur = *pplist;
*pplist = cur->next;
free(cur);
cur = NULL;
}
// 查找,返回第一个元素x的节点
ListNode* Find(ListNode* plist, DataType x)
{
ListNode* cur = plist;
while (cur != NULL)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
// 在pos后面插入新节点,pos节点由Find函数获得
void InsertAfter(ListNode* pos, DataType x)
{
if (pos == NULL)
{
printf("pos为空,数据插入失败\n");
return;
}
ListNode* node = BuyNode(x);
node->next = pos->next;
pos->next = node;
}
// 删除pos节点
void Delete(ListNode** pplist, ListNode* pos)
{
if (pos == NULL)
{
printf("pos为空,数据删除失败\n");
return;
}
if (Empty(*pplist))
{
printf("单链表已为空,Delete失败\n");
return;
}
ListNode* cur = *pplist;
ListNode* prev = NULL;
while (cur)
{
if (cur->data == pos->data && prev == NULL)
{
// 删除第一个节点
*pplist = pos->next;
free(pos);
pos = NULL;
return;
}
if (cur->data == pos->data)
{
prev->next = pos->next;
free(pos);
pos = NULL;
return;
}
prev = cur;
cur = cur->next;
}
}
// 销毁链表
void Destroy(ListNode** pplist)
{
while (*pplist)
{
ListNode* cur = *pplist;
*pplist = cur->next;
free(cur);
cur = NULL;
}
printf("链表销毁成功\n");
}
三、test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SingleList.h"
int main()
{
ListNode* plist = NULL;
// 尾插数据
PushBack(&plist, 1);
PushBack(&plist, 3);
PushBack(&plist, 5);
PushBack(&plist, 7);
Print(plist);
// 头插数据
PushFront(&plist, 2);
PushFront(&plist, 4);
PushFront(&plist, 6);
PushFront(&plist, 8);
Print(plist);
// 尾删数据
PopBack(&plist);
PopBack(&plist);
PopBack(&plist);
Print(plist);
// 头删数据
PopFront(&plist);
PopFront(&plist);
PopFront(&plist);
Print(plist);
// 在查找的元素后面插入节点
InsertAfter(Find(plist, 1), -1);
InsertAfter(Find(plist, 2), -2);
InsertAfter(Find(plist, -2), 22);
Print(plist);
// 删除查找到的节点
Delete(&plist, Find(plist, -2));
Delete(&plist, Find(plist, -1));
Delete(&plist, Find(plist, 22));
Delete(&plist, Find(plist, 100)); // pos为空,数据删除失败!
Print(plist);
// Delete删空链表
Delete(&plist, Find(plist, 2));
Delete(&plist, Find(plist, 1));
Delete(&plist, Find(plist, 1)); // pos为空,数据删除失败!
Print(plist);
// 销毁链表,先插入数据测试
PushBack(&plist, 1);
PushBack(&plist, 2);
PushBack(&plist, 3);
Print(plist); // 1 -> 2 -> 3 -> NULL
Destroy(&plist); // 链表销毁成功
Print(plist);
}