复习之数据结构_带头结点的双向链表

简单理解

我们会发现,单链表由于只有next域,所以,如果想要访问某个元素的前驱结点,那么只能从头开始遍历到该元素的前一个元素。效率非常的低下。
于是,为了方便的访问前驱、后继,双向链表应运而生。
在这里插入图片描述
如上图所示,就是一个简单的双向链表示意图。
双向链表共含有三个元素:
1、存放数据;
2、后继指针域;
3、前驱指针域。

增删图解

双向链表的插入和删除操作需要特别注意边界问题。
正常的插入如下图:
插入情景:将新结点 p 插入到指定结点 q 的后面
在这里插入图片描述
看上去这四个步骤好像没有什么问题,但是,步骤2在处理边界问题的时候容易崩溃,原因如下图:
在这里插入图片描述
故,正确的插入操作需要对步骤2添加条件判断:

	p->next = q->next;
	if (p->next != NULL)
	{
    
    
		p->next->prio = p;
	}
	q->next = p;
	p->prio = q;

删除操作原理同上,在此就不多做赘述了。
我们来看代码:

头文件

头文件命名为:dlist.h

#pragma once
// 带头结点的双向链表,不循环

typedef struct DNode
{
    
    
	int data;
	struct DNode* next;// 后继指针
	struct DNode* prio;// 前驱指针
}DNode,*DList;

// 初始化函数
void InitList(DList plist);

// 判空
bool IsEmpty(DList plist);

// 获取数据长度
int GetLength(DList plist);

// 头插
bool Insert_head(DList plist, int val);

// 尾插
bool Insert_tail(DList plist, int val);

// 在plist中查找关键字key,找到返回目标地址,失败返回NULL
DList Search(DList plist, int key);

// 删除plist中的第一个key
bool DeleteVal(DList plist, int key);

// 打印输出所有数据
void Show(DList plist);

// 逆置
void Reverse(DList plist);

// 清空数据
void Clear(DList plist);

// 销毁动态内存
void Destroy(DList plist);

代码文件

代码文件命名为:dlist.cpp

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "dlist.h"

// 初始化函数
void InitList(DList plist)
{
    
    
	assert(plist != NULL);
	if (plist == NULL) return;

	plist->next = NULL;
	plist->prio = NULL;
}

// 判空
bool IsEmpty(DList plist)
{
    
    
	assert(plist != NULL);

	return plist->next == NULL;
}

// 获取数据长度
int GetLength(DList plist)
{
    
    
	assert(plist != NULL);

	int count = 0;
	for (DNode* p = plist->next; p != NULL; p = p->next)
	{
    
    
		count += 1;
	}
	return count;
}

// 头插
bool Insert_head(DList plist, int val)
{
    
    
	assert(plist != NULL);

	DNode* p = (DNode*)malloc(sizeof(DNode));
	p->data = val;

	// 将p插在头结点的后面
	p->next = plist->next;
	// 特别注意,这里需要加判断
	if (p->next != NULL)
	{
    
    
		// 如果是第一次插入结点,前面p->next = plist->next == NULL;
		// 此时p->next->prio <==> NULL->prio,会发生访问冲突
		p->next->prio = p;
	}
	plist->next = p;
	p->prio = plist;

	return true;
}

// 尾插
bool Insert_tail(DList plist, int val)
{
    
    
	assert(plist != NULL);

	DNode* p = plist;
	while (p->next != NULL)
	{
    
    
		p = p->next;
	}

	DNode* q = (DNode*)malloc(sizeof(DNode));
	q->data = val;

	// 将q插入在尾结点p的后面
	q->next = p->next;
	p->next = q;
	q->prio = p;

	return true;
}

// 在plist中查找关键字key,找到返回目标地址,失败返回NULL
DList Search(DList plist, int key)
{
    
    
	assert(plist != NULL);

	for (DNode* p = plist->next; p != NULL; p = p->next)
	{
    
    
		if (key == p->data)
		{
    
    
			return p;
		}
	}
	return NULL;
}

// 删除plist中的第一个key
bool DeleteVal(DList plist, int key)
{
    
    
	assert(plist != NULL);

	DNode* p = Search(plist, key);
	if (p == NULL)
	{
    
    
		return false;
	}
	// 将p从链表中剔除
	p->prio->next = p->next;
	if (p->next != NULL)
	{
    
    
		p->next->prio = p->prio;
	}

	free(p);
	return true;
}

// 打印输出所有数据
void Show(DList plist)
{
    
    
	assert(plist != NULL);

	for (DNode* p = plist->next; p != NULL; p = p->next)
	{
    
    
		printf("%d ",p->data);
	}
	printf("\n");
}

// 逆置
void Reverse(DList plist)
{
    
    
	assert(plist != NULL);

	// p定位到最后一个数据结点
	DNode* p = plist->next;
	while (p->next != NULL)
	{
    
    
		p = p->next;
	}
	// 处理头结点
	plist->next = p;
	// 处理所有的next域
	while (p->prio != plist)
	{
    
    
		p->next = p->prio;
		p = p->prio;
	}
	if(p->prio == plist)
	{
    
    
		p->next = NULL;
	}

	// 接着处理prio域
	p = plist->next;
	DNode* q = plist;
	while (p != NULL)
	{
    
    
		p->prio = q;
		p = p->next;
		q = q->next;
	}
}

// 清空数据
void Clear(DList plist)
{
    
    
	Destroy(plist);
}

// 销毁动态内存
void Destroy(DList plist)
{
    
    
	assert(plist != NULL);

	DNode* p;
	while (plist->next != NULL)
	{
    
    
		p = plist->next;
		plist->next = p->next;

		free(p);
	}
}

测试用例

用如下的测试代码来检验我们所写的程序:

#include <stdio.h>
#include <iostream>
#include "dlist.h"

int main()
{
    
    
	DNode head;
	InitList(&head);
	for (int i = 0; i < 7; i++)
	{
    
    
		Insert_tail(&head, i);
	}
	Show(&head);
	Reverse(&head);
	Show(&head);

	return 0;
}

输出结果如下:
在这里插入图片描述

笔者这里着重检查了逆置函数,顺带检验了插入和打印函数,其他的没有在本测试代码中体现,有兴趣的可以自行验证。

参考资料

【1】严蔚敏. 数据结构(C语言版). 北京:清华大学出版社,2009:30.

猜你喜欢

转载自blog.csdn.net/m0_46308273/article/details/114149087