1.链表概念
链表在逻辑上是线性的,但是在物理存储的过程中,不一定是线性的,它通过指针实现连续存储数据元素。
结构:
2.代码展示
SList.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
//单链表的销毁
void SListDestory(SListNode** pplist);
// 单链表打印
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);
SList.c
#include"SList.h"
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* psl = (SListNode*)malloc(sizeof(SListNode));
psl->data = x;
psl->next = NULL;
return psl;
}
// 单链表打印
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
//1.空链表
//2.非空链表
SListNode* psl = (SListNode*)malloc(sizeof(SListNode));
psl->data = x;
psl->next = NULL;
if (*pplist == NULL)
{
*pplist = psl;
}
else
{
SListNode* tail = *pplist;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = psl;
}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
//先存头指针,再更新头部。
SListNode* psl = (SListNode*)malloc(sizeof(SListNode));
psl->data = x;
psl->next = NULL;
psl->next = *pplist;
*pplist = psl;
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{ //1.空链表
//2.单个链表
//3.多个链表
if (*pplist == NULL)
{
return;
}
if ((*pplist)->next == NULL)
{
free(*pplist);//一定要先释放再置NULL,防止野指针问题。
*pplist = NULL;
}
else
{
SListNode* prev = NULL;
SListNode* tail = *pplist;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
//1.空链表
//2.多个链表
if (*pplist == NULL)
{
return;
}
SListNode* cur = (*pplist)->next;
free(*pplist); //这里也要注意释放空间
*pplist = cur;
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
while (plist !=NULL)
{
if (plist->data == x)
{
return plist;
}
plist = plist->next;
}
return plist;
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
SListNode* cur = (SListNode*)malloc(sizeof(SListNode));
cur->data = x;
cur->next = NULL;
cur->next = pos->next;//一定要先把后面的指针存起来,再改变指向。
pos->next = cur;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
if (pos == NULL || pos->next == NULL)
{
return;
}
SListNode* next = pos->next->next;
free(pos->next);
pos->next = next;
}
//单链表的销毁
void SListDestory(SListNode** pplist)
{
if (*pplist == NULL)
{
return ;
}
while ((*pplist)->next != NULL)
{
SListNode* cur = (*pplist)->next;
free(*pplist);
*pplist = cur;
}
free(*pplist);
*pplist = NULL;//释放完成要置空,不然会存在野指针的问题
}
void test()
{
SListNode* psl = NULL;/* BuySListNode(1);*/
SListPushBack(&psl, 4);
SListPushBack(&psl, 5);
SListPushBack(&psl, 6);
SListPushBack(&psl, 7);
SListPrint(psl);
SListPushFront(&psl, 3);
SListPushFront(&psl, 2);
SListPushFront(&psl, 1);
SListPrint(psl);
SListPopBack(&psl);
SListPrint(psl);
SListPopFront(&psl);
SListPrint(psl);
SListNode* ret=SListFind(psl, 2);
SListInsertAfter(ret, 0);
SListPrint(psl);
SListEraseAfter(ret);
SListPrint(psl);
SListDestory(&psl);
SListPrint(psl);
return;
}
test.c
#include"SList.h"
int main()
{
test();
system("pause");
return 0;
}
3.结果展示
4.心得体会
首先需要注意的是,在对链表实现增删查改的过程中,需要传递二级指针,其次要注意删除某个数时,要先将它的下一个指针保存下来,然后再释放当前空间,然后在连接两个指针。最后就要考虑空链表,单个链表,多个链表这种边界情况。
5.存在问题
为什么在pos前后删除的时候,传递指针就可以改变链表的值?而头插,头删,尾插,尾删时就要传递二级指针才能改变呢?一级指针为什么头删的时候也不行呢?
同样的是改变了链表的结构,查看地址也没找出答案。
找到答案了!!!
这个是二级指针。
这个是一级指针解法(错误)
通过对比,我们来解释一下两个过程,首先来说直接传递指针来进行头删除,头删的时候,我们通过查看ppsl psl &ppsl &psl,发现只是将指针重新临时拷贝到一个空间,然后对这个指针进行操作,但是执行完成返回的时候,还是返回了之前指针的地址。临时变量消失,原来的指针还是指向了原位置,我执行的过程中释放了原来指针指向的空间,所以会出现指向随机值,我将空间不释放,得到下面的图,也确实证明了我们想法。
再来看看二级指针,二级指针,我们通过查看地址发现,这个是实实在在的传递了地址过去,回来接收的也是原地址,所以这个删除时,是对指针的地址进行了操作,完成了数据的改变。
下来我来查看一下Pos为什么能实现插入。
通过地址查看,我们可以看到,即使是创建了一个临时的变量,去存储当前的指针的值,我们要改变的是,当前位置的下一个所以对临时变量指向的地址的下一个地址,和实际原来ret指向的空间是一个空间,所以这里能完成数据的更新,即使是临时变量释放之后。
要改变数据一定要去操作指针,当然操作指针的位置一定要和临时变量指向的地址相同,否则就要传二级指针,去达到访问同一块的地址的目的,不然临时变量释放之后,原来的指针指向还没有改变。