俗话说得好,不懂链表的程序员,不配称为C/C++程序员。
继单向链表和循环链表之后,今天给大家分享链表的最后一片文章,双向链表。
如果不懂单向链表点下面这条链接,前去学习,学习!
https://blog.csdn.net/cpp_learner/article/details/105015219
如果不懂循环链表点下面这条链接,前去学习,学习!
https://blog.csdn.net/cpp_learner/article/details/105026894
单链表中每个结点除了存储自身数据之后,还存储了下一个结点的地址,因此可以轻松访问下一个结点,以及后面的后继结点,但是如果想访问前面的结点就不行了,再也回不去了。
例如删除结点 p 时,要先找到它的前一个结点 q,然后才能删掉 p 结点,单向链表只能往后走,不能向前走。如果需要向前走,怎么办呢?
可以在单链表的基础上给每个元素附加两个指针域,一个存储前一个元素的地址,一个存储下一个元素的地址。这种链表称为双向链表。
其结构体定义:
typedef struct _LinkNode {
int data; //结点的数据域
struct _LinkNode *next; //下一个节点的指针域
struct _LinkNode *prev; //上一个结点的指针域
}LinkNode, LinkList; //LinkList 为指向结构体 LNode 的指针类型
双向链表的定义与初始化
和单向链表相比,双向链表多了一个指针。
初始化后,两个指针都指向了NULL。
// 定义双向链表
typedef struct Link {
int date;
struct Link* prev; // 前一个
struct Link* next; // 下一个
}LinkList, LinkNode;
// 初始化双向链表
bool inItDoubleLink(LinkList*& List) {
List = new LinkList; // 分配内存
if (!List) { // 内存分配失败
cout << "初始化失败!" << endl;
return false;
}
List->prev = NULL; // 因为是头节点,所以头节点的prev指向NULL
List->next = NULL; // 因为是初始化,所以头节点的next指向NULL
List->date = -1; // 头节点一般情况下是不存储值的
}
头插法
他也是有两种情况:空链表的情况与链表中有值的情况。
每种情况有自己的实现代码,注意一下。
// 头插法
bool doubleLinkInsertFront(LinkList*& List, LinkNode *Node) { // 参数一:链表的头节点指针的引用;参数二:待插入的新节点
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
if (List->next == NULL) { // 情况一:空链表的情况
Node->next = List->next; // 新节点的next指向头节点的next
Node->prev = List; // 新节点的prev指向头节点
List->next = Node; // 头节点的next指向新节点
} else { // 情况二:链表中有值
List->next->prev = Node; // 原节点一的prev指向新节点
Node->next = List->next; // 新节点的next指向头结点的next
Node->prev = List; // 新节点的prev指向头节点
List->next = Node; // 头节点的next指向新节点
}
return true;
}
但是两种情况的代码实现又有相似之处,他们可以简化代码为:
// 以上if...else可以简化为:
if (List->next != NULL) List->next->prev = Node;
Node->next = List->next; // 新节点的next指向头结点的next
Node->prev = List; // 新节点的prev指向头节点
List->next = Node; // 头节点的next指向新节点
尾插法
尾插法,需要循环遍历到最后一个节点,才可以进行插入插入操作。
/ 尾插法
bool doubleLinkInsertBack(LinkList*& List, LinkNode* Node) {
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点
while (p->next) p = p->next; // p指向最后一个节点
Node->next = p->next; // 新节点的next指向尾节点的next(也就是:Node->next = NULL)
Node->prev = p; // 新节点的prev指向p
p->next = Node; // p的next指向新节点
}
任意位置插入
双向链表不需要像单向链表和循环链表一样,需要找到待插入结点的前一个节点,然后在进行一些运算插入。
双向链表因为是双向的,所以可以直接找到待插入位置的节点然后直接进行插入。
// 任意位置插入
bool doubleLinkInsert(LinkList*& List, int i, int e) { // 参数二:插入位置;参数三:插入节点的元素
if (!List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
LinkNode* q = NULL;
int j = 1;
while (p && j < i) { // 找到插入位置的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0;触发条件二
if (!p || j > i) return false; // i值不合法的情况:i > n || i <= 0
q = new LinkNode; // 分配内存
q->date = e;
q->next = p; // q的next指向p
q->prev = p->prev; // q的prev指向p的prev
p->prev->next = q; // p的prev的next指向q
p->prev = q; // p的prev指向q
return true;
}
只要理解好前指针prev和后指针next的关系,双向链表就不会那么难了!
获取链表中指定节点位置的值
bool gainDoubleLinkValue(LinkList*& List, int i, int& e) {
if (!List || !List->next) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p && j < i) { // 找到i对应的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0;触发条件二
if (!p || j > i) return false; // i值不合法的情况:i > n || i <= 0
e = p->date; // 将p结点的值赋值给e返回
return true;
}
while循环找到该位置后,将该节点的值赋值给e返回。
修改指定位置节点的值
bool doubleLinkAlterValue(LinkList*& List, int i, int e) {
if (!List || !List->next) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p && j < i) { // 找到i对应的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0;触发条件二
if (!p || j > i) return false; // i值不合法的情况:i > n || i <= 0
p->date = e;
return true;
}
while循环找到该位置后,将该节点的值修改为e;
删除链表中的一个节点:根据节点的位置删除
也是有两种情况删除,这里就将一种,另一种,小伙伴们有兴趣的可以自行试一下。
// 删除链表中的一个节点:根据节点的位置删除
bool doubleLinkDelete_nodeLocation(LinkList*& List, int i) { // 参数二:待删除节点的位置
if (!List || !List->next) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p && j < i) { // 找到i对应的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0;触发条件二
if (!p || j > i) return false; // i值不合法的情况:i > n || i <= 0
// 如果p是最后一个节点,则无需则行该条语句(只需要将最后一个节点的前一个节点的next指向NULL就行了)
if (p->next != NULL) p->next->prev = p->prev; // p的下一个节点的prev指向p的上一个节点
p->prev->next = p->next; // p的上一个节点的next指向p的下一个节点
delete p;
return true;
}
注意:
如果是删除最后一个节点,那么直接将该节点的前一个节点的next指向NULL就行了。
如果删除的不是最后一个节点,那么还需要待删除该节点的下一个节点的prev指向待删除节点的前一个节点。
销毁链表
// 销毁链表
bool doubleLinkDestroy(LinkList*& List) {
if (!List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List;
while (List) { // 节点不为假,则行while循环
List = List->next; // 节点指向自己的下一个节点
delete p; // 释放掉节点的内存
p = List; // p指向List节点
}
return true;
}
将双向链表中所有节点的内存都释放掉!
输出双向链表中的值
// 输出双向链表中的值
void doubleLinkPrint(LinkList*& List) {
if (!List || List->next == NULL) { // 合法性检查
cout << "链表为空!" << endl;
return;
}
LinkList* p = List; // 定义临时节点指向头节点
// 正序输出
cout << "正序输出:" << endl;
while (p->next) { // 当节点本身的next为真,循环
cout << p->next->date << "\t";
p = p->next;
}
cout << endl;
// 逆序输出
//cout << "逆序输出:" << endl; // p已经指向最后一个节点
//while (p->prev && p != List) { // 当p的prev为真,且p不等于头节点,循环
// cout << p->date << "\t";
// p = p->prev;
//}
//cout << endl;
}
因为双向链表可以找到自己的前一个节点,所以它可以逆序输出链表中的值。
测试代码:
#include <iostream>
#include <Windows.h>
using namespace std;
// 定义双向链表
typedef struct Link {
int date;
struct Link* prev; // 前一个
struct Link* next; // 下一个
}LinkList, LinkNode;
// 初始化双向链表
bool inItDoubleLink(LinkList*& List) {
List = new LinkList; // 分配内存
if (!List) { // 内存分配失败
cout << "初始化失败!" << endl;
return false;
}
List->prev = NULL; // 因为是头节点,所以头节点的prev指向NULL
List->next = NULL; // 因为是初始化,所以头节点的next指向NULL
List->date = -1; // 头节点一般情况下是不存储值的
}
// 头插法
bool doubleLinkInsertFront(LinkList*& List, LinkNode *Node) { // 参数一:链表的头节点指针的引用;参数二:待插入的新节点
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
if (List->next == NULL) { // 情况一:空链表的情况
Node->next = List->next; // 新节点的next指向头节点的next
Node->prev = List; // 新节点的prev指向头节点
List->next = Node; // 头节点的next指向新节点
} else { // 情况二:链表中有值
List->next->prev = Node; // 原节点一的prev指向新节点
Node->next = List->next; // 新节点的next指向头结点的next
Node->prev = List; // 新节点的prev指向头节点
List->next = Node; // 头节点的next指向新节点
}
/***********************************************************/
// 以上if...else可以简化为:
//if (List->next != NULL) List->next->prev = Node;
//Node->next = List->next; // 新节点的next指向头结点的next
//Node->prev = List; // 新节点的prev指向头节点
//List->next = Node; // 头节点的next指向新节点
/************************************************************/
return true;
}
// 尾插法
bool doubleLinkInsertBack(LinkList*& List, LinkNode* Node) {
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点
while (p->next) p = p->next; // p指向最后一个节点
Node->next = p->next; // 新节点的next指向尾节点的next(也就是:Node->next = NULL)
Node->prev = p; // 新节点的prev指向p
p->next = Node; // p的next指向新节点
}
// 任意位置插入
bool doubleLinkInsert(LinkList*& List, int i, int e) { // 参数二:插入位置;参数三:插入节点的元素
if (!List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
LinkNode* q = NULL;
int j = 1;
while (p && j < i) { // 找到插入位置的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0;触发条件二
if (!p || j > i) return false; // i值不合法的情况:i > n || i <= 0
q = new LinkNode; // 分配内存
q->date = e;
q->next = p; // q的next指向p
q->prev = p->prev; // q的prev指向p的prev
p->prev->next = q; // p的prev的next指向q
p->prev = q; // p的prev指向q
return true;
}
// 获取链表中指定节点位置的值
bool gainDoubleLinkValue(LinkList*& List, int i, int& e) {
if (!List || !List->next) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p && j < i) { // 找到i对应的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0;触发条件二
if (!p || j > i) return false; // i值不合法的情况:i > n || i <= 0
e = p->date; // 将p结点的值赋值给e返回
return true;
}
// 修改指定位置节点的值
bool doubleLinkAlterValue(LinkList*& List, int i, int e) {
if (!List || !List->next) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p && j < i) { // 找到i对应的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0;触发条件二
if (!p || j > i) return false; // i值不合法的情况:i > n || i <= 0
p->date = e;
return true;
}
// 删除链表中的一个节点:根据节点的位置删除
bool doubleLinkDelete_nodeLocation(LinkList*& List, int i) { // 参数二:待删除节点的位置
if (!List || !List->next) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p && j < i) { // 找到i对应的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0;触发条件二
if (!p || j > i) return false; // i值不合法的情况:i > n || i <= 0
// 如果p是最后一个节点,则无需则行该条语句(只需要将最后一个节点的前一个节点的next指向NULL就行了)
if (p->next != NULL) p->next->prev = p->prev; // p的下一个节点的prev指向p的上一个节点
p->prev->next = p->next; // p的上一个节点的next指向p的下一个节点
delete p;
return true;
}
// 销毁链表
bool doubleLinkDestroy(LinkList*& List) {
if (!List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List;
while (List) { // 节点不为假,则行while循环
List = List->next; // 节点指向自己的下一个节点
delete p; // 释放掉节点的内存
p = List; // p指向List节点
}
return true;
}
// 输出双向链表中的值
void doubleLinkPrint(LinkList*& List) {
if (!List || List->next == NULL) { // 合法性检查
cout << "链表为空!" << endl;
return;
}
LinkList* p = List; // 定义临时节点指向头节点
// 正序输出
cout << "正序输出:" << endl;
while (p->next) { // 当节点本身的next为真,循环
cout << p->next->date << "\t";
p = p->next;
}
cout << endl;
// 逆序输出
//cout << "逆序输出:" << endl; // p已经指向最后一个节点
//while (p->prev && p != List) { // 当p的prev为真,且p不等于头节点,循环
// cout << p->date << "\t";
// p = p->prev;
//}
//cout << endl;
}
int main(void) {
LinkList* list = NULL;
LinkNode* node = NULL;
// 初始化双向链表
inItDoubleLink(list);
// 头插法
int n = 5;
while (n > 0) {
node = new LinkNode;
node->date = n;
if (doubleLinkInsertFront(list, node)) {
cout << "插入成功!" << endl;
} else {
cout << "插入失败!" << endl;
}
n--;
}
doubleLinkPrint(list);
// 尾插法
while (n < 5) {
node = new LinkNode;
node->date = n;
if (doubleLinkInsertBack(list, node)) {
cout << "插入成功!" << endl;
} else {
cout << "插入失败!" << endl;
}
n++;
}
doubleLinkPrint(list);
// 任意位置插入
n = 1;
int i, e;
cout << "请输入任意位置插入的位置和元素:";
while (n-- > 0) {
cin >> i >> e;
if (doubleLinkInsert(list, i, e)) {
cout << "插入成功!" << endl;
doubleLinkPrint(list);
} else {
cout << "插入失败!" << endl;
doubleLinkPrint(list);
}
}
// 获取链表中指定节点位置的值
if (gainDoubleLinkValue(list, 3, e)) {
cout << "获取第三个节点的值成功,值为:" << e << endl;
} else {
cout << "获取第三个节点的值失败!" << endl;
}
// 修改指定位置节点的值
if (doubleLinkAlterValue(list, 5, 666)) {
cout << "修改第五个节点的值为666成功!" << endl;
doubleLinkPrint(list);
} else {
cout << "修改第五个节点的值失败!" << endl;
doubleLinkPrint(list);
}
// 删除链表中的一个节点:根据节点的位置删除
if (doubleLinkDelete_nodeLocation(list, 11)) {
cout << "删除节点成功!" << endl;
doubleLinkPrint(list);
} else {
cout << "删除节点失败!" << endl;
doubleLinkPrint(list);
}
if (doubleLinkDestroy(list)) {
cout << "链表销毁成功!" << endl;
} else {
cout << "链表销毁失败!" << endl;
}
// 输出
doubleLinkPrint(list);
system("pause");
return 0;
}