带头双向循环链表

带头双向循环链表

  • 一,前言
  • 二,正文
    • 1.申请一个节点
    • 2初始化链表
    • 3.链表打印
    • 4.链表查找
    • 5.链表在pos位置之后插入x
    • 6.链表的头插
    • 7.链表尾插
    • 8.链表删除pos位置的值
    • 9.链表的尾删
    • 10.链表头删
    • 11.销毁链表
  • 三,结语

一,前言

带头双向循环链表作为单链表的升级版,有着更强的实用性,更少的特殊情况。其结构如图
带头双向循环链表
如图,我们先定义结构体,每个节点都有头(prev)尾(rear)两个指针指向该节点的前后节点。

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* prev;
	struct SListNode* next;
}SListNode;

在实际操作中,我们要设置一个该节点类型的指针,再初始化使其指向一个不存储数据的节点(将其称为头节点(如图中的head节点)),进行完该操作才可以进行其他操作。
在这里插入图片描述

二,正文

1.申请一个节点

因为后面的初始化,插入,头插操作都需要malloc一个节点,所以我们单独写一个申请节点的函数,使代码更简练

SListNode* BuySListNode(SLTDateType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

2初始化链表

初始化链表即将主函数设置的空指针指向一个动态申请的头节点,并将头节点的头尾指针指向自己
在这里插入图片描述
这里因为要改变plist指向的地址,所以要用二级指针。这里我将头节点的data赋值为-1。如果看不懂二级指针,可以用另一种写法。在初始化函数里定义一个指针,申请一个节点,使该指针指向该节点,再返回该指针

void InitSListNode(SListNode** pplist)
{
	*pplist = BuySListNode(-1);
	(*pplist)->prev = *pplist;
	(*pplist)->next = *pplist;
}

3.链表打印

这部分唯一要这样的就是判断打印的条件

void SListPrint(SListNode* plist)
{
	assert(plist);
	SListNode* cur = plist->next;
	if (cur == plist)
	{
		printf("链表为空");
	}
	while(cur != plist)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

4.链表查找

一般查找是伴随着插入或删除使用的,这里未查找到返回空指针,会触发在插入或删除中的断言(assert),表示未查找到数据。

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	assert(plist);
	SListNode* cur = plist->next;
	while (cur != plist)
	{
 		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

5.链表在pos位置之后插入x

先判断pos是不是空指针,来验证查找是否成功。然后申请一个节点,将它插在pos指向的节点后面。注意插入操作的顺序,要先处理新节点的头尾指针,再处理pos的下一个节点的头指针,最后是pos的尾指针。不用考虑链表只有头节点的特殊情况。

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

6.链表的头插

头插就是在头节点后插入一个节点,可以把头节点地址传到在pos位置之后插入x的函数中,也可以将插入操作的逻辑重新写一遍

void SListPushFront(SListNode* plist, SLTDateType x)
{
	assert(plist);
	SListInsertAfter(plist, x);
	/*SListNode* newnode = BuySListNode(x);
	newnode->next = plist->next;
	newnode->prev = plist;
	plist->next->prev = newnode;
	plist->next = newnode;*/
}

7.链表尾插

和头插一样的逻辑,也可以传plist->prev的地址到pos之后插入x的函数,这里就不写了。pos位置的删除函数一样可以用的头删和尾删里去。

void SListPushBack(SListNode* plist, SLTDateType x)
{
	assert(plist);
	SListNode* newnode = BuySListNode(x);
	newnode->next = plist;
	newnode->prev = plist->prev;
	plist->prev->next = newnode;
	plist->prev = newnode;
}

8.链表删除pos位置的值

删除和插入逻辑相反,要先处理前后的节点,再处理pos指向的节点。要注意的是要提前判断链表是否为空。

void SListEraseAfter(SListNode* pos)
{
	assert(plist);
	if (plist->next == plist && plist->prev == plist)
	{
		printf("链表为空,删除失败\n");
		return;
	}
	SListNode* posnext = pos->next;
	SListNode* posprev = pos->prev;
	posprev->next = posnext;
	posnext->prev = posprev;
	free(pos);
	pos = NULL;
}

9.链表的尾删

先定义cur指针指向倒数第二个节点,将cur->next指向的节点释放后再进行下一步操作

void SListPopBack(SListNode* plist)
{
	assert(plist);
	assert(plist->next != plist && plist->prev != plist);
	SListNode* cur = plist->prev->prev;
	free(cur->next);
	cur->next = plist;
	plist->prev = cur;
}

10.链表头删

与尾删逻辑基本一样,但我在这里定义的cur表示要删掉的节点

void SListPopFront(SListNode* plist)
{
	assert(plist);
	if (plist->next == plist && plist->prev == plist)
	{
		printf("链表为空,删除失败\n");
		return;
	}
	SListNode* cur = plist->next;
	plist->next = cur->next;
	cur->next->prev = plist;
	free(cur);
	cur = NULL;
}

11.销毁链表

这里我的思路是设置一个循环,不断头删至只剩头节点,再单独释放头节点

void SLTDestroy(SListNode* plist)
{
	assert(plist);
	while (plist->next!=plist)
	{
		SListPopFront(plist);
	}
	free(plist);
	plist = NULL;
}

三,结语

每一种操作都有不同代码编辑方法,操作的目的使为了解决问题,没必要全部按网上的代码写。以上就是带头双向循环链表的基本操作,代码仅供参考,如果有错误还请大家指出,谢谢

  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是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对象,通过头节点来连接链表的首尾,形成双向循环链表

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值