数据结构学习笔记(5.线性表之双向循环链表)
在前两节的基础上,实现双向循环链表。
本节知识点:
1.双向循环链表的结构:
上面就是双向循环链表的结构图,对于双向链表的插入有3个位置,A、B、C。
但是在插入第一个元素的时候(其实插入第一个元素的时候,就是循环建立的时候),有些特殊,所以就画了一个图,如下:
本节代码:
DcLinkList.c:
/******************************************************************************************************* 文件名:DcLinkList.c 头文件:DcLinkList.h 时间: 2013/08/26 作者: Hao 功能: 可以复用 带有增 删 改 查 功能的双向循环链表 难道: 1.typedef struct Str_DcLinkList DcLinkListNode; //这个结构体是链表的真身 struct Str_DcLinkList //每一个链表元素的结构都会包含这个结构 因为当给链表元素强制类型 { //转换成(DcLinkListNode* )的时候 其实就是要开始对每个元素中的 DcLinkListNode进行赋值了 DcLinkListNode* next; }; 这个链表结构在链表元素中起到的作用 是本节的难点 2.切记一个问题 就是已经是链表中元素的 千万不要再往链表中添加了 否则链表一定出现无穷的错误 3.对于pos值的问题 add、get、del三个函数中 的链表都是 从1开始的到length 0是链表头 在add函数中pos为0的时候是和pos为1的情况是一样的 都是头插法 0~~~~~无穷大 在get函数中pos为0的时候是获得链表头 地址 0~~~~~length 在del函数中pos为0的时候是无效的 del失败 1~~~~~length *******************************************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <malloc.h> #include "DcLinkList.h" typedef struct str_list_head //这个是链表头 其实也可以当作一个没有前驱的 链表元素 元素的内容是链表长度 { //DcLinkListNode* next; DcLinkListNode head; //这个参数要特别重视 每一个链表元素结构的第一个参数一定是 DcLinkListNode //因为在寻找链表元素后继的时候 其实就是将链表元素强制类型转换成 DcLinkListNode* 然后给next进行赋值 其实就是给 DcLinkListNode变量赋值 DcLinkListNode* slider; int length; //链表长度 }list_head; /******************************************************************************************************* 函数名: Creat_DcLinkListHead 函数功能:创建一个链表的链表头 并给链表头分配空间 参数: void 返回值:ret 成功返回链表头地址 失败返回NULL *******************************************************************************************************/ DcLinkList* Creat_DcLinkListHead(void) { list_head* ret = NULL; ret = (list_head* )malloc( sizeof(list_head)*1 ); if(NULL != ret) //malloc分配成功 { ret->length = 0; //ret -> next = NULL; ret->head.next = NULL; ret->head.pre = NULL; ret->slider = NULL; } return (DcLinkList* )ret; } /******************************************************************************************************* 函数名:Destroy_DcLinkListHead 函数功能:释放一个链表头指针 参数:DcLinkList* head 链表头指针 返回值: ret 释放成功返回1 释放失败返回0 *******************************************************************************************************/ int Destroy_DcLinkListHead(DcLinkList* head) { int ret = 0; list_head* lhead = (list_head* )head; if( NULL != lhead ) { free(lhead); ret = 1; } return ret; } /******************************************************************************************************* 函数名:Get_Length 函数功能:获得链表的长度 参数: DcLinkList* head 链表头指针 返回值: ret 成功返回链表长度 失败返回0 *******************************************************************************************************/ int Get_Length(DcLinkList* head) { int ret = 0; list_head* lhead = (list_head* )head; if( NULL != lhead ) { ret = lhead -> length; } return ret; } /******************************************************************************************************* 函数名:Clean_DcLinkListHead 函数功能: 清空链表 参数: DcLinkList* head 链表头指针 返回值:ret 成功返回1 失败返回0 *******************************************************************************************************/ int Clean_DcLinkListHead(DcLinkList* head) { int ret = 0; list_head* lhead = (list_head* )head; if( NULL != lhead ) { lhead -> length = 0; //lhead -> next = NULL; lhead -> head.next = NULL; lhead->head.pre = NULL; lhead->slider = NULL; ret = 1; } return ret; } /******************************************************************************************************* 函数名:Add_DcLinkList 函数功能:往链表里面添加一个链表元素 如果pos的值是0(就是链表头)和1(链表的第一元素 链表元素个数是从1开始算的)都是头插法 pos的值大于链表长度是尾插法 这里面pos值得注意的是 i=1 pos为a的时候 是把链表元素插入第a个元素的位置 当i=0 pos为a的时候 是把链表元素插入 第a个元素位置的后面 切忌:这里面0位置是链表头指针 从1开始是链表元素 参数: DcLinkList* head链表头指针 DcLinkListNode* Node插入元素的指针(被强制类型转化成DcLinkListNode*) int pos 插入位置 pos的有效值范围是 从0到无穷大 返回值: ret 插入成功返回1 插入失败返回0 *******************************************************************************************************/ int Add_DcLinkList(DcLinkList* head, DcLinkListNode* Node, int pos) { int ret = 0; int i = 0; list_head* lhead = (list_head* )head; DcLinkListNode* node = (DcLinkListNode* )head; DcLinkListNode* Last = NULL; ret=( NULL != node) && ( NULL != Node) && (pos >= 0); if(1 == ret) { for(i=1; ( (i<pos) && (node->next != NULL) ); i++) { node = node->next; } Node -> next = node -> next; node -> next = Node; if(NULL != Node->next) { Node->next->pre = Node; } Node->pre = node; if(lhead->length == 0)//第一次插入元素的时候把游标 指向这个元素 { lhead->slider = Node; } lhead -> length++; //这个一定要在后面调用 lhead->length值的前面更新 /*判断是否为头插法 所谓头插法 就是pos为0和1的情况 其实也就是没有进for循环的情况 剩下的无论pos为多少 进入多少次循环都没有头插法*/ if(node == (DcLinkListNode* )head) { Last =(DcLinkListNode* )Get_DcLinkListNode(lhead, lhead->length); //获得链表最后一个元素 Last->next = Node; //把头插法的数据连接到 链表的最后一个元素的后面 Node->pre = Last; } } return ret; } /******************************************************************************************************* 函数名:Get_DcLinkListNode 函数功能:获得链表中第pos个元素位置的链表元素 链表是从1开始的 0是链表头 pos为0的时候表示get链表头 参数: DcLinkList* head链表头指针 int pos获得链表元素的位置 pos的有效取值范围是 1 到 length 0是链表头 返回值: DcLinkListNode*类型 第pos个链表元素的地址 *******************************************************************************************************/ DcLinkListNode* Get_DcLinkListNode(DcLinkList* head, int pos) { int ret = 0; int i = 0; list_head* lhead = (list_head* )head; /*本来pos应该是有上限的 但是变成了循环链表pos理论上说就可以无穷大了 但是get函数应该是在链表中有值的情况下才成立的 即(lhead->length>0)*/ ret=( NULL != lhead) && (pos >= 0) && (lhead->length>0); if(1 == ret) { DcLinkListNode* node = (DcLinkListNode* )head; for(i=0; i<pos; i++) //执行 pos次 得到的是第pos位置的node { node = node->next; } return (DcLinkListNode*)node; } return NULL; } /******************************************************************************************************* 函数名:Del_DcLinkListNode 函数功能:删除链表中第pos位置的链表元素 参数: DcLinkList* head链表头指针 int pos删除链表元素的位置 pos是删除的链表元素的位置 跟get和add中的 pos是配套的 有效取值范围依然是 1到 length 在这个函数里面由于不能删除链表头 所以pos为0的时候无效 返回值: DcLinkListNode* ret这个返回值很重要 因为这个删除仅仅是把链表元素踢出了链表 并没有free开辟的内存 应该通过这个返回的地址free 释放内存 删除成功返回 删除链表元素的地址 删除失败返回 NULL *******************************************************************************************************/ DcLinkListNode* Del_DcLinkListNode(DcLinkList* head, int pos) { DcLinkListNode* ret = NULL; DcLinkListNode* Last = NULL; int i = 0; list_head* lhead = (list_head* )head; DcLinkListNode* first = lhead->head.next; if(( NULL != lhead) && (pos > 0) && (lhead->length>0)) { DcLinkListNode* node = (DcLinkListNode* )head; for(i=1; i<pos; i++)//执行 pos次 得到的是第pos位置的node 这个方法行不通 { //因为要想删除第pos位置的node 应该先找到它上一个链表元素 node = node->next; //所以这里面i=1 比get函数少执行了一次 得到第pos-1位置的node } /*判断是不是 pos为1的 情况删除头节点后面的第一个元素(这个是没有进入for循环的) 跟循环一圈后的情况不一样 */ /*循环一圈的是进入for循环的情况 此时的node不再是head了 而是链表最后一个元素*/ if(node == (DcLinkListNode* )head) { Last =(DcLinkListNode* )Get_DcLinkListNode(lhead, lhead->length); } ret = node->next; node->next = ret->next; /*判断是不是循环了一圈后回来的情况 */ if((first == ret) && (NULL == Last)) { Last =(DcLinkListNode* )Get_DcLinkListNode(lhead, lhead->length); } /*判断是否要删除链表中的第一个元素*/ if( Last != NULL ) { Last->next = ret->next; lhead->head.next = ret->next; ret->next->pre = Last; } if(1 != lhead->length) //判断删除的值是否为最后一个元素 { ret->next->pre = ret->pre; if(node == (DcLinkListNode* )head)//判断删除的值是否为第一个元素 { ret->next->pre = Last; } if(lhead->slider == ret) //判断删除的节点是否为游标的位置 { lhead->slider = ret->next; } } lhead->length--; //这个一定要写在 Get_DcLinkListNode 后面 不然的话 pos就为0了 /*判断链表是否 减到了空 如果链表中不再有元素 就把head.next赋值为NULL*/ /*单向链表不需要这个的原因 是因为单向链表的最后一个元素的next就是NULL 而双向链表没有NULL的了*/ if(0 == lhead->length) { lhead->head.next = NULL; lhead->head.pre = NULL; lhead->slider = NULL; } } return (DcLinkListNode*)ret; } /******************************************************************************************************* 函数名: DcLinkList_Slider 函数功能:获得当前游标指向的数据 参数: DcLinkList* head 返回值:成功返回 DcLinkListNode* ret 失败返回NULL *******************************************************************************************************/ DcLinkListNode* DcLinkList_Slider(DcLinkList* head) { DcLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; if( (NULL != lhead)&&(NULL != lhead->slider) )//保证slider是有效的 { ret = lhead->slider; } return ret; } /******************************************************************************************************* 函数名: DcLinkList_Reset 函数功能:重置游标 让游标指向head头节点后面的第一个元素 参数: DcLinkList* head 返回值:成功返回 当前游标的指向DcLinkListNode* ret 失败返回NULL *******************************************************************************************************/ DcLinkListNode* DcLinkList_Reset(DcLinkList* head) { DcLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; if(NULL != lhead) { lhead->slider = lhead->head.next; ret = lhead->slider; } return ret; } /******************************************************************************************************* 函数名: DcLinkList_Next 函数功能:使游标指向下一个元素 参数: DcLinkList* head 返回值:成功返回 前游标的指向DcLinkListNode* ret 失败返回NULL *******************************************************************************************************/ DcLinkListNode* DcLinkList_Next(DcLinkList* head) { DcLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; if((NULL != lhead)&&(NULL != lhead->slider)) //保证游标是有效的 { ret = lhead->slider; lhead->slider = ret->next; } return ret; } /******************************************************************************************************* 函数名: DLinkList_Pre 函数功能:使游标指向上一个元素 参数: DLinkList* head 返回值:成功返回 前游标的指向DLinkListNode* ret 失败返回NULL *******************************************************************************************************/ DcLinkListNode* DcLinkList_Pre(DcLinkList* head) { DcLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; if((NULL != lhead)&&(NULL != lhead->slider)) //保证游标是有效的 { ret = lhead->slider; lhead->slider = ret->pre; } return ret; } /******************************************************************************************************* 函数名: DcLinkList_Del 函数功能:删除链表中的某个指定元素 参数: DcLinkList* head DcLinkListNode* node为指定的元素 返回值:成功返回 删除的链表元素 失败返回NULL *******************************************************************************************************/ DcLinkListNode* DcLinkList_Del(DcLinkList* head,DcLinkListNode* node) { //这个函数主要是用来删除游标的返回值的 DcLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; int i=0; if((NULL != head)&&(NULL != node)) { DcLinkListNode* current = (DcLinkListNode*)lhead; for(i=1; i<=lhead->length; i++) { if(node == current->next) { ret = current->next; break; } current = current->next; } if(NULL == ret) //说明没有找到node { printf("put error!!!\n"); } else //找到了node { Del_DcLinkListNode(lhead,i); } } return ret;//返回删除的链表元素 }
DcLinkList.h:
#ifndef __DcLinkList_H__ #define __DcLinkList_H__ typedef void DcLinkList; //这个是为了 封装方便 typedef struct Str_DcLinkList DcLinkListNode; //这个结构体是链表的真身 struct Str_DcLinkList //每一个链表元素的结构都会包含这个结构 因为当给链表元素强制类型 { //转换成(DcLinkListNode* )的时候 其实就是要开始对每个元素中的 DcLinkListNode进行赋值了 DcLinkListNode* next; DcLinkListNode* pre; }; DcLinkList* Creat_DcLinkListHead(void); int Destroy_DcLinkListHead(DcLinkList* head); int Get_Length(DcLinkList* head); int Clean_DcLinkListHead(DcLinkList* head); int Add_DcLinkList(DcLinkList* head, DcLinkListNode* Node, int pos); DcLinkListNode* Get_DcLinkListNode(DcLinkList* head, int pos); DcLinkListNode* Del_DcLinkListNode(DcLinkList* head, int pos); DcLinkListNode* DcLinkList_Del(DcLinkList* head,DcLinkListNode* node); DcLinkListNode* DcLinkList_Next(DcLinkList* head); DcLinkListNode* DcLinkList_Pre(DcLinkList* head); DcLinkListNode* DcLinkList_Reset(DcLinkList* head); DcLinkListNode* DcLinkList_Slider(DcLinkList* head); #endif
main.c:
#include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <string.h> #include "DcLinkList.h" typedef struct _tag_str { DcLinkListNode head; int i; }str; int main() { int i = 0; str str1,str2,str3,str4,str5,str6,*strp; str1.i=1; str2.i=2; str3.i=3; str4.i=4; str5.i=5; str6.i=6; DcLinkList* head = NULL; head = Creat_DcLinkListHead(); Add_DcLinkList(head, (DcLinkListNode*)&str1, Get_Length(head)+1); Add_DcLinkList(head, (DcLinkListNode*)&str2, Get_Length(head)+1); Add_DcLinkList(head, (DcLinkListNode*)&str3, Get_Length(head)+1); Add_DcLinkList(head, (DcLinkListNode*)&str4, Get_Length(head)+1); Add_DcLinkList(head, (DcLinkListNode*)&str5, Get_Length(head)+1); Add_DcLinkList(head, (DcLinkListNode*)&str6, 3); for(i = 1; i<=Get_Length(head); i++) { strp = (str*)Get_DcLinkListNode(head, i); printf("%d\n",strp->i); } Del_DcLinkListNode(head, 1); printf("\n"); for(i = 1+5; i<=Get_Length(head)+5; i++) { strp = (str*)Get_DcLinkListNode(head, i); printf("%d\n",strp->i); } printf("\n"); DcLinkList_Reset(head); for(i = 1; i<=Get_Length(head)*3; i++) { DcLinkList_Pre(head); strp = (str*)DcLinkList_Slider(head); printf("%d\n",strp->i); } i = 6; while(i--) { Del_DcLinkListNode(head, i); } for(i = 1; i<=Get_Length(head); i++) { strp = (str*)Get_DcLinkListNode(head, i); printf("%d\n",strp->i); } Destroy_DcLinkListHead(head); return 0; }