带虚拟头结点的双向循环链表

双向 (循环)链表. 整个链表只能单方向从表头访问到表尾,这种结构的链表统称为 “单向链表”或“单链表”。. 如果算法中需要频繁地找某结点的前趋结点,单链表的解决方式是遍历整个链表,增加算法的时间复杂度,影响整体效率。. 为了快速便捷地解决这类问题,在单向链表的基础上,给各个结点额外配备一个指针变量,用于指向每个结点的直接前趋元素。. 这样的链表被称为“双向链表”或者“双链表”。.

循环双链表与循环单链表类似, 只是在某些操作上有所不同.

首先是定义, 这里的命名为:

pre: 前驱元素

next:后继元素

由此, 对于单向链表的尾指针, 相当于双向链表头结点的前驱元素

typedef struct ListNode{
	ListNode *pre, *next;
	int val;
}ListNode;

结点和链表的初始化:

ListNode *initListNode(int val){
	ListNode *node = (ListNode *)malloc(sizeof(ListNode));
	node->pre = node->next = NULL;
	node->val = val;
	return node;
}

LinkList *initLinkList(){
	LinkList *list = (LinkList *)malloc(sizeof(LinkList));
	list->head.val = 0;
	list->head.pre = list->head.next = &(list->head);
	return list;
}

这里list->head.pre = list->head.next = &(list->head)的原因与单向循环链表的tail及head.next是一样的, 与下面函数的操作有很大关联.

链表的清空与单链表有所不同(单向循环链表里面忘记提及,这里补一下):

void clearLinkList(LinkList *list){
	if(list == NULL) return;
	ListNode *p = list->head.next, *q;
	while(p != &(list->head)){
		q = p->next;
		clearListNode(p);
		p = q;
	}
	free(list);
	return;
}

这里将clear相当于destroy,想要分别实现两种操作请参考单向链表;

由于list->head.val维护了list的长度, 而双向链表可以有两种遍历方向, 于是可以考虑根据操作下标在链表中的位置来优化时间↓↓↓

插入和删除:        内部操作与普通双链表一致, 但是带有位置优化,使得期望时间复杂度低于O(n)

bool insert(LinkList *list, int ind, int val){
	if(list == NULL || ind < 0 || ind > list->head.val) return false;
	//考虑一下双向链表对于插入与删除的优化;

	ListNode *node = initListNode(val), *p = &(list->head);
	if(ind > (list->head.val >> 1)){
		ind = list->head.val + 1 - ind;
		while(ind--) p = p->pre;
		node->pre = p->pre;
		node->next = p;
		p->pre->next = node;
		p->pre = node;
	}else{
		while(ind--) p = p->next;
		node->next = p->next;
		node->pre = p;
		p->next->pre = node;
		p->next = node;
	}
	
	list->head.val++;
	return true;
}

删除:

bool erase(LinkList *list, int ind){
	if(list == NULL || ind < 0 || ind >= list->head.val) return false;
	
	ListNode *p = &(list->head), *q;
	if(ind > (list->head.val >> 1)){
		ind = list->head.val + 1 - ind;
		while(ind--) p = p->pre;
		q = p->pre->pre;
		clearListNode(p->pre);
		p->pre = q;
		q->next = p;
	}else{
		while(ind--) p = p->next;
		q = p->next->next;
		clearListNode(p->next);
		p->next = q;
		q->pre = p;
	}
	list->head.val--;
	return true;
}

由于双向链表的两个方向, 所以遍历方式也有两种: ordered顺序遍历, reverse逆序遍历

void orderedOutput(LinkList *list){
	if(list == NULL) return;
	puts("Ordered output:");
	printf("LinkList(%d): head-> ", list->head.val);
	for(ListNode *p = list->head.next; p != &(list->head); p = p->next){
		printf("%d-> ", p->val);
	}
	puts("head\n");
	return;
}

void reverseOutput(LinkList *list){
	if(list == NULL) return;
	puts("Reverse output:");
	printf("LinkList(%d): head<- ", list->head.val);
	for(ListNode *p = list->head.pre; p != &(list->head); p = p->pre){
		printf("%d<- ", p->val);
	}
	puts("head\n");
	return;
}

 下面是双向循环链表的完整代码:(带DEBUG)

#include<bits/stdc++.h>
using namespace std;

typedef struct ListNode{
	ListNode *pre, *next;
	int val;
}ListNode;

typedef struct LinkList{
	ListNode head;
}LinkList;

ListNode *initListNode(int val){
	ListNode *node = (ListNode *)malloc(sizeof(ListNode));
	node->pre = node->next = NULL;
	node->val = val;
	return node;
}

LinkList *initLinkList(){
	LinkList *list = (LinkList *)malloc(sizeof(LinkList));
	list->head.val = 0;
	list->head.pre = list->head.next = &(list->head);
	return list;
}

void clearListNode(ListNode *node){
	if(node == NULL) return;
	free(node);
	return;
}

void clearLinkList(LinkList *list){
	if(list == NULL) return;
	ListNode *p = list->head.next, *q;
	while(p != &(list->head)){
		q = p->next;
		clearListNode(p);
		p = q;
	}
	free(list);
	return;
}

bool insert(LinkList *list, int ind, int val){
	if(list == NULL || ind < 0 || ind > list->head.val) return false;
	//考虑一下双向链表对于插入与删除的优化;

	ListNode *node = initListNode(val), *p = &(list->head);
	if(ind > (list->head.val >> 1)){
		ind = list->head.val + 1 - ind;
		while(ind--) p = p->pre;
		node->pre = p->pre;
		node->next = p;
		p->pre->next = node;
		p->pre = node;
	}else{
		while(ind--) p = p->next;
		node->next = p->next;
		node->pre = p;
		p->next->pre = node;
		p->next = node;
	}
	
	list->head.val++;
	return true;
}

bool erase(LinkList *list, int ind){
	if(list == NULL || ind < 0 || ind >= list->head.val) return false;
	
	ListNode *p = &(list->head), *q;
	if(ind > (list->head.val >> 1)){
		ind = list->head.val + 1 - ind;
		while(ind--) p = p->pre;
		q = p->pre->pre;
		clearListNode(p->pre);
		p->pre = q;
		q->next = p;
	}else{
		while(ind--) p = p->next;
		q = p->next->next;
		clearListNode(p->next);
		p->next = q;
		q->pre = p;
	}
	list->head.val--;
	return true;
}

void orderedOutput(LinkList *list){
	if(list == NULL) return;
	puts("Ordered output:");
	printf("LinkList(%d): head-> ", list->head.val);
	for(ListNode *p = list->head.next; p != &(list->head); p = p->next){
		printf("%d-> ", p->val);
	}
	puts("head\n");
	return;
}

void reverseOutput(LinkList *list){
	if(list == NULL) return;
	puts("Reverse output:");
	printf("LinkList(%d): head<- ", list->head.val);
	for(ListNode *p = list->head.pre; p != &(list->head); p = p->pre){
		printf("%d<- ", p->val);
	}
	puts("head\n");
	return;
}
int main(){
	#define MAX_N 1010    //可以在这里修改测试的数据量
	srand(time(0));
	LinkList *list = initLinkList();
	for(int i = 0; i < MAX_N; i++){
		int op = rand() % 6, val = rand() * rand() % 10000000, ind = rand() % (list->head.val + 1);
		switch(op){
			case 0:
				if(erase(list, ind)) printf("Erase elem at %d from the list\n", ind);
				else puts("Failed to erase elem from the list");
				break;
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
				if(insert(list, ind, val)) printf("Insert %d at %d to the list\n", val, ind);
				else puts("Failed to insert elem to the list");
		}
		orderedOutput(list);
		reverseOutput(list);
	}
	clearLinkList(list);
	printf("%d", list->head.val);    //RE操作(逃
	return 0;
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
双向循环链表是一种特殊的链表,它的最后一个节点的 next 指针指向第一个节点,第一个节点的 prev 指针指向最后一个节点,因此可以实现循环遍历。头结点双向循环链表是在双向循环链表的基础上增加了一个头结点,可以方便地进行插入、删除等操作。以下是头结点双向循环链表的常见操作: ```c++ // 双向循环链表节点结构体 template <typename T> struct Node { T data; Node<T>* prev; Node<T>* next; Node() : data(T()), prev(nullptr), next(nullptr) {} Node(T _data, Node<T>* _prev = nullptr, Node<T>* _next = nullptr) : data(_data), prev(_prev), next(_next) {} }; // 头结点双向循环链表类 template <typename T> class DoubleLinkedList { public: DoubleLinkedList() : head(new Node<T>()), size(0) { head->prev = head->next = head; } ~DoubleLinkedList() { clear(); delete head; } // 在 index 处插入元素 void insert(int index, const T& value) { if (index < 0 || index > size) { throw std::out_of_range("Index out of range."); } Node<T>* cur = head; for (int i = 0; i < index; ++i) { cur = cur->next; } Node<T>* node = new Node<T>(value, cur, cur->next); cur->next->prev = node; cur->next = node; ++size; } // 删除 index 处的元素 void remove(int index) { if (index < 0 || index >= size) { throw std::out_of_range("Index out of range."); } Node<T>* cur = head->next; for (int i = 0; i < index; ++i) { cur = cur->next; } cur->prev->next = cur->next; cur->next->prev = cur->prev; delete cur; --size; } // 清空链表 void clear() { while (size > 0) { remove(0); } } // 获取第 index 个元素 T& get(int index) const { if (index < 0 || index >= size) { throw std::out_of_range("Index out of range."); } Node<T>* cur = head->next; for (int i = 0; i < index; ++i) { cur = cur->next; } return cur->data; } // 获取链表长度 int getSize() const { return size; } // 判断链表是否为空 bool isEmpty() const { return size == 0; } private: Node<T>* head; // 头结点 int size; // 链表长度 }; ``` 以上是头结点双向循环链表的常见操作,可以根据实际需求进行调整和扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值