单链表
文章目录
1.基本背景
之前介绍过顺序表的知识,我们都知道了顺序表的特性:
1. 物理地址连续的线性结构
2. 在数组上完成增删查改
3. 动态顺序表扩张空间大,操作灵活
而链表特点与之差异甚大
1. 物理地址不连续
2. 对链表节点进行操作
这也是要着重学习的原因。虽然在日常的程序编写中很少直接使用到链表的知识,但是在操作系统中却使用得十分广泛。(LINUX、RTOS之类的系统)
2.链表的结构
链表是由一个个节点组成,而一个个节点是由数据域与指针域构成,指针域之间也通过指针在不连续的物理结构上把离散的数据链接在一起。
这次要介绍的单链表的结构简单,但是对于初次接触的学习者来说却是最难上手实现的链表。
1)内存层面上的链表
2)单链表的基本结构
要想很系统很完备地实现单链表,第一项工作就是明确它的结构和逻辑:
(1)链表节点
不难发现,链表的节点就是一个个的结构体,这些结构体里边盛放着数据以及指向下一节点的指针
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;//数据
struct SListNode* next;//指针
}SLTNode;
(2)创建节点
我们可以通过动态开辟来实现创建节点的函数。
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//开辟空间
if (newnode == NULL)
{
perror("malloc failure");
exit(-1);
}
newnode->data = x;//赋值
newnode->next = NULL;//链表内指针域为空
return newnode;
}
(3)指定长度创建链表
SLTNode* CreateSList(int n)
{
SLTNode* phead = NULL, * ptail = NULL;//创建指针来放置链表头尾
for (int i = 0; i < n; i++)//利用循环来创建一串节点组成的链表
{
SLTNode* newnode = (SLTNode*)BuySLTNode(i + 1);
if (phead == NULL)
{
phead = newnode;
ptail = newnode;
}
else
{
ptail->next = newnode;
ptail = newnode;
}
}
return phead;//返回链表头
}
3.单链表的各项接口功能实现
1)链表的打印
想要直观地获取链表内容的话,我们得实现一个打印并展现出链表结构特点的函数
void SListPrint(SLTNode* phead)
{
SLTNode* pri = phead;
while (pri != NULL)
{
printf("%d->", pri->data);
pri = pri->next;
}
printf("NULL\n");
}
实现的效果如下:
2)链表的尾插
就像之前的顺序表一样,链表的尾插就是在链表最后加上一个节点
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = (SLTNode*)BuySLTNode(x);//创建新节点
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* temp = *pphead;
while (temp->next)//寻找原链表最后一个节点
{
temp = temp->next;
}
temp->next = newnode;
}
}
3)链表的尾删
尾删的功能就是删去最后一个节点
void SListPopBack(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)//删去最后一个节点的操作
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* del = *pphead;
while (del->next->next)
{
del = del->next;
}
free(del->next);
del->next = NULL;
}
}
4)链表的头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
assert(*pphead);//断言来避免空指针的情况出现
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
5)链表的头删
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);//断言来避免空指针的情况出现
SLTNode* newnode = (*pphead)->next;
free(*pphead);
*pphead = newnode;
}
6)链表的查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* goal = phead;//从头开始查找
while (goal)//查找的过程到链表尾部结束
{
if (goal->data == x)
{
printf("查找成功!\n");
return goal;
}
goal = goal->next;
}
printf("查找失败!\n");
return NULL;
}
7)链表的定位插入
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);//断言来保证合法性
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
8)链表的定位删除
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
assert(*pphead);
if (pos == *pphead)
{
SLTPopFront(pphead);//直接头删,简单直接
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;//跳过节点以达到删除某节点的效果
free(pos);//记得释放空间
pos = NULL;//指针的置空与否在这里无所谓
}
}
9)链表的销毁
void SListDestroy(SLTNode** pphead)
{
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
有关其他类型的链表,我以后会一一做介绍。