主要讲解释循环链表的一些定义和具体的操作。
一、基本定义:
1.单链表的局限:不可以循环。
2.循环链表的定义:将单链表中最后一个元素的next指向第一个元素。
3.循环链表拥有单链表的所有操作。
4.循环链表的插入和单链表插入的差别:
单链表的插入是 node->next = NULL; header->next = node;
循环链表的插入是:node->next = node->next; header->next = node;
5.单链表改制成循环链表需要注意的地方
(1)在插入函数中,需要增加一个if判断,判断length是否为0,如果length为0,那么要把node->next = node;
(2)合法性的检测时候不必要再检测pos是否小于length。只需要检测pos是否大于等于0。
(3)删除其他元素和单链表是一样的,特殊地方在删除第一个元素(具体的看程序中的代码注释),同时合法性 的检测中也不再需要pos的位置是否小于length的检测了。
二、基本定义(循环链表)
1.游标:在循环链表中定义一个当前指针,这个当前指针被称作为游标。游标是循环链表重要的一部分。
2.循环链表不同于单链表的操作:
(1)获取当前游标指向的数据元素
可以直接指定某一个数据元素,而不需要寻找我们要删除的元素的下标。
(2)将游标重置指向链表中的第一个元素
(3)将游标移动指向链表中的下一个元素
(4)直接指定删除链表中的某一个元素
三、单链表改写成为循环链表的基本操作
1.在插入函数中安全性的检测不必要再判断pos是否超过了list->length,因为这个时候链表是循环链表,所以没有具体的长度限制,不过我们要加入的判定是list->node != NULL;这个的目的是要判断插入的元素不可以为空。
具体实现代码如下:
int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
2.在插入函数中for循环的代码如下:
for(i=0; (i<pos) && (current->next != NULL); i++)
{
current = current->next;
}
这里要详细的说一下,首先这个current,这个current是由CircleListNode* current = (CircleListNode*)sList;这个语句得来的。目的是为了定义一个临时变量。
针对for循环的条件current->next != NULL;这里对current->next的判断,假如当pos=0的时候,那么将不会指向for循环。当pos比0大的时候,首先要找出pos位置,也就是i > pos的作用,假如我们的pos很大,但是我们的链表长度只有 20个,那么就要进行一下这个判断,这样就直接插在链表的末尾,这就是current->nex的作用。
然后再来说一下这个for循环的执行过程吧,这里深刻的体会到了一个程序员查数的时候习惯性的从0开始的原因了,这种思维要锻炼,需要大量的代码来喂。
for循环是从i= 0开始执行的,假如i = 3,那么for循环执行了三次,i= 0, i = 1, i = 2这里的current可以当成一个中间变量,第一步,head里的next给了current->next;第二步,第0个元素的next给了current,也就是head->next->next;第三步把第1个元素的next给了current,也就是head->next->next->next;这样这个时候current是指向第2个元素的next;而我们要的是第3个元素,所以最后的ret返回的应该是找的current->next指向的内容。也就是第2个数据结点的指针。
这样接下来的两个常规性的插入操作也就可以很容易的理解了。
node->next = current->next;
current->next = node;
我们上面说的都是常规性的插入,而且因为是循环链表,所以当我们使用尾插法的时候也不需要考虑太多的因素。
那么当我们从链表的头部开始插入元素的时候假如链表不是空的,那么我们可以执行上面的过程就可以成功插入元素了,但是假如我们的链表在我们插入之前一个元素都没有呢?我们插入元素后必须要执行node ->next = node;这个操作的目的就是在链表刚刚形成的时候就把循环构成。
具体的代码如下:
if( sList->length == 0 )
{
/*构建出循环部分,是这个圈形成*/
sList->slider = node;
node->next = node;
}
同时这里我还有一个疑问:我想问的是(i<pos) && (current->next != NULL)这一句不是已经判断了插入的元素是否为第一个元素了么?这两个判断不冲突么?其实这两个判断并不冲突,第一个判断current->next != NULL的主要作用是:这里对current->next的判断,假如当pos=0的时候,那么将不会指向for循环。而第二个判断的主要作用是:当插入第一个元素的时候就将链表形成,也就是在插入第一个元素的时候就将尾元素指向第一个元素,而且每一次第一个元素的位置都是在不断改变的。
具体的插入函数代码如下:
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
int i = 0;
if( ret )
{
CircleListNode* current = (CircleListNode*)sList;
for(i=0; (i<pos) && (current->next != NULL); i++)
{
current = current->next;
}
/*常规性的插入操作*/
node->next = current->next;
current->next = node;
if( sList->length == 0 )
{
/*构建出循环部分,是这个圈形成*/
sList->slider = node;
node->next = node;
}
sList->length++;
}
return ret;
}
头插法建表的示意图:
同时,头插法建表还存在着问题,因为假如我们的链表中有了一个或者若干个数据节点,那么这一个或者若干个数据节点就会形成一个循环链表,然后外面由链表头在连接,当我们使用头插法插入一个数据节点的时候,我们只执行了把数据节点插入到头节点和原来的链表第一个数据节点之间,这样我们的链表再一次变成了Q型,所以我们需要加入一个条件性的检测。在插入函数中加入的条件性检测代码如下:
if (current == (CircleListNode*)sList)
{
CircleListNode* last = CircleList_Get(sList, sList->length-1);
last->next = node;
}
假如不加入这个安全性的检测,那么我们在打印链表的时候会发现打印出来的是一个死循环的链表。
3.在get函数还有两点需要注意
(1)get这个函数的返回值是一个指向我们想要获取的数据节点的指针。
(2)get函数的最后有一条:ret = current->next,我们的整体代码如下:
CircleListNode* CircleList_Get(CircleList* list, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
int i = 0;
/*判定同上*/
if( (sList != NULL) && (pos >= 0) )
{
CircleListNode* current = (CircleListNode*)sList;
for(i=0; i<pos; i++)
{
current = current->next;
}
ret = current->next;
}
return ret;
}
在for循环中我们已经找到了一个current。,那么我们为什么还要进行ret = current ->next。这个一又回到了老问题了,这个时候的current其实是pos-1个(因为pos是从0开始的) 而我们要找的是pos个,所以实际返回值ret应该是ret = current->next;而且这个pos的值其实是单链表中的pos,所以在使用pos进行查询元素的时候查询的一定是按顺序存储的位置,不要去想着循环
4.在删除链表元素的函数中,首先定义了两个CircleListNode*类型,first和last,主要是为了对删除第一个元素时候的判定(CircleListNode*)CircleList_Get(sList, sList->length - 1);这是为了判定最后一个元素的位置然后在接下来的步骤里更改最后一个元素所指向的元素。
代码如下:
CircleListNode* first = sList->header.next;
CircleListNode* last = (CircleListNode*)CircleList_Get(sList, sList->length - 1);
同时,在老唐的代码中他对删除的元素是否是第一个元素进行了判定,
if( first == ret )
{
sList->header.next = ret->next;
last->next = ret->next;
}
如果是第一个元素,那么就头指向原来第一个元素的指向,同时,把最后一个元素指向原来第一个元素的指向。last->next = ret->next;这一句是多余的,因为current->next = ret->next;已经完成了这个功能。而sList->header.next = ret->next;这一句是必要的。
当我们没有进行循环直接删除第一元素的时候,为了保证链表仍然是一个完整的链表,而且是正规的(尾指向头),那么我们需要进行如上判定。
当我们没有进行循环而删除第一个元素的时候,而且删除这个元素以后链表中将不再又元素,那么我们需要进行如下的判定和操作:
if( sList->length == 0 )
{
sList->header.next = NULL;
sList->slider = NULL;
}
以上的情况可以总结为常规删除,如图:
当我们循环一周以后,而且要删除的元素的位置恰好是第一个元素,这个时候我们需要进行如下判定:
if (last != NULL)
{
slist->header.next = ret->next;
}
这样删除元素的示意图如下所示:
在删除函数中,当程序每次执行删除函数的时候我们都要首先进行程序遍历,以找到程序的last,但是实际上我们程序中使用到last的地方只有两处,这样的话我们可以对程序进行修改,提高删除函数的运行效率。
四、关于游标
1.定义:游标可以理解为一个当前指针。我们可以通过这个指针来遍历链表中的所有元素。
2.结构示意图如下:
3.游标的操作其实还是依赖着链表的其他操作的,比如说你要通过游标来删除链表中的一个元素,这个时候要首先找到要删除元素在链表中的位置,这个时候也就是游标在游走,然后在调用链表实现函数中的delete函数进行删除,如果要进行连续删除或者在距离游标较近的范围内进行删除这样操作游标会比较快,如果要删除链表靠近尾部的元素或者距离游标比较远的,其实游标的执行效率并不一定比普通的链表删除元素操作快太多的。关于游标的其他操作我们在下面的程序中体现。
五、循环链表的具体代码
1.链表的实现代码
#include <stdio.h>
#include <malloc.h>
#include "CircleList.h"
/*
构建一个链表信息
包括:链表的next,游标和长度
*/
typedef struct _tag_CircleList
{
CircleListNode header;
CircleListNode* slider;
int length;
} TCircleList;
/*
链表创建函数
返回值是为链表结构体申请的空间大小
*/
CircleList* CircleList_Create() // O(1)
{
/*
为链表的结构体分配空间
这个空间可以在链表的销毁函数中释放
*/
TCircleList* ret = (TCircleList*)malloc(sizeof(TCircleList));
if( ret != NULL )
{
/*开始进行初始化操作*/
ret->length = 0;
ret->header.next = NULL;
/*循环链表刚刚创建,所以游标为空*/
ret->slider = NULL;
}
return ret;
}
/*
链表的销毁操作
参数是已经创建了的链表指针
*/
void CircleList_Destroy(CircleList* list) // O(1)
{
free(list);
}
/*
链表的清空操作,参数是已经创建了的链表的指针
*/
void CircleList_Clear(CircleList* list) // O(1)
{
/*进行强制类型转换,把CircleList* list类型的指针转为TCircleList* sList*/
TCircleList* sList = (TCircleList*)list;
/*把链表的结构体恢复到初始状态*/
if( sList != NULL )
{
sList->length = 0;
sList->header.next = NULL;
sList->slider = NULL;
}
}
/*
获取链表长度函数
函数返回值为链表的长度
*/
int CircleList_Length(CircleList* list) // O(1)
{
TCircleList* sList = (TCircleList*)list;
/*如果链表为空,那么函数的返回值将返回-1,即报错*/
int ret = -1;
if( sList != NULL )
{
//将链表长度赋值给返回值
ret = sList->length;
}
return ret;
}
/*
在链表中插入一个元素
函数的返回值是一个int型变量,主要用来判断此函数是否执行成功
函数的参数:(1)已经创建了的链表指针(2)要插入的数据节点(从上层传递过来的是一个地址)
(3)要插入的位置
*/
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
/*初始化一个变量,for循环中使用*/
int i = 0;
if( ret )
{
CircleListNode* current = (CircleListNode*)sList;
/*
for(i=0; (i<pos) && (current->next != NULL); i++)
{
current = current->next;
}
/*常规性的插入操作*/
node->next = current->next;
current->next = node;
if( sList->length == 0 )
{
/*构建出循环部分,是这个圈形成*/
sList->slider = node;
node->next = node;
}
sList->length++;
}
return ret;
}
CircleListNode* CircleList_Get(CircleList* list, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
int i = 0;
/*判定同上*/
if( (sList != NULL) && (pos >= 0) )
{
CircleListNode* current = (CircleListNode*)sList;
for(i=0; i<pos; i++)
{
current = current->next;
}
/*最后的返回值是current->next所对应的元素*/
ret = current->next;
}
return ret;
}
CircleListNode* CircleList_Delete(CircleList* list, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
int i = 0;
if( (sList != NULL) && (pos >= 0) )
{
CircleListNode* current = (CircleListNoe*)sList;
CircleListNode* first = sList->header.next;
CircleListNode* last = (CircleListNode*)CircleList_Get(sList, sList->length - 1);
for(i=0; i<pos; i++)
{
current = current->next;
}
ret = current->next;
current->next = ret->next;
sList->length--;
if( first == ret )
{
sList->header.next = ret->next;
last->next = ret->next;
}
/*
如果要删除的元素恰好是游标所指向的元素
那么游标指向要删除元素的后一个元素。
*/
if( sList->slider == ret )
{
sList->slider = ret->next;
}
/*
判断链表是否为空
(1)复原头结点的指针
(2)游标重置复原为空
*/
if( sList->length == 0 )
{
sList->header.next = NULL;
sList->slider = NULL;
}
}
return ret;
}
/*
删除循环链表中的一个元素
*/
CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node) // O(n)
{
/*必要的安全性检测*/
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
int i = 0;
if( sList != NULL )
{
/*还是定义移动*/
CircleListNode* current = (CircleListNode*)sList;
for(i=0; i<sList->length; i++)
{
/*寻找node在链表中的逻辑位置*/
if( current->next == node )
{
ret = current->next;
break; //这个时候返回得到i的值
}
/*移动指向*/
current = current->next;
}
if( ret != NULL )
{
/*进行删除操作*/
CircleList_Delete(sList, i);
}
}
return ret;
}
/*
将游标重新指向链表的第一个元素
参数:创建链表返回的得到的链表指针。
返回游标的指针。
*/
CircleListNode* CircleList_Reset(CircleList* list) // O(1)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
/*合法性的检测*/
if( sList != NULL )
{
sList->slider = sList->header.next;
ret = sList->slider;
}
/*返回值用来判断我们的重置操作是否成功*/
return ret;
}
/*返回当前游标指向的数据元素*/
CircleListNode* CircleList_Current(CircleList* list) // O(1)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
if( sList != NULL )
{
ret = sList->slider;
}
return ret;
}
/*移动游标,把当前游标指向下一个元素*/
CircleListNode* CircleList_Next(CircleList* list) // O(1)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
/*
这个时候游标也不可以为空
如果游标为空,那么程序将会崩溃
*/
if( (sList != NULL) && (sList->slider != NULL) )
{
/*
把原来游标指向的数据元素赋值给ret
这里的ret被当作一个中间变量
*/
ret = sList->slider;
/*把游标的指向指向下一个元素*/
sList->slider = ret->next;
}
return ret;
}
链表实现的.h文件
#ifndef _CIRCLELIST_H_
#define _CIRCLELIST_H_
typedef void CircleList;
typedef struct _tag_CircleListNode CircleListNode;
struct _tag_CircleListNode
{
CircleListNode* next;
};
CircleList* CircleList_Create();
void CircleList_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);
#endif
3.测试程序
#include <stdio.h>
#include <stdlib.h>
#include "1.h"
struct Value
{
CircleListNode header;
int v;
};
int main(int argc, char *argv[])
{
int i = 0;
CircleList* list = CircleList_Create();
struct Value v1;
struct Value v2;
struct Value v3;
struct Value v4;
struct Value v5;
struct Value v6;
struct Value v7;
struct Value v8;
v1.v = 1;
v2.v = 2;
v3.v = 3;
v4.v = 4;
v5.v = 5;
v6.v = 6;
v7.v = 7;
v8.v = 8;
CircleList_Insert(list, (CircleListNode*)&v1, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v2, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v3, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v4, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v5, 5);
CircleList_Delete(list, 0);
for(i=0; i < CircleList_Length(list); i++)
{
struct Value* pv = (struct Value*)CircleList_Get(list, i);
printf("%d\n", pv->v);
}
printf("\n");
while( CircleList_Length(list) > 0 )
{
struct Value* pv = (struct Value*)CircleList_Delete(list, 0);
printf("%d\n", pv->v);
}
printf("\n");
CircleList_Insert(list, (CircleListNode*)&v1, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v2, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v3, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v4, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v5, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v6, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v7, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v8, CircleList_Length(list));
for(i=0; i<CircleList_Length(list); i++)
{
struct Value* pv = (struct Value*)CircleList_Next(list);
printf("打印链表中的内容:%d\n", pv->v);
}
printf("\n");
/*测试重置游标部分*/
struct Value* youbiao = (struct Value*) CircleList_Reset(list);
printf ("重置游标后,游标指向的内容为:%d\n", youbiao->v);
while (CircleList_Length(list) > 0)
{
/*删除游标所指向的元素*/
struct Value* pv = NULL;
for(i=1; i<3; i++)
{
CircleList_Next(list);
}
pv = (struct Value*)CircleList_Current(list);
printf("经过for循环后游标指向的内容为:%d\n", pv->v);
CircleList_DeleteNode(list, (CircleListNode*)pv);
for(i=0; i<CircleList_Length(list); i++)
{
/*
这个时候游标指向被删除元素的下一个元素也就是4
所以打印顺序是4567812
*/
struct Value* pv = (struct Value*)CircleList_Next(list);
printf("打印链表中的内容:%d\n", pv->v);
}
}
CircleList_Destroy(list);
return 0;
}