循环char 指针_三、循环链表和双向链表

本文深入探讨了循环链表和双向链表的数据结构,详细阐述了它们如何比单链表更高效。循环链表的插入、删除和遍历算法被解析,特别强调了尾结点的处理。双向链表则引入了前后指针,允许从任一结点双向遍历。插入和删除操作中,确保了两个指针的正确设置。通过实例展示了这两种链表的使用,如插入、删除和遍历操作,突显了它们的灵活性和实用性。
摘要由CSDN通过智能技术生成

接触过单链表后,我们要引申出循环链表和双向链表,他们比单链表结构更复杂,但是效率更高。

1. 循环链表

循环链表的定义很简单,即在单链表基础上,尾结点的next指针指向第一个结点(注意:不是头结点),这样整个链表就循环了。因为循环,我们可以从任何一个结点遍历到所需结点。

b7c7dc612e06d41beea7f7583cbd1a52.png

我们照样看下插入、删除算法,这次我们还将加一个遍历算法。首先是插入,循环链表的几点插入和单链表的插入唯一不同的地方在于:如果插入的是尾结点位置,尾节点的next指针必须指向第一个结点,而不是单链表的指向NULL,而如果插入的是第一个结点位置,那也必须将尾节点的next指针指向插入结点。

void InsertCirLinkList(CircularLinkList* cirLinkList, int pos, ElementType* element)
{
	CirLinkListNode* insertNode = (CirLinkListNode*)malloc(sizeof(CirLinkListNode));//定义插入结点,动态分配其内存
	insertNode->data = *element;//初始化插入结点的数据域
	insertNode->next = NULL;//初始化插入结点的指针域

	if (pos == 1)//如果插入的是第一个结点位置
	{
		//如果链表为空(插入的时候,链表没有其他结点)
		if (cirLinkList->length == 0)
		{
			cirLinkList->next = insertNode;	//头结点指向插入结点
			insertNode->next = insertNode;	//最后一个结点指向第一个结点(因为只有一个结点,所以自己指向自己)
			cirLinkList->length++;	//循环链表长度加一

			return;
		}
		//如果链表不为空(插入前,还有其他结点)
		else
		{
			CirLinkListNode* lastNode = cirLinkList->next;	//定义尾结点
			insertNode->next = cirLinkList->next;	//插入结点指向第二个结点(第二个结点原本为第一个结点)
			cirLinkList->next = insertNode;	//头指针指向插入结点
			for (int i = 0; i < cirLinkList->length; i++)//遍历链表
			{
				lastNode = lastNode->next;//遍历获得尾结点地址
			}
			lastNode->next = insertNode;	//尾节点指向插入结点(即最后一个结点指向第一个结点)
			cirLinkList->length++;	//循环链表长度加一

			return;
		}		
	}
	//如果插入的不是第一个结点位置
	else if (pos > 1)
	{
		CirLinkListNode* currentNode = cirLinkList->next;//初始化当前结点
		
		for (int i = 1; i < pos - 1; i++)//pos -1是重点
		{
			currentNode = currentNode->next;//遍历获得当前结点
		}
		insertNode->next = currentNode->next;	//插入结点指向当前结点原先指向的结点
		currentNode->next = insertNode;	//当前结点指向插入结点
		cirLinkList->length++;	//循环链表长度加一
		//如果插入的是最后一个结点
		if(pos == cirLinkList->length)
		{
			insertNode->next = cirLinkList->next;//插入结点指向第一个结点(最后一个结点指向第一个结点)

			return;
		}

		return;
	}
	//如果输入pos为负值或者大于链表长度,则提示输入无效
	else
	{
		printf("输入的pos值不存在");
		return;
	}
}

然后来看下删除算法,同理,如果删除的是尾结点,需要将前一个结点指向第一个结点,而如果删除的是第一个结点,则需要将尾结点指向第二个结点。

ElementType DelCirLinkListElement(CircularLinkList* cirLinkList, int pos)
{
	ElementType delData;	//删除结点的数据
	CirLinkListNode* delNode = cirLinkList->next;//删除结点
	//如果删除结点是第一个
	if (cirLinkList && pos == 1)
	{
		if (cirLinkList->length == 1)//循环链表如果只有一个结点
		{
			delData = delNode->data;//保存删除结点数据
			cirLinkList->next = NULL;//因为删除后就没结点了,这里必须指向空
			free(delNode);
			cirLinkList->length--;

			return delData;
		}
		else//循环链表不止一个结点
		{
			CirLinkListNode* lastNode = cirLinkList->next;
			for (int i = 1; i < cirLinkList->length; i++)
			{
				lastNode = lastNode->next;
			}

			delData = delNode->data;//保存删除结点数据
			cirLinkList->next = delNode->next;//头指针指向删除结点的下一个结点
			lastNode->next = delNode->next;	//尾指针指向第一个指针
			free(delNode);
			cirLinkList->length--;

			return delData;
		}	
	}
	//如果删除的结点不是第一个
	else if (cirLinkList && pos > 1)
	{
		CirLinkListNode* preNode = cirLinkList->next;//初始化删除节点的上一个结点
		for (int i = 1; i < pos-1; i++)//因为是上一个结点,所以是pos-1
		{
			preNode = preNode->next;//遍历得到删除节点的上一个结点
		}
		if (pos == cirLinkList->length)//如果删除的是最后一个结点
		{
			delNode = preNode->next;//上一个结点的next即删除结点
			delData = delNode->data;//保存删除结点数据
			preNode->next = cirLinkList->next;//将上一个结点的指针指向第一个结点(因为是循环链表,最后一个指向第一个)
			free(delNode);
			cirLinkList->length--;

			return delData;
		}
		else if(pos < cirLinkList->length)//如果删除的不是最后一个结点
		{
			delNode = preNode->next;//上一个结点的next即删除结点
			delData = delNode->data;//保存删除结点数据
			preNode->next = delNode->next;	//上一个结点的指针指向删除结点的下一个结点
			free(delNode);
			cirLinkList->length--;

			return delData;
		}
		
	}
	else//如果pos超出链表范围
	{
		printf("没有这个结点,删除失败!n");
		ElementType failed = { -999999, "没有这个结点" };

		return failed;
	}
}

最后来看下遍历循环链表,首先我们找到所需结点(这里的遍历不是循环的),然后从这个结点开始遍历整个链表,因为循环指向,所以无论从哪个结点开始遍历,我们最终都能获得整个循环链表的所有结点。

CirLinkListNode* FindCirLinkListElement(CircularLinkList* cirLinkList, char* name)
{
	CirLinkListNode* node = cirLinkList->next;
	for (int i = 0;  i < cirLinkList->length; i++)
	{
		if (node->data.name != name)
		{
			node = node->next;
		}
		else
		{			
			return node;
		}		
	}	
	return NULL;//没找到结点返回空
}

void LoopCirLinkList(CircularLinkList* cirLinkList, char* name)
{
	CirLinkListNode* node = FindCirLinkListElement(cirLinkList, name);
	if (node)
	{
		do
		{
			printf("%dt%sn", node->data.id, node->data.name);
			node = node->next;
		} while (node->data.name != name);
	}
	else
	{
		printf("头结点为空,不能遍历!n");
	}		
}

照例实现下打印函数。

void PrintCirLinkList(CircularLinkList* cirLinkList)
{
	CirLinkListNode* node = cirLinkList->next;//初始化第一个结点
	if (!node)
	{
		cirLinkList->length = 0;
		printf("循环链表为空!n");
		return;
	}		
	
	for (int i = 0; i < cirLinkList->length; i++)
	{
		printf("%dt%sn", node->data.id, node->data.name);
		node = node->next;
	}	
}

测试,先初始化循环链表并打印显示,然后删除1号结点(路飞),接着找到3号结点(山治),从3号结点开始遍历整个链表并打印。

void TestCircularLinkList()
{
    CircularLinkList circularLinkList;
    circularLinkList.length = 0;
    circularLinkList.next = NULL;
    InitCirLinkList(&circularLinkList, sizeof(dataArray) / sizeof(dataArray[0]), dataArray);
    PrintCirLinkList(&circularLinkList);
    ElementType delElement0 = DelCirLinkListElement(&circularLinkList, 1);
    printf("删除的是:%dt%sn", delElement0.id, delElement0.name);
    CirLinkListNode* findNode = FindCirLinkListElement(&circularLinkList, "山治");
    printf("从%s处重新排序:n", findNode->data.name);
    LoopCirLinkList(&circularLinkList, "山治");
}

编译运行,可以看到当路飞删除后,从山治开始排序,最终还是遍历了整个循环链表中的结点。

fba2f9a3f9803f896e55525c988b4d41.png

2. 双向链表

双向链表即每个结点有两个指针域,一个指向下一个结点,一个指向上一个结点。这样我们就能从任意一个结点就近的遍历到所需结点(不管是往前还是往后)。可以说是功能增强了的循环链表。

195de7c414933b36b124b444ff6086d6.png

首先定义双向链表的结点和链表结构,可以看到结点有两个指针域。

typedef struct DoubleLinkNode
{
	ElementType data;	//数据域
	struct DoubleLinkNode* previous;	//指向前一个结点的指针
	struct DoubleLinkNode* next;//指向下一个结点的指针
}DoubleLinkNode;

typedef struct DoubleLinkList
{
	DoubleLinkNode* next;//头指针
	int length;//双向链表的长度
}DoubleLinkList;

然后是插入算法,和循环链表不同的是,每个结点都需要设置“previous指针”

void InsertDouLinkElement(DoubleLinkList* doubleLinkList, int pos, ElementType element)
{
	//创建并初始化插入结点
	DoubleLinkNode* node = (DoubleLinkNode*)malloc(sizeof(DoubleLinkNode));
	node->data = element;
	node->next = NULL;
	node->previous = NULL;
	
	if (pos == 1)//插入的是第一个结点
	{
		if (!doubleLinkList || doubleLinkList->length == 0)
		{
			doubleLinkList->next = node;//头指针指向第一个结点
			doubleLinkList->length++;
			return;
		}
		else
		{
			node->next = doubleLinkList->next;
			doubleLinkList->next = node;//头指针指向第一个结点
			doubleLinkList->length++;
			return;
		}
	}
	else if (pos > 1 && pos <= doubleLinkList->length+1)//插入的位置在第二个到最后一个
	{
		DoubleLinkNode* currNode = doubleLinkList->next;//初始化第一个结点
		//从第二个结点开始遍历
		for (int i = 1; i < pos - 1; i++)
		{
			currNode = currNode->next;
		}

		if (pos != doubleLinkList->length+1)//插入的是中间结点
		{
			node->next = currNode->next;//插入结点后向指针指向下一个结点
			node->previous = currNode;//插入节点的前向指针指向它的上一个结点
			currNode->next = node;//前一个结点的后向指针指向插入结点
			node->next->previous = node;//插入结点的下一个结点的前向指针指向插入结点
			doubleLinkList->length++;
			return;
		}
		else//插入的是最后一个结点
		{
			node->next = NULL;
			node->previous = currNode;
			currNode->next = node;
			doubleLinkList->length++;
			return;
		}	
	}
	else
	{
		printf("插入的结点不合法!n");
	}
}

接着删除算法,同样注意两个指针的设置。

void DelDouLinkElement(DoubleLinkList* doubleLinkList, int pos)
{
	DoubleLinkNode* delNode = doubleLinkList->next;	//初始化删除结点为第一个结点
	for (int i = 1; i < pos; i++)//遍历pos位置上的结点
	{
		delNode = delNode->next;
	}
	if (pos == 1)//删除的是第一个结点
	{
		if (doubleLinkList->length > 1)//不止一个结点
		{	
			delNode->next->previous = NULL;
			doubleLinkList->next = delNode->next;
			free(delNode);
			doubleLinkList->length--;
			return;
		}
		else//就一个结点
		{
			doubleLinkList->next = NULL;
			free(delNode);
			doubleLinkList->length--;
			return;
		}
	}
	else if (pos > 1 && pos < doubleLinkList->length)//删除的是中间结点
	{
		
		delNode->previous->next = delNode->next;
		delNode->next->previous = delNode->previous;
		free(delNode);
		doubleLinkList->length--;
		return;
	}
	else if(pos == doubleLinkList->length)//删除的是最后一个结点
	{
		delNode->previous->next = NULL;
		free(delNode);
		doubleLinkList->length--;
		return;
	}
	else//删除不合法
	{
		printf("没有该结点,删除失败!n");
	}
}

最后测试下,首先初始化双向链表,接着插入一个996编号的结点,最后将这个996结点删除。

void TestDoubleLinkList()
{
    DoubleLinkList douLinkList;
    douLinkList.length = 0;
    douLinkList.next = NULL;
    InitDouLinkList(&douLinkList, sizeof(dataArray) / sizeof(dataArray[0]), dataArray);
    printf("初始化:n");
    PrintDouLinkList(&douLinkList);
    InsertDouLinkElement(&douLinkList, 4, { 996, "插入的测试" });
    printf("插入后:n");
    PrintDouLinkList(&douLinkList);
    DelDouLinkElement(&douLinkList, 4);
    printf("删除后:n");
    PrintDouLinkList(&douLinkList);
}

编译运行。

3f47e9d21acb57ba252c8443a790079b.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值