接触过单链表后,我们要引申出循环链表和双向链表,他们比单链表结构更复杂,但是效率更高。
1. 循环链表
循环链表的定义很简单,即在单链表基础上,尾结点的next指针指向第一个结点(注意:不是头结点),这样整个链表就循环了。因为循环,我们可以从任何一个结点遍历到所需结点。
我们照样看下插入、删除算法,这次我们还将加一个遍历算法。首先是插入,循环链表的几点插入和单链表的插入唯一不同的地方在于:如果插入的是尾结点位置,尾节点的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, "山治");
}
编译运行,可以看到当路飞删除后,从山治开始排序,最终还是遍历了整个循环链表中的结点。
2. 双向链表
双向链表即每个结点有两个指针域,一个指向下一个结点,一个指向上一个结点。这样我们就能从任意一个结点就近的遍历到所需结点(不管是往前还是往后)。可以说是功能增强了的循环链表。
首先定义双向链表的结点和链表结构,可以看到结点有两个指针域。
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);
}
编译运行。
本文深入探讨了循环链表和双向链表的数据结构,详细阐述了它们如何比单链表更高效。循环链表的插入、删除和遍历算法被解析,特别强调了尾结点的处理。双向链表则引入了前后指针,允许从任一结点双向遍历。插入和删除操作中,确保了两个指针的正确设置。通过实例展示了这两种链表的使用,如插入、删除和遍历操作,突显了它们的灵活性和实用性。
877

被折叠的 条评论
为什么被折叠?



