文章目录
- Ⅰ 概念及结构
- 1. 单链表的概念
- 2. 单链表的结构
- Ⅱ 基本操作实现
- 1. 定义单链表结点
- 2. 创建新结点
- 3. 单链表打印
- 4. 单链表尾插
- 5. 单链表头插
- 6. 单链表尾删
- 7. 单链表头删
- 8. 单链表查找
- 9. 在指定 pos 位置前插入结点
- 10. 删除指定 pos 位置的结点
- 11. 单链表销毁
本章实现的是不带头结点的单链表。
Ⅰ 概念及结构
1. 单链表的概念
- 链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
- 单链表的每个结点仅仅要存储该结点本身应该存储的数据,还要存储下一个与自己同类型结点的地址。
2. 单链表的结构
1. 物理结构
每个单链表结点都有一个指针域,用来存储下一个结点的地址。
2. 逻辑结构
为了方便理解和学习,使用箭头来表示每个结点的指针域所存储的是哪个结点的地址。
Ⅱ 基本操作实现
1. 定义单链表结点
- 每个结点都应该包含数据域和指针域两部分内容。
- 数据域的数据类型应该使用 typedef 来定义,防止以后更改数据域的数据类型。
typedef int SLTDataType; //每个单链表结点数据域的数据类型
typedef struct SListNode //定义一个单链表结点
{
SLTDataType data; //单链表的数据域
struct SListNode* next; //单链表的指针域,用于存放下一个同类型结点的地址
}SLNode; //单恋表的结点类型
2. 创建新结点
- 开辟一个结点空间,将传过来的数据 x 放到结点的数据域,然后将该结点的指针域置 NULL。
SLNode* BuySListNode(SLTDataType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode)); //开辟一个新结点空间
if (NULL == newnode) //如果开辟该结点失败
{
perror("malloc");
exit(-1);
}
newnode->data = x; //参数给新结点的数据域
newnode->next = NULL; //将新结点的指针域置空
return newnode; //返回开辟的新结点的地址
}
3. 单链表打印
1. 遍历链表
因为不能动指向首结点的指针,防止找不到首结点,所以此类操作都是利用额外的指针来实现的。
- 使用一个指针 cur (current) 指向当前结点,如果 cur 不为空,则说明链表还没走到头。打印 cur 所指向的结点的数据域,然后让 cur 指针指向下一个结点。
- 在打印的时候会有两种情况。
- 单链表为空:此时 cur 直接就指向 NULL,什么也不会打印。
- 单链表非空:打印 cur 指向结点的数据域的类容,然后将 cur 指向指向下一个结点,直到 cur 走到链表末尾后为止。
2. 代码实现
//单链表打印
void SListPrint(SLNode* plist)
{
SLNode* cur = plist; //指向单链表的首结点
while (cur != NULL) //单链表还没走到尾
{
printf("%d->", cur->data); //打印当前结点数据域的值
cur = cur->next; //继续指向下一个结点
}
printf("NULL\n");
}
3. 函数调用
SListPrint(plist);
4. 单链表尾插
1. 插入链表
将数据依次插入到单链表的尾部。
在进行单链表尾插时有两种情况:
- 单链表为空:直接创建一个新结点然后插入到单链表即可。
- 单链表非空:使用一个 tail 指针用来寻找单链表的尾部,要将新结点插入到 tail->next 为 NULL 的位置。
2. 代码实现
//传过来的是个指针变量的地址,需要使用二级指针 pplist 来接收
void SListPushBack(SLNode** pplist, SLTDataType x)
{
assert(pplist);
SLNode* newnode = BuySListNode(x); //创建一个新的结点
if (NULL == *pplist) //单链表当前为空
{
*pplist= newnode; //直接将新结点插入链表
}
else //单链表非空
{
SLNode* tail = *pplist; //tail 用于寻找单链表的尾结点
while (tail->next != NULL) //没有找到尾结点
{
tail = tail->next; //指向下一个结点
}
tail->next = newnode; //将新结点插入到尾结点之后,成为新的尾结点
}
}
3. 函数调用
5. 单链表头插
1. 插入链表
- 不管链表中有多少个结点,所有的数据都插入到链表的都一个位置。
2. 代码实现
//要改变链表内容,实参要传指针变量的地址,形参用二级指针接收
void SListPushFront(SLNode** pplist, SLTDataType x)
{
assert(pplist); //传过来的不能是 NULL 指针
SLNode* newnode = BuySListNode(x); //创建新结点
newnode->next = *pplist; //将首结点地址交给新结点指针域
*pplist= newnode; //让新结点成为新的首结点
}
3. 调用函数
6. 单链表尾删
- 每次将单链表尾部的的一个结点删除掉。
1. 实现方法
- 链表中只一个结点:直接释放链表指针指向的那个结点。
- 链表中有多个结点:使用一个 tail 指针寻找尾结点的前一个结点,如果 tail 所指向结点的下个结点的指针域为空,则说明 tail->next 指向的结点为尾结点,直接释放即可。
2. 实现代码
void SListPopBack(SLNode** pplist)
{
assert(pplist); //pplist 指向的 plist 不是 NULL
assert(*pplist); //plist 指向的链表不为空
if (NULL == (*pplist)->next) //1.一个结点
{
free(*pplist); //释放链表中唯一的一个结点
*pplist= NULL;
}
else //2.多个结点
{
SLNode* tail = *pplist; //使用 tail 找尾结点
while (tail->next->next != NULL) //当前结点的下下个结点不为空
{
tail = tail->next; //tail 指向链表下一个结点
}
free(tail->next); //释放 tail->next 指向的尾结点
tail->next = NULL; //将 tail->next 的值置空
}
}
3. 函数调用
7. 单链表头删
- 每次删除都只删除单链表的首结点。
1. 实现方法
- 链表有多个结点:先用一个临时指针变量保存首结点的地址,再让链表指针指向第二个结点,最后释放首结点。
- 链表只一个结点:步骤和上面相同,只不过只有一个结点时,链表指针在第二步会指向 NULL 而已。
2. 实现代码
void SListPopFront(SLNode** pplist)
{
assert(pplist); //传过来的不是 NULL
assert(*pplist); //链表不为空
SLNode* tmp = *pplist; //先保存链表首结点
*pplist= (*pplist)->next; //让链表指针指向第二个结点
free(tmp); //释放首结点
}
3. 函数调用
8. 单链表查找
- 在单链表中查找数据域的值等于形参 x 的结点,并返回该结点的地址。
- 如果链表中不存在 x 则返回 NULL。
实现代码
SLNode* SListFind(SLNode* plist, SLTDataType x)
{
assert(plist); //链表不为空
SLNode* cur = plist; //cur 用来访问当前结点
while (cur != NULL) //cur 没有走到链表末尾
{
if (cur->data == x) //当前结点数据域的值等于形参 x
{
return cur; //返回当前结点的地址
}
else
{
cur = cur->next; //继续往后找数据域等于 x 的结点
}
}
return cur; //链表中没有 x,返回 NULL
}
调用函数
9. 在指定 pos 位置前插入结点
1. 实现方法
- 定义一个 cur (current) 指针指向当前结点,如果当前结点的下一个结点的地址等于 pos 的地址,则将 pos 指向的结点插入到新结点后,再将新结点插入到 cur 结点后。
2. 实现代码
void SLTInsert(SLNode** pplist, SLNode* pos, SLTDataType x)
{
assert(pplist); //传过来的不能是空指针
assert((*pplist) && (pos)); //要么同时不为空
assert(!(*pplist) && !(pos)); //要么同时为空
SLNode* newnode = BuySListNode(x); //创建新结点
if (*pplist == pos) //链表只有一个结点 - 头插
{
SListPopFront(pplist,x) //新结点作首结点
}
else //找 pos 位置的前一个
{
SLNode* cur = *pplist; //cur 是 pos 位置的前一个结点
while (cur->next != pos) //当前结点的下个结点不是 pos
{
cur = cur->next; //cur 向后寻找 pos 结点
}
newnode->next = pos; //让新结点指针域指向 pos 结点
cur->next = newnode; //当前结点的指针域指向新结点
}
}
调用函数
10. 删除指定 pos 位置的结点
1. 实现思路
- 在删除 pos 位置的结点前,先用一个前趋指针 pre 保存 pos 结点的前一个结点。
- 让 pre 指向 pos 的下一个结点。
- 销毁 pos 指向的结点。
2. 实现代码
void SLTErase(SLNode** pplist, SLNode* pos)
{
assert(pplist);
assert((*pplist) && (pos)); //两个指针同时不为空
SLNode* pre = NULL; //当前位置的前一个结点
SLNode* cur = *pplist; //用来寻找 pos 结点
while (cur != NULL)
{
if (cur != pos)
{
pre = cur;
cur = cur->next;
}
else //cur 找到了 pos 指向的结点
{
pre->next = cur->next; //pos 的前一个结点指向 pos 的后一个结点
free(cur); //删除 pos 指向的结点
cur = NULL;
}
}
}
11. 单链表销毁
- 持续对单链表连续执行头删。
实现代码
void SLTDestroy(SLNode** pplist)
{
assert(pplist); //链表指针不能为空
SLNode* cur = *pplist;
while (cur) //链表不为空则执行头删
{
SLNode* next = cur->next; //保存当前结点的下一个结点
free(cur); //释放当前结点
cur = next; //对下一个结点执行以上操作
}
*pplist = NULL; //无论链表原来为不为空最后都执行这步
}