文章首发:http://pjf.name/post-111.html
本文基于"姓名标识-非商业性-相同方式分享 4.0国际"协议创作或转载,转载原创文章请注明来源于疯子的自留地,否则请勿转载或再转载,谢谢合作:)
前面复习了单链表,接着复习循环链表.这里以单链表做示范.
什么是循环链表呢?一般的链表中尾结点的next指向的是NULL值,如果我们现在指向最后一个结点,当我们需要指向第一个结点时就只有重新从头结点开始,浪费了时间,现在我们把尾结点的next指向头结点,这样EndNode->next就是FirstNode的地址了不是?这就叫循环链表.
<<大话>>中说循环链表中不用头指针了,改为尾指针,即它的next值指向尾结点而非头结点.这样Header->next->next就是头结点了,确实很方便,但是依旧用头指针可以吗?我们首先来看下,如果头指针依旧指向头结点,那么我们会发现,如果我们在一个链表处插入一个新的头结点,那么按照循环链表的定义来说,我们应该让尾结点指向这个新的头结点,但是问题出来了,尾结点的内存地址是多少呢?不知道.同样的,如果删除头结点也会出现这个问题.因此,循环链表不用头指针而改用尾指针就解决了这个问题.这也是我自己在练习的时候发现的,困扰我的问题也就迎刃而解了.
当然,循环链表属于链表的子类,依旧有遍历,整表创建,整表删除,插入,删除,查找操作.以下是相关代码:
链表结构:
1 | typedef struct Node{ |
2 | char Data; |
3 | struct Node *next; |
4 | }Node; |
5 | typedef struct Node *List; |
1.整表插入
01 | BOOL CreateCircularList(List *L, int Length) //循环列表的整表创建 |
02 | { |
03 | List EndNode,NewEndNode,FirstNode; |
04 | int temp; |
05 | char Chart= 'a' ; |
06 |
07 | *L=(List) malloc ( sizeof (Node)); //为尾指针分配内存 |
08 | if (!(*L)) |
09 | return false ; |
10 | EndNode=*L; |
11 | for (temp=0;temp<Length;temp++,Chart++) |
12 | { |
13 | NewEndNode=(List) malloc ( sizeof (Node)); //为新的尾结点分配内存 |
14 | if (!NewEndNode) |
15 | return false ; |
16 | NewEndNode->Data=Chart; //为新的尾结点赋予相应的值 |
17 | EndNode->next=NewEndNode; //旧的尾结点的next指向新的尾结点的地址 |
18 | EndNode=NewEndNode; //把EndNode后移一位 |
19 | if (0==temp) //保存头结点的信息 |
20 | FirstNode=EndNode; |
21 | } |
22 | EndNode->next=FirstNode; //尾结点的next指向头结点 |
23 | (*L)->next=EndNode; //尾指针的next指向尾结点 |
24 | (*L)->Data=temp; //保存链表的长度 |
25 |
26 | return true ; |
27 | } |
2.整表删除
01 | BOOL EmptyCircularList(List *L) //循环链表的整表删除 |
02 | { |
03 | List DeleteNode,EndNode; |
04 | int temp; |
05 |
06 | if (0==(*L)->Data) |
07 | return false ; //当循环链表为空时,返回false |
08 |
09 | EndNode=(*L)->next; //取得尾结点的地址 |
10 |
11 | for (temp=1;temp<( int )((*L)->Data);temp++) //从首节点开始删除,一直到尾结点的前一个结点 |
12 | { |
13 | DeleteNode=EndNode->next; //取得要删除的结点(即头结点) |
14 | EndNode->next=DeleteNode->next; //把头结点的next结点改为新的头结点 |
15 | free (DeleteNode); //释放旧的头结点的内存 |
16 | } |
17 |
18 | free (EndNode); //释放尾结点的内存 |
19 | (*L)->next=NULL; //头指针指向NULL |
20 | (*L)->Data=0; //链表长度改为0 |
21 | |
22 | return true ; |
23 | } |
3.插入操作
01 | BOOL InsertDataCircularList(List *L, int InsertLocation, char InsertData) |
02 | { |
03 | List InsertNodePre,InsertNode; |
04 | int temp; |
05 |
06 | if (InsertLocation<1) //如果插入的位置小于1,则默认新插入的数据为首结点 |
07 | InsertLocation=1; |
08 | if (InsertLocation>(( int )(*L)->Data)) //如果插入的位置大于链表的长度,则默认新插入的位数为尾结点 |
09 | InsertLocation=(*L)->Data; |
10 |
11 | InsertNodePre=*L; //将将新增结点的前一个结点设为头指针,原因见删除结点操作的该语句 |
12 | for (temp=0;temp<InsertLocation;temp++) //将InsertNodePre指向欲新增的结点的前一个结点 |
13 | { |
14 | InsertNodePre=InsertNodePre->next; //结点后移一位 |
15 | } |
16 |
17 | InsertNode=(List) malloc ( sizeof (Node)); //为要插入的结点分配内存 |
18 | if (!InsertNode) |
19 | return false ; |
20 | InsertNode->Data=InsertData; //给插入的结点的数据域赋值 |
21 | InsertNode->next=InsertNodePre->next; //指定要插入的结点next值 |
22 | InsertNodePre->next=InsertNode; //将要插入的结点的前一个结点的next指向这个插入的结点 |
23 |
24 | if ((*L)->Data==temp+1) //如果这个新插入的结点为新的尾结点,则更新尾指针的next值 |
25 | (*L)->next=InsertNode; |
26 | (*L)->Data++; //链表的长度加1 |
27 |
28 | return true ; |
29 | } |
4.删除操作
01 | BOOL DeleteDataCircularList(List *L, int DeleteLocation, char *OutputDeleteValue) |
02 | { |
03 | int temp; |
04 | List DeleteNodePre,DeleteNode,DeleteNodeNext; |
05 |
06 | if (DeleteLocation<1) //如果删除的位置小于1,则将删除位置设为头结点 |
07 | DeleteLocation=1; |
08 | if (DeleteLocation>(( int )(*L)->Data)) //如果删除的位置大于链表长度,则将删除位置设置为尾结点 |
09 | DeleteLocation=(( int )(*L)->Data); |
10 |
11 | DeleteNodePre=*L; //首先将结点首先设置为头指针,为什么是头指针而不是头结点或者尾结点呢,因为如果是头结点下面的for循环的话就是删除正确的删除结点后的第2个结点了,而如果设为尾结点的话则是正确的删除结点后的那个结点 |
12 | |
13 | for (temp=0;temp<DeleteLocation;temp++) //将结点位置设置为与删除的前一个结点 |
14 | { |
15 | DeleteNodePre=DeleteNodePre->next; //结点后移一位 |
16 | } |
17 |
18 | DeleteNode=DeleteNodePre->next; |
19 | DeleteNodeNext=DeleteNode->next; |
20 | DeleteNodePre->next=DeleteNodeNext; |
21 | *OutputDeleteValue=DeleteNode->Data; |
22 | free (DeleteNode); //释放欲删除结点的内存 |
23 |
24 | if (temp==(( int )(*L)->Data)) //如果删除的节点是尾结点,那么我们让尾指针指向新的尾结点 |
25 | (*L)->next=DeleteNodePre; |
26 | (*L)->Data--; //链表的结点数减去1 |
27 |
28 | return true ; |
29 | } |
5.遍历链表
01 | BOOL LookUpCircularList(List L) |
02 | { |
03 | List CurrentNode; |
04 | int count=1; |
05 |
06 | if (0==L->Data) //如果链表为空,返回错误 |
07 | return false ; |
08 | CurrentNode=L->next->next; //当前结点指向链表头结点 |
09 | while ((L->next)!=CurrentNode) //不断输出当前结点的数据域的值 |
10 | { |
11 | printf ( "%3c" ,CurrentNode->Data); |
12 | if (0==count%6) |
13 | printf ( "\n" ); |
14 | CurrentNode=CurrentNode->next; //结点后移 |
15 | count++; |
16 | } |
17 | printf ( "%3c\n" ,CurrentNode->Data); //输出尾结点的值 |
18 |
19 | return true ; |
20 | } |
然后用一个小程序跑一下,以下是代码:
01 | int main( void ) |
02 | { |
03 | List Cl; |
04 | char InsertData,DeleteData; |
05 | int InsertPosition,DeletePosition; |
06 |
07 | if ( true ==CreateCircularList(&Cl,26)) |
08 | printf ( "循环链表创建成功!" ); |
09 | else |
10 | return false ; |
11 | printf ( "现在链表数据如下:\n" ); |
12 | LookUpCircularList(Cl); |
13 | printf ( "请输入要插入的字符:" ); |
14 | scanf ( "%c" ,&InsertData); |
15 | fflush (stdin); |
16 | printf ( "请输入要插入的位置:" ); |
17 | scanf ( "%d" ,&InsertPosition); |
18 | if ( true ==InsertDataCircularList(&Cl,InsertPosition,InsertData)) |
19 | printf ( "插入数据成功!插入的字符为%c," ,InsertData); |
20 | else |
21 | return false ; |
22 | printf ( "新的链表数据如下:\n" ); |
23 | LookUpCircularList(Cl); |
24 | printf ( "请输入要删除的数据的位置:" ); |
25 | if (1== scanf ( "%d" ,&DeletePosition)) |
26 | DeleteDataCircularList(&Cl,DeletePosition,&DeleteData); |
27 | else |
28 | return false ; |
29 | printf ( "删除数据成功!删除的字符为:%c\n" ,DeleteData); |
30 | fflush (stdin); |
31 | printf ( "是否清空链表?1代表清空,0代表不清空:" ); |
32 | scanf ( "%d" ,&DeletePosition); |
33 | if (1==DeletePosition) |
34 | { |
35 | if ( true ==EmptyCircularList(&Cl)) |
36 | printf ( "清空数据成功!现在链表长度为%d\n" ,Cl->Data); |
37 | } |
38 | else |
39 | printf ( "数据未清空!链表长度为%d\n" ,Cl->Data); |
40 | fflush (stdin); |
41 | getchar (); |
42 | return 0; |
43 | } |
运行结果
当然,有了循环链表,还有双向链表,时间不早了,明天再回顾