C -- 循环链表

循环链表

什么是循环链表

  • 单链表(仅含一个指针域,每个节点存在直接后继)
  • 尾指向头(形成一个循环)

单链表的话只能从链表头开始遍历,才能访问到所有的节点,而循环链表,由于形成了一个圈,从任意一个节点出发我们都可以访问到所有的元素。

所以定义循环链表类型时,我们可以用一个指针标识头,一个指针标识尾,在之后的操作中添加一条语句,将tail->next = head; 添加这样一条语句,我们即可完成一个循环链表的构建。不过感觉这样做好像有点没意思?。。。

其实用一个指针标识尾就可以了,不过这样做的话必须添加一个头结点,这个头结点中不含数据,它只指向真正的表头(注意是头结点,不是头指针,为什么添加?方便表示。)。也就是说无论链表是否为空,这个头结点始终是存在的,这是一个标识头结点,如果链表为空,那么头结点的next就要指向自己,这样代表一个空的循环链表。而如果没有头结点,空循环链表其实是没法表示的,只能用某种特定的描述代表一个空循环链表,(比如说像普通单链表一样,头尾指针相等都为空,来代表空循环链表;头尾指针不等,那么这个循环链表必定不空,那么需要将此表表示成循环链表,链接尾和头)

特点

循环链表,区别于单链表的最大特点:尾巴指向头,形成一个圈。

实现(添加头结点法)

结点类型以及循环链表类型定义及描述:

typedef struct _node{
	ElemType data;
	struct _node * next;
}Node;

typedef struct _looplist{
	//循环链表只需要标识其尾部即可,因为头部其实就是rear->next->next,因为包含一个头结点,头结点的下一个才是真正意义的链表头
	Node * rear;	
	int size;
}LoopList;

一、初始化

由于增加了头结点,也就意味着我们初始化的时候需要先申请一个头结点:

void InitLpList(LoopList * lplist)
{
	//申请头结点
	Node * pnew = (Node * pnew) malloc(sizeof(Node));
	//自己指向自己
	pnew->next = pnew;
	//更新链表信息,此时尾指针指向的是头结点
	lplist->rear = pnew;
	lplist->size = 0;
}

二、是否为空

相应的是否为空循环链表的判断方法也有变化:

bool LpListIsEmpty(LoopList * lplist)
{
	return lplist->rear->next == lplist->rear;
	//也可以return lplist->size == 0;
}

三、添加

添加的话,还是根据单链表的思路进行,添加第一个,添加后续:

bool AddToLpList(LoopList * lplist, ElemType data)
{
	Node * pnew = (Node *)malloc(sizeof(Node));
	if(pnew == NULL)
		return false;
	CopyToNode(pnew,data);
	
	pnew->next = lplist->rear->next;//无论是第一个还是后续的,新节点next指向头结点就可以了
	
	if(LpListIsEmpty(lplist))	//添加第一个
		lplist->rear->next->next = pnew;//头结点指向第一个新节点
	else	//添加后续的
		lplist->rear->next = pnew;//添加到尾
		
	lplist->rear = pnew;//无论是第一个还是后续的添加,都需要更新链表尾
	return true;
}

四、插入

也类似于单链表(插入前我们先要判断是否是一个空循环链表):

  • 插在第一个位置的话实际上就是插在头结点之后;
  • 插在最后一个位置的话,需要将这个结点的next指向头结点,同时需要更新尾巴,其实就是添加一个结点到非空循环链表的操作(不妨偷个懒,当用户要插在最后一个位置,直接调用AddToLpList()函数);
  • 根据用户指定的位置,插在中间的话,循环链表的rear信息不需要更改,只需找到用户指定位置,插入即可;
bool InsertLpList(LoopList * lplist, int index, ElemType data)
{
	//先判断用户的index是否合法
	if(index < 1 || index > (lplist->size + 1))
		return false;
	
	if(index == lplist->size + 1)//插在尾
	{
		AddToLpList(lplist,data);//就是添加操作
		lplist->size++;
		return true;
	}

	Node * pnew = (Node *) malloc(sizeof(Node));
	//未分配到,提前返回
	if(pnew == NULl)
		return false;
	CopyToNode(pnew,data);
	
	if(index == 1 && lplist->size >= 1)//插在头
	{
		pnew->next = lplist->rear->next->next;//指向之前的第一个结点	
		lplist->rear->next->next = pnew;//头结点指向新的第一个节点
	}else if(index > 1 && index < lplist->size + 1){//插在中间
		//找到要插入的位置的前一个指针
		Node * temp;
		int i = 1;
		for(temp = lplist->rear->next->next;i != index - 1;temp = temp->next)
			continue;
		pnew->next = temp->next;
		temp->next = pnew;
	}

	lplist->size++;
	
	return true;
}

五、删除

也差不多像链表一样考虑:

  • 删除的是否是最后一个元素,是的话更新链表为空;
  • 删除的如果不是最后一个,考虑删除的是第一个位置,或者中间位置,或者最后的尾;相应的操作也差不多类似于插入的步骤;

这里就省略这个删除的实现了。。。写的有点多,其实也就和单链表差不多的,当加深下印象吧。。。

总结

主要还是考虑问题的思维方式,只要理解了精髓,具体实现都是可以完成的。只不过有的人实现可能更简洁(如果定义类型的方式上有优势,那么其他部分就相应的会轻松很多,也会更简洁),有的麻烦。。(比如说我。。),但是核心都是差不多的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值