带头双向循环链表相关操作

带头双向循环链表

这种结构没有缺陷,除了在定义结点是有一点点麻烦,但操作上却简单很多

带头使得情况简单,不再需要考虑第一个结点是否改变;双向循环使得操作简单,找尾变得十分简单

带了头结点之后,就意味着不用再使用二级指针。因为链表的第一个结点不可能会再被改变,永远只会是头结点;而且插入时考虑链表是否为空、删除时考虑链表中是否只有一个结点都不需要了,因为链表的第一个结点始终是头结点

相关操作
结点的定义
typedef int DSLDataType;

typedef struct DSLNode
{
	struct DSLNode* prev;
	struct DSLNode* next;
	DSLDataType data;
}DSLNode;

prev和next两个指针实现了双向

新建结点
DSLNode* BuyDSLNode(DSLDataType x)
{
	DSLNode* newnode = (DSLNode*)malloc(sizeof(DSLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->prev = NULL;
	newnode->next = NULL;
	newnode->data = x;

	return newnode;
}
初始化链表

这里的初始化需要注意,最初的链表并不是没有结点的,而是有一个哨兵位的头结点

DSLNode* InitDSL()
{
	DSLNode* phead = BuyDSLNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

由于这里涉及第一个结点的改变,可以使用二级指针。但我们为了保持和下面函数的形参一致,选择用返回值的方式来改变一个结点

这里BuyDSLNode(-1)中的值可以随意写,反正也不会用到

注意,尽管链表中只有一个哨兵位的头结点,依然要满足双向循环的要求

打印链表

这里的关键在于循环的结束条件。cur从phead->next的开始走,当cur==phead时结束

void PrintDSL(DSLNode* phead)
{
	assert(phead);

	DSLNode* cur = phead->next;

	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}

	printf("NULL\n");
}

这里断言的原因:按道理来说,带头双向循环链表是不可能为空的,最起码还有个头结点。但就怕有人传参传错了,传了个空链表过来

这之后函数中凡是出现assert(phead)都是因为上述原因

尾插
void DSLPushBack(DSLNode* phead, DSLDataType x)
{
	assert(phead);
    
	//DSLNode* newnode = BuyDSLNode(x);
	//
	进行尾插
	找原本的尾
	//DSLNode* tail = phead->prev;

	新结点与原本的尾相连
	//tail->next = newnode;
	//newnode->prev = tail;

	新结点与头结点相连
	//phead->prev = newnode;
	//newnode->next = phead;

	//DSLInert是插在pos之前,所以这里要传phead
	DSLInsert(phead, x);
}
尾删
void DSLPopBack(DSLNode* phead)
{
	assert(phead);
	assert(phead->next != NULL);//防止链表中只有一个哨兵位头结点

	/*DSLNode* tail = phead->prev;
	DSLNode* tailprev = tail->prev;

	tailprev->next = phead;
	phead->prev = tailprev;
	free(tail);*/

	DSLErase(phead->prev);
}
头插
void DSLPushFront(DSLNode* phead, DSLDataType x)
{
	assert(phead);
	//DSLNode* newnode = BuyDSLNode(x);

	两种写法.个人更倾向于第二种
	第一种,不额外定义指针,直接改变链接关系。但这种写法要先改与后面的链接关系
	///*newnode->next = phead->next;
	//phead->next->prev = newnode;
	//phead->next = newnode;
	//newnode->prev = phead;*/

	第二种,保存原本的第一个结点。这种写法不需要考虑链接关系更改先后
	//DSLNode* first = phead->next;
	//newnode->next = first;
	//first->prev = newnode;
	//newnode->prev = phead;
	//phead->next = newnode;

	DSLInsert(phead->next, x);
}
头删
void DSLPopFront(DSLNode* phead)
{
	assert(phead);
	assert(phead->next != NULL);

	/*DSLNode* first = phead->next;
	DSLNode* second = first->next;

	phead->next = second;
	second->prev = phead;
	free(first);*/

	DSLErase(phead->next);
}
查找
DSLNode* DSLFind(DSLNode* phead, DSLDataType x)
{
	assert(phead);
	DSLNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}
在pos之前插入
void DSLInsert(DSLNode* pos, DSLDataType x)
{
	assert(pos);
	DSLNode* newnode = BuyDSLNode(x);
	DSLNode* posprev = pos->prev;

	posprev->next = newnode;
	newnode->prev = posprev;
	newnode->next = pos;
	pos->prev = newnode;
}

当pos指向第一个结点时可以实现头插

当pos指向phead时可以实现尾插

删除pos
void DSLErase(DSLNode* pos)
{
	assert(pos);

	DSLNode* posprev = pos->prev;
	DSLNode* posnext = pos->next;

	free(pos);
	posprev->next = posnext;
	posnext->prev = posprev;
}
判断链表是否为空
bool DSLEmpty(DSLNode* phead)
{
	assert(phead);
	return phead == phead->next;
}

若phead->next指向自己即为空

返回链表长度
size_t DSLSize(DSLNode* phead)
{
	DSLNode* cur = phead->next;
	size_t size = 0;

	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}

	return size;
}

注意有些代码会将链表长度保存在phead的data中。这样的写法其实是工程经验不足的表现。如果现在的数据类型不再是int,而是char,且链表的长度超过了127,那么就会出现问题了。所以这样写是不好的

销毁链表
void DSLDestroy(DSLNode* phead)
{
	DSLNode* cur = phead->next;

	while (cur != phead)
	{
		DSLNode* curnext = cur->next;
		free(cur);
		cur = curnext;
	}

	free(phead);
}

为保持整体代码形参一致,这里就不传二级指针了。只不过这样就要在外部将phead=NULL

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值