【数据结构】循环链表API及实现

数据类型及API声明

循环链表是指首尾相连的链表,尾部元素指向头部元素,本文使用的模型是尾部元素指向0位置元素,而不是指向链表头。

//链表句柄
typedef void CircleList;

//循环链表的结点
typedef struct CircleListNode CircleListNode;

//循环链表的头结点
typedef struct CircleListHead CircleListHead;

//创建一个循环链表
CircleList* CircleList_Create();

//销毁一个循环链表
void List_Destroy(CircleList* list);

//清空一个循环链表
void CircleList_Clear(CircleList* list);

//返回循环链表的长度
int CircleList_Length(CircleList* list);

//插入一个元素
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos);

//返回一个元素
CircleListNode* CircleList_Get(CircleList* list, int pos);

//按位置删除一个元素
CircleListNode* CircleList_Delete(CircleList* list, int pos);

//按结点值删除一个元素
CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node);

//重置游标指向第一个结点元素
CircleListNode* CircleList_Reset(CircleList* list);

//返回当前游标
CircleListNode* CircleList_Current(CircleList* list);

//游标下移
CircleListNode* CircleList_Next(CircleList* list);

循环链表的API实现

循环链表结点结构体

链表结点包含了指向下一个结点的指针。

struct CircleListNode
{
	CircleListNode* next;
};

表头结构体

表头包含一个链表结构体head,用于指向链表的0号结点,包含一个游标slider,用于指示当前结点,包含一个长度length,用于指示链表元素个数

struct CircleListHead
{
	CircleListNode	head;
	CircleListNode* slider;
	int				length;
};

创建循环链表并返回循环链表句柄

  • 函数功能:创建一个循环链表并返回循环链表句柄,其实就是创建一个表头
  • 函数参数:无,链式结构不需要提前分配内存,而线性表的顺序结构需要提前分配内存
  • 函数返回:表头
CircleList* CircleList_Create()
{
	CircleListHead* pHead = NULL;
	pHead = (CircleListHead*)malloc(sizeof(CircleListHead));
	if (pHead == NULL)
	{
		printf("head = (CircleListHead*)malloc(sizeof(CircleListHead)) err\n");
		return NULL;
	}
	memset(pHead, 0, sizeof(CircleListHead));
	return pHead;
}

销毁循环链表

  • 函数功能:销毁循环链表就是释放表头资源,销毁循环链表前需要逐个删除链表结点
  • 函数参数:循环链表的句柄
  • 函数返回:无
void CircleList_Destroy(CircleList* list)
{
	CircleListHead* pHead = NULL;
	if (list == NULL)
	{
		printf("list == NULL\n");
		return;
	}
	pHead = (CircleListHead*)list;
	free(pHead);
}

清空循环链表

  • 函数功能:返回到循环链表刚创建的状态,即表头指针域指向NULL,长度置为0
  • 函数参数:循环链表的句柄
  • 函数返回:无
void CircleList_Clear(CircleList* list)
{
	CircleListHead* pHead = NULL;
	if (list == NULL)
	{
		printf("list == NULL\n");
		return;
	}
	pHead = (CircleListHead*)list;
	pHead->head.next = NULL;
	pHead->slider = NULL;
	pHead->length = 0;
}

返回循环链表元素个数

  • 函数功能:获取循环链表长度,即结点个数
  • 函数参数:循环链表句柄
  • 函数返回:节点个数
int CircleList_Length(CircleList* list)
{
	CircleListHead* pHead = NULL;
	if (list == NULL)
	{
		printf("list == NULL\n");
		return -1;
	}
	pHead = (CircleListHead*)list;
	return pHead->length;
}

插入元素

  • 函数功能:在循环链表中插入一个元素,被插入的元素已经在主调函数分配好内存,只需把链表结点CircleListNode串接起来即可
  • 函数参数:list 循环链表句柄 node 待插入元素 pos 插入位置
  • 函数返回:成功返回0
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos)
{
	int i = 0;
	CircleListHead* pHead = NULL;
	CircleListNode* pCurrent = NULL;
	if ((list == NULL) || (node == NULL) || (pos < 0))
	{
		printf("err: (list == NULL) || (node == NULL) || (pos < 0)\n");
		return -1;
	}
	pHead = (CircleListHead*)list;
	pCurrent = &(pHead->head);
	for (i = 0; i < pos; i++)
	{
		pCurrent = pCurrent->next;
	}
	node->next = pCurrent->next;
	pCurrent->next = node;
	//第一次插入要设置游标位置
	if (CircleList_Length(list) == 0)
	{
		pHead->slider = node;
	}
	pHead->length++; //必须要先加1在判断是不是头插法,
					 //因为插入后长度就变了,如果函数在最后加1,那么 pHead->length-1就不是尾部元素了
					 //那样的话CircleList_Get(list, pHead->length - 1)获取的是尾部元素的上一个元素
	//如果是在0号位置插入(即头插法),pos为0,pCurrent不会移动
	if (pCurrent == (&(pHead->head)))
	{
		//获取尾部元素
		CircleListNode* pLast = CircleList_Get(list, pHead->length - 1);
		//node->next = pCurrent->next;
		//pCurrent->next = node;
		//形成环
		pLast->next = node;
	}
	return 0;
}

插入元素示意图

(1)普通位置插入

 (2)尾部插入

 (3)首次插入

 (4)头部插入

 根据上面的四种图片分析,前三种情况下,插入和普通链表的插入一样,借助一个辅助指针变量pCurrent,pCurrent为当前指针位置,初始状态他应该指向表头,当我们需要在pos号位置插入元素时,就将pCurrent后移pos位,比如要在2号位置插入,将pCurrent后移2次,那么pCurrent将指在1号结点处,而1号结点的next域指向2号结点,这时只需要把待插入结点插在pCurrent后面即可完成插入。但是第四种情况,也就是在0号位置插入时(头插法),因为循环链表要形成一个环,所以此时应该获取尾部结点,把尾部结点的指针域指向头部元素,也就是新插入的node元素,来形成一个循环。

返回指定位置元素

  • 函数功能:获取一个指定位置的元素
  • 函数参数:list 循环链表句柄 pos 要获取的元素标号
  • 函数返回:获取的结点元素
CircleListNode* CircleList_Get(CircleList* list, int pos)
{
	int i = 0;
	CircleListHead* pHead = NULL;
	CircleListNode* pCurrent = NULL;
	if ((list == NULL) || (pos < 0))
	{
		printf("err: (list == NULL) || (pos < 0)\n");
		return NULL;
	}
	pHead = (CircleListHead*)list;
	pCurrent = &(pHead->head);
	for (i = 0; i < pos; i++)
	{
		pCurrent = pCurrent->next;
	}
	return pCurrent->next;
}

删除结点并返回被删除结点

  • 函数功能:按位置删除一个结点并返回该结点
  • 函数参数:list 循环链表句柄 pos 要删除的链表结点位置
  • 函数返回:被删除的结点
CircleListNode* CircleList_Delete(CircleList* list, int pos)
{
	int i = 0;
	CircleListHead* pHead = NULL;
	CircleListNode* pCurrent = NULL;
	CircleListNode* pLast = NULL;
	CircleListNode* pTemp = NULL;
	if ((list == NULL) || (pos < 0))
	{
		printf("err: (list == NULL) || (pos < 0)\n");
		return NULL;
	}
	pHead = (CircleListHead*)list;
	pCurrent = &(pHead->head);
	for (i = 0; i < pos; i++)
	{
		pCurrent = pCurrent->next;
	}
	//如果删除0位置元素,需要获取尾部元素
	if (pos == 0)
	{
		pLast = CircleList_Get(list, pHead->length - 1); //和插入相反,应该先获取尾部元素,再lenth--
	}
	//先获取pLast,再进行删除操作,否则获取的就不是本来的尾部元素了
	pTemp = pCurrent->next;
	pCurrent = pTemp->next;
	pHead->length--;
	if (pLast != NULL)
	{
		//pLast == NULL说明本身链表为空,只有一个链表头,0号位置为NULL
		//不为空,就把尾结点指针域指向现在的0号结点
		pLast->next = pTemp->next;
	}
	if (pHead->length == 0)
	{
		//删除一个结点后,链表为空
		pHead->head.next = NULL;
		pHead->slider = NULL;
	}
	if (pTemp == pHead->slider)
	{
		//游标元素被删,游标下移
		pHead->slider = pTemp->next;
	}
	return pTemp;
}

删除元素示意图

(1)普通位置删除

(2)尾部删除 

(3)头部删除 

 对于前两种情况,删除结点需要借助两个辅助指针,一个pCurrent指示当前结点,其作用类似于插入节点中的pCurrent,另一个pTemp用于保存被删除结点,删除时,只需要pCurrent的指针域指向pTemp指针域的内容即可完成删除,和普通链表删除元素的操作一样。对于第三中情况,需要借助第三个辅助指针pLast,因为要形成环,所以删除0号结点后,要求出原来的尾部元素(删除元素前的尾部元素,而不是删除元素后的尾部元素,这是有本质区别的,详情可见代码中的注释),把原来的尾部元素,指向删除后的0号元素,也就是删除前的1号元素,所以需要一个辅助指针pLast来指向原来的尾部元素。

按结点删除元素

  • 函数功能:按结点删除一个结点并返回该结点
  • 函数参数:list 循环链表句柄 node要删除的链表结点元素
  • 函数返回:被删除的结点
CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node)
{
	int i = 0;
	CircleListHead* pHead = NULL;
	CircleListNode* pCurrent = NULL;
	CircleListNode* pTemp = NULL;
	if ((list == NULL) || (node == NULL))
	{
		printf("err: (list == NULL) || (node == NULL)\n");
		return NULL;
	}
	pHead = (CircleListHead*)list;
	pCurrent = &(pHead->head);
	for (i = 0; i < pHead->length; i++)
	{
		if (pCurrent == node)
		{
			pTemp = pCurrent;
			break;
		}
		pCurrent = pCurrent->next;
	}
	if (pTemp != NULL)
	{
		CircleList_Delete(list, i - 1);
	}
	return pTemp;
}

重置游标

  • 函数功能:将游标指向第一个元素
  • 函数参数:list 循环链表句柄 
  • 函数返回:游标位置
CircleListNode* CircleList_Reset(CircleList* list)
{
	CircleListHead* pHead = NULL;
	if (list == NULL)
	{
		printf("list == NULL\n");
		return NULL;
	}
	pHead = (CircleListHead*)list;
	pHead->slider = pHead->head.next;
	return pHead->slider;
}

返回当前游标

  • 函数功能:获取游标当前指向的结点
  • 函数参数:list 循环链表句柄 
  • 函数返回:游标位置
CircleListNode* CircleList_Current(CircleList* list)
{
	if (list == NULL)
	{
		printf("list == NULL\n");
		return NULL;
	}
	return ((CircleListHead*)list)->slider;
}

游标下移

  • 函数功能:获取游标当前指向的结点,并将游标下移
  • 函数参数:list 循环链表句柄 
  • 函数返回:下移前的游标位置
CircleListNode* CircleList_Next(CircleList* list)
{
	CircleListNode* pTemp = NULL;
	if (list == NULL)
	{
		printf("err: (list == NULL)\n");
		return NULL;
	}
	pTemp = ((CircleListHead*)list)->slider;
	((CircleListHead*)list)->slider = pTemp->next;
	return pTemp;
}

测试函数

#include "CircleList.h"

typedef struct MyData
{
	CircleListNode* node;
	int data;
}MyData;

int main()
{
	int i = 0;
	int ret = 0;
	MyData m[10];
	CircleList* mList = NULL;

	for (i = 0; i < 10; i++)
	{
		m[i].data = i + 1;
	}

	mList = CircleList_Create();
	if (mList == NULL)
	{
		ret = -1;
		printf("CircleList_Create() err: %d\n", ret);
		return -1;
	}

	printf("链表长度:");
	for (i = 0; i < 10; i++)
	{
		ret = CircleList_Insert(mList, (CircleListNode*)&m[i], 0);
		if (ret != 0)
		{
			ret = -2;
			printf("CircleList_Insert() err: %d\n", ret);
			return ret;
		}
		printf("%d ", CircleList_Length(mList));
	}
	printf("\n");

	for (i = 0; i < 20; i++)
	{
		if ((i == 0) || (i == 10))
		{
			printf("\n循环链表内容:");
		}
		printf("%d ", ((MyData*)(CircleList_Get(mList, i)))->data);
	}

	for (i = 0; i < 10; i++)
	{
		CircleList_Delete(mList, 0);
	}

	printf("\n当前长度:%d \n", CircleList_Length(mList));
	CircleList_Destroy(mList);

	system("pause");
	return ret;
}

具体代码资源已上传,可在下面的链接免费下载

循环链表API及实现(关键步骤详细代码注释)https://download.csdn.net/download/qq_43471489/85045405

posted @ 2022-03-30 06:45  Mindtechnist  阅读(2)  评论(0编辑  收藏  举报  来源