C++ 入门算法,新手必看之:双向“链表”(三)

俗话说得好,不懂链表的程序员,不配称为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;
}
发布了50 篇原创文章 · 获赞 46 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/cpp_learner/article/details/105028442
今日推荐