数据结构 :: 双向链表的设计与实现

数据结构 :: 双向链表的设计与实现

说明:本文属于读书笔记。笔者将以讲述的方式表达全片文章。故文中提到的某些字词是非正式术语,只是笔者本人的理解性词语。

前言:此前已有文章讲述了单链表的设计与实现。如需了解,直飞链接:(有头单链表)(无头单链表
强调!!!双链表实际并不是特别重要,正如前面的文章中所说,数据结构的基础实现,已有前人栽树,后人乘凉就好了。设计与实现只是加强个人对逻辑的推演,加深对数据结构基础的认知。
并且说明:链表的重心应该在单链表。链表是数据结构中的链式存储结构的实现。顺序存储结构与链式存储结构是后面更“高级”的数据结构的实现基础!!!


目录


1. 单链表与双链表的区别

之前设计与实现两种形式的单链表,大家应该不难发现单链表就好比单向车道,只能顺着一个方向走。如果现在要求将链表中的数据,逆序输出!!!怎么办?显然单链表是无法实现的。因为,总是它从前一个结点拿到地址后再去找后一个结点。

在这里插入图片描述

单链表就是只能实现从前往后式的功能操作,现在就是要实现同时可以从后往前的操作。(实现逆序输出链表数据)

在这里插入图片描述

单链表与双链表的区别:

  1. 单链表是使用一个指针指向链表,实现了依照结点的结构特点对数据进行访问。
    双链表就是同时使用两个指针指向链表,且一个指向第一个结点,另一个指向尾部结点。
  2. 单链表结点的结构特点,致使了其对数据访问操作的方式。此前,单链表结点只含有一个指针域(用于存储下一个结点的地址信息)。现在使用两个指针域(同时存储前后结点的地址信息)。

2. 双链表的设计与实现

如前文所述,**链表结点的结构特点,致使了其对数据访问操作的方式。**故为实现双链表的功能,进行如下设计。

2.1 结点结构

仿照单链表结点,其中单链表使用一个指针域,实现了对下一个数据的存储。现在,我们给予双链表两个指针域,分别存储前一个结点的地址信息与后一个结点的地址信息。(代码如下)

struct Node*{
	int data;			/* 存储数据 */
	struct Node* prev;  /* 存储前一个结点的地址信息 */
	struct Node* next;	/* 存储后一个结点的地址信息 */
};
2.2 创建新结点

前文提及,双链表将会使用两个指针,分别指向首尾结点。同时,在新建结点过程中,笔者使用 memcpy() 函数。(实现注意事项具体见代码及注释!

/* 
	两个指针使用全局变量,一般不推荐使用,此处为了方便而使用,
	建议读者理解思路之后自行修改操作
*/
struct Node* pHead;
struct Node* pTail;

struct Node* createNode(int data){
	// 1. 申请内存
	struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
	// 2. 赋空
#if 0
	// 原来的方式
	pnew->prev = NULL;
	pnew->next = NULL;
#else
	// 使用 memcpy() 函数:使用与多个同时操作
	memcpy(pnew,0,sizeof(struct Node));	// 内存统一置空
#endif
	// 3. 数据赋值
	pnew->data = data;
	// 4. 返回结点
	return pnew;
}

2.3 元素插入

在双链表中,笔者将不在使用原来的区分形式写多个函数的插入方式,现在统一写成一个函数直接操作。

方式:

  1. 指定一个选择参数,作为如同头插法、尾插法、指定插入方式的选择 参数 flag。(-1 表示尾插,0 表示头插,正数表示指定插入的位置)
  2. 特殊情况:链表为空,直接挂载。

实现注意事项具体见代码及注释!

在这里插入图片描述

链表的学习与设计实现一定要通过作图来加深影响。后文将不提供图片示意!!!请读者自行绘图分析。

void insertNode(int data,int flag){
	// 1. 申请新结点
	struct Node* pnew = createNode(data);
	// 2. 判断链表是否为空
	if(NULL == pHead){
		// 链表为空直接挂载
		pHead = pTail = pnew;
		return;
	}
	// 3. 根据flag的值进行操作
	if(0 == flag){
		// 看图!!!!!
		pnew->next = pHead;
		pHead->prev = pnew;
		pHead = pnew;
	}
	elseif (-1 == flag){
		// 看图!!!!!
		pTail->next = pnew;
		pnew->prev = pTail;
		pTail = pnew;
	}
	else{
		// 创建游标结点
		struct Node* ptemp = pHead;
		for(int i = 0;i < flag-2  ;i++){
			// 链表过短
			if(pTail == ptemp){	// 等价于尾插法
				ptemp->next = pnew;
				pnew->prev = ptemp;
				ptemp = pnew;
			}
			// 移动游标
			ptemp = ptemp->next;
		}
		pnew->next = ptemp->next;
		ptemp->next = pnew;
		pnew->prev = ptemp;
		ptemp->next = pnew;
		
	}

}

2.4 遍历链表(顺序、逆序)

在双链表的遍历中,可以 实现顺序、逆序遍历 ,此时需要传入一个 flag(0 为顺序;1 为逆序) 指定遍历方式。由于链表首位都有指针,故遍历时,顺序遍历移动 pHead,逆序遍历移动 pTail。实现注意事项具体见代码及注释!

void travel(int flag){
	// 1. 判断链表是否为空
	if(NULL == pHead)
	{
	printf("链表为空!\n");
		return;
	}
	// 2. 顺序遍历
	// 设置游标
	struct Noed* ptemp;
	if(0 == flag){
		ptemp = pHead;
		printf("顺序遍历:");
		while(ptemp ){
			printf("%d ",ptemp ->data);
			ptemp = ptemp -next;
		}
		printf("遍历完成!\n");
	}
	elseif(1 == flag){
		ptemp = pTail;
		printf("顺序遍历:");
		while(ptemp ){
			printf("%d ",ptemp ->data);
			ptemp = ptemp -prev;
		}
		printf("遍历完成!\n");
	}
	else{
		printf("输入错误!flag(0 为顺序;1 为逆序)\n");
	}
}

2.5 统计结点数

简单计数。从头至尾。(实现注意事项具体见代码及注释!

int getCount(){
	// 1. 如果链表为空返回 0
	if(NULL == pHead) return 0;
	// 2. 一个结点
	if(pHead == pTail) return 1;
	// 3. 结点数大于1,设置游标
	int count = 0;
	struct Node* ptemp = pHead;
	for (ptemp = pHead;ptemp;ptemp = ptemp->next)
		count++;
}

2.6 删除结点

本篇中使用删除指定位置上的结点,即传入的是“索引+1”,由于时双向链表,首尾均可操作,本篇将实现对于非首尾删除时,二分区域进行删除(略微提升性能)。(实现注意事项具体见代码及注释!

void deleteNode(int pos){
	// 1. 指定位置不合法,小于等于 0,或大于链表长度。
	if(pos <= 0 || pos > getCount()){
		printf("传入的参数值不合法!操作失败!\n");
		return;
	}
	// 2. 创建游标
	struct Node* pDel;
	// 3. 分情况
	if (1 == pos){	// 删除第一个结点
		if(1 == count)	{// 排除可能只有一个结点
			free(pHead);
			pHead = pTail = NULL;
			return;
		}
		// 暂存要删除的结点
		pDel = pHead;
		// 下一个结点的 prev 置空
		pHead->next->prev = NULL;
		// pHead 指向下一个结点
		pHead = pHead->next;
		// 释放目标结点
		free(pDel);
		return;
	}
	if (count == pos){	// 删除最后一个结点
		// 暂存要删除的结点
		pDel = pTail;
		// 前一个结点的 next 置空
		pTail->prev ->next = NULL;
		// pHead 指向下一个结点
		pTail= pTail->prev;
		// 释放目标结点
		free(pDel);
		return;
	}

	if (pos < (count / 2)){ // 前半部分
		pDel = pHead;
		for(int i = 0;i<pos-1;i++){
			pDel = pDel->next;
		}
		// 被删除结点的前一个结点的 next 指向被删除结点的后一个结点
		pDel->prev->next = pDel->next;
		// 被删除结点的后一个结点的 prev 指向被删除结点的前一个结点
		pDel->next->prev = pDel->prev;
		// 释放 被删除结点
		free(pDel);
	}else{	// 后半部分
		for(int i = 0;i<pos-1;i++){
			pDel = pDel->prev;
		}
		// 被删除结点的前一个结点的 next 指向被删除结点的后一个结点
		pDel->next->prev = pDel->prev;
		// 被删除结点的后一个结点的 prev 指向被删除结点的前一个结点
		pDel->prev->next = pDel->next;
		// 释放 被删除结点
		free(pDel);
	}
}

3. 结语

到此,笔者简单的与大家分享了有头单链表、无头单链表、双链表的设计与实现。链表的基础操作就分享到这!
链表的真正使用情形肯定远不止这些!!!例如,学生信息管理系统项目中我们还会涉及排序问题等,包括有时还会遇到循环链表。此后的内容用法将在实际应用中来具体实现。
感谢阅读,如对代码有疑问或出现错误,请评论或留言,笔者会及时更正。

实操系列与功能补充指引!(待更新)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
数据结构课程设计中,C++双向链表是一个常见的数据结构之一。它是一种线性数据结构,由多个节点组成,每个节点包含两个指针,一个指向前一个节点,一个指向后一个节点。双向链表相比于单向链表,可以实现双向遍历。 在C++中,可以通过定义一个双向链表类来实现双向链表的功能。以下是一个简单的C++双向链表实现示例: ```cpp #include <iostream> // 双向链表节点定义 class Node { public: int data; Node* prev; Node* next; }; // 双向链表类定义 class DoublyLinkedList { private: Node* head; // 头节点指针 public: DoublyLinkedList() { head = nullptr; } // 在链表头部插入节点 void insertAtHead(int value) { Node* newNode = new Node(); newNode->data = value; newNode->prev = nullptr; newNode->next = head; if (head != nullptr) { head->prev = newNode; } head = newNode; } // 在链表尾部插入节点 void insertAtTail(int value) { Node* newNode = new Node(); newNode->data = value; newNode->next = nullptr; if (head == nullptr) { newNode->prev = nullptr; head = newNode; return; } Node* temp = head; while (temp->next != nullptr) { temp = temp->next; } temp->next = newNode; newNode->prev = temp; } // 打印链表元素 void printList() { Node* temp = head; while (temp != nullptr) { std::cout << temp->data << " "; temp = temp->next; } std::cout << std::endl; } }; int main() { DoublyLinkedList dll; dll.insertAtHead(3); dll.insertAtHead(2); dll.insertAtHead(1); dll.printList(); // 输出:1 2 3 dll.insertAtTail(4); dll.insertAtTail(5); dll.printList(); // 输出:1 2 3 4 5 return 0; } ``` 以上是一个简单的C++双向链表实现示例。你可以通过调用`insertAtHead`和`insertAtTail`方法来插入节点,通过调用`printList`方法来打印链表元素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NPC的白话文谈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值