文章目录
前言
之前笔者介绍了初阶数据结构的基础知识 , 那么链表也是占比很重要的一个知识,让我们一起领略吧 !
一、什么是链表 ?
★ 概念:链表是一种物理存储结构上非连续 、非顺序的存储结构 ,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
▲ :简单来说就是 链表在物理结构上不一定连续 , 但逻辑结构是连续的 。这里的物理结构呢就是指数据存储的方式 。例如:顺序表数据存储一定是连续的
★ 图示:
以上便是物理结构不连续 , 逻辑结构连续 。
二、链表的分析
★ 节点:链表是由一个一个节点构成 , 上图每一个圆圈代表一个节点 。节点中包含:存储的数据 、指向下一个节点的指针 。
以上的节点是我们申请出来的空间 , 因为不涉及扩容所以用到 malloc 函数。点击可前往官网学习 malloc 函数!
综上,要写出一个链表就 必须有节点 , 一个一个节点就构成了一个完整的链表。
◬图例:
以上就是一个完整的单向链表 , Next 指针会指向下一个存储数据的节点 , 这样就让乱序的数据顺利的连接。
三、链表的实现
★ 链表实现相关接口
1. 单链表的头插
2. 单链表的头删
3. 单链表的尾插
4. 单链表的尾删
5. 单链表的查找
6. 单链表的打印
7. 单链表在 pos 之后插入 (pos 前一个节点后插入数据)
8. 单链表删除 pos 之后的值
9. 单链表的销毁
◬ :思考 : 1. 为什么不在 pos 之前插入数据 ? 2. 为什么不删除 pos 位置的值 ?
■ 为什么不在 pos 之前插入数据 ?
首先,在链表中的插入很简单 , 不需要移动数据 , 只需改变指针的指向即可完成。 pos 之后插入数据也就意味这在 pos 前一个节点后插入数据 , 若要在 pos 之前插入数据 , 那么就要知道 pos 之前的前一个节点 , 这样就得遍历链表 , 时间复杂度为 O(N). 这样就会增加代码得复杂性和效率 。 然而 , 在 pos 之后呢 , 我们直接改指针的指向就可以达到目的 , 不需要再次遍历链表了 。
■ 为什么不删除 pos 位置的值 ?
这个与上述同理 , 我们不需要再次遍历链表增加代码的复杂度和效率 。
★ 单链表的实现
▲ 申请节点
分析
首先链表的第一步就是申请相关节点 ,还是三个相关文件 : SList.c 、SList.h 、test.c
单链表接口的声明
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SNodeDateType;
typedef struct SListNode
{
SNodeDateType data; // 存储的数据
struct SListNode* Next; // 指向下一个节点的指针
}SLNode;
//申请节点
SLNode* ByNode(SNodeDateType x);
// 单链表打印
void SListPrint(SLNode* plist);
// 单链表尾插
void SListPushBack(SLNode** pplist, SNodeDateType x);
// 单链表的头插
void SListPushFront(SLNode** pplist, SNodeDateType x);
// 单链表的尾删
void SListPopBack(SLNode** pplist);
// 单链表头删
void SListPopFront(SLNode** pplist);
// 单链表查找
SLNode* SListFind(SLNode* plist, SNodeDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SLNode* pos, SNodeDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SLNode* pos);
因为之后的接口使用需要申请节点 , 所以申请节点函数封装一个函数 , 后续使用只需调用即可。
申请节点
#include "SList.h"
//申请节点
SLNode* ByNode(SNodeDateType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
//申请成功
newnode->data = x;
newnode->Next = NULL;
return newnode;
}
▲ 尾插
传参分析
◬ :链表的声明中传参用的是二级指针 , 为什么呢?
给出测试代码:
#include "SList.h"
int main()
{
SLNode* list = NULL;
//头插
SLTPushFront(list, 2);
SListPrint(list);
return 0;
}
头插代码
//单链表的头插
void SLTPushFront(SLNode* phead, SNodeDateType x)
{
//assert(phead);
SLNode* newnode = ByNode(x);
newnode->Next = phead;
phead = newnode; //新节点成为头节点
}
调试图以及运行结果
以上调试后便可知道结果了 , 头插代码传参是一级指针 , phead 进行头插不会影响list 这个指针 ,因为: 形参是实参的一份临时拷贝 ,需要改变实参形参部分就必须传地址 , 那么一级指针的地址就是二级指针 。
★ 尾插
// 单链表尾插
void SListPushBack(SLNode** pplist, SNodeDateType x)
{
assert(pplist);
SLNode* ptail = *pplist;
//申请节点
SLNode* node = ByNode(x);
//链表为空
if(ptail == NULL)
{
*pplist = node;
}
else
{
//找尾
while(ptail->Next)
{
ptail = ptail->Next;
}
//ptail 就是尾
ptail->Next = node;
}
}
▲ 头插
头插
// 单链表的头插
void SListPushFront(SLNode** pplist, SNodeDateType x)
{
assert(pplist);
//申请节点
SLNode* node = ByNode(x);
node->Next = *pplist;
*pplist = node;
}
▲ 尾删
// 单链表的尾删
void SListPopBack(SLNode** pplist)
{
//链表不能为空
assert(pplist && (*pplist));
//链表只有一个节点
if ((*pplist)->Next == NULL) //-> 优先级高于*
{
free(*pplist);
*pplist = NULL;
}
else {
//链表有多个节点
SLNode* prev = *pplist;
SLNode* ptail = *pplist;
while (ptail->Next)
{
prev = ptail;
ptail = ptail->Next;
}
//prev ptail
free(ptail);
ptail = NULL;
prev->Next = NULL;
}
}
▲ 头删
//单链表头删
void SListPopFront(SLNode** pplist)
{
//链表不能为空
assert(pplist && *pplist);
SLNode* Next = (*pplist)->Next; //-> 优先级高于*
free(*pplist);
*pplist = Next;
}
▲ 打印
// 单链表打印
void SListPrint(SLNode* plist)
{
SLNode* pur = plist;
//遍历链表逐一打印
while(pur)
{
printf("%d->", pur->data);
pur = pur->Next;
}
printf(" NULL\n");
}
▲ 查找
// 单链表查找
SLNode* SListFind(SLNode* plist, SNodeDateType x)
{
SLNode* pur = plist;
while (pur)//等价于pcur != NULL
{
if (pur->data == x)
{
return pur;
}
pur = pur->Next;
}
//pur == NULL
return NULL;
}
▲ 单链表在pos位置之后插入
void SListInsertAfter(SLNode* pos, SNodeDateType x)
{
assert(pos);
SLNode* node = SLTBuyNode(x);
//pos -> newnode -> pos->next
node->Next = pos->Next;
pos->Next = node;
}
▲ 单链表删除pos位置之后的值
void SListEraseAfter(SLNode* pos)
{
assert(pos && pos->Next);
SLNode* del = pos->Next;
//pos del del->next
pos->Next = del->Next;
free(del);
del = NULL;
}
▲ 销毁链表
void SListDesTroy(SLNode** pplist)
{
assert(pplist && *pplist);
SLNode* pur = *pplist;
while (pur)
{
SLNode* next = pur->Next;
free(pur);
pur = next;
}
*pplist = NULL;
}
四、整体代码
#define _CRT_SECURE_NO_WARNINGS
#include "SList.h"
//申请节点
SLNode* ByNode(SNodeDateType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
//申请成功
newnode->data = x;
newnode->Next = NULL;
return newnode;
}
//
// 单链表打印
void SListPrint(SLNode* plist)
{
SLNode* pur = plist;
//遍历链表逐一打印
while(pur)
{
printf("%d->", pur->data);
pur = pur->Next;
}
printf(" NULL\n");
}
// 单链表尾插
void SListPushBack(SLNode** pplist, SNodeDateType x)
{
assert(pplist);
SLNode* ptail = *pplist;
//申请节点
SLNode* node = ByNode(x);
//链表为空
if(ptail == NULL)
{
*pplist = node;
}
else
{
//找尾
while(ptail->Next)
{
ptail = ptail->Next;
}
//ptail 就是尾
ptail->Next = node;
}
}
// 单链表的头插
void SListPushFront(SLNode** pplist, SNodeDateType x)
{
assert(pplist);
//申请节点
SLNode* node = ByNode(x);
node->Next = *pplist;
*pplist = node;
}
// 单链表的尾删
void SListPopBack(SLNode** pplist)
{
//链表不能为空
assert(pplist && (*pplist));
//链表只有一个节点
if ((*pplist)->Next == NULL) //-> 优先级高于*
{
free(*pplist);
*pplist = NULL;
}
else {
//链表有多个节点
SLNode* prev = *pplist;
SLNode* ptail = *pplist;
while (ptail->Next)
{
prev = ptail;
ptail = ptail->Next;
}
//prev ptail
free(ptail);
ptail = NULL;
prev->Next = NULL;
}
}
//单链表头删
void SListPopFront(SLNode** pplist)
{
//链表不能为空
assert(pplist && *pplist);
SLNode* Next = (*pplist)->Next; //-> 优先级高于*
free(*pplist);
*pplist = Next;
}
// 单链表查找
SLNode* SListFind(SLNode* plist, SNodeDateType x)
{
SLNode* pur = plist;
while (pur)//等价于pcur != NULL
{
if (pur->data == x)
{
return pur;
}
pur = pur->Next;
}
//pur == NULL
return NULL;
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SLNode* pos, SNodeDateType x)
{
assert(pos);
SLNode* node = SLTBuyNode(x);
//pos -> newnode -> pos->next
node->Next = pos->Next;
pos->Next = node;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SLNode* pos)
{
assert(pos && pos->Next);
SLNode* del = pos->Next;
//pos del del->next
pos->Next = del->Next;
free(del);
del = NULL;
}
//销毁链表
void SListDesTroy(SLNode** pplist)
{
assert(pplist && *pplist);
SLNode* pur = *pplist;
while (pur)
{
SLNode* next = pur->Next;
free(pur);
pur = next;
}
*pplist = NULL;
}
测试代码
#include "SList.h"
int main()
{
SLNode* list = NULL;
SListPushBack(&list, 1);
SListPushBack(&list, 2);
SListPushBack(&list, 3);
SListPrint(list);
SListPushFront(&list, 4);
SListPrint(list);
SListPopBack(&list);
SListPrint(list);
SListPopFront(&list);
SListPrint(list);
return 0;
}
运行结果
总结
以上是笔者对单链表的相关介绍,后续还会更多关于数据结构的知识 持续关注哦 !