🚀 写在最前:在上一篇文章,学习了线性表的顺序存储——顺序表,首先定义了线性表的逻辑结构以及该数据结构的相关操作,并使用顺序存储实现了相关操作,这篇文章将使用链式存储实现线性表的相关操作,以单链表为主。
🚀:求个关注😀,让我们一起探索计算机的奥秘!
目录
一、线性表的链式存储
1)🔊简单理解线性表的链式存储
还是以绪论部分的图为例,对于一个线性表来说,它实际在计算中存储位置随机,他们的逻辑结构通过指针一一链接,就是线性表的链式存储。
2)🔊线性表链式存储的分类
对于链式存储来说,其通过指针链接的方式不同可分为单链表(就如上图所示)、双链表、循环单链表、循环双链表、静态链表。
这里同样的留个印象就行,后面都会具体学习这几种链表。
3)🔊单链表的定义
线性表的链式存储又称单链表,它指通过一组任意的存储春单元来存储线性表中的数据元素。对于链表中的每一个节点来说,它有一个数据域用于存储数据,一个指针域用于链接数据即存放后继节点的地址。
二、单链表的相关操作
文件结构
以下操作,都以带头节点的链表为例 。
①初始化单链表
要实现的功能,得到一个空空链表且含有一个头指针。
LinkList_one.h中的内容,在这里实际定义数据结构以及数据结构的操作。
#pragma once
#include<stdio.h>
#include<stdlib.h>
//数据元素重定义为Elemtype
typedef int Elemtype;
//定义节点类型
typedef struct LNode {
Elemtype data; //数据域 数据类型Elemtype
struct LNode* next; //指针域 指向LNode类型的指针
}LNode,*LinkList;
//定义数据结构的操作
//初始化单链表
bool InitList(LinkList &L);
//插入节点操作——特定位序插入
//指定节点前插操作
//打印链表
//按值查找节点
//删除节点操作
//求单链表表长
LinkList_one.c中的内容,在这里实际实现数据结构的操作。
bool InitList(LinkList &L) {
//申请一个头节点
L = (LNode*)malloc(sizeof(LNode));
if (L == NULL) {
printf("malloc空间申请失败");
return false;
}else {
L->next = NULL; //将头节点的next置空
}
return true;
}
test.c中的内容,测试实现的数据结构操作是否好用。
#include"LinkList_one.h"
int main() {
LinkList L = NULL; //定义一个链表指针
//初始化链表
if (InitList(L)) {
printf("初始化成功!\n");
}
return 0;
}
运行结果:
初始化成功!
D:\C_WorkSpace\LinkList\x64\Debug\LinkList.exe (进程 2708)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
②插入节点操作
特定位序插入(后插)
LinkList_one.h中的内容
//插入节点操作——特定位序插入
bool ListInsert(LinkList L, int i, Elemtype e);
LinkList_one.c中的内容
//插入节点操作——特定位序插入
bool ListInsert(LinkList L, int i, Elemtype e) {
LNode* p = L; //指针p指向当前节点
int j = 0; //记录指针指向第几个节点,头节点为第0个节点
//找到第i-1个节点,在i-1个节点后面插入Elemtype e
while (p!= NULL && j < i - 1) {
p = p->next;
j++;
}
if (p == NULL) {
printf("插入位置不合法!");
return false;
}
LNode* tmp = (LNode*)malloc(sizeof(LNode));
if (tmp == NULL) {
printf("空间开辟失败导致插入失败。");
}else{
tmp->data = e; //将数据放入新节点的数据域
tmp->next = p->next;
p->next = tmp;
}
}
test.c中的内容
#include"LinkList_one.h"
int main() {
LinkList L1 = NULL; //定义一个链表指针
//初始化链表
if (InitList(L1)) {
printf("初始化成功!\n");
}
ListInsert(L1, 1, 4);
ListInsert(L1, 1, 8);
ListInsert(L1, 3, 10);
printf("位置1元素值是%d\n", L1->next->data);
printf("位置2元素值是%d\n", L1->next->next->data);
printf("位置3元素值是%d\n", L1->next->next->next->data);
ListInsert(L1, 5, 10);
return 0;
}
运行结果:
初始化成功!
位置1元素值是8
位置2元素值是4
位置3元素值是10
插入位置不合法!
D:\C_WorkSpace\LinkList\x64\Debug\LinkList.exe (进程 21936)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
指定节点前插操作
LinkList_one.h中的内容
//指定节点前插操作
bool ListPriorInsert(LNode* node, Elemtype e);
LinkList_one.c中的内容
//指定节点前插操作
bool ListPriorInsert(LNode* node, Elemtype e) {
assert(node);
LNode* tmp = (LNode*)malloc(sizeof(LNode));
if (tmp == NULL) {
printf("空间申请失败导致节点插入失败\n");
return false;
}
else {
//先将节点进行尾插
tmp->next = node->next;
node->next = tmp;
//节点的存储数据调换位置
tmp->data = node->data;
node->data = e;
return true;
}
}
test.c中的内容
#include"LinkList_one.h"
int main() {
LinkList L1 = NULL; //定义一个链表指针
//初始化链表
if (InitList(L1)) {
printf("初始化成功!\n");
}
ListInsert(L1, 1, 4);
ListInsert(L1, 1, 8);
ListInsert(L1, 3, 10);
printf("位置1元素值是%d\n", L1->next->data);
printf("位置2元素值是%d\n", L1->next->next->data);
printf("位置3元素值是%d\n", L1->next->next->next->data);
ListPriorInsert(L1->next, 1000); //在第一个节点的
printf("位置1元素值是%d\n", L1->next->data);
return 0;
}
运行结果:
初始化成功!
位置1元素值是8
位置2元素值是4
位置3元素值是10
位置1元素值是1000
D:\C_WorkSpace\LinkList\x64\Debug\LinkList.exe (进程 17496)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
③打印链表
LinkList_one.h中的内容
//打印链表
void ListPrint(LinkList L);
LinkList_one.c中的内容
//打印链表
void ListPrint(LinkList L) {
if (L->next == NULL) {
printf("此链表为空\n");
}
else {
LNode* p = L->next;
while (p!=NULL){
printf("%d-->", p->data);
p = p->next;
}
printf("\n");
}
}
test.c中的内容
#include"LinkList_one.h"
int main() {
LinkList L1 = NULL; //定义一个链表指针
//初始化链表
if (InitList(L1)) {
printf("初始化成功!\n");
}
ListInsert(L1, 1, 4);
ListInsert(L1, 1, 8);
ListInsert(L1, 3, 10);
printf("位置1元素值是%d\n", L1->next->data);
printf("位置2元素值是%d\n", L1->next->next->data);
printf("位置3元素值是%d\n", L1->next->next->next->data);
ListPriorInsert(L1->next, 1000); //在第一个节点的
printf("位置1元素值是%d\n", L1->next->data);
ListPrint(L1);
return 0;
}
运行结果:
初始化成功!
位置1元素值是8
位置2元素值是4
位置3元素值是10
位置1元素值是1000
1000-->8-->4-->10-->
D:\C_WorkSpace\LinkList\x64\Debug\LinkList.exe (进程 16680)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
④按值查找节点
LinkList_one.h中的内容。
//按值查找节点
LNode* LocateElem(LinkList L, Elemtype value);
LinkList_one.c中的内容。
//按值查找节点
LNode* LocateElem(LinkList L, Elemtype value) {
LNode* p = L->next;
while (p != NULL) {
if (p->data == value) {
return p;
}
p = p->next;
}
return NULL;
}
test.c中的内容。
#include"LinkList_one.h"
int main() {
LinkList L1 = NULL; //定义一个链表指针
//初始化链表
if (InitList(L1)) {
printf("初始化成功!\n");
}
ListInsert(L1, 1, 4);
ListInsert(L1, 1, 8);
ListInsert(L1, 3, 10);
printf("位置1元素值是%d\n", L1->next->data);
printf("位置2元素值是%d\n", L1->next->next->data);
printf("位置3元素值是%d\n", L1->next->next->next->data);
ListPriorInsert(L1->next, 1000); //在第一个节点的
printf("位置1元素值是%d\n", L1->next->data);
ListPrint(L1);
LNode* tmp = LocateElem(L1, 10);
printf("tmp指向的数据为%d\n", tmp->data);
return 0;
}
运行结果:
初始化成功!
位置1元素值是8
位置2元素值是4
位置3元素值是10
位置1元素值是1000
1000-->8-->4-->10-->
tmp指向的数据为10
D:\C_WorkSpace\LinkList\x64\Debug\LinkList.exe (进程 8984)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
⑥删除节点操作
LinkList_one.h中的内容。
//删除节点操作
Elemtype ListDelete(LinkList L, int i);
LinkList_one.c中的内容。
//删除节点操作
Elemtype ListDelete(LinkList L, int i) {
LNode* p = L; //p指向第一个节点
int j = 0; //定位指针p指向第几个节点
//找到第i-1的节点
while (p!= NULL && j < i - 1) {
p = p->next;
j++;
}
if (p == NULL || p->next == NULL) {
printf("删除位置不合法");
return false;
}
LNode* tmp = p->next;
Elemtype element = tmp->data;
p->next = tmp->next;
free(tmp);
return element;
}
test.c中的内容。
#include"LinkList_one.h"
int main() {
LinkList L1 = NULL; //定义一个链表指针
//初始化链表
if (InitList(L1)) {
printf("初始化成功!\n");
}
ListInsert(L1, 1, 4);
ListInsert(L1, 1, 8);
ListInsert(L1, 3, 10);
printf("位置1元素值是%d\n", L1->next->data);
printf("位置2元素值是%d\n", L1->next->next->data);
printf("位置3元素值是%d\n", L1->next->next->next->data);
ListPriorInsert(L1->next, 1000); //在第一个节点的
printf("位置1元素值是%d\n", L1->next->data);
ListPrint(L1);
LNode* tmp = LocateElem(L1, 10);
printf("tmp指向的数据为%d\n", tmp->data);
printf("删除之前:\n");
ListPrint(L1);
ListDelete(L1, 1);
printf("删除之后:\n");
ListPrint(L1);
return 0;
}
运行结果:
初始化成功!
位置1元素值是8
位置2元素值是4
位置3元素值是10
位置1元素值是1000
1000-->8-->4-->10-->
tmp指向的数据为10
删除之前:
1000-->8-->4-->10-->
删除之后:
8-->4-->10-->
D:\C_WorkSpace\LinkList\x64\Debug\LinkList.exe (进程 25416)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
⑦求单链表表长
LinkList_one.h中的内容。
//求单链表表长
int Length(LinkList L);
LinkList_one.c中的内容。
//求单链表表长
int Length(LinkList L) {
int count = 0;
LNode* p = L->next;
while (p != NULL) {
count++;
p = p->next;
}
return count;
}
test.c中的内容
#include"LinkList_one.h"
int main() {
LinkList L1 = NULL; //定义一个链表指针
//初始化链表
if (InitList(L1)) {
printf("初始化成功!\n");
}
ListInsert(L1, 1, 4);
printf("表长为%d\n", Length(L1));
ListInsert(L1, 1, 8);
printf("表长为%d\n", Length(L1));
ListInsert(L1, 3, 10);
printf("位置1元素值是%d\n", L1->next->data);
printf("位置2元素值是%d\n", L1->next->next->data);
printf("位置3元素值是%d\n", L1->next->next->next->data);
ListPriorInsert(L1->next, 1000); //在第一个节点的
printf("位置1元素值是%d\n", L1->next->data);
ListPrint(L1);
printf("表长为%d\n", Length(L1));
LNode* tmp = LocateElem(L1, 10);
printf("tmp指向的数据为%d\n", tmp->data);
printf("删除之前:\n");
ListPrint(L1);
printf("表长为%d\n", Length(L1));
ListDelete(L1, 1);
printf("删除之后:\n");
ListPrint(L1);
printf("表长为%d\n", Length(L1));
return 0;
}
运行结果:
初始化成功!
表长为1
表长为2
位置1元素值是8
位置2元素值是4
位置3元素值是10
位置1元素值是1000
1000-->8-->4-->10-->
表长为4
tmp指向的数据为10
删除之前:
1000-->8-->4-->10-->
表长为4
删除之后:
8-->4-->10-->
表长为3
D:\C_WorkSpace\LinkList\x64\Debug\LinkList.exe (进程 24904)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
三、链式存储和顺序存储对比
通过实现线性表顺序存储(顺序表)的操作和线性表链式存储(链表)的操作对比,可以总结出以下各自的优缺点。
对于顺序表:
- 优点:随机存取(直接下标取数据,时间复杂度为O(1)),存储密度高,所有空间都用来存放数据。
- 缺点:要求有大片的连续空间,改变存储容量不方便。
对于链表:
- 不要求有很大的连续空间,改变存储容量方便。
- 不可以随机存取,同时要消耗一定的空间来存储指针。