一、引入
-
动态顺序表:
-
插入数据,空间不够了,要增容
-
要求数据是依次存储
-
-
缺陷:
- 如果空间不够,增容。增容会付出一定性能消耗,其次可能存在一定的空间浪费。
- 如果100个空间,满了,然后增容到200(每次增容*2),但是我实际就插入了10个数据,如果不在插入,那么就有90个空间浪费了。
- 头部或者中部左右的插入删除效率低,为O(n),每次头插都要将n个数据后移。
-
如何解决?
- 1、空间上,按需给空间
- 2、不要求物理空间连续,头部和中间的插入就不需要挪动数据。
- 3、引出—>链表
二、基本介绍
- 链表的概念和结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
链表的物理结构:(实实在在的内存中是如何存储表示他们之间关系的)
- 首先需要一个头指针(图中为p)指向第一个元素的地址
三、代码实现
SList.h头文件:
// Created by zjc on 2024/10/19 17:15
//#ifndef SLSIT_SLIST_H
//#define SLSIT_SLIST_H
//
//#endif //SLSIT_SLIST_H
#pragma once // 防止头文件被多次包含
#include "stdio.h"
#include "stdlib.h"
// typedef定义类型,方便后面更改
typedef int SLTDataType;
struct SListNode {
// 1.定义两个变量,一个是值(数据),一个是地址(下一个数据的地址)
// 类型和存储的值
SLTDataType data;
// 指向下一个节点的指针(地址);类型是一样的也是结构体
struct SListNode *next;
};
// 进行下命名,更方便
typedef struct SListNode SLTNode;
// 打印
// 注意:不会改变链表的头指针,传一级指针
void SListPrint(SLTNode *phead);
// 接口:
// 注意:可能会改变链表的头指针,传二级指针
// 1.尾插 传入结构体指针和插入的数据x
void SListPushBack(SLTNode **phead, SLTDataType x);
// 2.头插 传入结构体指针和插入的数据x
void SListPushFront(SLTNode **pphead, SLTDataType x);
// 3.头删
void SListPopFront(SLTNode **pphead);
// 4.尾删
void SListPopBack(SLTNode **pphead);
// 5.查找
SLTDataType* SListFind(SLTNode* phead,SLTDataType x);
// 6.随机插入
void SListInsert(SLTNode **pphead,SLTNode* pos,SLTDataType x);
// 7.随机删除
void SListErase(SLTNode **pphead,SLTNode* pos);
/*
* 有些地方给的是int,直接给的下标
// 6.随机插入
void SListInsert(SLTNode **pphead,int pos,SLTDataType x);
// 7.随机删除
void SListErase(SLTNode **pphead,int pos);
*/
接口实现SList.c:
#include "SList.h"
// 0.打印
void SListPrint(SLTNode *phead) {
SLTNode *cur = phead;
// 当前指针位置等于NULL结束
while (cur != NULL) {
printf("%d->", cur->data);
// 然后到下一个位置,将下一个地址赋值给当前
cur = cur->next;
}
printf("NULL");
printf("\n");
}
// 0.空间开辟
SLTNode *BuySListNode(SLTDataType x) {
// 分配空间:
// 定义结构体指针(地址)并且分配空间
SLTNode *newnode = (SLTNode *) malloc(sizeof(SLTNode));
// 开辟空间插入的数据
newnode->data = x;
// 开辟空间的下一个指针为NULL
newnode->next = NULL;
// 将新开的空间返回
return newnode;
}
- 尾插
// 1. 尾插
void SListPushBack(SLTNode **pphead, SLTDataType x) {
// 分配空间:
SLTNode *newnode = BuySListNode(x);
// 一开始phead是NULL,是无法取到地址的,所以要进行判断,如果为空,将开辟空间的地址赋值给phead
if (*pphead == NULL) {
*pphead = newnode;
} else // 如果说有多个就去找尾
{
// 找到尾结点的指针;因为每个节点存储下一个节点的地址
SLTNode *tail = *pphead;
while (tail->next != NULL) {
tail = tail->next;
}
// 尾结点指针指向下一个地址就是新开辟的地址
// 尾节点链接新节点
tail->next = newnode;
}
}
- 头插
// 2.头插
void SListPushFront(SLTNode **pphead, SLTDataType x) {
// 分配空间: 初始化
SLTNode *newnode = BuySListNode(x);
// 将之前的头指针指向第一个元素的地址赋值给新开辟的空间的next
newnode->next = *pphead;
// 此时的phead需要存第一个节点的地址
*pphead = newnode;
}
- 头删
// 3.头删
void SListPopFront(SLTNode **pphead) {
// 先把下一个指针保存起来,这里需要括起来,因为->和*优先级一样
// **pphead解引用才是 SLTNode*
SLTNode *next = (*pphead)->next;
// 如果直接将当前位置free是找不到下一个
free(*pphead);
// 将开始的pphead的地址赋值给头指针
*pphead = next;
}
- 尾删
// 4.尾删
void SListPopBack(SLTNode **pphead) {
/*
注意:
1、注意链表为空的情况
2、链表只有一个的情况,那么第一个next都为空,循环就不会进入
3、最后释放了,就会存在野指针
*/
// 1.如果链表是空,就不进行删除
if (*pphead == NULL) {
return;
} else if ((*pphead)->next == NULL) // 2.只有一个节点的处理
{
// 直接将这个节点free,将pphead置为NULL
free(*pphead);
*pphead = NULL;
} else { // 3.一个以上节点
// 定义一个指针找到tail的前一个节点
SLTNode *prev = NULL;
SLTNode *tail = *pphead;
while (tail->next != NULL) {
// tail往下走的时候,将我自己的位置给prev
prev = tail;
tail = tail->next;
}
// free掉tail
free(tail);
// 将tail前一个节点的指向指为NULL
prev->next = NULL;
}
}
- 查找
// 5.查找
SLTDataType *SListFind(SLTNode *phead, SLTDataType x) {
// 将首地址赋值给cur
SLTNode *cur = phead;
// while(cur != NULL){ 这种写法也可以
// cur不等于0则继续
while (cur) {
// 如果当前的数据是查找的则返回
if (cur->data == x) {
return cur;
}
// 查找不到就继续下一个节点
cur = cur->next;
}
return NULL;
}
- 随机插入
// 6.随机插入
// 在pos的前面插入x
void SListInsert(SLTNode **pphead, SLTNode *pos, SLTDataType x) {
// 如果pos等于pphead,就相当于头插
if (pos == *pphead) {
SListPushFront(pphead, x);
} else { // 如果不是头插就是下面的逻辑
// 开辟空间
SLTNode *newnode = BuySListNode(x);
// 也可以这样,prev的下一个节点等于pos就相当于找到了
SLTNode *prev = *pphead;
// 循环到下一个节点,prev就是pos前面的节点
while (prev->next != pos) {
prev = prev->next;
}
// 插入空间的地址赋值给pos前一个的next
prev->next = newnode;
newnode->next = pos;
}
}
- 随机删除
// 7.随机删除
void SListErase(SLTNode **pphead, SLTNode *pos) {
// 如果说pos等于第一个节点,就相当于头删
if (pos == *pphead) {
SListPopFront(pphead);
} else {
// 也可以这样,prev的下一个节点等于pos就相当于找到了
SLTNode *prev = *pphead;
// 循环到下一个节点,prev就是pos前面的节点
while (prev->next != pos) {
prev = prev->next;
}
// 删除的前一个节点需要指向删除的下一个节点
prev->next = pos->next;
free(pos);
}
}
测试文件Test.c:
// Created by zjc on 2024/10/19 19:11
#include "SList.h"
void TestSList1() {
// 定义一个头指针为空plist/phead
SLTNode *plist = NULL;
// 尾插
// 注意:这里需要传递地址,结构体指针指向的还是另一个地址
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushFront(&plist, 4);
// 头删
SListPopFront(&plist);
// 打印
SListPrint(plist);
}
void TestSList2() {
// 定义一个头指针为空plist/phead
SLTNode *plist = NULL;
// 尾插:
// 注意:这里需要传递地址,结构体指针指向的还是另一个地址
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
// 尾删:
SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPrint(plist);
}
void TestSList3() {
// 定义一个头指针为空plist/phead
SLTNode *plist = NULL;
// 尾插:
// 注意:这里需要传递地址,结构体指针指向的还是另一个地址
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
// 想要在3的前面插入一个30
//SLTNode *pos = SListFind(plist, 3);
// 想要在1的前面插入一个10
SLTNode *pos = SListFind(plist, 1);
// 如果说pos不为空,则将数据插入
if (pos) {
// 找到位置后,在作为参数传过来
SListInsert(&plist, pos, 10);
}
SListPrint(plist);
}
void TestSList4() {
// 定义一个头指针为空plist/phead
SLTNode *plist = NULL;
// 尾插:
// 注意:这里需要传递地址,结构体指针指向的还是另一个地址
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
// 1、直接传递的直接提指针,不传递下标
// 想要删除数据等于3的节点
SLTNode *pos = SListFind(plist, 3);
// 如果说pos不为空,则删除,为空则说明没找到该结构体
if (pos) {
// 找到位置后,在作为参数传过来
SListErase(&plist, pos);
}
SListPrint(plist);
// 来一遍头删:
pos = SListFind(plist, 1);
// 如果说pos不为空,则删除,为空则说明没找到该结构体
if (pos) {
// 找到位置后,在作为参数传过来
SListErase(&plist, pos);
}
SListPrint(plist);
}
int main() {
// TestSList1();
// TestSList2();
// TestSList3();
TestSList4();
return 0;
}