写在前面
更新情况记录:
最近更新时间 | 更新次数 |
---|---|
2022/10/3 | 2 |
参考博客以及链接:
(非常感谢这些博主们的文章,将我的一些疑问得到解决。)
参考博客链接与书籍 |
---|
《数据结构》陈越 |
总目录: |
【数据结构】第二篇 链表(1)单链表
0.前言
本节专讲单链表。
1.链表的概念及结构
概念:链表是一种物理存储结构上的非连续、非结构的存储结构,数据元素的逻辑顺序是通过链表中指针链接次序实现的。
逻辑结构
物理结构
注意点:
1.从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
2.现实中的节点一般是从堆上申请出来的
3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
2.带哨兵或者不带哨兵
3.单链表的实现(无头、单向、非循环)
温馨小提示:其余链表形式的基础是单链表,先学会这个再写其他形式。
3.2单链表的结构代码
typedef int STDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SListNode;
3.3单链表的接口
//1.动态申请一个节点
SListNode* BuySListNode(SLTDataType x);
//2.单链表打印
void SListPrint(SListNode* phead);
//3.单链表尾插
void SListPushBack(SListNode** pphead,SLTDataType x);
//4.单链表头插
void SListPushFront(SListNode** pphead,SLTDataType x);
//5.单链表的尾删
void SListPopFront(SListNode** pphead);
//6.单链表的头删
void SListPopBack(SListNode** pphead);
//7.单链表查找
SListNode* SListFind(SListNode* pphead,SLTDataType x);
//8.单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos,SLTDataType x);
//9.单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
3.4单链表的实现
1.动态申请一个节点
SLTNode* BuySListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
2.单链表打印
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
3.单链表尾插
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySListNode(x);
SLTNode* tail = *pphead;
if (*pphead == NULL)
{
*pphead = newnode;
return;
}
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
4.单链表头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
5.单链表的尾删
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
// 温柔的检查
if (*pphead == NULL)
{
return;
}
// 暴力检查
//assert(*pphead != NULL);
// 1、一个节点
// 2、多个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
// 找尾
/*SLTNode* prev = NULL;
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
prev->next = NULL;
free(tail);
tail = NULL;*/
SLTNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
6.单链表的头删
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
// 温柔的检查
if (*pphead == NULL)
{
return;
}
// 暴力检查
//assert(*pphead != NULL);
SLTNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
del = NULL;
}
7.单链表查找
语言描述:不断迭代,假如有返回,如果没有就返回NULL。
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data==x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
8.单链表在pos位置之后插入x
注:pos是通过SListFind函数找到的。
语言描述:将newnode指向 pos的下一个,然后将pos的下一个更新为newnode。
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
9.单链表删除pos位置之后的值
注:pos是通过SListFind函数找到的。
语言描述:定义一个cur指针,然后让cur指向pos的下一个,让pos下一个 指向 cur的下一个,
将cur直接释放(free),保险一点可以让cur=NULL;
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
SLTNode* cur = pos->next;
pos->next = cur->next;
free(cur);
cur = NULL;
}
4.关于带哨兵的链表
为链表增加一个空的“头结点”,真正的元素链接在这个空结点之后。
4.1 带头结点的作用
为了可以方便修改第一个结点的数据,可以在链表的第一个结点前面,再增加一个空的“头结点”,也叫哨兵结点,这样做的好处是,无论在哪里插入(比如头)或者删除,phead的值一直指向固定的空结点,不会改变。
4.2 带头结点插入与删除的实现
1.单链表在pos位置之后插入x
注:这样就能头插了。查找的开始是从哨兵结点开始的,这样pos才能指向哨兵,实现头插的功能,下面的删除也一样。
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
2.单链表删除pos位置之后的值
注:这样就能实现头删了。
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
SLTNode* cur = pos->next;
pos->next = cur->next;
free(cur);
cur = NULL;
}
4.3 解题技巧
如果写题目的时候遇到不带头的链表,可以自己额外申请一个结点作为哨兵结点,实现头插与头删的简化,结束时应该释放头结点。详细请看《数据结构(基础C语言实现)题目之线性表》。
5.单链表题目与解题技巧
这部分将写在《数据结构(基础C语言实现)题目之线性表》一文中。
链接(待更新中):