概括:主要说明双向链表的基本概念和具体操作以及源代码。
一、基本概念
1.有了单链表以后我们可以把内存中小块的空间联系在一起,并且把每一个小块都存储上我们想要存储的数值。但是单链表只有一个next,我们每一次都要从头开始遍历整个链表,这样的话如果我们对单链表进行逆序访问那么将是一项很耗时的操作。
2.双向链表解决了上面的问题,我们不单对每一个数据节点都设置一个next,同时还有一个pre指针,这样我们可以完成对链表的双向查找。
3.双向链表的结构示意图如下所示:
二、把单链表更改成为双向链表
1.首先更改链表中的header,链表中的header对应的是一个结构体,如下所示:
struct _tag_DLinkListNode { DLinkListNode* next; DLinkListNode* pre; };
在这里相对于单链表增加了一个pre指针,作前驱。
2.在create函数中初始化将header.pre以及clear函数中的header.next都设置为NULL。
ret->header.pre = NULL;
3.其他地方的操作还要注意在插入一个节点和删除一个节点函数中的改变。
三、双向链表的具体操作
1.双向链表的创建
DLinkList* DLinkList_Create() { TDLinkList* ret = (TDLinkList*)malloc(sizeof (TDLinkList)); if (ret != NULL) { ret->length = 0; ret->slider = NULL; ret->header.next = NULL; ret->header.pre = NULL; } return ret; }
这里的TDLinkList* ret = (TDLinkList*)malloc(sizeof (TDLinkList));是为链表头获取空间,链表中其他数据节点的空间是在主函数中定义的,也就是插入链表时候由 结构体进行定义。
2.双向链表的清除、销毁、以及获取长度
这一部分要注意返回值的定义和返回值的类型。
3.双向链表的插入操作
在链表插入函数中,一共有四点需要注意。
(1)普通的插入方式,不是头插法也不是尾插法,只是单纯的把一个数据节点插入到链表中。基本操作示意图如下:
这样我们基本上使用四步操作就可以解决:
current->next = node; node->pre = current; next->pre = node; node->pre = current;
(2)如果插入的节点是采用头插法,而且链表中已经存在其他元素,那么我们要进行判定,因为这个时候不可以再单纯的令current = node->pre,这个时候的node是第一个元素,所以它的pre应该指向NULL。
代码实现:
if (current == (DLinkListNode*)slist) { node->pre = NULL; }
(3)当我们插入的节点是链表中的第0个位置,并且链表中没有其他元素,也就是链表的长度为0的时候,这个时候我们插入的元素的node->pre指向的current是头结点,显然不应该这样,也应该令node->pre = NULL,虽然老唐的判定是slist->length = 0但是我自己的判定current == (DLinkListNode*)slist已经包含了老唐的情况。
这种情况的代码实现:
if (current == (DLinkListNode*)slist) { node->pre = NULL; }
(4)插入节点的第三种特殊情况:当我们从链表的尾部插入元素,这个时候current是倒数第一个节点,next是NULL,我们要插入的节点node在插入完成以后,node->next应该指向空。
这种情况下的代码实现:
if (next != NULL) { // node->next = next; next->pre = node; }
(5)对然第二种情况已经实现了slist->length == 0的情况,但是假如我们的slist->length == 0的时候,我们的游标并没有指向当前插入的元素,为了解决这个问题,代码实现如下:
if (slist->length == 0) { slist->slider = node; }
插入函数的整体实现如下所示:
int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos) { TDLinkList *slist = (TDLinkList*)list; int ret = (slist != NULL); ret = ret && (pos >= 0); ret = ret && (node != NULL); int i = 0; if (ret) { DLinkListNode*current = (DLinkListNode*)slist; DLinkListNode*next = NULL; for (i = 0; (i < pos) && (current->next != NULL); i++) { current = current->next; } next = current->next; current->next = node; node->next = next; if (next != NULL) { // node->next = next; next->pre = node; } node->pre = current; if (current == (DLinkListNode*)slist) { node->pre = NULL; } if (slist->length == 0) { slist->slider = node; } slist->length++; } return ret; }
4.双向链表的删除元素操作
删除操作除了常规的操作以外也存在一些特殊的情况。
(1)常规的删除链表中的一个元素,也就是在这个双向链表中的不是第一个元素,也不是最后元素,而且这个时候的双向链表已经有了一定的长度。如下图所示:
具体的代码实现如下:
current->next = next; next->node = current;
(2)删除链表中的元素的第二种情况,我们要删除链表中的第0个元素,这个时候我们头结点的next被赋值为next(第1个节点),但是这个时候第一个节点应该变为第0个节点,而第0个节点指向的为header,所以这个时候next->pre = NULL。
代码实现:
current->next = next; next->pre= NULL;
同时这种情况下要对next节点是否是为NULL 进行判定。
(3)删除了第0个元素以后,链表中不再有其他元素,也就是我们删除的元素是链表中的唯一节点,这个时候我们只需要将current->next = next,而并不需要进行next->pre =current,因为这个时候根本就不存在next->pre的情况了。
这里不再需要next->pre = current,具体的代码实现如下:
if (next != NULL)
我们这种情况下next = = NULL,所以我们不再指向if下面的代码。
(4)如果删除的链表中的最后一个节点,如果这个节点为空,那么只执行current->next = next,这个判定if (next != NULL)已经完成,不再执行有关next->pre的操作。
具体代码实现如下:
if (next != NULL)
删除函数的具体实现如下:
(5)如果我们要删除的节点恰好是游标现在所指向的元素,那么我们需要将游标指向next。
具体代码实现如下:
if (slist->slider == ret) { slist->slider = next; }
删除函数的具体实现代码如下:
DLinkListNode* DLinkList_Delete(DLinkList* list, int pos) { TDLinkList* slist = (TDLinkList*)list; DLinkListNode * ret = NULL; int i = 0; if ((slist != NULL) && (pos >=0) && (pos < slist->length)) { DLinkListNode* current = (DLinkListNode*)(slist); DLinkListNode*next = NULL; for (i = 0; i < pos; i++) { current = current->next; } ret = current->next; next->pre = current;*/ next = ret->next; current->next = next; if (next != NULL) { next->pre = current; if (current == (DLinkListNode*)slist) { current->next = NULL; next->pre = NULL; } } if (slist->slider == ret) { slist->slider = next; } slist->length--; } return ret; }
四、测试程序以及游标
1.测试程序
(1)我们的插入方式如下:
DLinkList_Insert(list, (DLinkListNode*)&v1, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v2, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v3, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v4, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v5, DLinkList_Length(list));
我们采用的尾插法,就是每一次插入一个元素都是从链表的尾部插入。
(2)我们在操作游标之前,不必要对游标进行复位,如果不对游标进行复位,那么我们采用尾插法将会把元素的游标挤到第一个位置,那么我们就可以正常操作游标了。
(3)如果我们采用头插法插入元素,插入方式如下:
DLinkList_Insert(list, (DLinkListNode*)&v1, 0); DLinkList_Insert(list, (DLinkListNode*)&v2, 0); DLinkList_Insert(list, (DLinkListNode*)&v3, 0); DLinkList_Insert(list, (DLinkListNode*)&v4, 0); DLinkList_Insert(list, (DLinkListNode*)&v5, 0);
(4)在我们进行游标操作之前,我们要对游标进行复位,因为头插法将会把游标顺序的挤到最后一个位置,这个时候如果我们朦胧的将游标再向后移动一个将会导致程序的崩溃,但是这个时候向前移动并不会出错。
五、源代码
1.双向链表实现.c文件
#include <stdio.h> #include <stdlib.h> #include "1.h" /************************************************************************ *这个结构体里定义的是链表头的信息,我们的链表操作和链表遍历都离不开链表头 ************************************************************************/ typedef struct student { DLinkListNode header; DLinkListNode *slider; //游标 int length; }TDLinkList; /*********************************************************************************************** *函数名: DLinkList_Create *参数:void *返回值:DLinkList*类型,是一个void*类型,然后再由接收函数进行强制类型转换 *功能:创建链表,并返回链表头 ***********************************************************************************************/ DLinkList* DLinkList_Create() { /* 为链表头获得空间,链表中其他数据节点的空间是在主函数中定义的,也就是插入链表时候由 结构体进行定义。 */ TDLinkList* ret = (TDLinkList*)malloc(sizeof (TDLinkList)); if (ret != NULL) { ret->length = 0; ret->slider = NULL; ret->header.next = NULL; ret->header.pre = NULL; } return ret; } /*********************************************************************************************** *函数名: DLinkList_Destroy *参数:DLinkList* list 传进来的是链表头 *返回值:void *功能:销毁链表头 ***********************************************************************************************/ void DLinkList_Destroy(DLinkList* list) { free(list); } /*********************************************************************************************** *函数名: DLinkList_Clear *参数:DLinkList* list 传进来的是链表头 *返回值:void *功能:清空链表,并把链表头信息清空 ***********************************************************************************************/ void DLinkList_Clear(DLinkList* list) { TDLinkList *slist = (TDLinkList*)list; if (slist != NULL) { slist->length = 0; slist->header.next = NULL; slist->header.pre = NULL; slist->slider = NULL; } } /*********************************************************************************************** *函数名: DLinkList_Length *参数:DLinkList* list 传进来的是链表头 *返回值:int类型的整数 *功能:获得链表长度,并将链表的长度返回 ***********************************************************************************************/ int DLinkList_Length(DLinkList* list) { /*首先给返回值赋初值,如果函数的返回值为-1,则证明链表并不存在*/ int ret = -1; TDLinkList *slist = (TDLinkList*)list; if (slist != NULL) { ret = slist->length; } return ret; } /*********************************************************************************************** *函数名: DLinkList_Insert *参数:DLinkList* list 传进来的是链表头 DLinkListNode* node 要插入的数据节点,其实是我们 *实际要插入的数据节点的指针 int pos 要插入链表中的位置(注意这个是从0开始算起的) *返回值:int类型的整数 *功能:如果插入元素成功返回1,否则返回其他。 ***********************************************************************************************/ int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos) { TDLinkList *slist = (TDLinkList*)list; int ret = (slist != NULL); ret = ret && (pos >= 0); ret = ret && (node != NULL); int i = 0; if (ret) { DLinkListNode*current = (DLinkListNode*)slist; DLinkListNode*next = NULL; for (i = 0; (i < pos) && (current->next != NULL); i++) { current = current->next; } next = current->next; current->next = node; node->next = next; if (next != NULL) { // node->next = next; next->pre = node; } node->pre = current; if (current == (DLinkListNode*)slist) { node->pre = NULL; } if (slist->length == 0) { slist->slider = node; } slist->length++; } return ret; } /*********************************************************************************************** *函数名: DLinkList_Get *参数:DLinkList* list 传进来的是链表头 int pos 要插入链表中的位置(注意这个是从0开始算起的) *返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针 *功能:通过传进来的链表指针和位置,可以获得这个位置上的数据节点信息。 ***********************************************************************************************/ DLinkListNode* DLinkList_Get(DLinkList* list, int pos) { TDLinkList* slist = (TDLinkList*)list; DLinkListNode* ret = NULL; int i = 0; if ((slist != NULL)&& (pos >= 0) && (pos < slist->length)) { DLinkListNode*current = (DLinkListNode*)slist; //DLinkListNode*next = NULL; for (i = 0; i < pos; i++) { current = current->next; } /*current永远都是我们要找的节点的前一个节点*/ ret = current->next; } return ret; } /*********************************************************************************************** *函数名: DLinkList_Delete *参数:DLinkList* list 传进来的是链表头 int pos 要插入链表中的位置(注意这个是从0开始算起的) *返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针 *功能:通过传进来的链表指针和位置,可以获取删除指定位置上的元素,并对指定位置上的元素进行删除。 ***********************************************************************************************/ DLinkListNode* DLinkList_Delete(DLinkList* list, int pos) { TDLinkList* slist = (TDLinkList*)list; DLinkListNode * ret = NULL; int i = 0; if ((slist != NULL) && (pos >=0) && (pos < slist->length)) { DLinkListNode* current = (DLinkListNode*)(slist); DLinkListNode*next = NULL; for (i = 0; i < pos; i++) { current = current->next; } ret = current->next; next->pre = current;*/ next = ret->next; current->next = next; if (next != NULL) { next->pre = current; if (current == (DLinkListNode*)slist) { current->next = NULL; next->pre = NULL; } } if (slist->slider == ret) { slist->slider = next; } slist->length--; } return ret; } /*********************************************************************************************** *函数名: DLinkList_DeleteNode *参数:DLinkList* list 传进来的是链表头 int pos 要插入链表中的位置(注意这个是从0开始算起的) *返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针 *功能:通过传进来的链表指针和位置,通过游标指向我们要删除的元素,然后调用DLinkList_Delete函数 进行删除。 ***********************************************************************************************/ DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node) { TDLinkList* slist = (TDLinkList*)list; DLinkListNode * ret = NULL; int i = 0; if (slist != NULL) { DLinkListNode* current = (DLinkListNode*)(slist); for (i = 0; i < slist->length; i++) { if (current->next == node) { ret = current->next; break; } current = current->next; } if (current != NULL) { DLinkList_Delete (list, i); } } return ret; } /*********************************************************************************************** *函数名: DLinkList_Reset *参数:DLinkList* list 传进来的是链表头 *返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针 *功能:通过传进来的链表指针将游标重新指向头结点所指向的下一个元素的位置,也就是所谓的游标复位。 进行删除。 ***********************************************************************************************/ DLinkListNode* DLinkList_Reset(DLinkList* list) { TDLinkList* slist = (TDLinkList*)list; DLinkListNode* ret = NULL; if (slist != NULL) { slist->slider = slist->header.next; ret = slist->slider; } return ret; } /*********************************************************************************************** *函数名: DLinkList_Current *参数:DLinkList* list 传进来的是链表头 *返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针 *功能:通过传进来的指针,找到游标当前指向的元素,并将这个当前元素返回。 ***********************************************************************************************/ DLinkListNode* DLinkList_Current(DLinkList* list) { TDLinkList* slist = (TDLinkList*)list; DLinkListNode* ret = NULL; if (slist != NULL) { ret = slist->slider; } return ret; } /*********************************************************************************************** *函数名: DLinkList_Next *参数:DLinkList* list 传进来的是链表头 *返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针 *功能:通过传进来的指针,找到游标指向前一个元素,并将这个前一个元素返回。 ***********************************************************************************************/ DLinkListNode* DLinkList_Next(DLinkList* list) { TDLinkList* slist = (TDLinkList*)list; DLinkListNode* ret = NULL; if( (slist != NULL) && (slist->slider != NULL) ) { ret = slist->slider; slist->slider = ret->next; } return ret; } /*********************************************************************************************** *函数名: DLinkList_Pre *参数:DLinkList* list 传进来的是链表头 *返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针 *功能:通过传进来的指针,找到游标指向前一个元素,并将这个前一个元素返回。 ***********************************************************************************************/ DLinkListNode* DLinkList_Pre(DLinkList* list) { TDLinkList* slist = (TDLinkList*)list; DLinkListNode* ret = NULL; if (slist != NULL && slist->slider != NULL) { slist->slider = slist->slider->pre; ret = slist->slider; } return ret; }
2.双向链表的头文件
#ifndef _1_H_ #define _1_H_ typedef void DLinkList; typedef struct _tag_DLinkListNode DLinkListNode; /*这个结构体是聊表头的一个成员*/ struct _tag_DLinkListNode { DLinkListNode* next; DLinkListNode* pre; }; DLinkList* DLinkList_Create(); void DLinkList_Destroy(DLinkList* list); void DLinkList_Clear(DLinkList* list); int DLinkList_Length(DLinkList* list); int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos); DLinkListNode* DLinkList_Get(DLinkList* list, int pos); DLinkListNode* DLinkList_Delete(DLinkList* list, int pos); DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node); DLinkListNode* DLinkList_Reset(DLinkList* list); DLinkListNode* DLinkList_Current(DLinkList* list); DLinkListNode* DLinkList_Next(DLinkList* list); DLinkListNode* DLinkList_Pre(DLinkList* list); #endif
3.测试程序
#include <stdio.h> #include <stdlib.h> #include "1.h" /* run this program using the console pauser or add your own getch, system("pause") or input loop */ struct Value { DLinkListNode header; int v; }; int main(int argc, char *argv[]) { int i = 0; DLinkList* list = DLinkList_Create(); struct Value* pv = NULL; struct Value v1; struct Value v2; struct Value v3; struct Value v4; struct Value v5; v1.v = 1; v2.v = 2; v3.v = 3; v4.v = 4; v5.v = 5; DLinkList_Insert(list, (DLinkListNode*)&v1, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v2, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v3, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v4, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v5, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v1, 0); DLinkList_Insert(list, (DLinkListNode*)&v2, 0); DLinkList_Insert(list, (DLinkListNode*)&v3, 0); DLinkList_Insert(list, (DLinkListNode*)&v4, 0); DLinkList_Insert(list, (DLinkListNode*)&v5, 0); for(i=0; i<DLinkList_Length(list); i++) { pv = (struct Value*)DLinkList_Get(list, i); printf("插入的元素为:%d ", pv->v); } printf(" "); //DLinkList_Delete(list, 0); //DLinkList_Delete (list)*/ for(i=0; i<DLinkList_Length(list); i++) { pv = (struct Value*)DLinkList_Next(list); printf("%d ", pv->v); } printf(" "); DLinkList_Reset(list); DLinkList_Next(list); pv = (struct Value*)DLinkList_Current(list); printf("%d ", pv->v); DLinkList_DeleteNode(list, (DLinkListNode*)pv); pv = (struct Value*)DLinkList_Current(list); printf("%d ", pv->v); DLinkList_Pre(list); pv = (struct Value*)DLinkList_Current(list); printf("%d ", pv->v); printf("Length: %d ", DLinkList_Length(list)); DLinkList_Destroy(list); return 0; }