数据结构---单向链表

BOSS攻略 2026-07-28 21:03:13

顺序表回顾—引入链表缺陷:

1.空间不够了,增容。(增容会带来缺陷,增容会付出一定的性能消耗,其次可能存在一定的空间浪费)

例如:当我有一百个空间,满了之后,增容扩大两倍,但是我插入之后,只插入了10个数据,所以浪费了90个空间。

2.头部或者中部左右的插入删除的效率低。->O(N)

如何解决:

1.空间上,按需给空间。

2.不要求物理空间连续,头部和中部的插入,就不需要挪动数据。

解决方案:链表

一、链表是什么?1.链表的分类1.单向链表:单向链表的特点是链表的链接方向是单向的,对链表的访问需要通过顺序读取从头部开始。

2.双向链表:双向链表也叫双链表,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。

3.循环链表:循环链表的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。

4.线性链表:线性链表是一种简单的链表结构,每个结点只包含一个指向下一个结点的指针。

5.树链表:树链表是一种层次化的链表结构,它的每个结点可以有多个子结点,通过指针连接。

6.网链表:网链表是一种复杂一点的链表结构,它的每个结点可以有多个指向前驱和后继的指针。

等等…

本次内容讲的是单向链表。

2.单向链表注意:最后一个指针应该指向空指针。

链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

从逻辑结构看:链表是线性的,是想象出来的。

从物理结构看:

第二个空间存的是第二个节点的地址。

节点的地址是malloc来的,也可以认为是随机的,malloc是操作系统随机找的一个空闲的空间。

最后一个节点存的是空指针。

顺序表和链表的区别就是:

物理上是连续的存储的,链表的物理空间是不连续的。

链表的定义:

代码语言:javascript复制typedef struct SListNode

{

SLTDataType data;

struct SListNode* next;//结构体中不能有结构体嵌套,但是可以是结构体指针,

//指针就是一个地址,表示4个字节,指针指向的下一个节点是结构体

}SLT;注意:结构内部是不能用typedef的结构体类型。

二、链表的应用1.链表的打印注意:这里的SLT类型是上文定义的结构体类型

代码语言:javascript复制void SListPrint(SLT* plist)

{

SLT* cur=plist;

while (cur != NULL)

{

printf("%d->", cur->data);

cur = cur->next;

}

printf("NULL\n");

}2.建立新的节点代码语言:javascript复制SLT* BuySListNode(SLTDataType x)

{

SLT* newnode = (SLT*)malloc(sizeof(SLT));

newnode->data = x;

newnode->next = NULL;

return newnode;

}3.尾插和首插代码语言:javascript复制//尾插

void SListPushBack(SLT** pplist, SLTDataType x)

{

SLT* newnode = BuySListNode(x);

if (*pplist == NULL)

{

*pplist = newnode;//形参是实参的零时拷贝,出了定义域会销毁

}

else

{

SLT* tail = *pplist;

while (tail->next != NULL)

{

tail = tail->next;

}

//尾节点,链接新节点

tail->next = newnode;

}

}

//头插

void SListPushFront(SLT** pplist, SLTDataType x)

{

SLT* newnode = BuySListNode(x);

newnode->next = *pplist;

*pplist = newnode;

}4.尾删和头删代码语言:javascript复制//尾删

void SListPopBack(SLT** pplist)

{

if (*pplist == NULL)

{

return;

}

else if ((*pplist)->next == NULL)

{

free(*pplist);

*pplist = NULL;

return;

}

SLT* tail = *pplist;

SLT* prev = NULL;//保存上一个节点的指针变量

while (tail->next != NULL)

{

prev = tail;//将上一个节点存起来

tail = tail->next;//找下一个节点

}

free(tail);//free掉下一个空间

prev->next = NULL;//找到倒数第二个节点指向的下一个节点,并置为空指针。防止变为野指针

tail = NULL;//置位空指针

}

//头删

void SListPopFront(SLT** pplist)

{

SLT* tail = (*pplist)->next;//将下一个节点的位置给存起来

free(*pplist);//将这个节点给free掉

*pplist = NULL;//置空

*pplist = tail;//找到下一个节点,并将其赋值为头结点

}5.查找链表中的数据代码语言:javascript复制//查找链表数据

SLT* SListFind(SLT* plist, SLTDataType x)

{

SLT* cur = plist;

while (cur != NULL)

{

if (cur->data == x)

{

return cur;

}

cur = cur->next;

}

return NULL;

}6.在指定pos位置之前插入数据或者删除pos位置数据代码语言:javascript复制//在pos位置前插入x

void SListInsert(SLT** pplist, SLT* pos, SLTDataType x)

{

if (*pplist == pos)

{

SListPushFront(pplist, x);

}

else

{

SLT* newnode = BuySListNode(x);

SLT* prev = *pplist;

while (prev->next != pos)

{

prev = prev->next;

}

newnode->next = pos;

prev->next = newnode;

}

}

//删除pos位置的数

void SListErase(SLT** pplist, SLT* pos)

{

if (pos == *pplist)

{

SListPopFront(pplist);

}

else

{

SLT* prev = *pplist;

while (prev->next != pos)

{

prev = prev->next;

}

SLT* tail = pos->next;

free(pos);

prev->next = tail;

}

}三、如何使用链表应用中的函数及函数封装1.在pos前插入指定x代码语言:javascript复制void TestSlist3()

{

SLT* plist = NULL;

SListPushFront(&plist, 1);

SListPushFront(&plist, 2);

SListPushFront(&plist, 3);

SListPushFront(&plist, 4);

SListPrint(plist);

SLT* pos = SListFind(plist, 4);

if (pos)

{

SListInsert(&plist, pos, 30);

}

printf("\n");

SListPrint(plist);

}

int main()

{

TestSlist3();

return 0;

}注意:删除pos位置的数也与上述代码雷同

2.封装为3个文件1. Slist.h代码语言:javascript复制#pragma once//防止重复包含

#include

#include

typedef int SLTDataType;

typedef struct SListNode

{

SLTDataType data;

struct SListNode* next;//结构体中不能有结构体嵌套,但是可以是结构体指针,

//指针就是一个地址,表示4个字节,指针指向的下一个节点是结构体

}SLT;

void SListPrint(SLT* plist);

void SListPushBack(SLT** plist, SLTDataType x);

void SListPushFront(SLT** pplist, SLTDataType x);

void SListPopBack(SLT** pplist);

void SListPopFront(SLT** pplist);

//查找位置

SLT* SListFind(SLT* plist, SLTDataType x);

//在pos前面插入一个数

void SListInsert(SLT** pplist,SLT*pos,SLTDataType x);

//删除pos位置的值

void SListErase(SLT** pplist,SLT*pos);2. test.c文件代码语言:javascript复制#include "Slist.h"

void TestSlist1()

{

SLT *plist=NULL;

SListPushBack(&plist, 1);

SListPushBack(&plist, 2);

SListPushBack(&plist, 3);

SListPushBack(&plist, 4);

SListPushBack(&plist, 5);

SListPrint(plist);

}

void TestSlist2()

{

SLT* plist = NULL;

SListPushFront(&plist, 1);

SListPushFront(&plist, 2);

SListPushFront(&plist, 3);

SListPushFront(&plist, 4);

SListPrint(plist);

SListPopFront(&plist);

SListPopBack(&plist);

printf("\n");

SListPrint(plist);

}

void TestSlist3()

{

SLT* plist = NULL;

SListPushFront(&plist, 1);

SListPushFront(&plist, 2);

SListPushFront(&plist, 3);

SListPushFront(&plist, 4);

SListPrint(plist);

SLT* pos = SListFind(plist, 4);

if (pos)

{

SListInsert(&plist, pos, 30);

}

printf("\n");

SListPrint(plist);

}

void TestSlist4()

{

SLT* plist = NULL;

SListPushFront(&plist, 1);

SListPushFront(&plist, 2);

SListPushFront(&plist, 3);

SListPushFront(&plist, 4);

SListPrint(plist);

SLT* pos = SListFind(plist, 3);

if (pos)

{

SListErase(&plist, pos);

}

printf("\n");

SListPrint(plist);

}

int main()

{

TestSlist4();

return 0;

}注意:Slist.c文件就是上面函数,这里不过多重复阐述

四、总结链表相较于顺序表来说难度有所上升,但是掌握链表基本概念,练习一些算法题还是很好掌握的。