链表
1.1 链表的概念及结构
链表:是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
- 分类:带头/不带头,单向/双向,循环/不循环,排列组合后一共有8种不同的链表。
🔵特点:
① 逻辑上连续,但是物理上可能连续可能不连续。
② 创建的空间一般在堆上。(记得free
)
1.2 接口实现
// 无头+单向+非循环链表
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;//数据域
struct SListNode* next;//指针域 //结构体自引用
}SListNode;
1.动态申请一个节点
为了避免后续创建结点操作的冗余,我们可以编写一个申请结点的函数。
//创建一个新结点,返回新结点地址
SListNode* BuySLTNode(SLTDataType x)
{
SListNode* node = (SListNode*)malloc(sizeof(SListNode));//向新结点申请空间
if (node == NULL)
{
printf("malloc fail\n");
exit(-1);
}
node->data = x;//将数据赋值到新结点的数据域
node->next = NULL;//将新结点的指针域置空
return node;//返回新结点地址
}
2.打印
//打印链表
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;//接收头指针
while (cur != NULL)//判断链表是否打印完毕
{
printf("%d->", cur->data);//打印数据
cur = cur->next;//指针指向下一个结点
}
printf("NULL\n");//打印NULL,表明链表最后一个结点指向NULL
}
3.头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
SListNode* newnode = BuySLTNode(x);//创建一个新的结点
newnode->next = *pplist;//新结点的指针域指向下一个结点
*pplist = newnode;//重新设置头结点
}
4.尾插
void SListPushBack(SListNode** pplist, SLTDataType x)
{
SListNode* newnode = BuySLTNode(x);//创建一个新结点
if (*pplist == NULL)//如果链表为空
{
*pplist = newnode;//直接把新节点给头指针
}
else
{
SListNode* tail = *pplist;//临时创建tail目的是寻找尾部
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
5.头删
void SListPopFront(SListNode** pplist)
{
if (*pplist == NULL)
{
return;
//assert(*pplist!=NULL)
}
else
{
SListNode* temp = *pplist;
*pplist = (*pplist)->next;
free(temp);
temp = NULL;
}
}
6.尾删
//尾删
void SListPopBack(SListNode** pplist)
{
SListNode* prev = NULL;
if (*pplist == NULL)//空
{
return;
//assert(*pplist!=NULL);
}
else if((*pplist)->next==NULL)//只有一个结点
{
free(*pplist);
*pplist = NULL;
}
else
{
SListNode* tail = *pplist;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;//free后记得置空
prev->next = NULL;
}
}
void SListPopBack(SListNode** pplist)
{
if (*pplist == NULL)//空
{
return;
//assert(*pplist!=NULL);
}
else if((*pplist)->next==NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
SListNode* tail = *pplist;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next= NULL;
}
}
7.某一个位置前/后插
//在给定位置之后插入
void SListInsertAfter(SListNode* pos, SLTDataType x)
{
assert(pos);//确保传入地址不为空
SListNode* newnode = BuySLTNode(x);//申请一个新结点
newnode->next = pos->next;//让新结点的指针域指向地址为pos的结点的下一个结点
pos->next = newnode;//让地址为pos的结点指向新结点
}
//在给定位置之前插入
void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x)
{
assert(pos);//确保传入地址不为空
SListNode* newnode = BuySLTNode(x);//申请一个新结点
if (pos == *pplist)//判断给定位置是否为头指针指向的位置
{
newnode->next = pos;//让新结点的指针域指向地址为pos的结点
*pplist = newnode;//让头指针指向新结点
}
else
{
SListNode* prev = *pplist;//接收头指针
while (prev->next != pos)//找到地址为pos的结点的前一个结点
{
prev = prev->next;
}
newnode->next = prev->next;//让新结点的指针域指向地址为pos的结点
prev->next = newnode;//让前一个结点指向新结点
}
}
8.某一个位置前/后删
//删除给定位置之后的值
void SListErasetAfter(SListNode* pos)
{
assert(pos);//确保传入地址不为空
if (pos->next == NULL)//判断传入地址是否为最后一个结点的地址
{
return;
}
SListNode* after = pos->next;//待删除的结点
pos->next = after->next;//让pos结点指向待删除的结点的下一个结点
free(after);//释放结点
after = NULL;//及时置空
}
//删除给定位置的值
void SListErasetCur(SListNode** pplist, SListNode* pos)
{
assert(pos);//确保传入地址不为空
if (pos == *pplist)//判断待删除的结点是否为第一个结点
{
*pplist = pos->next;//让头指针指向第二个结点
free(pos);//释放第一个结点
pos=NULL;//及时置空
}
else
{
SListNode* prev = *pplist;//接收头指针
while (prev->next != pos)//找到待删除结点的前一个结点
{
prev = prev->next;
}
prev->next = pos->next;//让待删除的结点的前一个结点指向待删除结点的后一个结点
free(pos);//释放待删除结点
pos = NULL;//及时置空
}
}
9.查找数据
SListNode* SListFind(SListNode* plist, SLTDataType x)
{
SListNode* cur = plist;
while (cur)
{
if (x == cur->data)
return cur;
else
{
cur = cur->next;
}
}
return NULL;
}
[ 拓展 ] 多个数值查找并统计:
SListNode* pos = SListFind(plist, 2);
int i = 0;
while (pos)
{
pos = SListFind(plist, 2);
i += 1;
}
10.修改数据
查找后修改数据
void SListModify(SListNode* pos, SLTDataType x)
{
pos->data = x;
}
11.删除整个链表
void SListDestory(SLTNode** pphead)
{
assert(*pphead);//判断头是否为空
SLTNode* cur = *pphead;//将头结点的地址保存在cur
while (cur)
{
SLTNode* next = cur->next;//next指向待删除结点的下一个结点
free(cur);//删除结点
cur = next;//指针指向删除完毕后的下一个结点
}
*pphead = NULL;//置空
}
1.3 顺序表和链表的区别
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间上 | 连续 | 逻辑连续,物理不一定连续 |
随机访问 | ✅ | ❌ |
任意位置插入或者删除元素 | 时间复杂度: O ( N ) O(N) O(N) | 改变指针指向 |
应用场景 | 元素高效存储+频繁访问 | 任意结点插入/删除 |
缓存利用率 | 高 | 低 |