带头双向循环链表的实现

文件分类

测试模块:Test.c文件

功能声明和实现模块:List.c和List.h文件

链表的定义

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。

示意图:

链表节点结构体声明:

typedef int LTDataType;
//使用typedef便于后面改动单链表中储存的数据类型

typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

双向链表各种功能的实现

动态申请一个节点

由于每次插入数据都需要动态增加一个节点,所以把申请节点写成一个函数,便于每次插入时调用该函数。

LTNode* BuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}
链表初始化

创建双向链表的哨兵位,并返回该节点。

LTNode* LTInIt()
{
	LTNode* phead = BuyNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
 双向链表打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	printf("guard<==>");
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
判断双向链表是否为空

当哨兵位的下一个节点还是自己时,则说明该双向链表为空,返回true,否则返回false。

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
双向链表尾插

双向链表由于有哨兵位节点,尾插不需要分空链表和非空链表两种情况。因此只需要传一级指针作为参数。


void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyNode(x);
	phead->prev->next = newnode;
	newnode->prev = phead->prev;
	phead->prev = newnode;
	newnode->next = phead;
}
双向链表尾删

删除前需要检查是否为空,不为空时,改变要删除节点前面指针的next指向为后面的指针,后面的指针的prev指向为前面的指针,然后把删除的节点使用free释放掉。具体代码如下:

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* ptail = phead->prev;
	phead->prev = ptail->prev;
	ptail->prev->next = phead;
	free(ptail);
	ptail = NULL;
}
双向链表头插

头插直接插在哨兵位的后面,将原本哨兵位后面的节点的prev指针指向要插入的节点,哨兵位的next指针指向要插入的节点,要插入的节点的prev指针指向哨兵位节点,插入的节点的next节点指向本哨兵位后面的节点。

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyNode(x);
	phead->next->prev = newnode;
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
}
双向链表头删

删除前需要检查是否为空,不为空时,直接改变哨兵位节点的next指向为第一个节点的下一个节点,然后将第一个节点的下一个节点的prev指针指向哨兵位节点,然后使用free释放掉要删除的节点。代码如下:

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* front = phead->next;
	front->next->prev = phead;
	phead->next = front->next;
	free(front);
	front = NULL;
}
双向链表的查找

首先判断是否为空,不为空时,遍历一遍链表查找是否有等于x的值,如果有,返回该节点,如果没有,返回NULL。

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
双向链表在pos位置前面插入

找到pos位置之前的节点front,然后改变pos和front的指向,具体代码如下:

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = BuyNode(x);
	LTNode* front = pos->prev;
	pos->prev = newnode;
	newnode->next = pos;
	front->next = newnode;
	newnode->prev = front;
}
双向链表删除pos位置的节点

找到pos位置前后的节点,front和next,将front节点的next指针指向next节点,将next节点的prev指针指向front节点,然后使用free释放掉pos节点。

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* front = pos->prev;
	LTNode* next = pos->next;
	front->next = next;
	next->prev = front;
	free(pos);
	pos = NULL;
}
双向链表的销毁

将双向链表依次遍历并释放,最后将哨兵位节点指向NULL。

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

完整代码

List.h文件完整代码
#pragma once

#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

// 创建返回链表的头结点.
LTNode* LTInIt();

// 双向链表销毁
void LTDestory(LTNode* phead);

// 双向链表打印
void LTPrint(LTNode* phead);

bool LTEmpty(LTNode* phead);

// 双向链表尾插
void LTPushBack(LTNode* phead, LTDataType x);

// 双向链表尾删
void LTPopBack(LTNode* phead);

// 双向链表头插
void LTPushFront(LTNode* phead, LTDataType x);

// 双向链表头删
void LTPopFront(LTNode* phead);

// 双向链表查找
LTNode* LTFind(LTNode* phead, LTDataType x);

// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x);

// 双向链表删除pos位置的节点
void LTErase(LTNode* pos);
List.c文件完整代码
#define _CRT_SECURE_NO_WARNINGS 1

#include"list.h"

//动态申请一个节点
LTNode* BuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}

// 创建返回链表的头结点.
LTNode* LTInIt()
{
	LTNode* phead = BuyNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

// 双向链表销毁
void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

// 双向链表打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	printf("guard<==>");
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

//判断是否为空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

// 双向链表尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTInsert(phead, x);
	/*LTNode* newnode = BuyNode(x);
	phead->prev->next = newnode;
	newnode->prev = phead->prev;
	phead->prev = newnode;
	newnode->next = phead;*/
}

// 双向链表尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTErase(phead->prev);
	/*LTNode* ptail = phead->prev;
	phead->prev = ptail->prev;
	ptail->prev->next = phead;
	free(ptail);
	ptail = NULL;*/
}

// 双向链表头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTInsert(phead->next, x);
	/*LTNode* newnode = BuyNode(x);
	phead->next->prev = newnode;
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;*/
}

// 双向链表头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTErase(phead->next);
	/*LTNode* front = phead->next;
	front->next->prev = phead;
	phead->next = front->next;
	free(front);
	front = NULL;*/
}

// 双向链表查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = BuyNode(x);
	LTNode* front = pos->prev;
	pos->prev = newnode;
	newnode->next = pos;
	front->next = newnode;
	newnode->prev = front;
}

// 双向链表删除pos位置的节点
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* front = pos->prev;
	LTNode* next = pos->next;
	front->next = next;
	next->prev = front;
	free(pos);
	pos = NULL;
}
test.c文件完整代码
#define _CRT_SECURE_NO_WARNINGS 1

#include"list.h"

void test()
{
	LTNode* plist = LTInIt();
	LTPushBack(plist, 0);
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPushFront(plist, -1);
	LTPushFront(plist, -2);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTNode* retnode = LTFind(plist, 3);
	LTPrint(retnode);
	LTInsert(retnode, 8);
	LTPrint(plist);

	LTDestory(plist);
}

int main()
{
	test();
	return 0;
}

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是Java实现带头双向循环链表的完整源码,供参考: ``` public class DoublyCircularLinkedList<T> { private Node<T> head; // 头节点 // 节点类 private static class Node<T> { T data; Node<T> prev; Node<T> next; Node(T data) { this.data = data; this.prev = null; this.next = null; } } // 构造函数 public DoublyCircularLinkedList() { head = new Node<>(null); head.prev = head; head.next = head; } // 在链表末尾添加元素 public void add(T data) { Node<T> node = new Node<>(data); node.prev = head.prev; node.next = head; head.prev.next = node; head.prev = node; } // 在指定位置插入元素 public void insert(int index, T data) { Node<T> node = new Node<>(data); Node<T> p = head.next; int i = 0; while (p != head && i < index) { p = p.next; i++; } if (p == head || i > index) { throw new IndexOutOfBoundsException(); } node.prev = p.prev; node.next = p; p.prev.next = node; p.prev = node; } // 删除指定位置的元素 public void remove(int index) { Node<T> p = head.next; int i = 0; while (p != head && i < index) { p = p.next; i++; } if (p == head || i > index) { throw new IndexOutOfBoundsException(); } p.prev.next = p.next; p.next.prev = p.prev; p.prev = null; p.next = null; } // 获取指定位置的元素 public T get(int index) { Node<T> p = head.next; int i = 0; while (p != head && i < index) { p = p.next; i++; } if (p == head || i > index) { throw new IndexOutOfBoundsException(); } return p.data; } // 获取链表长度 public int size() { Node<T> p = head.next; int size = 0; while (p != head) { size++; p = p.next; } return size; } } ``` 该代码实现带头双向循环链表的数据结构,支持在链表末尾添加元素、在指定位置插入元素、删除指定位置的元素、获取指定位置的元素、获取链表长度等操作。在算法实现中,通过一个Node类来表示链表中的节点,包含数据域、前驱指针和后继指针。同时,链表的头节点也是一个Node对象,通过头节点来连接链表的首尾,形成双向循环链表
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值