文章目录
各部分的解释已经以注释形式写于代码中。
1.基本操作
1.1 结构体定义
typedef struct DNode { // 定义双链表结点类型
int data; // 数据域
// 单链表只能单向检索,无法逆向检索
// 双链表有前驱以及后继指针,指向前驱以及后继结点
// 但由于双链表多了一个指针,每个结点所占据的空间也会增加
struct DNode *prior, *next; // 前驱和后继结点指针
} DNode, *DLinklist;
1.2 初始化
// 初始化
bool InitDLinkList(DLinklist &L) {
// 为头结点申请分配空间
L = (DNode *)malloc(sizeof(DNode));
// 内存不足的情况
if(L == NULL) {
return false;
}
// 头结点的prior指针永远指向NULL
L->prior = NULL;
// 头结点之后暂时无结点,设为NULL
L->next = NULL;
return true;
}
1.3 判空
// 判断双链表是否为空(带头结点)
bool Empty(DLinklist L) {
// 头结点指针域指向空即双链表为空
if(L->next == NULL) {
return true;
} else {
return false;
}
}
1.4 结点后插入_1
// 双链表插入
// 在p结点后插入s结点
bool InsertNextDNode(DNode *p, DNode *s) {
// 无效输入
if(p == NULL || s == NULL) {
return false;
}
// 先令s后继指针指向后继结点
// 再令后继结点的前驱指针指向s
// 再令s前驱指针指向p
// 最后令前驱结点后继指针指向s
// 顺序注意不要在连接前断开即可
s->next = p->next;
// 如果p结点无后继结点,后继指针指向NULL取不了前驱指针,会报错
if(p->next != NULL) {
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
1.5 结点后插入_2
// 双链表插入
// 由于王道书上给出的示例是插入结点,这里给出一个插入数据的实现
bool InsertNextDNode_2(DNode *p, int e) {
// 无效输入
if(p == NULL) {
return false;
}
// 申请空间
DNode* s = (DNode*)malloc(sizeof(DNode));
// 内存分配失败的情况
if(s == NULL) {
return false;
}
// 先令s后继指针指向后继结点
// 再令后继结点的前驱指针指向s
// 再令s前驱指针指向p
// 最后令前驱结点后继指针指向s
// 顺序注意不要在连接前断开即可
s->data = e;
s->next = p->next;
// 如果p结点无后继结点,后继指针指向NULL取不了前驱指针,会报错
if(p->next != NULL) {
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
1.6 按位序插入
// 按位序插入
// 在第i个位置插入元素e
bool ListInsert(DLinklist &L, int i, int e) {
// 判断插入位序是否存在问题
if(i < 1)
return false;
DNode *p; // 指针p指向当前扫描到的结点
int j = 0; // 当前p指向的是第几个结点
p = L; // 初始令p指向头结点
// 通过循环,找到第i-1个结点
// 如果i大于链表长度,则不可插入,此时p指向最后结点的指针域,即NULL
while(p != NULL && j < i - 1) {
p = p->next;
j++;
}
// p值为NULL说明遍历完成后仍未为找到位序为i的结点,说明i大于链表长度,i不合法
if (p == NULL) {
return false;
}
// 在第i-1个结点后插入新结点
DNode *s = (DNode *)malloc(sizeof(DNode));
// 内存分配失败的情况
if(s == NULL) {
return false;
}
// 先令s后继指针指向后继结点
// 再令后继结点的前驱指针指向s
// 再令s前驱指针指向p
// 最后令前驱结点后继指针指向s
// 顺序注意不要在连接前断开即可
s->data = e;
s->next = p->next;
// 如果p结点无后继结点,后继指针指向NULL取不了前驱指针,会报错
if(p->next != NULL) {
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
1.7 删除后继结点
// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p) {
// 无效输入
if(p == NULL) {
return false;
}
// 找到p的后继结点q
DNode *q = p->next;
// p为最后一个结点的情况,next指针指向NULL,无法执行删除操作
if(q == NULL) {
return false;
}
// 用前驱结点的后继指针指向后继结点
p->next = q->next;
// q为最后一个结点的情况
if(q->next != NULL) {
// 若q不是最后一个结点,那么就用q后继结点的前驱指针指向前驱结点
q->next->prior = p;
}
// 不要忘记释放空间!!!
free(q);
return true;
}
1.8 按位查找
// 按位查找,返回第i个元素
DNode* GetElem(DLinklist L, int i) {
// 判断插入位序是否存在问题
// 由于此是带头结点的情况,而头结点可以视作是第0个结点
// 所以与之前遍历情况不同
if(i < 0)
return NULL;
DNode *p; // 指针p指向当前扫描到的结点
int j = 0; // 当前p指向的是第几个结点
p = L; // 初始令p指向头结点
// 通过循环,找到第i个结点
// 如果i大于链表长度,则找不到此节点,此时p指向最后结点的指针域,即NULL
// 如果i初始为0,会直接返回头结点地址
while(p != NULL && j < i) {
p = p->next;
j++;
}
return p;
}
1.9 按值查找
// 按值查找,找到数据域等于e的结点
DNode* LocateElem(DLinklist L, int e) {
DNode* p = L->next;
// 从第一个结点开始查找数据域为e的结点
// 找不到,则会在遍历后指向NULL
while (p != NULL && p->data != e) {
p = p->next;
}
return p;
}
1.10 销毁链表
// 销毁双链表
void DestoryList(DLinklist &L) {
// 循环释放各个数据节点的空间
while(L->next != NULL) {
DeleteNextDNode(L);
}
// 释放头结点空间
free(L);
// 头指针指向NULL
L = NULL;
}
1.11 输出所有链表元素
// 输出所有链表元素
void PrintList(DLinklist L) {
if(Empty(L)) {
printf("the list is empty");
}
DNode* p = L->next;
while(p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
2.完整代码
#include<stdio.h>
#include<stdlib.h>
typedef struct DNode { // 定义双链表结点类型
int data; // 数据域
// 单链表只能单向检索,无法逆向检索
// 双链表有前驱以及后继指针,指向前驱以及后继结点
// 但由于双链表多了一个指针,每个结点所占据的空间也会增加
struct DNode *prior, *next; // 前驱和后继结点指针
} DNode, *DLinklist;
// 初始化
bool InitDLinkList(DLinklist &L) {
// 为头结点申请分配空间
L = (DNode *)malloc(sizeof(DNode));
// 内存不足的情况
if(L == NULL) {
return false;
}
// 头结点的prior指针永远指向NULL
L->prior = NULL;
// 头结点之后暂时无结点,设为NULL
L->next = NULL;
return true;
}
// 判断双链表是否为空(带头结点)
bool Empty(DLinklist L) {
// 头结点指针域指向空即双链表为空
if(L->next == NULL) {
return true;
} else {
return false;
}
}
// 双链表插入
// 在p结点后插入s结点
bool InsertNextDNode(DNode *p, DNode *s) {
// 无效输入
if(p == NULL || s == NULL) {
return false;
}
// 先令s后继指针指向后继结点
// 再令后继结点的前驱指针指向s
// 再令s前驱指针指向p
// 最后令前驱结点后继指针指向s
// 顺序注意不要在连接前断开即可
s->next = p->next;
// 如果p结点无后继结点,后继指针指向NULL取不了前驱指针,会报错
if(p->next != NULL) {
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
// 双链表插入
// 由于王道书上给出的示例是插入结点,这里给出一个插入数据的实现
bool InsertNextDNode_2(DNode *p, int e) {
// 无效输入
if(p == NULL) {
return false;
}
// 申请空间
DNode* s = (DNode*)malloc(sizeof(DNode));
// 内存分配失败的情况
if(s == NULL) {
return false;
}
// 先令s后继指针指向后继结点
// 再令后继结点的前驱指针指向s
// 再令s前驱指针指向p
// 最后令前驱结点后继指针指向s
// 顺序注意不要在连接前断开即可
s->data = e;
s->next = p->next;
// 如果p结点无后继结点,后继指针指向NULL取不了前驱指针,会报错
if(p->next != NULL) {
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
// 按位序插入
// 在第i个位置插入元素e
bool ListInsert(DLinklist &L, int i, int e) {
// 判断插入位序是否存在问题
if(i < 1)
return false;
DNode *p; // 指针p指向当前扫描到的结点
int j = 0; // 当前p指向的是第几个结点
p = L; // 初始令p指向头结点
// 通过循环,找到第i-1个结点
// 如果i大于链表长度,则不可插入,此时p指向最后结点的指针域,即NULL
while(p != NULL && j < i - 1) {
p = p->next;
j++;
}
// p值为NULL说明遍历完成后仍未为找到位序为i的结点,说明i大于链表长度,i不合法
if (p == NULL) {
return false;
}
// 在第i-1个结点后插入新结点
DNode *s = (DNode *)malloc(sizeof(DNode));
// 内存分配失败的情况
if(s == NULL) {
return false;
}
// 先令s后继指针指向后继结点
// 再令后继结点的前驱指针指向s
// 再令s前驱指针指向p
// 最后令前驱结点后继指针指向s
// 顺序注意不要在连接前断开即可
s->data = e;
s->next = p->next;
// 如果p结点无后继结点,后继指针指向NULL取不了前驱指针,会报错
if(p->next != NULL) {
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p) {
// 无效输入
if(p == NULL) {
return false;
}
// 找到p的后继结点q
DNode *q = p->next;
// p为最后一个结点的情况,next指针指向NULL,无法执行删除操作
if(q == NULL) {
return false;
}
// 用前驱结点的后继指针指向后继结点
p->next = q->next;
// q为最后一个结点的情况
if(q->next != NULL) {
// 若q不是最后一个结点,那么就用q后继结点的前驱指针指向前驱结点
q->next->prior = p;
}
// 不要忘记释放空间!!!
free(q);
return true;
}
// 销毁双链表
void DestoryList(DLinklist &L) {
// 循环释放各个数据节点的空间
while(L->next != NULL) {
DeleteNextDNode(L);
}
// 释放头结点空间
free(L);
// 头指针指向NULL
L = NULL;
}
// 按位查找,返回第i个元素
DNode* GetElem(DLinklist L, int i) {
// 判断插入位序是否存在问题
// 由于此是带头结点的情况,而头结点可以视作是第0个结点
// 所以与之前遍历情况不同
if(i < 0)
return NULL;
DNode *p; // 指针p指向当前扫描到的结点
int j = 0; // 当前p指向的是第几个结点
p = L; // 初始令p指向头结点
// 通过循环,找到第i个结点
// 如果i大于链表长度,则找不到此节点,此时p指向最后结点的指针域,即NULL
// 如果i初始为0,会直接返回头结点地址
while(p != NULL && j < i) {
p = p->next;
j++;
}
return p;
}
// 按值查找,找到数据域等于e的结点
DNode* LocateElem(DLinklist L, int e) {
DNode* p = L->next;
// 从第一个结点开始查找数据域为e的结点
// 找不到,则会在遍历后指向NULL
while (p != NULL && p->data != e) {
p = p->next;
}
return p;
}
// 输出所有链表元素
void PrintList(DLinklist L) {
if(Empty(L)) {
printf("the list is empty");
}
DNode* p = L->next;
while(p != NULL) {
printf("%d ", p->data);
p = p->next;
}
// 跳过头结点将循环条件改为p->prior != NULL即可
// while(p != NULL) {
// // 后向遍历
// p = p->next;
// // 前向遍历
// p = p->prior;
// }
printf("\n");
}
int main() {
DLinklist L;
InitDLinkList(L);
PrintList(L);
ListInsert(L, 1, 1);
ListInsert(L, 2, 5);
ListInsert(L, 3, 7);
PrintList(L);
InsertNextDNode_2(GetElem(L, 1), 3);
PrintList(L);
printf("locate_3:%d\n", LocateElem(L, 3)->data);
// 删除的是下一个结点
DeleteNextDNode(GetElem(L, 2));
PrintList(L);
DestoryList(L);
}