Day2:单链表的基本操作
Posted 雨轩(小宇)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day2:单链表的基本操作相关的知识,希望对你有一定的参考价值。
单链表
目录 | 目录 |
---|---|
顺序表 | 单链表(不带附加头结点) |
前言
- 上一节实现了顺序表的基本操作,这一节我们来学习单链表(不带头结点),实现单链表的一些基本操作以及注意事项。
1、单链表的定义
-
单链表是链表中最简单的链表表示,也叫线性链表。链表是不连续的,用指针表示结点间的关系,一个节点包含两个域:
数据域:一般用data表示,存储一个数据元素
指针域:存放一个指针,指向下一个结点的开始存储地址,起到链接两个结点的作用 -
单链表的结构
head:头指针,指向第一个结点
datan:尾结点,没有后继,next指向NULL
head=NULL的话,则说明该链表为空。
每个节点之间的关系:
第一个结点与头指针:a1=head;
第一个与第二个结点:a2=a1->next;
…
最后一个结点:a(n)=a(n-1)->next,a(n)->next=NULL
2、单链表的特点
- 长度很方便的进行扩充
- 逻辑顺序与物理顺序可不一致
- 可连续存储数据,也可以不连续
- 每个结点都有指针域,存储空间消耗比较大
3、单链表的基本操作
1、头文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>//bool头文件
typedef int SLTDataType;//重命名,方便更改数据类型
typedef struct SListNode
{
SLTDataType data; //数据域
struct SListNode* next; //指针域
}SLTNode;
2、初始化
- 初始化,给头指针开辟空间,开辟成功置空。
void SListInit(SLTNode** phead)
{
assert(phead);
*phead = (SLTNode*)malloc(sizeof(SLTNode));
if (*phead == NULL)
{
perror("SListInit");
return;
}
(*phead) = NULL;
}
3、打印
- 遍历根据单链表的结构进行遍历即可,记住不要用头指针去遍历,对头指针进行重命名为p在去进行遍历,可读性高
//遍历
void SListPrint(SLTNode* phead)
{
SLTNode* p = phead;
while (p)
{
printf("%d->", p->data);//打印数据
p = p->next;//指向下一个结点,知道该结点为NULL
}
printf("NULL\\n");
}
4、创建新节点
- malloc开辟内存给node,开辟失败则退出,开辟成功则将node->data置为x,node->next 置空
//创建节点
SLTNode* BuySListNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("BuySListNode");
exit(-1);
}
node->data = x;
node->next = NULL;
return node;
}
5、尾插
- 当为空表时,相当于在链表头部插入结点,需要修改指针:newnode->next = *pphead; *pphead = newnode;
- 非空表时,在current的下一个节点后插入新节点newnode,需要修改指针:
newnode->next = tail->next; tail->next = newnode; - 这里利用了BuySListNode这个函数创建新节点,所以只用写current->next = newnode; 读者可以根据自己实现的方法去改变
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
if (*pphead == NULL)//如果链表为NULL,则第一个节点地址给头结点
{
SLTNode* newnode = BuySListNode(x);//创建新节点
*pphead = newnode;//将新节点地址给头节点
}
else
{
SLTNode* tail = *pphead;
//当有节点存在时
while (tail->next)//这里判断下一个结点是否为空,不能是判断该结点是否为空,否则会直接找到末尾,末尾为NULL,插入会失败
{
tail = tail->next;
}
SLTNode* newnode = BuySListNode(x);//调用函数,创建新节点
tail->next = newnode;
}
}
6、尾删
- 当只有一个结点时,用tail保存首元结点,修改指针:tail = *pphead; free(tail); tail=NULL;
- 当有多个结点,用prev保存前驱节点,tail为后继结点,遍历链表,找到尾结点,修改指针:prev = tail; tail = tail->next; free(tail); prev->next = NULL;
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
//暴力方式
assert(*pphead);//检测链表是否为空
//温和方式
/*if (*pphead == NULL)
{
return;
}*/
SLTNode* prev = NULL;
SLTNode* tail = *pphead;
if (tail->next == NULL)
{
free(tail);
tail = NULL;
}
else
{
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
7、头插
- 头插顾名思义在第一个节点之前插入,成为第一个节点,修改指针:newnode->next = head; head = newnode;
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;//指向第一个节点
*pphead = newnode;//newnode地址给pphead成为第一个节点
}
8、头删
1.头删,删除第一个节点,让头指针指向第二个结点,修改指针:next = head->next; head = next;
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* nextnode = (*pphead)->next;
*pphead = nextnode;
free(*pphead);
*pphead = NULL;
}
9、插入
- 单链表一般在pos位置之后去插入结点
- 头部,修改指向:newnode->next = head; head = newnode;
- 既不是第一个结点也不是最后一个结点,修改指向:newnode->next = current->next; current->next = newnode;
- 最后一个结点,修改指向:newnode->next = current->next; current->next = newnode;
//在pos位置之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
10、删除
- 在第一个结点处删除,修改指向:del= head; head = head->next;
- 在中间或者尾部删除,修改指向:del= current->next; current->next = del->next;
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* nextnode = pos->next;
pos->next = pos->next->next;
free(nextnode);
nextnode = NULL;
}
11、查找
- 一个一个结点查找值等于x的结点,找到返回该结点,反之返回NULL,修改指针:cur = cur->next;
SLTNode* SListFind(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
12、销毁
- 一个一个结点释放,修改指针:next = cur->next; free(cur); cur = next;
void SListDestory(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
13、获取链表节点个数
- 有一个结点,size+1,修改指针:cur = cur->next;
size_t SListSize(SLTNode* phead)
{
size_t size = 0;
SLTNode* cur = phead;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
4、单链表的注意事项
注意事项:
- 尽量不要用头指针进行操作,可以使用类型重命名,防止找不到头指针,丢失链表,找不到头指针是非常麻烦的一件事,每个操作都涉及头结点,比如头插头删,遍历,反转链表等,都需要头指针,这样做的目的是增强可读性
- 遍历链表时要检查链表尾部是否为空
- 插入和删除结点时,需要找到插入点或删除点之前的结点
- 插入结点时,需要注意malloc开辟
- 删除结点,需要用free释放开辟的内存,防止内存泄漏(经常忘记)
5、扩展
关于插入和删除的问题:
- 为什么不在pos位置之前去插入结点:单链表是单向的,找到一个结点的后继结点相对来说比较容易,反之选择去找一个结点的前继结点是比较麻烦。
- 为什么不删除pos位置的结点:跟第一个问题一样,删除pos位置的结点也是需要去找pos位置的前驱结点,让前驱结点指向pos位置的后去结点才能将pos位置的结点删除,也是比较麻烦的。
解决办法:后面的循环双链表可以解决此类问题
6、源码
- 需要源码的点击下面的链接即可查看:
gitee: https://gitee.com/deng_yuniubi/data-structure.
GitHub: https://github.com/yuxuanniu6/Data-Struct.
以上是关于Day2:单链表的基本操作的主要内容,如果未能解决你的问题,请参考以下文章