0.前言🐑
🌵🌵 大家好啊,2天不见,甚是想念,呜呜网课要结束了,今天就要开始线下上课了,ε=(′ο`*)))唉,美好生活不复返了。话不多说,今天开始回顾链表中的无头单向非循环链表。
🌵🌵
本节重点:
- 链表&顺序表对比
- 单链表各个接口的实现
1.链表 🐱
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
关于顺序表的不足:
- 扩容有性能消耗且有可能存在空间浪费。
扩容时,如果扩小了,大量插入数据时,频繁扩容,性能消耗较大;如果扩大了,又会存在空间浪费。 例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。 - 头部或中间插入数据时,需要挪动数据,降低效率
链表优点
- 按需申请内存,不存在空间浪费
- 任意位置O(1)时间插入删除数据
链表缺点
- 不支持下标的随机访问
总结:顺序表和链表相辅相成,使用要看具体应用场景
2.单链表🐶
struct SListNode
{
int data;
struct SListNode* next;
};
void Test()
{
struct SListNode* node1 = (struct SListNode*)malloc(sizeof(struct SListNode));
struct SListNode* node2 = (struct SListNode*)malloc(sizeof(struct SListNode));
struct SListNode* node3 = (struct SListNode*)malloc(sizeof(struct SListNode));
struct SListNode* node4 = (struct SListNode*)malloc(sizeof(struct SListNode));
printf("%p\n", node1);
printf("%p\n", node2);
printf("%p\n", node3);
printf("%p\n", node4);
}
地址均不连续,堆上使用的空间地址由高到低
逻辑结构&物理结构
链表逻辑结构线性,但物理结构是非线性的。
pList(头结点)是指针变量,存的是第一个节点的地址
当我们看到链表的实现都有箭头指向下一个节点,但实际上是没有箭头的,只不过是把下一个节点的地址存到了当前节点的next的值
链表组合
-
单向双向 -
带头不带头 -
循环非循环
最多有8种组合
虽然有这么的组合,但实际中最常用的只有2种:
-
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。 -
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
3.单链表实现🐦
定义
typedef int SListDataType;
typedef struct SListNode
{
SListDataType data;
struct SListNode *next;
} SListNode;
创建节点
SListNode *CreateNewNode(SListDataType x)
{
SListNode *newNode = (SListNode *)malloc(sizeof(SListNode));
if (newNode == NULL)
{
printf("malloc newNode fail\n");
exit(-1);
}
else
{
newNode->data = x;
newNode->next = NULL;
}
return newNode;
}
打印
void SListPrint(SListNode* pList)
{
SListNode* cur = pList;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
提问:这里需要用断言吗?为什么不需要?
记住:一定不能为空的才需要用断言,此处既是链表为空,那我们就什么也不打印就好了啊,断言太暴力了。
提问:这里需要传二级指针吗?为什么?
可以传,但没必要,Print只是打印数据,不会修改数据,因此不需要传二级指针。
查找
查找不需要修改,也就不用传址调用,也就不需要传二级指针。
但如果传了也没问题。
SListNode *SListFind(SListNode *plist, SListDataType x)
{
SListNode *cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
效果展示:
尾删
-
没有节点,无法删除,直接return -
一个节点,直接free掉并置空即可,但注意需要对实参进行操作,所以需要传实参地址,也就是传二级指针 注意:free 之后要置空,因为free掉的是指针指向的内容,但指针还是指向那块空间的,因此要把指针置空 -
多个节点 多个节点时,删除尾节点需要把prev->next置为NULL 再把tail free掉。
如果用多个节点的代码去针对单个节点的情况,会产生解引用空指针的情况。
void SListPopBack(SListNode** ppList)
{
if(*ppList == NULL)
{
return;
}
else if((*ppList)->next == NULL)
{
free(*ppList);
*ppList = NULL;
}
else
{
SListNode* prev = NULL;
SListNode* tail = *ppList;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
效果展示:
传引用:
void SListPopBack(SListNode*& pList)
{
if(pList == NULL)
{
return;
}
else if((pList)->next == NULL)
{
free(pList);
pList = NULL;
}
else
{
SListNode* prev = NULL;
SListNode* tail = pList;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
效果展示:
头删
-
没有节点: 直接return -
单个节点: -
多个节点:
void SListPopFront(SListNode **ppList)
{
if (*ppList == NULL)
{
return;
}
SListNode *next = (*ppList)->next;
free(*ppList);
*ppList = next;
}
效果展示:
传引用:
void SListPopFront(SListNode *&pList)
{
if (pList == NULL)
{
return;
}
SListNode *next = pList->next;
free(pList);
pList = next;
}
效果展示:
头插
void SListPushFront(SListNode *&pList, SListDataType x)
{
SListNode *newNode = CreateNewNode(x);
newNode->next = pList;
pList = newNode;
}
如果头插时链表为空,也是可以的。 pList == NULL,newNode->next 指向NULL,然后再让pList指向newNode,完美解决。
传引用的写法:
void SListPushFront(SListNode *&plist, SListDataType x)
{
SListNode *newNode = CreateNewNode(x);
newNode->next = plist;
plist = newNode;
}
尾插
- 链表本身为空,直接插入就好。
- 链表不为空,遍历找到尾。
- 注意需要用二级指针,因为在判断空链表的情况时,需要对实参sList进行操作,才需要传址调用。
*ppList 其实就是 pList
void SListPushBack(SListNode **ppList, SListDataType x)
{
SListNode* newNode = CreateNewNode(x);
if(*ppList == NULL)
{
*ppList = newNode;
}
else
{
SListNode* tail = *ppList;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
带上头节点可以不用传址调用,因为不需要修改plist
演示效果:
如果不想用二级指针,也可以传引用。
void SListPushBack(SListNode *&pList, SListDataType x)
{
SListNode* newNode = CreateNewNode(x);
if(pList == NULL)
{
pList = newNode;
}
else
{
SListNode* tail = pList;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
效果展示:
pos后插入
2种插入方式:
注意要操作顺序,如果先让pos-> next 指向了newNode 之后就找不到pos的下一个了。
第二种:
将pos的下一个临时保存起来就行,这样顺序就没关了。
void SListInserAfter(SListNode *pos, SListDataType x)
{
assert(pos);
SListNode *newNode = CreateNewNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
void SListInserAfter(SListNode *pos, SListDataType x)
{
assert(pos);
SListNode *newNode = CreateNewNode(x);
SListNode* next = pos->next;
pos->next = newNode;
newNode->next = next;
}
效果展示:
pos前插入
除了要让 newNode 指向pos,还需pos之前的节点指向 newNode ,因此需要找到pos的前一个位置,只能从头开始遍历,非常麻烦且不实用。
- 多个节点的情况:
- 如果pos是第一个节点呢?
如果还让prev指向newNode,那就发生了空指针解引用的问题。可以直接用 if 过滤掉这种情况。 pos是第一个节点的,其实就相当于是头插,但是头插的话,需要改变实参的sList,要让传进来的pList指向newNode,因此还需要传二级指针或者传引用。
void SListInserBefore(SListNode** ppList, SListNode* pos, SListDataType x)
{
assert(pos);
SListNode* newNode = CreateNewNode(x);
if(*ppList == pos)
{
newNode->next = pos;
*ppList = newNode;
}
else
{
SListNode* prev = NULL;
SListNode* cur = *ppList;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = newNode;
newNode->next = cur;
}
}
效果展示:
传引用的写法:
void SListInserBefore(SListNode *&pList, SListNode *pos, SListDataType x)
{
assert(pos);
SListNode *newNode = CreateNewNode(x);
if (pList == pos)
{
newNode->next = pos;
pList = newNode;
}
else
{
SListNode *prev = NULL;
SListNode *cur = pList;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = newNode;
newNode->next = cur;
}
}
效果展示:
提问:
在一个无头(不告诉头指针)单链表的某一个节点前面插入一个值x,怎么插?
这里没告诉头,就不能从头开始遍历去找pos的前一个了。 其实我们可以先后插,然后交换data
pos后擦除
-
只有一个节点: 没有可删除的,直接return -
多个节点 先记录pos的下一个节点,如何让pos指向它的下一个节点的下一个节点,再free 之前记录的pos的下一个节点并置空。 -
后一个为空时,同样适用。
void SListEraseAfter(SListNode *pos)
{
assert(pos);
if (pos->next == NULL)
{
return;
}
else
{
SListNode *next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
}
pos擦除
-
多个节点,需要找pos位置的前一个节点prev,然后free pos,让prev指向pos后面的那个 -
pos指向的是第一个节点,其实就相当于头删,需要改变实参,因此传二级指针或传引用。需要先保存pList的下一个节点,然后free pos,再让pList指向next
void SListEraseCur(SListNode** ppList, SListNode* pos)
{
if(pos == *ppList)
{
SListNode* next = (*ppList)->next;
free(*ppList);
*ppList = next;
}
else
{
SListNode* prev = NULL;
SListNode* cur = *ppList;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = cur->next;
free(cur);
cur = NULL;
}
}
效果展示:
void SListEraseCur(SListNode *&pList, SListNode *pos)
{
if (pos == pList)
{
SListNode *next = pList->next;
free(pList);
pList = next;
}
else
{
SListNode *prev = NULL;
SListNode *cur = pList;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = cur->next;
free(cur);
cur = NULL;
}
}
效果展示:
4.源代码:🐘
SLinkList.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
typedef int SListDataType;
typedef struct SListNode
{
SListDataType data;
struct SListNode *next;
} SListNode;
void SListPrint(SListNode *pList);
SListNode *CreateNewNode(SListDataType x);
void SListPushBack(SListNode *&pList, SListDataType x);
void SListPushFront(SListNode *&pList, SListDataType x);
void SListPopBack(SListNode *&pList);
void SListPopFront(SListNode *&pList);
SListNode *SListFind(SListNode *plist, SListDataType x);
void SListInserAfter(SListNode *pos, SListDataType x);
void SListInserBefore(SListNode *&pList, SListNode *pos, SListDataType x);
void SListEraseAfter(SListNode *pos);
void SListEraseCur(SListNode *&pList, SListNode *pos);
SLinkList.cpp
#include "SLinkList.h"
void SListPrint(SListNode *pList)
{
SListNode *cur = pList;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
SListNode *CreateNewNode(SListDataType x)
{
SListNode *newNode = (SListNode *)malloc(sizeof(SListNode));
if (newNode == NULL)
{
printf("malloc newNode fail\n");
exit(-1);
}
else
{
newNode->data = x;
newNode->next = NULL;
}
return newNode;
}
void SListPushBack(SListNode *&pList, SListDataType x)
{
SListNode *newNode = CreateNewNode(x);
if (pList == NULL)
{
pList = newNode;
}
else
{
SListNode *tail = pList;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
void SListPushFront(SListNode *&pList, SListDataType x)
{
SListNode *newNode = CreateNewNode(x);
newNode->next = pList;
pList = newNode;
}
void SListPopBack(SListNode *&pList)
{
if (pList == NULL)
{
return;
}
else if ((pList)->next == NULL)
{
free(pList);
pList = NULL;
}
else
{
SListNode *prev = NULL;
SListNode *tail = pList;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
void SListPopFront(SListNode *&pList)
{
if (pList == NULL)
{
return;
}
SListNode *next = pList->next;
free(pList);
pList = next;
}
SListNode *SListFind(SListNode *plist, SListDataType x)
{
SListNode *cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void SListInserAfter(SListNode *pos, SListDataType x)
{
assert(pos);
SListNode *newNode = CreateNewNode(x);
SListNode *next = pos->next;
pos->next = newNode;
newNode->next = next;
}
void SListInserBefore(SListNode *&pList, SListNode *pos, SListDataType x)
{
assert(pos);
SListNode *newNode = CreateNewNode(x);
if (pList == pos)
{
newNode->next = pos;
pList = newNode;
}
else
{
SListNode *prev = NULL;
SListNode *cur = pList;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = newNode;
newNode->next = cur;
}
}
void SListEraseAfter(SListNode *pos)
{
assert(pos);
if (pos->next == NULL)
{
return;
}
else
{
SListNode *next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
}
void SListEraseCur(SListNode *&pList, SListNode *pos)
{
if (pos == pList)
{
SListNode *next = pList->next;
free(pList);
pList = next;
}
else
{
SListNode *prev = NULL;
SListNode *cur = pList;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = cur->next;
free(cur);
cur = NULL;
}
}
Test.cpp
#include "SLinkList.h"
void Test1()
{
SListNode *sList = NULL;
SListPrint(sList);
}
void Test2()
{
SListNode *sList = NULL;
SListPushBack(sList, 1);
SListPushBack(sList, 2);
SListPushBack(sList, 3);
SListPrint(sList);
SListPushFront(sList, 1);
SListPushFront(sList, 2);
SListPushFront(sList, 3);
SListPrint(sList);
}
void Test3()
{
SListNode *sList = NULL;
SListPushBack(sList, 1);
SListPushBack(sList, 2);
SListPushBack(sList, 3);
SListPrint(sList);
SListPopBack(sList);
SListPrint(sList);
SListPopFront(sList);
SListPrint(sList);
}
void Test4()
{
SListNode *sList = NULL;
SListPushBack(sList, 1);
SListPushBack(sList, 2);
SListPushBack(sList, 3);
SListPrint(sList);
SListPopFront(sList);
SListPrint(sList);
SListPopFront(sList);
SListPrint(sList);
SListPopFront(sList);
SListPrint(sList);
SListPopFront(sList);
SListPrint(sList);
}
void Test5()
{
SListNode *sList = NULL;
SListPushBack(sList, 1);
SListPushBack(sList, 2);
SListPushBack(sList, 3);
SListPrint(sList);
SListNode *pos = SListFind(sList, 3);
if (pos)
{
pos->data = 30;
printf("找到了并修改为30\n");
}
else
{
printf("找不到\n");
}
SListPrint(sList);
}
void Test6()
{
SListNode *sList = NULL;
SListPushBack(sList, 1);
SListPushBack(sList, 2);
SListPushBack(sList, 3);
SListPrint(sList);
SListNode *pos = SListFind(sList, 2);
SListInserAfter(pos, 10);
SListPrint(sList);
SListInserBefore(sList, pos, 20);
SListPrint(sList);
SListNode *pos2 = SListFind(sList, 1);
SListInserBefore(sList, pos2, 1000);
SListPrint(sList);
SListNode *pos3 = SListFind(sList, 1000);
SListEraseAfter(pos3);
SListPrint(sList);
}
void Test7()
{
SListNode *sList = NULL;
SListPushBack(sList, 1);
SListPushBack(sList, 2);
SListPushBack(sList, 3);
SListPrint(sList);
SListNode *pos1 = SListFind(sList, 2);
SListEraseCur(sList, pos1);
SListPrint(sList);
SListNode *pos2 = SListFind(sList, 3);
SListEraseCur(sList, pos2);
SListPrint(sList);
}
int main(int argc, char const *argv[])
{
Test7();
system("pause");
return 0;
}
5.尾声🐜
🌵🌵 今天的单链表就回顾到这里啦。 写文不易,如果有帮助烦请点个赞~ 👍👍👍
🌹🌹Thanks?(・ω・)ノ🌹🌹
👀👀由于笔者水平有限,在今后的博文中难免会出现错误之处,本人非常希望您如果发现错误,恳请留言批评斧正,希望和大家一起学习,一起进步ヽ( ̄ω ̄( ̄ω ̄〃)ゝ,期待您的留言评论。 附GitHub仓库链接
|