前言
- 由于顺序表中存在缺陷或者不能适用其他情况,所以进一步去学习链表。
概念
- 是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。
结构
- 链表有好几种结构,这里学习的是最基础的
无头+单向+非循环的链表,这几个词可以暂时忽略
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
- 逻辑结构: 为方便理解、使用的想象出的结构,可以形象的表达出链表的特点,可以搭配物理逻辑理解。
- 物理结构: 链表实际在内存中存储的形式
- 上图的代码解释
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
int main() {
//这里是从后向前创建,之后会实现接口函数,就是正常顺序
SListNode plist3; //创建结构体变量plist3
plist3.data = 3; //plist3的data设值
plist3.next = NULL; //由于plist3之后没有数据,所以下一个节点指针设空
SListNode plist2; //创建结构体变量plist2
plist2.data = 2; //plist2的data设值
plist2.next = &plist3; //plist2的next指向plist3,所以取plist3的地址赋值给plist2.next
SListNode plist1; //创建结构体变量plist1
plist1.data = 1; //plist1的data设值
plist1.next = &plist2; //plist1的next指向plist2
//这样就可以通过plist1找到plist2、plist3
SListNode* p2 = plist1.next; // *p2现在就是&plist2(plist2的结构体指针),但通常不会这么手动赋值下一个节点
//更多是这样使用
SListNode* cur = &plist1; //现在cur是plist1的结构体指针
cur = cur->next; //等同于 cur = plist1.next 现在cur就是plist2的结构体指针
//通过cur来遍历链表
while(cur != NULL)
{
//cur->data = ... 更改当前节点数据
cur = cur->next;
}
return 0;
}
对比
-
链表
-
- 优点:
链表的插入和删除操作的效率较高,可以把通过地址直接改变节点的指向。
更合理地使用空间,不需要的空间、节点直接删除释放。
基本上没有空间限制,只与内存空间大小有关。
- 优点:
-
- 缺点:
占用额外的空间以存储指针(浪费空间,不连续存放,malloc开辟,空间碎片多) 。
不能进行索引/随机访问,查找时,需要循环链表访问,需要从开始节点一个一个节点去查找元素访问。
- 缺点:
-
接口函数
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode** plist);
顺序表的每个节点都是动态开辟的,为了方便使用,所以写成函数
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* new_node = (SListNode*)malloc(sizeof(SListNode)); //动态申请空间
assert(new_node); //断言malloc是否成功
new_node->data = x; //插入数据
new_node->next = NULL; //next置空
return new_node; //返回节点的指针
}
// 单链表打印
void SListPrint(SListNode* plist)
{
//assert(plist); //断言指针
SListNode* cur = plist;
while (cur) //当前节点不为空的时候打印数据
{
printf("%d ", cur->data);
cur = cur->next; //把下一个节点的指针赋值给cur
}
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist); //断言指针
SListNode* new_node = BuySListNode(x); //创建新节点
if (!(*pplist)) //当链表为空时(*pplist==NULL)
{
*pplist = new_node; //直接将新创建的节点指针赋值给*pplist
}
else //当链表不为空时
{
SListNode* tail = *pplist; //为了方便使用,将*pplist赋值给tail,接下来就不需要对二级指针操作
while (tail->next != NULL) //当tail->next保存了指针时进入循环,直到找到最后一个有数据的节点
{
tail = tail->next; //将下一个节点指针赋值给tail
}
tail->next = new_node; //将新创建的节点指针赋值给tail->next,就是插入到最后一个节点后
}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist); //断言指针
SListNode* new_node = BuySListNode(x); //创建新节点
new_node->next = *pplist; //将链表的第一个节点指针赋值给new_node->next
*pplist = new_node; //将new_node赋值给*pplist, new_node现在就是链表第一个节点
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist); //断言指针
assert(*pplist); //断言指针
SListNode* tail = *pplist;
if (!tail->next) //当链表只有一个节点时
{
free(tail); //释放空间
*pplist = NULL; //将链表置空
}
else //当链表有两个及以上节点时
{
while (tail->next->next) //当tail下一个节点的next不为空时
{
tail = tail->next; //将tail下一个节点赋值给tail
}
//当tail下一个节点的next为空时,那么tail的下一个节点就是最后一个节点
free(tail->next); //释放掉tail的下一个节点
tail->next = NULL; //tail的next置空
}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(pplist); //断言指针
assert(*pplist); //断言指针
SListNode* head = (*pplist)->next; //将链表第一个节点的next赋值给head,head将是链表新的第一个节点
free(*pplist); //释放掉第一个节点
*pplist = head; //将head作为链表第一个节点
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist); //断言指针
SListNode* cur = plist;
while (cur) //当cur不为空时
{
if (cur->data == x) //如果cur->data等于x
{
return cur; //返回当前节点指针
}
else //不等就向后寻找
cur = cur->next;
}
return NULL; //链表没有x,返回空指针
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos); //断言指针
SListNode* new_node = BuySListNode(x); //创建新节点
new_node->next = pos->next; //将pos下一个节点的指针赋值给new_node->next
pos->next = new_node; //将new_node赋值给pos的next
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
assert(pos); //断言指针
assert(pos->next); //断言pos不是最后一个节点
SListNode* next = pos->next->next; //保存pos之后的第二个节点指针
free(pos->next); //释放pos之后的第一个节点空间
pos->next = next; //将pos的下一个节点设置为之前保存的next
}
// 单链表的销毁
void SListDestroy(SListNode** pplist)
{
assert(pplist); //断言指针
assert(*pplist); //断言指针
SListNode* cur = *pplist;
while (cur) //当链表不为空时
{
SListNode* next = cur->next; //保存下一个节点指针
free(cur); //释放当前节点空间
cur = next; //将下一个节点设置为当前节点
}
*pplist = NULL; //将链表置空
}
完整代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode** plist);
#define _CRT_SECURE_NO_WARNINGS
#include "SList.h"
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* new_node = (SListNode*)malloc(sizeof(SListNode)); //动态申请空间
assert(new_node); //断言malloc是否成功
new_node->data = x; //插入数据
new_node->next = NULL; //next置空
return new_node; //返回节点的指针
}
// 单链表打印
void SListPrint(SListNode* plist)
{
//assert(plist); //断言指针
SListNode* cur = plist;
while (cur) //当前节点不为空的时候打印数据
{
printf("%d ", cur->data);
cur = cur->next; //把下一个节点的指针赋值给cur
}
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist); //断言指针
SListNode* new_node = BuySListNode(x); //创建新节点
if (!(*pplist)) //当链表为空时(*pplist==NULL)
{
*pplist = new_node; //直接将新创建的节点指针赋值给*pplist
}
else //当链表不为空时
{
SListNode* tail = *pplist; //为了方便使用,将*pplist赋值给tail,接下来就不需要对二级指针操作
while (tail->next != NULL) //当tail->next保存了指针时进入循环,直到找到最后一个有数据的节点
{
tail = tail->next; //将下一个节点指针赋值给tail
}
tail->next = new_node; //将新创建的节点指针赋值给tail->next,就是插入到最后一个节点后
}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist); //断言指针
SListNode* new_node = BuySListNode(x); //创建新节点
new_node->next = *pplist; //将链表的第一个节点指针赋值给new_node->next
*pplist = new_node; //将new_node赋值给*pplist, new_node现在就是链表第一个节点
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist); //断言指针
assert(*pplist); //断言指针
SListNode* tail = *pplist;
if (!tail->next) //当链表只有一个节点时
{
free(tail); //释放空间
*pplist = NULL; //将链表置空
}
else //当链表有两个及以上节点时
{
while (tail->next->next) //当tail下一个节点的next不为空时
{
tail = tail->next; //将tail下一个节点赋值给tail
}
//当tail下一个节点的next为空时,那么tail的下一个节点就是最后一个节点
free(tail->next); //释放掉tail的下一个节点
tail->next = NULL; //tail的next置空
}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(pplist); //断言指针
assert(*pplist); //断言指针
SListNode* head = (*pplist)->next; //将链表第一个节点的next赋值给head,head将是链表新的第一个节点
free(*pplist); //释放掉第一个节点
*pplist = head; //将head作为链表第一个节点
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist); //断言指针
SListNode* cur = plist;
while (cur) //当cur不为空时
{
if (cur->data == x) //如果cur->data等于x
{
return cur; //返回当前节点指针
}
else //不等就向后寻找
cur = cur->next;
}
return NULL; //链表没有x,返回空指针
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos); //断言指针
SListNode* new_node = BuySListNode(x); //创建新节点
new_node->next = pos->next; //将pos下一个节点的指针赋值给new_node->next
pos->next = new_node; //将new_node赋值给pos的next
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
assert(pos); //断言指针
assert(pos->next); //断言pos不是最后一个节点
SListNode* next = pos->next->next; //保存pos之后的第二个节点指针
free(pos->next); //释放pos之后的第一个节点空间
pos->next = next; //将pos的下一个节点设置为之前保存的next
}
// 单链表的销毁
void SListDestroy(SListNode** pplist)
{
assert(pplist); //断言指针
assert(*pplist); //断言指针
SListNode* cur = *pplist;
while (cur) //当链表不为空时
{
SListNode* next = cur->next; //保存下一个节点指针
free(cur); //释放当前节点空间
cur = next; //将下一个节点设置为当前节点
}
*pplist = NULL; //将链表置空
}