IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 数据结构(3)——双链表的实现 -> 正文阅读

[数据结构与算法]数据结构(3)——双链表的实现

在数据结构(2)中,我实现了其中一种较为常用的链表形式-单向无头非循环链表。那在这篇博客中,我来实现另一种——双向带头循环链表

结构示意图:
在这里插入图片描述
该链表

  1. 包含一个哨兵位的头结点;
  2. 每一个结点都有两个指针,分别指向上一个结点的后指针和下一个结点的前指针;
  3. 最后一个结点的后指针指向哨兵位头结点的前指针,构成闭环;

双向有头循环链表结构较为复杂,实现起来也需要较多的思维量和代码量,但是在运用时会比单向链表方便很多。这次我们也是分为三个文件:

  1. Dlist.h
  2. Dlist.c
  3. Test.c

一、Dlist.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int DataType;
struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	DataType data;
};

我们要实现的功能函数有:

//1.创建一个新结点
//这是内部函数使用的,不是对外开放的接口函数
struct ListNode* BuyListNode(DataType x);

//2.创建链表+初始化
struct ListNode* ListInit();

//3.打印
void ListPrint(struct ListNode* pHead);

//4.尾插
void PushBack(struct ListNode* pHead, DataType x);

//5.头插
void PushFront(struct ListNode* pHead, DataType x);

//6.尾删
void ListPopBack(struct ListNode* pHead);

//7.头删
void ListPopFront(struct ListNode* pHead);

//8.查找
struct ListNode* ListFind(struct ListNode* pHead,DataType x);

//9.在所选位置的前面插入
void ListInsert(struct ListNode* pos, DataType x);

//10.指定位置删除
void ListErase(struct ListNode* pos);

//11.链表判空
int If_List_IsEmpty(struct ListNode* pHead);

//12.计算链表结点个数
int ListSize(struct ListNode* pHead);

//13.释放整个链表
void ListDestroy(struct ListNode** pHead);

二、Dlist.c
那么,我们来具体实现各个功能函数:

1.创建一个新结点

struct ListNode* BuyListNode(DataType x)
{
	struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

2.创建链表+初始化

struct ListNode* ListInit()
{
	struct ListNode* pHead=BuyListNode(0);
	
	//为了实现循环,先让两个指针都指向自己
	pHead->next = pHead;
	pHead->prev = pHead;
	return pHead;
} 

3.打印

在单链表的遍历打印中,循环终止的条件是找尾。但由于双链表是循环的,没有真正意义上的尾,因此对循环终止条件的思考就尤为重要了。

void ListPrint(struct ListNode* pHead)
{
	struct ListNode* cur = pHead->next;
	//注意循环终止的条件
	//循环从头开始,再次到达头时,循环结束
	while (cur != pHead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

4.尾插

示意图:
在这里插入图片描述
根据示意图可知,尾插的本质就是四个指针的变化

void PushBack(struct ListNode* pHead, DataType x)
{
	assert(pHead);

    //根据链表双向的特性,头结点的prev指针指向的就是链表的尾结点
	struct ListNode* tail = pHead->prev;
	struct ListNode* newNode = BuyListNode(x);

	tail->next = newNode;
	newNode->prev = tail;
	newNode->next = pHead;
	pHead->prev = newNode;	
}

5.头插

示意图:
在这里插入图片描述

void PushFront(struct ListNode* pHead,DataType x)
{
	assert(pHead);
	struct ListNode* newNode = BuyListNode(x);
	struct ListNode* nextpoint = pHead->next;

	pHead->next=newNode;
	newNode->prev = pHead;
	newNode->next = nextpoint;
	nextpoint->prev = newNode;
}

在单链表的头插中,我们将链表中只有一个结点的情况和大于一个结点的情况分开讨论。但在双链表的头插中,我们发现这段代码同时适用于两种情况
因为如果插入的是第一个结点,那么pHead->next还是pHead,即nextPoint与pHead相同,最后四句相当于是相同的两句写了两遍。

在编写代码时,一定要充分考虑到可能出现的各种极端情况。

6.尾删

示意图:
在这里插入图片描述

void ListPopBack(struct ListNode* pHead)
{
	assert(pHead);
	//如果链表中只有pHead一个结点,下面的free(teil)会把pHead释放掉,这是不行的
	assert(pHead->next!= pHead);

	struct ListNode* tail = pHead->prev;
	struct ListNode* prevtail = tail->prev;
	free(tail);

	prevtail->next = pHead;
	pHead->prev = prevtail;
}

如果链表中除了pHead外只有一个结点,以上代码也是适用的。
因为prevail->tail=pHead就相当于pHead->tail=pHead,pHead->next=pretail就相当于pHead->next=pHead,考虑到双向链表的循环性,这是没有问题的。

7.头删

示意图:
在这里插入图片描述

注意:头删不是删除pHead,pHead是哨兵位头结点!而是从pHead后面的结点开始删。

void ListPopFront(struct ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);

	struct ListNdoe* first = pHead->next;
	struct ListNode* second = pHead->next->next;

	free(first);
	
	pHead->next = second;
	second->prev = pHead;
}

8.查找

struct ListNode* ListFind(struct ListNode* pHead,DataType x)
{
	assert(pHead);

	struct ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

9.指定位置插入

示意图:
在这里插入图片描述

void ListInsert(struct ListNode* pos, DataType x)
{
	assert(pos);

	struct ListNode* newNode = BuyListNode(x);
	struct ListNode* posprev = pos->prev;
	 
	posprev->next = newNode;
	newNode->prev = posprev;
	newNode->next = pos;
	pos->prev = newNode;
}

10.指定位置删除

void ListErase(struct ListNode* pos)
{
	assert(pos);

	struct ListNode* prevpos = pos->prev;
	struct ListNode* nextpos = pos->next;

	prevpos->next = nextpos;
	nextpos->prev = prevpos;

	free(pos);
}

头插尾插、头删尾删都可以由Insert和Erase复用得到。因此如果要快速编写双链表,先写ListInsert和ListErase,再通过复用得到其他的功能函数。

11.链表判空

int If_List_IsEmpty(struct ListNode* pHead)
{
	return pHead->next == NULL ? 1 : 0;
}

12.计算链表结点个数

这个函数类似于打印,遍历时的终止条件也是cur==pHead。

int ListSize(struct ListNode* pHead)
{
	int count = 0;
	struct ListNode* cur = pHead->next;
	while (cur!=pHead)
	{
		cur = cur->next;
		count++;
	}
	return count;
}

13.删除整个链表

void ListDestroy(struct ListNode* pHead)
{
	assert(pHead);

	struct ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		struct ListNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(pHead);
	pHead = NULL;
}

三、Test.c
现在,我们对所写的双链表进行简单的测试:

#include "List.h"

void Testfunc1()
{
	struct ListNode* pHead =ListInit();
	 
	printf("            尾插: ");
	PushBack(pHead, 1);
	PushBack(pHead, 2);
	PushBack(pHead, 3);
	PushBack(pHead, 4);
	ListPrint(pHead);

	printf("            头插: ");
	PushFront(pHead, 0);
	PushFront(pHead, -1);
	PushFront(pHead, -2);
	PushFront(pHead, -3);
	ListPrint(pHead);

	struct ListNode* temp = ListFind(pHead, 3);
	printf("在3的前面插入100: ");
	ListInsert(temp, 100);
	ListPrint(pHead);

	printf("           删除3: ");
	ListErase(temp);
	ListPrint(pHead);


	printf("            尾删: ");
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPrint(pHead);

	printf("            头删: ");
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPrint(pHead);

	int IsEmpty = If_List_isEmpty(pHead);
	if (IsEmpty == 1)
	{
		printf("空链表\n");
	}
	else
	{
		printf("不是空链表,目前有 %d 个结点\n",ListSize(pHead));
	}

	ListDestroy(pHead);
}

int main()
{
	Testfunc1();
	return 0;

运行结果:
在这里插入图片描述

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-09-02 11:37:50  更:2021-09-02 11:39:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/30 1:12:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码