数据结构基础(4)
单链表:无法逆向检索,有时候不太方便
双链表:可进可退,存储密度更低一点点
双链表的初始化:
#include <stdbool.h>
#include <stdlib.h>
// 定义双链表节点结构体
typedef struct DNode {
// 数据域,实际使用时需根据需求替换 ElemType(如 int、char 等)
ElemType data;
struct DNode *prior; // 指向前驱节点的指针
struct DNode *next; // 指向后继节点的指针
} DNode, *DLinklist;
// 初始化双链表(带头结点)
bool InitDLinkList(DLinklist &L) {
L = (DNode *)malloc(sizeof(DNode)); // 分配一个头结点
if (L == NULL) return false; // 内存不足,分配失败
L->prior = NULL; // 头结点的 prior 永远指向 NULL
L->next = NULL; // 头结点之后暂时还没有节点
return true;
}
// 判断双链表是否为空(带头结点)
bool Empty(DLinklist L) {
if (L->next == NULL) return true; // 头结点的 next 为 NULL 则链表为空
else return false;
}
// 测试函数
void testDLinkList() {
// 初始化双链表
DLinklist L;
InitDLinkList(L);
// 后续可补充对链表操作、测试的代码...
}
双链表的插入:
typedef struct DNode {
// 数据域,实际使用时需根据需求替换 ElemType(如 int、char 等)
ElemType data;
// 指向前驱节点的指针
struct DNode *prior;
// 指向后继节点的指针
struct DNode *next;
} DNode;
// 在 p 结点之后插入 s 结点
bool InsertNextDNode(DNode *p, DNode *s) {
// 非法参数检查
if (p == NULL || s == NULL) return false;
s->next = p->next;
// 如果 p 结点有后继结点
if (p->next != NULL) {
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
修改指针时要注意顺序
双链表的删除:
// 假设已定义双链表节点结构体 DNode
typedef struct DNode {
// 数据域,实际使用时需根据需求替换 ElemType(如 int、char 等)
ElemType data;
// 指向前驱节点的指针
struct DNode *prior;
// 指向后继节点的指针
struct DNode *next;
} DNode;
// 删除 p 结点的后继结点
bool DeleteNextDNode(DNode *p) {
// p 为 NULL,无法操作
if (p == NULL) return false;
// 找到 p 的后继结点 q
DNode *q = p->next;
// p 没有后继结点,无法删除
if (q == NULL) return false;
p->next = q->next;
// 如果 q 不是最后一个结点,调整后继结点的前驱
if (q->next != NULL) {
q->next->prior = p;
}
// 释放结点 q 的空间
free(q);
return true;
}
销毁一个双链表:
// 销毁双链表
void DestoryList(DLinklist &L) {
// 循环释放各个数据结点
while (L->next != NULL) {
DeleteNextDNode(L);
}
free(L); // 释放头结点
L = NULL; // 头指针指向 NULL
}
双链表的遍历:
- 后向遍历(从某个节点往后遍历,包含可能的头节点等起始点 )
while (p != NULL) {
// 对结点 p 做相应处理,如打印等操作
p = p->next;
}
- 前向遍历(从某个节点往前遍历,包含可能的尾节点等起始点 )
while (p != NULL) {
// 对结点 p 做相应处理,如打印等操作
p = p->prior;
}
- 前向遍历(跳过头节点,从第一个数据节点往前遍历 )
while (p->prior != NULL) {
// 对结点 p 做相应处理,如打印等操作
p = p->prior;
}
双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现。
时间复杂度:O(n)
循环单链表:
单链表:
表尾结点的next指针指向NULL
从一个结点出发只能找到后续的各个节点
循环单链表:
表尾结点的next指针指向头结点
从一个结点出发可以找到其他任何一个结点
// 定义循环单链表结点结构
typedef struct LNode {
ElemType data; // 数据域,存储数据元素
struct LNode *next; // 指针域,指向下一个结点
} LNode, *LinkList;
// 初始化循环单链表
bool InitList(LinkList &L) {
// 分配头结点内存
L = (LNode *)malloc(sizeof(LNode));
if (L == NULL) // 内存分配失败
return false;
L->next = L; // 头结点 next 指向自身,形成循环
return true;
}
// 判断循环单链表是否为空
bool Empty(LinkList L) {
// 头结点 next 指向自身则为空表
return L->next == L;
}
// 判断结点 p 是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode *p) {
// 表尾结点的 next 指向头结点
return p->next == L;
}
当为空表时:就像是单手抱住空虚的自己
循环双链表:
双链表:
表头结点的prior指向NULL;表尾结点的next指向NULL
循环双链表:
表头结点的prior指向表尾结点;表尾结点的next指向头结点
// 定义循环双链表结点结构
typedef struct DNode {
ElemType data; // 数据域,存储数据元素
struct DNode *prior; // 前驱指针,指向前一个结点
struct DNode *next; // 后继指针,指向后一个结点
} DNode, *DLinklist;
// 初始化空的循环双链表
bool InitDLinkList(DLinklist &L) {
L = (DNode *)malloc(sizeof(DNode)); // 分配一个头结点
if (L == NULL) // 内存分配失败
return false;
L->prior = L; // 头结点的 prior 指向自身
L->next = L; // 头结点的 next 指向自身
return true;
}
// 判断循环双链表是否为空
bool Empty(DLinklist L) {
// 头结点 next 指向自身则为空表
return L->next == L;
}
// 判断结点 p 是否为循环双链表的表尾结点
bool isTail(DLinklist L, DNode *p) {
// 表尾结点的 next 指向头结点
return p->next == L;
}
// 测试函数:初始化并调用循环双链表操作
void testDLinkList() {
// 初始化循环双链表
DLinklist L;
InitDLinkList(L);
// 后续可扩展:插入、删除、遍历等操作...
}
当为空表时:就像是双手抱住空虚的自己