前言
顺序表有自己独特的优势,但是当我们在运行过程中会发现空间会有不小的损耗,而且时间复杂度也会很大。那么有没有其他的办法来解决这样的问题呢?这样,我们就引入了链表。
1.概念与结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
我们可以将链表比作一个个车厢。如下形象图:
与顺序表不同的是,链表中的每节车间都是独立申请下来的空间,我们称为结点 。
如图所示,我们可以看到结点的组成主要有两个部分:数据+下一个结点的地址。
在链表中,每个结点都是独立申请的,我们需要通过指针变量来保存下一个结点位置,才能找到下一个结点的位置。
1.1 链表的性质
1.在逻辑上是连续的,在物理结构上不一定连续;
2.结点一般是从堆上申请的;
3.申请的空间可能连续,也可能不连续。(即结点之间的地址不一定是连续的)
2.链表的实现过程及解析
2.1 定义链表的结构
//定义链表的结构
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
在定义结构中,存储的数据我们不确定,所以我们将int进行重命名,这样可以快速改变存储的类型。
接下来,进行测试,在text.c中:
2.1.1 创建一个链表
//创建一个链表,并打印链表
void createSList()
{
//链表是由一个个的结点组成的
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node2->data = 2;
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
node3->data = 3;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node4->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
}
int main()
{
createSList();
return 0;
}
具体结构可如下图所示:
2.2 链表的打印
//打印链表
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
最后运行结果为:
可见这样是正确的。首先我们用pcur来作为判断条件,当pcur为NULL时,说明到达了最后一个结点,这样每次取每个结点的data就可以将链表中的内容打印出来。
2.3 插入数据
2.3.1 尾插
具体过程如上图所示。
2.3.1.1 申请新结点
在插入数据时,我们需要申请一个新结点 :
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc fail!");
exit(1);
}
node->data = x;
node->next = NULL;
return node;
}
2.3.1.2 代码实现
因为我们要知道头结点在哪里,所以我们应该定义一个新变量,这样phead所指向的位置保持不变。
void SLTPushBack(SLTNode* phead, SLTDataType x)
{
//申请新节点
SLTNode* newnode = SLTBuyNode(x);
if (phead == NULL)
{
phead = newnode;
}
else
{
//找尾结点
SLTNode* pcur = phead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = newnode;
}
}
你以为这样是对的吗?当我们调试出来之后就会发现,并没有像我们想象中的那样,那么,是为什么呢?那么,我们下期再见~
哈哈,骗你的~
其实,在顺序表中,我么遇到了同样的问题,当时我只是一笔略过了,那么究竟是为什么呢?在很早之前,我们就强调了传值和传址的区别,所以,现在的你知道是为什么了吗?当我们要改变其中的数据时,我们就要考虑传址调用。
所以,这里要使用二级指针来进行代码改善!
//插入数据
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//申请新节点
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾结点
SLTNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = newnode;
}
}
要记得在最开始时要有断言!
2.3.2 头插
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
2.4 删除数据
2.4.1 尾删
//删除数据
//尾删
void SLTPopBack(SLTNode** pphead)
{
//链表为空,不可以执行删除
assert(pphead && *pphead);//pphead表示参数不能为空,*pphead表示链表不为空
//找 prev ptail
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
但是,当我们运行之后可以看到:
其中,返回值不为0,说明存在问题,那么问题时什么呢?
原因是我们忽略了链表只有一个结点的情况!
所以,改进如下:
//删除数据
//尾删
void SLTPopBack(SLTNode** pphead)
{
//链表为空,不可以执行删除
assert(pphead && *pphead);//pphead表示参数不能为空,*pphead表示链表不为空
//找 prev ptail
//只有一个结点的情况
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
可以看出,当前的代码是正确的。
2.4.2 头删
2.5 查找数据
因为查找数据时,不会改变链表中的内容,所以只需要用传值调用就可以了。
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
2.6 在指定位置插入数据
2.6.1 在指定位置之前插入数据
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
//找前一个结点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
然而,如果进行验证可以看见:
返回值不为0,则该代码是错误的。那么,错在哪里呢?
应该考虑头插的情况!
正确的代码应该是:
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTBuyNode(x);
//找前一个结点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
2.6.2 在指定位置之后插入数据
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
2.7 删除指定位置数据
2.7.1 删除pos位置的数据
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
//头删
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
注意在这种情况下,我们也要考虑头删的情况。
2.7.2 删除pos之后的结点
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
需要注意的是,我们应该用del作为中间的变量,最终实现释放的作用。
2.8 销毁链表
销毁链表实际上就是将一个个的结点进行释放的过程。
//销毁链表
void SListDestroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
需要注意的是,我们在每一个函数的实现之后,就要进行检验!!!
3.总的代码
3.1 SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义链表的结构
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//打印
void SLTPrint(SLTNode* phead);
//插入数据
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//删除数据
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);
3.2 SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//打印链表
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc fail!");
exit(1);
}
node->data = x;
node->next = NULL;
return node;
}
//插入数据
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//申请新节点
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾结点
SLTNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = newnode;
}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//删除数据
//尾删
void SLTPopBack(SLTNode** pphead)
{
//链表为空,不可以执行删除
assert(pphead && *pphead);//pphead表示参数不能为空,*pphead表示链表不为空
//找 prev ptail
//只有一个结点的情况
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTBuyNode(x);
//找前一个结点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
//头删
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
//销毁链表
void SListDestroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
3.3 test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//创建一个链表,并打印链表
void createSList()
{
//链表是由一个个的结点组成的
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node2->data = 2;
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
node3->data = 3;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node4->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//第一个结点的地址传过去
SLTNode* plist = node1;
SLTPrint(plist);
}
void SListTest01()
{
SLTNode* plist = NULL;
/*SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);*/
SLTPushFront(&plist, 1);
SLTPushFront(&plist, 2);
SLTPushFront(&plist, 3);
SLTPushFront(&plist, 4);
SLTPrint(plist);
/*SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);*/
/*SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);*/
SLTNode* find = SLTFind(plist, 4);
/*if (find == NULL)
{
printf("没找到\n");
}
else
{
printf("找到了\n");
}*/
//SLTInsertAfter(find, 11);
//SLTPrint(plist);
//SLTEraseAfter(find);
SListDestroy(&plist);
SLTPrint(plist);
}
int main()
{
//createSList();
SListTest01();
return 0;
}
好了,今天就到这里,我们下一个知识点见(* ̄︶ ̄) !