目录
2.3_5 双链表
第二章单链表中学到:
单链表:无法逆向检索,有时候不太方便;
双链表:双向链表,可进可退,存储密度更低;
存储密度 = (结点数据本身所占的存储量)/(结点结构所占的存储总量)
计算结构体大小时需要考虑其内存布局,结构体在内存中存放是按单元存放的,每个单元多大取决于结构体中最大基本类型的大小。
一、双链表的定义
// 双向链表定义
typedef struct DNode{ // 定义双链表结点类型
ElemType data; // 数据域
struct DNode *prior, *next; // 前驱和后继指针
}DNode, *DLinklist;
二、双链表的初始化(带头结点)
#include <iostream>
using namespace std;
typedef int ElemType; // 数据域元素为int型
// 双向链表定义
typedef struct DNode{ // 定义双链表结点类型
ElemType data; // 数据域
struct DNode *prior, *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;
}else{
return false;
}
}
int main(){
// 定义并初始化一个双向链表
DLinklist L;
InitDLinkList(L);
return 0;
}
初始化双向链表传参时使用的是 DLinklist &L 想强调的是,这里引用了一个双向链表;分配头结点时使用 DNode * 想强调的是 malloc 出了一个结点;
DLinklist 与 DNode * 是等价的。
三、双链表的插入
// 在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
s->next = p->next; // 为新结点s的后继指针赋值
p->next->prior = s; // 为p后的旧结点的前驱赋指针值
s->prior = p; // 为新结点s的前驱指针赋值
p->next = s; // 为p后继指针赋值
}
如果p恰好是最后一个结点,就不用再做第二步 p->next->prior = s; // 为p后的旧结点的前驱赋指针值 了,所以为了更加严谨,单独做一个判断,判断p结点是否为最后一个结点。
// 在p结点之后插入s结点(增加对p结点为最后一个结点的判断)
bool InsertNextDNode(DNode *p, DNode *s){
if(P == NULL || s == NULL){ // 非法参数
return false;
}
s->next = p->next; // 为新结点s的后继指针赋值
if(p->next != NULL){ // 如果p结点有后续结点
p->next->prior = s; // 为p后的旧结点的前驱指针赋值
}
s->prior = p; // 为新结点s的前驱指针赋值
p->next = s; // 为p后继指针赋值
return true;
}
在p结点之后插入s结点新结点称为后插操作;想要完成按位序插入前插操作,则需找到p的前驱结点,做p前驱结点的后插操作。
四、双链表的删除与销毁
// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p){
if(p == NULL){ // 非法参数
return false;
}
DNode *q = p->next; // 找到p的后继结点q
if(q == NULL){ // q没有后继结点
return false;
}
if(q->next !