【数据结构】手撕双向链表

目录

前言

[1. 双向链表](#1. 双向链表)

带头双向循环链表的结构

[2. 链表的实现](#2. 链表的实现)

[2.1 初始化](#2.1 初始化)

[2.2 尾插](#2.2 尾插)

[2.3 尾删](#2.3 尾删)

[2.4 头插](#2.4 头插)

[2.5 头删](#2.5 头删)

[2.6 在pos位置之前插入](#2.6 在pos位置之前插入)

[2.7 删除pos位置](#2.7 删除pos位置)

3.双向链表完整源码

List.h

List.c


前言

在上一期中我们介绍了单链表,也做了一些练习题,在一些题中使用单链表会十分繁琐。因为单链表只能正着走,不能倒着走,例如:回文、逆置。本期我们将学习带头双向循环链表。

1. 双向链表

带头双向循环链表的结构

特点:带头双向循环链表结构最复杂,一般用在单独存储数据。结构虽然结构复杂,但是使用代码实现以后会发现结构会带来多优势,实现反而简单了。

2. 链表的实现

2.1 初始化

复制代码
LTNode* LTInit()
{
	LTNode* phead = CreateLTNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

2.2 尾插

带哨兵位的链表尾插时不用判断是否有节点,两种情况的插入相同,而且还不用传递二级指针。

复制代码
void LTPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);

	LTNode* newnode = CreateLTNode(x);
	phead->prev->next = newnode;
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev = newnode;
}

2.3 尾删

在尾删时我们通过 assert(phead->next != phead); 判断链表是否有节点。同时这个代码就有普遍性,不用单独考虑剩一个节点的情况。

复制代码
void LTPopBack(LTNode* phead)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;
	free(tail);
	phead->prev = tailprev;
	tailprev->next = phead;
}

2.4 头插

头删重要的是赋值的顺序,顺序错误会找不到后面的节点,导致内存泄漏。带哨兵位的链表不需要传递二级指针,因为改变的是结构体的变量。

复制代码
void LTPushFront(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* newnode = CreateLTNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

2.5 头删

我们可以多定义几个指针来保存后面节点的地址,这样就不会造成节点的丢失,不用考虑赋值的顺序,会更加方便。

复制代码
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* tail = phead->next;
	LTNode* next = tail->next;
	phead->next = next;
	next->prev = phead;
	free(tail);
	tail = NULL;
}

2.6 在pos位置之前插入

复制代码
void LTInsert(LTNode* pos, LTDateType x)
{
	assert(pos);

	LTNode* posprev = pos->prev;
	LTNode* newnode = CreateLTNode(x);
	posprev->next = newnode;
	newnode->prev = posprev;
	newnode->next = pos;
	pos->prev = newnode;
}

2.7 删除pos位置

复制代码
void LTErase(LTNode* pos)
{
	assert(pos);

	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;
	posprev->next = posnext;
	posnext->prev = posprev;
}

3.双向链表完整源码

List.h

复制代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LTDateType;

typedef struct ListNode
{
	LTDateType val;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

LTNode* LTInit();

void LTPrint(LTNode* phead);

void LTPushBack(LTNode* phead, LTDateType x);

void LTPushFront(LTNode* phead, LTDateType x);

void LTPopBack(LTNode* phead);

void LTPopFront(LTNode* phead);

LTNode* LTFind(LTNode* phead, LTDateType x);

void LTInsert(LTNode* pos, LTDateType x);

void LTErase(LTNode* pos);

void LTDestroy(LTNode* phead);

List.c

复制代码
#include"List.h"

LTNode* CreateLTNode(LTDateType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;
}

LTNode* LTInit()
{
	LTNode* phead = CreateLTNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("<=>哨兵位<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

void LTPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);

	LTNode* newnode = CreateLTNode(x);
	phead->prev->next = newnode;
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev = newnode;
}

void LTPushFront(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* newnode = CreateLTNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

void LTPopBack(LTNode* phead)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;
	free(tail);
	phead->prev = tailprev;
	tailprev->next = phead;
}

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* tail = phead->next;
	LTNode* next = tail->next;
	phead->next = next;
	next->prev = phead;
	free(tail);
	tail = NULL;
}

LTNode* LTFind(LTNode* phead, LTDateType x)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
		{
			return cur;
		}
			cur = cur->next;
	}
	return NULL;
}

void LTInsert(LTNode* pos, LTDateType x)
{
	assert(pos);

	LTNode* posprev = pos->prev;
	LTNode* newnode = CreateLTNode(x);
	posprev->next = newnode;
	newnode->prev = posprev;
	newnode->next = pos;
	pos->prev = newnode;
}

void LTErase(LTNode* pos)
{
	assert(pos);

	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;
	posprev->next = posnext;
	posnext->prev = posprev;
}

void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

通过上面链表的实现,我们已经感受到了带头双向循环链表的方便和简单,它不需要去考虑链表是否有元素,还可以找到前一个元素,在我们使用中提供很大的便利。

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。

相关推荐
zwenqiyu1 小时前
非线性字符串数据结构串讲
数据结构·c++·学习·算法
东华万里2 小时前
第32篇 数据结构入门 单链表的增删查改实现
数据结构·链表
文祐3 小时前
C语言用双向链表实现单调递减(递增)队列
c语言·开发语言·链表
OOJO5 小时前
哈希表实现
数据结构·哈希算法·散列表
剑挑星河月7 小时前
234. 回文链表
java·数据结构·算法·leetcode·链表
六bring个六7 小时前
链表学习(常规链表)
数据结构·学习·链表
tachibana21 天前
hot100 回文链表(234)
java·网络·数据结构·leetcode·链表
aaaameliaaa1 天前
进制练习题【找出只出现一次的数字、交换两个变量(不创建临时变量)、统计二进制中1的个数、打印整数二进制的奇数位和偶数位、求两个数二进制中不同位的个数】
c语言·数据结构·笔记·算法
-dzk-1 天前
【系统架构设计师】案例分析篇
开发语言·数据结构·python·算法·架构·系统架构·架构设计
东华万里1 天前
第30篇 代码习惯 初学C与数据结构有感
c语言·数据结构·大学生专区