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

循环链表是另一种形式的链式存储结构。 它的特点是表中最后一个结点的 指针 域指向 头结点 ,整个链表形成一个环。

单循环链表——在 单链表 中,将终端结点的 指针 域NULL改为指向表头结点或开始结点即可。 多重链的循环链表——将表中结点链在多个环上。 用 尾指针tail表示的单循环链表对开始结点a1和终端结点an查找时间都是O (1)。 而表的操作常常是在表的首尾位置上进行,因此,实用中多采用尾指针表示单循环链表。

(转载自网络, 侵删)

 

单向循环链表与单向链表类似(具体参见单向链表), 下面主要提到其不同之处;

下面是循环链表的表头定义:        对比单向链表多了一个tail指针;

typedef struct LinkList{
	ListNode head, *tail;	//	head为虚拟头结点, 其val为链表; tail为尾指针, 指向链表的最后一个元素 
}LinkList;

表头的初始化:        至于为什么list->tail与list->head.next要先初始化成&(list->head)

                        可以感性的理解为循环链表需要首尾相接, 最初的空链表头尾指针重合

                        具体原因与下面各个函数实现均有关, 这里不做详细说明。

LinkList *initLinkList(){	//	初始化一个空链表 
	LinkList *list = (LinkList *)malloc(sizeof(LinkList));
	list->head.next = list->tail = &(list->head);	
	
						//	tail指向链表元素结点中的最后一个结点,
						//	并且满足tail->head = &(tail->head), 初始tail为&(head->head);
	list->head.val = 0;	//	利用虚拟头结点head的val成员存储链表长度, 并在insert及erase中维护;
	return list;
}

由于尾结点的存在, 使得尾插法得以O(1)的时间复杂度实现

链表的插入操作:

bool insert(LinkList *list, int ind, int val){
//	printf("ind = %d, list->length = %d\n", ind, list->head.val);
	if(list == NULL || ind < 0 || ind > list->head.val) return false;	//	每种操作都要进行的合法性判断,	数据结构安全性的保证;
	//	上面利用 || 运算的短路性保证了判断的合法性
	//	利用initListNode()函数生成所需结点 
	ListNode *node = initListNode(val); 
	
	if(ind == list->head.val){	//	list->head.val 即为list->length;	也就是此时插入为尾插 
		list->tail->next = node;	//	先将之前的尾结点的next指向待插入结点 
		list->tail = node;			//	再将链表尾指针指向该节点, 即尾结点的更新 
		list->tail->next = &(list->head);
	}else{	//	否则为非尾插, 不需要更新尾结点 
		ListNode *p = &(list->head);
		while(ind--) p = p->next;
		//	此时p为待插入位置的前一个元素 
		//	p->next为待插入位置的后一个元素 
		node->next = p->next;	//	此行代码与下一行不可交换 
		p->next = node;			//	否则会丢失p->next的引用 
	}
	
	//	至此完成插入, 不要忘记维护链表长度(list->head.val += 1)
	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);
	if(ind == list->head.val - 1){	//	同尾插, 尾部元素的删除也需要维护tail 
		while(ind--) p = p->next;
		
		//	此时p为list->tail的前驱, 也即新尾结点;
		 clearListNode(list->tail);	//	先释放掉尾结点的空间 
		 list->tail = p;			//	然后尾指针前移 
		 p->next = &(list->head);	//	维护链表的循环性 
	}else{	//同非尾插 
		while(ind--) p = p->next;

		ListNode *q = p->next->next;
		clearListNode(p->next);
		p->next = q;
	}
	
	//最后维护链表长度
	list->head.val--;
	return true; 
}

由于list->tail->next != NULL 链表的遍历也与单向链表有所区别

单向循环链表的遍历:

void output(LinkList *list){
	if(list == NULL) return;
	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");
}

下面是完整实现代码:

#include<bits/stdc++.h>
using namespace std;
typedef struct ListNode{
	ListNode *next;
	int val;
}ListNode;

typedef struct LinkList{
	ListNode head, *tail;	//	head为虚拟头结点, 其val为链表; tail为尾指针, 指向链表的最后一个元素 
}LinkList;

ListNode *initListNode(int val){	//	初始化一个权值为val的结点并返回; 
	ListNode *node = (ListNode *)malloc(sizeof(ListNode));
	node->next = NULL;
	
	node->val = val;
	return node;
}

LinkList *initLinkList(){	//	初始化一个空链表 
	LinkList *list = (LinkList *)malloc(sizeof(LinkList));
	list->head.next = list->tail = &(list->head);	
	
						//	tail指向链表元素结点中的最后一个结点,
						//	并且满足tail->head = &(tail->head), 初始tail为&(head->head);
	list->head.val = 0;	//	利用虚拟头结点head的val成员存储链表长度, 并在insert及erase中维护;
	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){
//	printf("ind = %d, list->length = %d\n", ind, list->head.val);
	if(list == NULL || ind < 0 || ind > list->head.val) return false;	//	每种操作都要进行的合法性判断,	数据结构安全性的保证;
	//	上面利用 || 运算的短路性保证了判断的合法性
	//	利用initListNode()函数生成所需结点 
	ListNode *node = initListNode(val); 
	
	if(ind == list->head.val){	//	list->head.val 即为list->length;	也就是此时插入为尾插 
		list->tail->next = node;	//	先将之前的尾结点的next指向待插入结点 
		list->tail = node;			//	再将链表尾指针指向该节点, 即尾结点的更新 
		list->tail->next = &(list->head);
	}else{	//	否则为非尾插, 不需要更新尾结点 
		ListNode *p = &(list->head);
		while(ind--) p = p->next;
		//	此时p为待插入位置的前一个元素 
		//	p->next为待插入位置的后一个元素 
		node->next = p->next;	//	此行代码与下一行不可交换 
		p->next = node;			//	否则会丢失p->next的引用 
	}
	
	//	至此完成插入, 不要忘记维护链表长度(list->head.val += 1)
	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);
	if(ind == list->head.val - 1){	//	同尾插, 尾部元素的删除也需要维护tail 
		while(ind--) p = p->next;
		
		//	此时p为list->tail的前驱, 也即新尾结点;
		 clearListNode(list->tail);	//	先释放掉尾结点的空间 
		 list->tail = p;			//	然后尾指针前移 
		 p->next = &(list->head);	//	维护链表的循环性 
	}else{	//同非尾插 
		while(ind--) p = p->next;

		ListNode *q = p->next->next;
		clearListNode(p->next);
		p->next = q;
	}
	
	//最后维护链表长度
	list->head.val--;
	return true; 
}

bool update(LinkList *list, int ind, int val){
	if(list == NULL || ind < 0 || ind >= list->head.val) return false;	//	同上 
	
	ListNode *p = &(list->head);
	while(ind--) p = p->next;
	p->val = val;
	return true;
}

void output(LinkList *list){
	if(list == NULL) return;
	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");
}
int main(){
	#define MAX_N 100
	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");
		}
		output(list);
	}
	clearLinkList(list);
	printf("%d", list->head.val);
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值