数据结构学习之双向链表结构

转载请注明来自:

http://blog.csdn.net/ab198604

 

        在前面总结的单向链表结构的基础上,现在开始着手实践实践双向链表结构,如果充分理解了单向链表数据结构,那对双向链表结构的理解也就不再困难,换个角度而言,双向链表是单向链表的扩展,如果从数据结构代码的定义上来看,双向链表需要维护三个数据内容:数据(data)、前指针(prev)和后指针(next)。与单向链表结构相比较,在程序代码中需要多维护一个prev指针域,双向链表如下图所示:

        由此带来的好处在于:

        1 对链表数据的遍历操作不仅仅能向后遍历(如单链表),而且还能够向前遍历寻找元素,对链表数据的操作更加灵活。

        2 可以直接删除某一个数据元素,我认为这是比较重要的一方面,因为对单链表而言,如果要删除某一个数据元素,需要遍历至此元素之前的一个结点才能删除,而双向链表可以遍历到某一元素,然后可以直接对它进行删除操作。

        和单向链表类似,本次内容也主要从以下三个方面来讨论双向链表的程序设计:

        一、双向链表结构及接口定义

[cpp]  view plain  copy
  1. /* 
  2.  * filename: dlist.h 
  3.  * author: zhm 
  4.  * date: 2012-12-08 
  5.  */  
  6.   
  7. #ifndef _DLIST_H  
  8. #define _DLIST_H  
  9.   
  10. #include <stdlib.h>  
  11.   
  12. /* define a structure for the list element*/  
  13. typedef struct DListElmt_  
  14. {  
  15.     void *data;  
  16.     struct DListElmt_ *prev;  
  17.     struct DListElmt_ *next;  
  18. }DListElmt;  

        上面结构体定义的是双向链表需要维护的每个元素,每个元素中包含三项内容,除此之外,还需要定义一个结构体用于维护整个链表,代码如下所示:

[cpp]  view plain  copy
  1. /* define a structure for the double linked list */  
  2. typedef struct DList_  
  3. {  
  4.     int size;  
  5.     void (*destroy)(void *data);  
  6.     void (*match)(const void *key1, const void *key2);  
  7.     DListElmt *head;  
  8.     DListElmt *tail;  
  9. }DList;  

        对双向链表的数据管理和维护主要有以上内容,其中包含链表元素的个数(size),销毁链表的函数指针(destroy), 头、尾链表元素指针。除此之外,还需要定义与链表有关的相关操作接口以及宏定义,代码如下所示:

[cpp]  view plain  copy
  1. /* define public interface */  
  2. void dlist_init(DList *list, void (*destroy)(void *data));  
  3. void dlist_destroy(DList *list);  
  4. int dlist_ins_prev(DList *list, DListElmt *element, const void *data);  
  5. int dlist_ins_next(DList *list, DListElmt *element, const void *data);  
  6. int dlist_remove(DList *list, DListElmt *element, void **data);  
  7.   
  8. #define dlist_size(list) ((list)->size) //get the size of the list.  
  9. #define dlist_head(list) ((list)->head) //get the head element  
  10. #define dlist_tail(list) ((list)->tail) //get the tail element  
  11. #define dlist_is_head(element) ((element)->prev == NULL ? 1 : 0) //whether the element is head or not  
  12. #define dlist_is_tail(element) ((element)->next == NULL ? 1 : 0) //whether the element is tail or not  
  13. #define dlist_data(element) ((element)->data) //get the data of the element  
  14. #define dlist_prev(element) ((element)->prev) //get the prev element  
  15. #define dlist_next(element) ((element)->next) //get the next element  
  16.   
  17. #endif  

        总体来说,与单链表的操作差不多,与双向链表相关的操作有:

        (1) 双向链表的初始化;

        (2) 双向链表的销毁;

        (3) 双向链表的插入元素操作:有前向插入和后向插入之分

        (4) 双向链表元素的删除;

        (5) 相关宏定义,主要是为了方便代码的维护和操作。

 

        二、双向链表的接口操作细节实现

        1 双向链表的初始化.

[cpp]  view plain  copy
  1. /* 
  2.  * filename: dlist.c 
  3.  * author:zhm 
  4.  * date: 2012-12-08 
  5.  */  
  6.   
  7. #include <stdlib.h>  
  8. #include <string.h>  
  9. #include "dlist.h"  
  10.   
  11. /* dlist_init */  
  12. void dlist_init(DList *list, void (*destroy)(void *data))  
  13. {  
  14.     list->size = 0;  
  15.     list->destroy = destroy;  
  16.     list->head = NULL;  
  17.     list->tail = NULL;  
  18.   
  19.     return;  
  20. }  

        主要对双向链表结构体的内容赋初始值,这个比较简单。

        2 双向链表的销毁

[cpp]  view plain  copy
  1. /* dlist_destroy */  
  2. void dlist_destroy(DList *list)  
  3. {  
  4.     void *data;  
  5.     while(dlist_size(list) > 0)  
  6.     {  
  7.         if( dlist_remove(list, dlist_tail(list), (void **)&data) == 0 //from the head to destroy  
  8.                 && list->destroy != NULL )  
  9.         {  
  10.             list->destroy(data);  
  11.         }  
  12.     }  
  13.   
  14.     memset(list, 0, sizeof(DList));  
  15.     return;  
  16. }  

        需要注意的是,此函数内调用的是双向链表的元素删除函数,通过外层的循环迭代来删除每个元素,最终将整个链表的大小变为0,退出迭代。在删除完每个元素后,还需要将元素内的数据域所占用的空间释放。关于双向链表的元素删除函数实现细节(dlist_remove)后面再作说明。

        3 双向链表的插入元素操作(前向插入)

[cpp]  view plain  copy
  1. /* dlist_ins_prev */  
  2. int dlist_ins_prev(DList *list, DListElmt *element, const void *data)  
  3. {  
  4.     DListElmt *new_element;  
  5.   
  6.     //Do not allow a NULL unless the list is empty  
  7.     if( element == NULL && dlist_size(list) != 0 )  
  8.         return -1;  
  9.   
  10.     new_element = (DListElmt *)malloc(sizeof(DListElmt));  
  11.     if( new_element == NULL )  
  12.         return -1;  
  13.   
  14.     new_element->data = (void *)data;  
  15.   
  16.     if( dlist_size(list) == 0 )  
  17.     {  
  18.         list->head = new_element;  
  19.         list->tail = new_element;  
  20.         new_element->prev = NULL;  
  21.         new_element->next = NULL;  
  22.     }  
  23.     else  
  24.     {  
  25.         new_element->next = element;  
  26.         new_element->prev = element->prev;  
  27.           
  28.         if( element->prev == NULL )  
  29.         {  
  30.             list->head = new_element;  
  31.         }  
  32.         else  
  33.         {  
  34.             element->prev->next = new_element;  
  35.         }  
  36.           
  37.         element->prev = new_element;  
  38.     }  
  39.   
  40.     /* Adjust the size */  
  41.     list->size++;  
  42.   
  43.     return 0;     
  44. }  

        所谓前向插入,顾名思义,是从双向链表中某一个元素结点的前面位置插入新的元素,此函数有三个参数,element表示要插入的参考结点位置,需要注意的是:

        若element = NULL, 则双向链表应该为空,否则退出并返-1;

        若element != NULL,则需要在element->prev位置插入元素,插入的新元素的数据域为第三个参数data.另外还需要考虑当element为head结点时的情况

 

        4 双向链表的插入元素操作(后向插入)

[cpp]  view plain  copy
  1. /* dlist_ins_next */  
  2. int dlist_ins_next(DList *list, DListElmt *element, const void *data)  
  3. {  
  4.     DListElmt *new_element;  
  5.   
  6.     //do not allow a NULL unless the list is empty  
  7.     if( element == NULL && dlist_size(list) != 0 )   
  8.         return -1;  
  9.   
  10.     //allocate the memory for the new element.  
  11.     new_element = (DListElmt *)malloc(sizeof(DListElmt));  
  12.     if( new_element == NULL )  
  13.         return -1;  
  14.   
  15.     //fill the data to the element  
  16.     new_element->data = (void *)data;  
  17.   
  18.     //insert the element to the list  
  19.     if( dlist_size(list) == 0 )  
  20.     {  
  21.         //the list is empty  
  22.         new_element->prev = NULL;  
  23.         new_element->next = NULL;  
  24.         list->head = new_element;  
  25.         list->tail = new_element;  
  26.     }  
  27.     else  
  28.     {  
  29.         //the list is not empty  
  30.         if( dlist_next(element) == NULL )  
  31.         {  
  32.             list->tail = new_element;  
  33.         }  
  34.         else  
  35.         {  
  36.             new_element->next->prev = new_element;  
  37.         }  
  38.         new_element->next = element->next;  
  39.         new_element->prev = element;  
  40.           
  41.         element->next = new_element;  
  42.     }  
  43.   
  44.     //adjust the size  
  45.     list->size++;  
  46.     return 0;  
  47. }  

        后向插入与前向插入类似,不过与前向不同的是,后向插入时需要注意考虑当插入的参考结点为tail的情况,如上代码中if( dlist_next(element) == NULL)时,这主要是一些细节问题,很容易忽略。插入完成后,需要对链表元素大小进行累加操作。

 

        5 双向链表元素的删除
        额,继续看代码吧!~~

[cpp]  view plain  copy
  1. /* dlist_remove */  
  2. int dlist_remove(DList *list, DListElmt *element, void **data)  
  3. {  
  4.     //do not allow a NULL or a empty list  
  5.     if( element == NULL || dlist_size(list) == 0 )  
  6.         return -1;  
  7.   
  8.     /* remove the element from the list */  
  9.     *data = element->data;  
  10.       
  11.     if( element == list->head )  
  12.     {  
  13.         list->head = element->next;  
  14.         if( list->head == NULL )  
  15.         {  
  16.             list->tail = NULL;  
  17.         }  
  18.         else  
  19.         {  
  20.             element->next->prev = NULL;  
  21.         }  
  22.     }  
  23.     else  
  24.     {  
  25.         element->prev->next = element->next;  
  26.   
  27.         if( element->next == NULL ) //be care of the last element;  
  28.         {  
  29.             list->tail = element->prev;  
  30.         }  
  31.         else  
  32.         {  
  33.             element->next->prev = element->prev;  
  34.         }  
  35.     }  
  36.   
  37.     /* free the sotrage */  
  38.     free(element);  
  39.   
  40.     /* adjust the size */  
  41.     list->size--;  
  42.   
  43.     return 0;  
  44. }  

        注意第三个参数,用于保存被删除元素中的数据域,之所以保存这个数据域,是因为在设计时考虑到通过性,此数据域需要由用户在使用时自己维护,在需要的时候由用户自己分配和释放空间,这种设计灵活性比较高而且通用性较好。第二个参数element决定了需要删除的元素即element本身,前提是双向链表不能为空。并且删除完后需要释放元素结点空间,并调整元素大小。此外需要特别注意考虑被删除的结点是头结点和尾结点时需要对双向链表的head和tail进行维护。
       

        三、双向链表的应用举例

        本应用程序比较简单,主要目的在于应用双向链表的每个接口操作及宏定义内容。知道如何使用双向链表。在本例中,用一个结构体表示长方体,内含三个变量,分别为长、宽、高,如下所示:

[cpp]  view plain  copy
  1. /* 
  2.  * filename:main.c 
  3.  * author:zhm 
  4.  * date:2012-12-08 
  5.  */  
  6.   
  7. #include<stdio.h>  
  8. #include<stdlib.h>  
  9. #include<string.h>  
  10.   
  11. #include "dlist.h"  
  12.   
  13. typedef struct Cuboid_  
  14. {  
  15.     int length;  
  16.     int width;  
  17.     int height;  
  18. }Cuboid;  

        此数据类型所产生的变量将作为双向链表元素数据的data数据域。在给每个元素的data数据域赋值之前,将调用下面这个实例化函数,用于产生每个Cuboid类型的数据对象,并返回其指针,代码如下所示:

[cpp]  view plain  copy
  1. Cuboid *cube_instance(const int length, const int width, const int height)  
  2. {  
  3.     Cuboid *cb_ptr;  
  4.     cb_ptr = (Cuboid *)malloc(sizeof(Cuboid));  
  5.     if( cb_ptr == NULL )  
  6.         return NULL;  
  7.   
  8.     cb_ptr->length = length;  
  9.     cb_ptr->width = width;  
  10.     cb_ptr->height = height;  
  11.   
  12.     return cb_ptr;  
  13. }  

        上面函数使用场景如下所示,从main函数中截取部分代码展示。

[cpp]  view plain  copy
  1. /* main */  
  2. int main(int argc, char **argv)  
  3. {  
  4.     int i;  
  5.     DList dlist_exp;  
  6.     DListElmt *p = NULL;  
  7.     Cuboid *cb1_ptr, *cb2_ptr, *cb3_ptr, *cb4_ptr, *cb5_ptr;  
  8.     Cuboid *cb_ptr;  
  9.   
  10.     //cb1_ptr ~ cb5_ptr are the data of the 5 elements.  
  11.     cb1_ptr = cube_instance(1,2,3);  
  12.     cb2_ptr = cube_instance(6,10,8);  
  13.     cb3_ptr = cube_instance(5,20,30);  
  14.     cb4_ptr = cube_instance(17,100,25);  
  15.     cb5_ptr = cube_instance(3,6,9);  
  16.          ......  
  17. }  

        如以上代码通过调用cube_instance()函数产生五个长方体对象,并且每个对象有各自的长宽高数值,每个对象用长方体指针Cuboid *来维护。下面我们对dlist_exp双向链表进行初始化,然后将插入5个元素,每个元素的数据域将一一赋予上述长方体对象指针。代码如下所示:

[cpp]  view plain  copy
  1. int main(int argc, char **argv)  
  2. {  
  3.     ......  
  4.   
  5.     //init the double linked list.  
  6.     dlist_init(&dlist_exp, destroy);  
  7.   
  8.     //insert the 5 elements into the dlist   
  9.     dlist_ins_next(&dlist_exp, NULL, (void *)cb1_ptr );  //insert data:cb1  
  10.     p = dlist_head(&dlist_exp); //get the address of the first element  
  11.     dlist_ins_next(&dlist_exp, p , (void *)cb2_ptr );   //insert data:cb2    cb1- cb2  
  12.     p = dlist_next(p);          //pointer to the element containing the data cb2.  
  13.     dlist_ins_prev(&dlist_exp, p, (void *)cb3_ptr );    //insert data:cb3   cb1- cb3- cb2  
  14.     dlist_ins_prev(&dlist_exp, p, (void *)cb4_ptr );    //insert data:cb4   cb1- cb3- cb4- cb2  
  15.     p = dlist_prev(p);          //pointer to the element conatining the data cb4.  
  16.     dlist_ins_prev(&dlist_exp, p, (void *)cb5_ptr );      //insert data:cb5   cb1- cb3- cb5- cb4- cb2  
  17.        ......  
  18. }  

        请注意插入的顺序(前向插入及后向插入,注释中已经表示插入后链表的顺序,这里不再细说。后面我们将展示插入完成后的链表遍历输出操作,并显示每个元素的data域值,代码如下:

[cpp]  view plain  copy
  1. //now the sequence is: head->cb1->cb3->cb5->cb4->cb2  
  2. printf("traverse and print:\n");  
  3. p = dlist_head(&dlist_exp); //get the head element;  
  4. for( i = 0; i < dlist_size(&dlist_exp); i++ )  
  5. {  
  6.     cb_ptr = (Cuboid *)dlist_data(p); //get the element's data, every data is a Cuboid's pointer.  
  7.     printf("i = %d: ",i);  
  8.     printf("length = %d, width = %d, height = %d\n",  
  9.             cb_ptr->length,  
  10.             cb_ptr->width,  
  11.             cb_ptr->height);  
  12.     p = dlist_next(p); //pointer to next element;  
  13. }  


        为了展示删除链表中的元素结点操作,这里打算删除第3个结点的元素,即包含数据cb5(3,6,9)长方体的元素,删除后再次遍历显示每个元素的data域值,代码如下:

[cpp]  view plain  copy
  1. //we'll remove the third element:that's containing the data of cb5(3,6,9)  
  2. p = dlist_head(&dlist_exp);  
  3. p = dlist_next(p);  
  4. p = dlist_next(p);  
  5. dlist_remove(&dlist_exp, p, (void **)&cb_ptr);  
  6. printf("the data of the third element: length = %d, width = %d, height = %d\n",  
  7.         cb_ptr->length,  
  8.         cb_ptr->width,  
  9.         cb_ptr->height);  
  10. destroy(cb_ptr); //free the memory  
  11.   
  12. //now we'll show you the remained elements,the sequence is :(head)cb1->cb3->cb4->cb2(tail)  
  13. printf("after remove the third elements:\n");  
  14. p = dlist_head(&dlist_exp);  
  15. for(i = 0; i < dlist_size(&dlist_exp); i++ )  
  16. {  
  17.     cb_ptr = (Cuboid *)dlist_data(p);  
  18.     printf("i = %d: ",i);  
  19.     printf("length = %d, width = %d, height = %d\n",  
  20.             cb_ptr->length,  
  21.             cb_ptr->width,  
  22.             cb_ptr->height);  
  23.     p = dlist_next(p);  
  24. }  

        上述代码比较简单,这里也不再进行说明。最后就是销毁链表操作,代码如下:

[cpp]  view plain  copy
  1. ......   
  2. //destroy the double linked list  
  3.  dlist_destroy(&dlist_exp);  
  4.  printf("after destroy the list,its size = %d\n", dlist_size(&dlist_exp));  
  5. ......  

 

        最后,我将main.c源代码整个展示出来,有需要的朋友可以自己动手运行一下,加深印象,main.c源码如下:

[cpp]  view plain  copy
  1. /* 
  2.  * filename:main.c 
  3.  * author:zhm 
  4.  * date:2012-12-08 
  5.  */  
  6.   
  7. #include<stdio.h>  
  8. #include<stdlib.h>  
  9. #include<string.h>  
  10.   
  11. #include "dlist.h"  
  12.   
  13. typedef struct Cuboid_  
  14. {  
  15.     int length;  
  16.     int width;  
  17.     int height;  
  18. }Cuboid;  
  19.   
  20. Cuboid *cube_instance(const int length, const int width, const int height)  
  21. {  
  22.     Cuboid *cb_ptr;  
  23.     cb_ptr = (Cuboid *)malloc(sizeof(Cuboid));  
  24.     if( cb_ptr == NULL )  
  25.         return NULL;  
  26.   
  27.     cb_ptr->length = length;  
  28.     cb_ptr->width = width;  
  29.     cb_ptr->height = height;  
  30.   
  31.     return cb_ptr;  
  32. }  
  33.   
  34. /*destroy */  
  35. void destroy(void *data)  
  36. {  
  37.     free(data);  
  38.     return;  
  39. }  
  40.   
  41.   
  42. /* main */  
  43. int main(int argc, char **argv)  
  44. {  
  45.     int i;  
  46.     DList dlist_exp;  
  47.     DListElmt *p = NULL;  
  48.     Cuboid *cb1_ptr, *cb2_ptr, *cb3_ptr, *cb4_ptr, *cb5_ptr;  
  49.     Cuboid *cb_ptr;  
  50.   
  51.     //cb1_ptr ~ cb5_ptr are the data of the 5 elements.  
  52.     cb1_ptr = cube_instance(1,2,3);  
  53.     cb2_ptr = cube_instance(6,10,8);  
  54.     cb3_ptr = cube_instance(5,20,30);  
  55.     cb4_ptr = cube_instance(17,100,25);  
  56.     cb5_ptr = cube_instance(3,6,9);  
  57.   
  58.     //init the double linked list.  
  59.     dlist_init(&dlist_exp, destroy);  
  60.   
  61.     //insert the 5 elements into the dlist   
  62.     dlist_ins_next(&dlist_exp, NULL, (void *)cb1_ptr );  //insert data:cb1  
  63.     p = dlist_head(&dlist_exp); //get the address of the first element  
  64.     dlist_ins_next(&dlist_exp, p , (void *)cb2_ptr );   //insert data:cb2    cb1- cb2  
  65.     p = dlist_next(p);          //pointer to the element containing the data cb2.  
  66.     dlist_ins_prev(&dlist_exp, p, (void *)cb3_ptr );    //insert data:cb3   cb1- cb3- cb2  
  67.     dlist_ins_prev(&dlist_exp, p, (void *)cb4_ptr );    //insert data:cb4   cb1- cb3- cb4- cb2  
  68.     p = dlist_prev(p);          //pointer to the element conatining the data cb4.  
  69.     dlist_ins_prev(&dlist_exp, p, (void *)cb5_ptr );      //insert data:cb5   cb1- cb3- cb5- cb4- cb2  
  70.   
  71.     //now the sequence is: head->cb1->cb3->cb5->cb4->cb2  
  72.     printf("traverse and print:\n");  
  73.     p = dlist_head(&dlist_exp); //get the head element;  
  74.     for( i = 0; i < dlist_size(&dlist_exp); i++ )  
  75.     {  
  76.         cb_ptr = (Cuboid *)dlist_data(p); //get the element's data, every data is a Cuboid's pointer.  
  77.         printf("i = %d: ",i);  
  78.         printf("length = %d, width = %d, height = %d\n",  
  79.                 cb_ptr->length,  
  80.                 cb_ptr->width,  
  81.                 cb_ptr->height);  
  82.         p = dlist_next(p); //pointer to next element;  
  83.     }  
  84.   
  85.     //we'll remove the third element:that's containing the data of cb5(3,6,9)  
  86.     p = dlist_head(&dlist_exp);  
  87.     p = dlist_next(p);  
  88.     p = dlist_next(p);  
  89.     dlist_remove(&dlist_exp, p, (void **)&cb_ptr);  
  90.     printf("the data of the third element: length = %d, width = %d, height = %d\n",  
  91.             cb_ptr->length,  
  92.             cb_ptr->width,  
  93.             cb_ptr->height);  
  94.     destroy(cb_ptr); //free the memory  
  95.   
  96.     //now we'll show you the remained elements,the sequence is :(head)cb1->cb3->cb4->cb2(tail)  
  97.     printf("after remove the third elements:\n");  
  98.     p = dlist_head(&dlist_exp);  
  99.     for(i = 0; i < dlist_size(&dlist_exp); i++ )  
  100.     {  
  101.         cb_ptr = (Cuboid *)dlist_data(p);  
  102.         printf("i = %d: ",i);  
  103.         printf("length = %d, width = %d, height = %d\n",  
  104.                 cb_ptr->length,  
  105.                 cb_ptr->width,  
  106.                 cb_ptr->height);  
  107.         p = dlist_next(p);  
  108.     }  
  109.   
  110.     //destroy the double linked list  
  111.     dlist_destroy(&dlist_exp);  
  112.     printf("after destroy the list,its size = %d\n", dlist_size(&dlist_exp));  
  113.     return 0;  
  114. }  

        通过编译后,程序的执行结果如下所示:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值