【数据结构】循环链表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