单链表的介绍和实现
- 一.基本概念
- 1.基本结构
- 2.结构体节点的定义:
- 二.功能接口的实现
- 0.第一个节点:plist
- 1打印链表
- 2创建一个节点
- 3.头插
- 4.头删
- 5.尾插
- 6.尾删
- 7.查找
- 8.在pos之前插入x
- 9.在pos之后插入x
- 10.删除pos位置
- 11.删除pos的后一个位置
- 12.链表释放
- 三.整体代码
一.基本概念
1.基本结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
逻辑结构:前面的叫做数据域,后面叫做指针域,指针是用来保存下一个节点的地址的。在逻辑上是这样连续的但是在实际的物理结构上面空间上不是连续的。
物理结构:这些都是节点,在内存中是通过前一个节点保存后一个节点的地址这样的话就可以连续去寻找链表数据。
2.结构体节点的定义:
二.功能接口的实现
0.第一个节点:plist
如果我们想要创建好一个链表并且去使用他我们需要有这些节点并且通过结构体的成员变量去连接我们的这个结构体构成一个链表的状态因为我们的方向是单向的所以每一次使用的时候我们应该需要知道第一个节点的地址,并且第一个节点它在逻辑上必须处于链表的第一个位置,我们在进行接口的操作都是依赖于我们的第一个节点。(第一个节点如果要变化那么我们的在主函数中的第一个节点的地址就要变化)(地址的变化是需要传二级指针的)
1打印链表
打印链表只需要循环遍历一次链表的数据内容。(不需要对第一个节点的地址发生改变)
//打印链表
void SLTprint(SLTNode* plist)
{
//循环遍历不去更改头所以不需要传二级指针
//打印之前我们需要注意我们是否有数据打印
//说明链表已经没有数值或者还没有节点数据
assert(plist!=NULL);
SLTNode* cur = plist;
//一个节点的下一个是空就不打印数据了
while (cur)
{
printf("%d->", cur->data);
//循环的一个条件
cur = cur->next;
}
printf("NULL\n");
}
2创建一个节点
一个新节点的创建需要注意一个参数和一个返回值,参数是我们的节点的数据值,返回的参数是这个节点的地址,因为我们只能通过存储地址的方式进行链表的连接。
//创建一个节点
SLTNode* BuySListNode(STLDatatype x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode*));
if (newnode == NULL)
{
perror("malloc file\n");
exit(-1);
//开辟空间失败直接退出程序
}
//使用这个节点
newnode->data = x;
//因为这是一个新的节点我们的下一个数据还没有给它
//它现在不需要去存储下一个节点的地址。
newnode->next = NULL;
return newnode;
}
3.头插
头插:我们的头节点每经过一次头插就要改变前面提过我们去使用链表的时候是需要第一个节点的指针的。这个指针如果改变是需要影响到接口外的实参的。(需要传二级指针改变)
//头插
//头插
void SLTpushfront(SLTNode** pphead, STLDatatype x)
{
//插入一个数据需要一个新的节点
SLTNode* newnode = BuySListNode(x);
//是有数据,说明*pphead不是空。
//如果直接改变第一个的地址那么。第二个数据就找不到了。
newnode->next = *pphead;
*pphead = newnode;
}
4.头删
头删需要把第一个节点给释放,原来的第二个作为新的头,需要对我们的第一个节点进行地址的改变(需要传二级指针)
void SLTpopfront(SLTNode** pphead)
{
//删除数据如果没有数据就不去删除
assert(*pphead != NULL);
//不止一个数据,保存第二个节点的地址
SLTNode* newhead = (*pphead)->next;
free(*pphead);
*pphead = NULL;
//释放老节点,拿到新节点
*pphead = newhead;
}
5.尾插
不去修改第一个节点,可以传二级也可以传一级。
void SLTpushback(SLTNode** pphead, STLDatatype x)
{
SLTNode* newnode = BuySListNode(x);
//当没有数据的时候
if (*pphead == NULL)
{
//对一个地址的修改
*pphead = newnode;
}
else
{
SLTNode* cur = *pphead;
//下一个是NULL就是尾
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = newnode;
}
}
6.尾删
有可能删除第一个所以需要传二级指针。
//尾删
void SLTpopback(SLTNode** pphead)
{
assert(*pphead);
//当链表中只剩下最后一个节点的时候,需要把头指针也制空。
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
//找尾
SLTNode* cur = *pphead;
SLTNode* prev = *pphead;
while (cur->next != NULL)
{
//cur下一个是NULL说明现在的这个就是尾
prev = cur;
cur = cur->next;
}
//产生野指针的问题,确实释放了但是我们cur的前面一个指向的是一个野指针。
free(cur);
prev->next = NULL;
}
}
7.查找
1.查找的区域不可以为空,返回一个节点地址。
SLTNode* SLTFind(SLTNode* phead, STLDatatype x)
{
assert(phead);
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
}
8.在pos之前插入x
1.如果pos是第一个位置相当于头插可以复用前面的代码也可以自己写一个。
2.有可能会改到头所以必须要传二级指针。
void SLTInsert(SLTNode** pphead, SLTNode* pos, STLDatatype x)
{
SLTNode* newnode = BuySListNode(x);
if (pos == *pphead)
{
SLTpushfront(pphead, x);
}
else
{
SLTNode* cur = *pphead;
while (cur->next!=pos)
{
cur=cur->next;
}
cur->next = newnode;
newnode->next = pos;
}
}
9.在pos之后插入x
void SLTInsertAfter(SLTNode* pos, STLDatatype x)
{
SLTNode* newnode = BuySListNode(x);
SLTNode* next = pos->next;
pos->next = newnode;
newnode->next = next;
}
10.删除pos位置
// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
if (pos == *pphead)
{
//头删
SLTpopfront(pphead);
}
else
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
SLTNode* newhead = cur->next->next;
free(pos);
pos = NULL;
cur->next = newhead;
}
}
11.删除pos的后一个位置
// 删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos)
{
assert(pos->next != NULL);
if (pos->next->next == NULL)
{
free(pos->next);
pos->next = NULL;
pos->next = NULL;
}
else
{
SLTNode* next = pos->next->next;
free(pos->next);
pos->next = NULL;
pos->next = next;
}
}
12.链表释放
// 单链表的销毁
void SListDestroy(SLTNode** plist)
{
assert(plist != NULL);
//只需要释放不需要改变,所以一级就可以
SLTNode* cur = *plist;
while (cur->next!=NULL)
{
SLTNode* next = cur->next;
cur->next = NULL;
free(cur);
cur = NULL;
cur = next;
SLTprint(cur);
}
cur->next = NULL;
free(cur);
cur = NULL;
//原来的节点地址被释放,随机值但不是NULL
*plist = NULL;
}
三.整体代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"SLTNode.h"
//打印链表
void SLTprint(SLTNode* plist)
{
//循环遍历不去更改头所以不需要传二级指针
//打印之前我们需要注意我们是否有数据打印
//说明链表已经没有数值或者还没有节点数据
assert(plist!=NULL);
SLTNode* cur = plist;
//一个节点的下一个是空就不打印数据了
while (cur)
{
printf("%d->", cur->data);
//循环的一个条件
cur = cur->next;
}
//实参是一个空指针但是存贮空指针的这个空间不是
printf("NULL\n");
}
//创建一个节点
SLTNode* BuySListNode(STLDatatype x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode*));
if (newnode == NULL)
{
perror("malloc file\n");
exit(-1);
//开辟空间失败直接退出程序
}
//使用这个节点
newnode->data = x;
//因为这是一个新的节点我们的下一个数据还没有给它
//它现在不需要去存储下一个节点的地址。
newnode->next = NULL;
return newnode;
}
//头插
void SLTpushfront(SLTNode** pphead, STLDatatype x)
{
//插入一个数据需要一个新的节点
SLTNode* newnode = BuySListNode(x);
//是有数据,说明*pphead不是空。
//如果直接改变第一个的地址那么。第二个数据就找不到了。
newnode->next = *pphead;
*pphead = newnode;
}
//头删
void SLTpopfront(SLTNode** pphead)
{
//删除数据如果没有数据就不去删除
assert(*pphead != NULL);
//不止一个数据,保存第二个节点的地址
SLTNode* newhead = (*pphead)->next;
free(*pphead);
//释放老节点,拿到新节点
*pphead = newhead;
}
//尾插
void SLTpushback(SLTNode** pphead, STLDatatype x)
{
SLTNode* newnode = BuySListNode(x);
//当没有数据的时候
if (*pphead == NULL)
{
//对一个地址的修改
*pphead = newnode;
}
else
{
SLTNode* cur = *pphead;
//下一个是NULL就是尾
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = newnode;
}
}
//尾删
void SLTpopback(SLTNode** pphead)
{
assert(*pphead);
//当链表中只剩下最后一个节点的时候,需要把头指针也制空。
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
//找尾
SLTNode* cur = *pphead;
SLTNode* prev = *pphead;
while (cur->next != NULL)
{
//cur下一个是NULL说明现在的这个就是尾
prev = cur;
cur = cur->next;
}
//产生野指针的问题,确实释放了但是我们cur的前面一个指向的是一个野指针。
free(cur);
prev->next = NULL;
}
}
SLTNode* SLTFind(SLTNode* phead, STLDatatype x)
{
assert(phead);
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
}
// 在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, STLDatatype x)
{
SLTNode* newnode = BuySListNode(x);
if (pos == *pphead)
{
SLTpushfront(pphead, x);
}
else
{
SLTNode* cur = *pphead;
while (cur->next!=pos)
{
cur=cur->next;
}
cur->next = newnode;
newnode->next = pos;
}
}
// 在pos以后插入x
void SLTInsertAfter(SLTNode* pos, STLDatatype x)
{
SLTNode* newnode = BuySListNode(x);
SLTNode* next = pos->next;
pos->next = newnode;
newnode->next = next;
}
// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
if (pos == *pphead)
{
//头删
SLTpopfront(pphead);
}
else
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
SLTNode* newhead = cur->next->next;
free(pos);
pos = NULL;
cur->next = newhead;
}
}
// 删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos)
{
assert(pos->next != NULL);
if (pos->next->next == NULL)
{
free(pos->next);
pos->next = NULL;
pos->next = NULL;
}
else
{
SLTNode* next = pos->next->next;
free(pos->next);
pos->next = NULL;
pos->next = next;
}
}
// 单链表的销毁
void SListDestroy(SLTNode** plist)
{
assert(plist != NULL);
//只需要释放不需要改变,所以一级就可以
SLTNode* cur = *plist;
while (cur->next!=NULL)
{
SLTNode* next = cur->next;
cur->next = NULL;
free(cur);
cur = NULL;
cur = next;
SLTprint(cur);
}
cur->next = NULL;
free(cur);
cur = NULL;
//原来的节点地址被释放,随机值但不是NULL
*plist = NULL;
}