目录
1.链表的定义
数据结构-顺序表_七月不远.的博客-CSDN博客https://blog.csdn.net/weixin_58165485/article/details/123381369在这篇文章中,笔者介绍了线性表的概念,线性表的逻辑结构——表示元素之间一一对应的相邻关系,满足这种线性有序的数据表示方式都可以称为线性表,对于数据元素在空间上的储存并不做要求
线性表的顺序储存 (空间储存方式)称为顺序表
线性表的链式存储 (空间储存方式) 称为链表
因此,链表是一种在逻辑结构上线性有序,而物理存储结构上非连续,非顺序的存储结构
物理存储结构上的非连续体现在链表中结点的物理地址并不连续,各结点可能位于内存空间的不同地址处,如何将这样一种结构实现逻辑上的联系呢?
在链表存储中,每个结点不仅包含所存元素的信息,还包含元素之间逻辑关系的信息,以单链表为例——具体做法是让每一个结点保存相邻的下一个结点的地址
一个单链表简要的结构如下图所示
其中,L 是指向单链表的指针,单链表每个结点由两个部分组成
- 数据域——用来存放保存的数据
- 指针域——用来存放下一个结点的地址
值得注意的是,由于最后一个结点的后面不存在结点,因此需要将最后结点的指针域置为空( NULL )
单链表的结点定义为
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
常用的链表一般有单链表和双向循环链表,后者是更为完善的一种链表,为了弥补单链表只记录后续结点导致无法访问前面结点的问题,双向循环链表的每个结点由三部分组成——指向前结点的指针域,数据域以及指向后结点的指针域,简要结构如下图
注意:
- 双向循环链表的第一个结点为头结点 ,头结点内不存储有效信息
- 头结点的next指针指向链表中的第一个结点,并且头节点的prev指针指向链表中最后一个结点
- 链表的最后一个结点指向头结点
双向循环链表的结点定义为
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
int data;
}LTNode;
2.单链表的代码实现
常用的链表接口有:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//单向+不带头+不循环
//单链表打印
void SListPrint(SLTNode* plist);
//单链表尾插
void SListPushBack(SLTNode** pplist, SLTDataType x);
//单链表头插
void SListPushFront(SLTNode** pplist, SLTDataType x);
//单链表尾删
void SListPopBack(SLTNode** pplist);
//单链表头删
void SListPopFront(SLTNode** pplist);
//单链表查找
SLTNode* SListFind(SLTNode* plist, SLTDataType x);
//单链表在pos之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x);
//在pos之前插入(很麻烦,不建议)
void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x);
//单链表在pos之后删除
void SListEraseAfter(SLTNode* pos);
//单链表删除pos
void SListEraseCur(SLTNode** pplist, SLTNode* pos);
接口实现
#include "SList.h"
//打印
void SListPrint(SLTNode* plist)
{
SLTNode* cur = plist;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
//创建一个新节点
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
node->data = x;
node->next = NULL;
return node;
}
//尾插
void SListPushBack(SLTNode** pplist, SLTDataType x)
{
SLTNode* newnode = BuySLTNode(x);
if (*pplist == NULL)
{
*pplist = newnode;
}
//如果非空,找到尾(最后一个节点,其next域为NULL)
else
{
SLTNode* tail = *pplist;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
//头插
void SListPushFront(SLTNode** pplist, SLTDataType x)
{
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pplist; //先把当前结点的地址(存在plist里,这里就是*pplist)放到新结点的next
*pplist = newnode; //再将新节点的地址传给plist,这里也是*pplist
}
//尾删
void SListPopBack(SLTNode** pplist)
{
//没有节点
if (*pplist == NULL)
{
return;
}
//一个节点
else if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
//多个节点
else
{
SLTNode* prev = NULL;
SLTNode* tail = *pplist;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
//tail = NULL;
prev->next = NULL;
}
}
//头删
void SListPopFront(SLTNode** pplist)
{
if (*pplist == NULL)
{
return;
}
else
{
SLTNode* next = (*pplist)->next;
free(*pplist);
*pplist = next;
}
}
//查找
SLTNode* SListFind(SLTNode* plist, SLTDataType x)
{
SLTNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//在pos之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//在pos之前插入
void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
if (pos == *pplist)
{
newnode->next = pos;
*pplist = newnode;
}
SLTNode* prev = NULL;
SLTNode* cur = *pplist;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = newnode;
newnode->next = pos;
}
//单链表在pos之后删除
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
if (pos->next == NULL) //头删
{
return;
}
SLTNode* next = pos->next->next;
free(pos->next);
pos->next = next;
}
//单链表删除pos
void SListEraseCur(SLTNode** pplist, SLTNode* pos)
{
assert(pos);
if (pos == *pplist)
{
SLTNode* next = pos->next;
free(*pplist);
*pplist = next;
next = NULL;
}
SLTNode* prev = NULL;
SLTNode* cur = *pplist;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
SLTNode* next = pos->next;
free(pos);
prev->next = next;
next = NULL;
}
3.双向循环链表的代码实现
常用接口
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
int data;
}LTNode;
struct List
{
LTNode* phead;
int size;
};
// 初始化
LTNode* ListInit();
// 打印
void ListPrint(LTNode* phead);
// 尾插
void ListPushBack(LTNode* phead, LTDataType x);
// 头插
void ListPushFront(LTNode* phead, LTDataType x);
// 尾删
void ListPopBack(LTNode* phead);
// 头删
void ListPopFront(LTNode* phead);
// 在pos位置之前插入x
void ListInsert(LTNode* pos, LTDataType x);
// 使用ListInsert实现尾插
void ListPushBackByInsert(LTNode* phead, LTDataType x);
// 使用ListInsert实现头插
void ListPushFrontByInsert(LTNode* phead, LTDataType x);
// 删除pos位置的节点
void ListErase(LTNode* pos);
// 使用ListErase实现尾删
void ListPopBackByListErase(LTNode* phead);
// 使用ListErase实现头删
void ListPopFrontByListErase(LTNode* phead);
// 判空
bool ListEmpty(LTNode* phead);
// 链表长度
int ListSize(LTNode* phead);
// 销毁
void ListDestory(LTNode* phead);
接口实现
#include "List.h"
// 新建一个结点的函数定义
LTNode* BuyListNode(LTDataType x)
{
// malloc一个新结点
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
// 初始化的函数定义
LTNode* ListInit()
{
LTNode* phead = BuyListNode(-1);
// 链表初始化只有一个头结点————prev和next都指向自己
phead->next = phead;
phead->prev = phead;
return phead;
}
// 打印的函数定义
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
// 从头结点的下一个结点开始打印
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
}
// 尾插的函数定义
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
// 1. 记录链表当前的最后结点的地址
// 2. 将原来的尾的next指向新结点
// 3. 将新结点链接起来
// 4. 更新头结点的prev指向新尾(newnode)
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
// 头插的函数定义
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
// 1. 记录链表当前的第一个结点的地址
// 2. 将新结点的地址给第一个结点的prev
// 3. 将新结点链接起来
// 4. 更新头结点的next指向新尾(newnode)
LTNode* next = phead->next;
next->prev = newnode;
newnode->next = next;
newnode->prev = phead;
phead->next = newnode;
}
// 尾删的函数定义
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* tail = phead->prev;
LTNode* tailprev = tail->prev;
free(tail);
tailprev->next = phead;
phead->prev = tailprev;
}
// 头删的函数定义
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* next = phead->next;
LTNode* nextaftre = next->next;
free(next);
nextaftre->prev = phead;
phead->next = nextaftre;
}
// 在pos位置之前插入x函数定义
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
// 使用ListInsert实现尾插
void ListPushBackByInsert(LTNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead, x);
}
// 使用ListInsert实现头插
void ListPushFrontByInsert(LTNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead->next, x);
}
// 删除pos位置的节点函数定义
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
// 使用ListErase实现尾删
void ListPopBackByListErase(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
ListErase(phead->prev);
}
// 使用ListErase实现头删
void ListPopFrontByListErase(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
ListErase(phead->next);
}
// 判空的函数定义
bool ListEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
// 求链表长度函数定义
int ListSize(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
int size = 0;
while (cur != phead)
{
++size;
}
return size;
}
// 销毁链表的函数定义
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
ListErase(cur);
cur = next;
}
free(phead);
}
4.链表和顺序表的对比
不同点 | 顺序表 | 链表 |
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持:O(1) | 不支持:O(N) |
任意位置插入或者删除元素 | 可能需要搬移元素,效率低O(N) | 只需修改指针指向 |
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |