一.概念
链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
写链表的方法有很多
- 有哨兵卫头节点的链表
- 无哨兵卫头节点的链表(需要传二级指针)
(1)有哨兵卫头节点的链表(head里面可以不赋值,就是随机值)
(2)无哨兵卫头节点的链表(需要传二级指针)
二.链表的实现(无哨兵卫头节点)
1.定义一个链表
typedef int SLTDataType; //定义SLDataType为int型,方便更改数据类型
typedef struct SListNode
{
SLTDataType data; //要存的数据
struct SListNode* next; //指向下一个结点的地址
}SLTNode;
2.初始化链表
这种链表的初始化很简单(直接在主函数中定义为NULL)
SLTNode* plist = NULL; //初始化链表为NULL
3.链表节点的创造
这个函数是一个频繁要调用的函数,在插入数据时都需要调用这个函数去构造一个节点
//创造一个节点
SLTNode* BuyListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //去malloc一个新的节点
if (newnode == NULL) //判断malloc是否成功
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x; //赋值
newnode->next = NULL; //节点的next定义为NULL
return newnode;
}
4.尾插
为什么要传二级指针?
- 前面初始化链表 SLTNode* plist = NULL; 要改变plist里的内容,就要传递地址,plist本身就是一个一级指针,一个一级指针的地址就需要二级指针来接收
- 在运用时
SLTNode* plist = NULL; SListPushBack(&plist, 1); //&plist是一个一级指针的地址,要用二级指针接受
//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x) //这里要用二级指针,要改变形参里的内容,就要传地址
{
assert(pphead); //断言,第一次进去时 *pphead 是NULL,但 pphead 是*pphead的地址,不为空
SLTNode* newnode = BuyListNode(x); //调用函数,去创造一个节点
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找到尾节点
SLTNode* tail = *pphead;
while (tail->next != NULL) //找到链表的结尾
{
tail = tail->next;
}
tail->next = newnode;
}
}
5.头插
头插也要用二级指针(只要是改动链表中的数据的,都要传递二级指针)
//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead); //断言,好习惯
SLTNode* newnode = BuyListNode(x); //调用函数,去创造一个节点
newnode->next = *pphead;
*pphead = newnode;
}
6.尾删
//尾删
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead != NULL); //链表不为NULL,如果为NULL,则会报错
if ((*pphead)->next == NULL) //只有一个节点,直接删空链表并释放空间
{
free(*pphead);
*pphead = NULL;
}
else //链表有多个节点,遍历寻找尾节点,并且要记录尾节点的前一个结点
{
SLTNode* tail = *pphead;
SLTNode* prev = NULL;
while (tail->next != NULL)
{
prev = tail; //记录尾节点的前一个节点,链表没办法找到前面的那个节点
tail = tail->next;
}
free(tail); //释放尾节点的空间
tail = NULL;
prev->next = NULL;
}
}
7.头删
头删比尾删简单,链表的头节点比尾节点好找
//头删
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
SLTNode* front = *pphead; //定义一个指针指向头节点
if (*pphead == NULL)
{
return;
}
else
{
*pphead = front->next;
}
free(front); //释放头节点的空间
front = NULL;
}
8.查找一个值(返回值是,这个数的地址)
不需要二级指针,查找数据,而不改变数据,所以不需要二级指针
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur) //遍历,找到链表中的值
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL; //没有,返回NULL
}
9. 在pos后插入一个节点
也可以在pos前面插入一个节点:这个插入比在后面插入麻烦很多,需要判断pos是否是第一个节点(头节点),还需要找到pos的前一个节点进行插入,倒不如在pos后面插入,简单,适合。
//在pos后插入,这个更适合,也简单
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos); //pos不能为NULL
SLTNode* newnode = BuyListNode(x); //调用函数,创造新节点
newnode->next = pos->next;
pos->next = newnode;
}
10.删除pos后的一个节点
//删除pos后一个节点
void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
assert(pphead);
assert(pos->next); //删除的节点不能为NULL
SLTNode* next = pos->next;
pos->next = next->next;
free(next); //释放next空间
next = NULL; //可有可无,next是形参,置不置空,没有影响,养成好习惯,加上也行
}
11.打印链表
打印链表就传用一级指针就行,只访问链表里的数据,不对链表修改,所以一级指针就行
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("\n");
}
12.释放空间
//释放链表
void SListDestory(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur) //遍历链表,一个节点一个节点的释放
{
SLTNode* next = cur->next;
free(cur);
cur = NULL;
cur = next;
}
*pphead = NULL;
}
总结
- 整个链表都是使用二级指针进行传参的,对于指针的应用应当更加熟练(知道一级指针是什么,二级指针是什么)
- 尽量不要去动链表的第一个节点(*pphead),要遍历或增删要定义指针去完成任务,最后 *pphead 就是这个链表的第一个节点,就能找到整个链表内容(可以通过定义的tail和cur去遍历链表)
全部代码
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType; //定义SLDataType为int型,方便更改数据类型
typedef struct SListNode
{
SLTDataType data; //要存的数据
struct SListNode* next; //指向下一个结点的地址
}SLTNode;
//创造一个节点
SLTNode* BuyListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //去malloc一个新的节点
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x; //赋值
newnode->next = NULL;
return newnode;
}
//打印
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("\n");
}
//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x) //这里要用二级指针,要改变形参里的内容,就要传地址
{
assert(pphead);
SLTNode* newnode = BuyListNode(x); //调用函数,去创造一个节点
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找到尾节点
SLTNode* tail = *pphead;
while (tail->next != NULL) //找到链表的结尾
{
tail = tail->next;
}
tail->next = newnode;
}
}
//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead); //断言,好习惯
SLTNode* newnode = BuyListNode(x); //调用函数,去创造一个节点
newnode->next = *pphead;
*pphead = newnode;
}
//尾删
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead != NULL);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
SLTNode* prev = NULL; //链表没办法找到前面的那个,所以要定义
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
//头删
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
SLTNode* front = *pphead;
if (*pphead == NULL)
{
return;
}
else
{
*pphead = front->next;
}
free(front);
front = NULL;
}
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur) //遍历,找到链表中的值
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
//在pos后插入,这个 更适合,也简单
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos后一个
void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
assert(pphead);
assert(pos->next);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
next = NULL; //可有可无
}
//释放链表
void SListDestory(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = NULL;
cur = next;
}
*pphead = NULL;
}
int main()
{
SLTNode* plist = NULL;
printf("头插:");
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPushFront(&plist, 4);
SListPushFront(&plist, 5);
SListPrint(plist);
printf("头删:");
SListPopFront(&plist);
SListPrint(plist);
printf("尾插:");
SListPushBack(&plist, 10);
SListPushBack(&plist, 20);
SListPrint(plist);
printf("尾删:");
SListPopBack(&plist);
SListPrint(plist);
printf("在2后面插入200:");
SLTNode* pos = SListFind(plist, 2);
if (pos) //pos不为NULl
{
SListInsertAfter(pos, 200);
}
SListPrint(plist);
printf("删除2后的200:");
if (pos)
{
SListEraseAfter(&plist, pos);
}
SListPrint(plist);
SListDestory(&plist); //释放链表
return 0;
}
感谢大家的观看,希望你能从这篇文章中学到一些东西(如有错误,提醒我,我会及时修改)
谢谢大家!!!