注:本文的主要目的是为了记录自己的学习过程,也方便与大家做交流。转载请注明来自:
http://blog.youkuaiyun.com/ab198604
在前面总结的单向链表结构的基础上,现在开始着手实践实践双向链表结构,如果充分理解了单向链表数据结构,那对双向链表结构的理解也就不再困难,换个角度而言,双向链表是单向链表的扩展,如果从数据结构代码的定义上来看,双向链表需要维护三个数据内容:数据(data)、前指针(prev)和后指针(next)。与单向链表结构相比较,在程序代码中需要多维护一个prev指针域,双向链表如下图所示:
由此带来的好处在于:
1 对链表数据的遍历操作不仅仅能向后遍历(如单链表),而且还能够向前遍历寻找元素,对链表数据的操作更加灵活。
2 可以直接删除某一个数据元素,我认为这是比较重要的一方面,因为对单链表而言,如果要删除某一个数据元素,需要遍历至此元素之前的一个结点才能删除,而双向链表可以遍历到某一元素,然后可以直接对它进行删除操作。
和单向链表类似,本次内容也主要从以下三个方面来讨论双向链表的程序设计:
一、双向链表结构及接口定义
/*
* filename: dlist.h
* author: zhm
* date: 2012-12-08
*/
#ifndef _DLIST_H
#define _DLIST_H
#include <stdlib.h>
/* define a structure for the list element*/
typedef struct DListElmt_
{
void *data;
struct DListElmt_ *prev;
struct DListElmt_ *next;
}DListElmt;
上面结构体定义的是双向链表需要维护的每个元素,每个元素中包含三项内容,除此之外,还需要定义一个结构体用于维护整个链表,代码如下所示:
/* define a structure for the double linked list */
typedef struct DList_
{
int size;
void (*destroy)(void *data);
void (*match)(const void *key1, const void *key2);
DListElmt *head;
DListElmt *tail;
}DList;
对双向链表的数据管理和维护主要有以上内容,其中包含链表元素的个数(size),销毁链表的函数指针(destroy), 头、尾链表元素指针。除此之外,还需要定义与链表有关的相关操作接口以及宏定义,代码如下所示:
/* define public interface */
void dlist_init(DList *list, void (*destroy)(void *data));
void dlist_destroy(DList *list);
int dlist_ins_prev(DList *list, DListElmt *element, const void *data);
int dlist_ins_next(DList *list, DListElmt *element, const void *data);
int dlist_remove(DList *list, DListElmt *element, void **data);
#define dlist_size(list) ((list)->size) //get the size of the list.
#define dlist_head(list) ((list)->head) //get the head element
#define dlist_tail(list) ((list)->tail) //get the tail element
#define dlist_is_head(element) ((element)->prev == NULL ? 1 : 0) //whether the element is head or not
#define dlist_is_tail(element) ((element)->next == NULL ? 1 : 0) //whether the element is tail or not
#define dlist_data(element) ((element)->data) //get the data of the element
#define dlist_prev(element) ((element)->prev) //get the prev element
#define dlist_next(element) ((element)->next) //get the next element
#endif
总体来说,与单链表的操作差不多,与双向链表相关的操作有:
(1) 双向链表的初始化;
(2) 双向链表的销毁;
(3) 双向链表的插入元素操作:有前向插入和后向插入之分
(4) 双向链表元素的删除;
(5) 相关宏定义,主要是为了方便代码的维护和操作。
二、双向链表的接口操作细节实现
1 双向链表的初始化.
/*
* filename: dlist.c
* author:zhm
* date: 2012-12-08
*/
#include <stdlib.h>
#include <string.h>
#include "dlist.h"
/* dlist_init */
void dlist_init(DList *list, void (*destroy)(void *data))
{
list->size = 0;
list->destroy = destroy;
list->head = NULL;
list->tail = NULL;
return;
}
主要对双向链表结构体的内容赋初始值,这个比较简单。
2 双向链表的销毁
/* dlist_destroy */
void dlist_destroy(DList *list)
{
void *data;
while(dlist_size(list) > 0)
{
if( dlist_remove(list, dlist_tail(list), (void **)&data) == 0 //from the head to destroy
&& list->destroy != NULL )
{
list->destroy(data);
}
}
memset(list, 0, sizeof(DList));
return;
}
需要注意的是,此函数内调用的是双向链表的元素删除函数,通过外层的循环迭代来删除每个元素,最终将整个链表的大小变为0,退出迭代。在删除完每个元素后,还需要将元素内的数据域所占用的空间释放。关于双向链表的元素删除函数实现细节(dlist_remove)后面再作说明。
3 双向链表的插入元素操作(前向插入)
/* dlist_ins_prev */
int dlist_ins_prev(DList *list, DListElmt *element, const void *data)
{
DListElmt *new_element;
//Do not allow a NULL unless the list is empty
if( element == NULL && dlist_size(list) != 0 )
return -1;
new_element = (DListElmt *)malloc(sizeof(DListElmt));
if( new_element == NULL )
return -1;
new_element->data = (void *)data;
if( dlist_size(list) == 0 )
{
list->head = new_element;
list->tail = new_element;
new_element->prev = NULL;
new_element->next = NULL;
}
else
{
new_element->next = element;
new_element->prev = element->prev;
if( element->prev == NULL )
{
list->head = new_element;
}
else
{
element->prev->next = new_element;
}
element->prev = new_element;
}
/* Adjust the size */
list->size++;
return 0;
}
所谓前向插入,顾名思义,是从双向链表中某一个元素结点的前面位置插入新的元素,此函数有三个参数,element表示要插入的参考结点位置,需要注意的是:
若element = NULL, 则双向链表应该为空,否则退出并返-1;
若element != NULL,则需要在element->prev位置插入元素,插入的新元素的数据域为第三个参数data.另外还需要考虑当element为head结点时的情况。
4 双向链表的插入元素操作(后向插入)
/* dlist_ins_next */
int dlist_ins_next(DList *list, DListElmt *element, const void *data)
{
DListElmt *new_element;
//do not allow a NULL unless the list is empty
if( element == NULL && dlist_size(list) != 0 )
return -1;
//allocate the memory for the new element.
new_element = (DListElmt *)malloc(sizeof(DListElmt));
if( new_element == NULL )
return -1;
//fill the data to the element
new_element->data = (void *)data;
//insert the element to the list
if( dlist_size(list) == 0 )
{
//the list is empty
new_element->prev = NULL;
new_element->next = NULL;
list->head = new_element;
list->tail = new_element;
}
else
{
//the list is not empty
if( dlist_next(element) == NULL )
{
list->tail = new_element;
}
else
{
new_element->next->prev = new_element;
}
new_element->next = element->next;
new_element->prev = element;
element->next = new_element;
}
//adjust the size
list->size++;
return 0;
}
后向插入与前向插入类似,不过与前向不同的是,后向插入时需要注意考虑当插入的参考结点为tail的情况,如上代码中if( dlist_next(element) == NULL)时,这主要是一些细节问题,很容易忽略。插入完成后,需要对链表元素大小进行累加操作。
5 双向链表元素的删除
额,继续看代码吧!~~
/* dlist_remove */
int dlist_remove(DList *list, DListElmt *element, void **data)
{
//do not allow a NULL or a empty list
if( element == NULL || dlist_size(list) == 0 )
return -1;
/* remove the element from the list */
*data = element->data;
if( element == list->head )
{
list->head = element->next;
if( list->head == NULL )
{
list->tail = NULL;
}
else
{
element->next->prev = NULL;
}
}
else
{
element->prev->next = element->next;
if( element->next == NULL ) //be care of the last element;
{
list->tail = element->prev;
}
else
{
element->next->prev = element->prev;
}
}
/* free the sotrage */
free(element);
/* adjust the size */
list->size--;
return 0;
}
注意第三个参数,用于保存被删除元素中的数据域,之所以保存这个数据域,是因为在设计时考虑到通过性,此数据域需要由用户在使用时自己维护,在需要的时候由用户自己分配和释放空间,这种设计灵活性比较高而且通用性较好。第二个参数element决定了需要删除的元素即element本身,前提是双向链表不能为空。并且删除完后需要释放元素结点空间,并调整元素大小。此外需要特别注意考虑被删除的结点是头结点和尾结点时需要对双向链表的head和tail进行维护。
三、双向链表的应用举例
本应用程序比较简单,主要目的在于应用双向链表的每个接口操作及宏定义内容。知道如何使用双向链表。在本例中,用一个结构体表示长方体,内含三个变量,分别为长、宽、高,如下所示:
/*
* filename:main.c
* author:zhm
* date:2012-12-08
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "dlist.h"
typedef struct Cuboid_
{
int length;
int width;
int height;
}Cuboid;
此数据类型所产生的变量将作为双向链表元素数据的data数据域。在给每个元素的data数据域赋值之前,将调用下面这个实例化函数,用于产生每个Cuboid类型的数据对象,并返回其指针,代码如下所示:
Cuboid *cube_instance(const int length, const int width, const int height)
{
Cuboid *cb_ptr;
cb_ptr = (Cuboid *)malloc(sizeof(Cuboid));
if( cb_ptr == NULL )
return NULL;
cb_ptr->length = length;
cb_ptr->width = width;
cb_ptr->height = height;
return cb_ptr;
}
上面函数使用场景如下所示,从main函数中截取部分代码展示。
/* main */
int main(int argc, char **argv)
{
int i;
DList dlist_exp;
DListElmt *p = NULL;
Cuboid *cb1_ptr, *cb2_ptr, *cb3_ptr, *cb4_ptr, *cb5_ptr;
Cuboid *cb_ptr;
//cb1_ptr ~ cb5_ptr are the data of the 5 elements.
cb1_ptr = cube_instance(1,2,3);
cb2_ptr = cube_instance(6,10,8);
cb3_ptr = cube_instance(5,20,30);
cb4_ptr = cube_instance(17,100,25);
cb5_ptr = cube_instance(3,6,9);
......
}
如以上代码通过调用cube_instance()函数产生五个长方体对象,并且每个对象有各自的长宽高数值,每个对象用长方体指针Cuboid *来维护。下面我们对dlist_exp双向链表进行初始化,然后将插入5个元素,每个元素的数据域将一一赋予上述长方体对象指针。代码如下所示:
int main(int argc, char **argv)
{
......
//init the double linked list.
dlist_init(&dlist_exp, destroy);
//insert the 5 elements into the dlist
dlist_ins_next(&dlist_exp, NULL, (void *)cb1_ptr ); //insert data:cb1
p = dlist_head(&dlist_exp); //get the address of the first element
dlist_ins_next(&dlist_exp, p , (void *)cb2_ptr ); //insert data:cb2 cb1- cb2
p = dlist_next(p); //pointer to the element containing the data cb2.
dlist_ins_prev(&dlist_exp, p, (void *)cb3_ptr ); //insert data:cb3 cb1- cb3- cb2
dlist_ins_prev(&dlist_exp, p, (void *)cb4_ptr ); //insert data:cb4 cb1- cb3- cb4- cb2
p = dlist_prev(p); //pointer to the element conatining the data cb4.
dlist_ins_prev(&dlist_exp, p, (void *)cb5_ptr ); //insert data:cb5 cb1- cb3- cb5- cb4- cb2
......
}
请注意插入的顺序(前向插入及后向插入,注释中已经表示插入后链表的顺序,这里不再细说。后面我们将展示插入完成后的链表遍历输出操作,并显示每个元素的data域值,代码如下:
//now the sequence is: head->cb1->cb3->cb5->cb4->cb2
printf("traverse and print:\n");
p = dlist_head(&dlist_exp); //get the head element;
for( i = 0; i < dlist_size(&dlist_exp); i++ )
{
cb_ptr = (Cuboid *)dlist_data(p); //get the element's data, every data is a Cuboid's pointer.
printf("i = %d: ",i);
printf("length = %d, width = %d, height = %d\n",
cb_ptr->length,
cb_ptr->width,
cb_ptr->height);
p = dlist_next(p); //pointer to next element;
}
为了展示删除链表中的元素结点操作,这里打算删除第3个结点的元素,即包含数据cb5(3,6,9)长方体的元素,删除后再次遍历显示每个元素的data域值,代码如下:
//we'll remove the third element:that's containing the data of cb5(3,6,9)
p = dlist_head(&dlist_exp);
p = dlist_next(p);
p = dlist_next(p);
dlist_remove(&dlist_exp, p, (void **)&cb_ptr);
printf("the data of the third element: length = %d, width = %d, height = %d\n",
cb_ptr->length,
cb_ptr->width,
cb_ptr->height);
destroy(cb_ptr); //free the memory
//now we'll show you the remained elements,the sequence is :(head)cb1->cb3->cb4->cb2(tail)
printf("after remove the third elements:\n");
p = dlist_head(&dlist_exp);
for(i = 0; i < dlist_size(&dlist_exp); i++ )
{
cb_ptr = (Cuboid *)dlist_data(p);
printf("i = %d: ",i);
printf("length = %d, width = %d, height = %d\n",
cb_ptr->length,
cb_ptr->width,
cb_ptr->height);
p = dlist_next(p);
}
上述代码比较简单,这里也不再进行说明。最后就是销毁链表操作,代码如下:
......
//destroy the double linked list
dlist_destroy(&dlist_exp);
printf("after destroy the list,its size = %d\n", dlist_size(&dlist_exp));
......
最后,我将main.c源代码整个展示出来,有需要的朋友可以自己动手运行一下,加深印象,main.c源码如下:
/*
* filename:main.c
* author:zhm
* date:2012-12-08
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "dlist.h"
typedef struct Cuboid_
{
int length;
int width;
int height;
}Cuboid;
Cuboid *cube_instance(const int length, const int width, const int height)
{
Cuboid *cb_ptr;
cb_ptr = (Cuboid *)malloc(sizeof(Cuboid));
if( cb_ptr == NULL )
return NULL;
cb_ptr->length = length;
cb_ptr->width = width;
cb_ptr->height = height;
return cb_ptr;
}
/*destroy */
void destroy(void *data)
{
free(data);
return;
}
/* main */
int main(int argc, char **argv)
{
int i;
DList dlist_exp;
DListElmt *p = NULL;
Cuboid *cb1_ptr, *cb2_ptr, *cb3_ptr, *cb4_ptr, *cb5_ptr;
Cuboid *cb_ptr;
//cb1_ptr ~ cb5_ptr are the data of the 5 elements.
cb1_ptr = cube_instance(1,2,3);
cb2_ptr = cube_instance(6,10,8);
cb3_ptr = cube_instance(5,20,30);
cb4_ptr = cube_instance(17,100,25);
cb5_ptr = cube_instance(3,6,9);
//init the double linked list.
dlist_init(&dlist_exp, destroy);
//insert the 5 elements into the dlist
dlist_ins_next(&dlist_exp, NULL, (void *)cb1_ptr ); //insert data:cb1
p = dlist_head(&dlist_exp); //get the address of the first element
dlist_ins_next(&dlist_exp, p , (void *)cb2_ptr ); //insert data:cb2 cb1- cb2
p = dlist_next(p); //pointer to the element containing the data cb2.
dlist_ins_prev(&dlist_exp, p, (void *)cb3_ptr ); //insert data:cb3 cb1- cb3- cb2
dlist_ins_prev(&dlist_exp, p, (void *)cb4_ptr ); //insert data:cb4 cb1- cb3- cb4- cb2
p = dlist_prev(p); //pointer to the element conatining the data cb4.
dlist_ins_prev(&dlist_exp, p, (void *)cb5_ptr ); //insert data:cb5 cb1- cb3- cb5- cb4- cb2
//now the sequence is: head->cb1->cb3->cb5->cb4->cb2
printf("traverse and print:\n");
p = dlist_head(&dlist_exp); //get the head element;
for( i = 0; i < dlist_size(&dlist_exp); i++ )
{
cb_ptr = (Cuboid *)dlist_data(p); //get the element's data, every data is a Cuboid's pointer.
printf("i = %d: ",i);
printf("length = %d, width = %d, height = %d\n",
cb_ptr->length,
cb_ptr->width,
cb_ptr->height);
p = dlist_next(p); //pointer to next element;
}
//we'll remove the third element:that's containing the data of cb5(3,6,9)
p = dlist_head(&dlist_exp);
p = dlist_next(p);
p = dlist_next(p);
dlist_remove(&dlist_exp, p, (void **)&cb_ptr);
printf("the data of the third element: length = %d, width = %d, height = %d\n",
cb_ptr->length,
cb_ptr->width,
cb_ptr->height);
destroy(cb_ptr); //free the memory
//now we'll show you the remained elements,the sequence is :(head)cb1->cb3->cb4->cb2(tail)
printf("after remove the third elements:\n");
p = dlist_head(&dlist_exp);
for(i = 0; i < dlist_size(&dlist_exp); i++ )
{
cb_ptr = (Cuboid *)dlist_data(p);
printf("i = %d: ",i);
printf("length = %d, width = %d, height = %d\n",
cb_ptr->length,
cb_ptr->width,
cb_ptr->height);
p = dlist_next(p);
}
//destroy the double linked list
dlist_destroy(&dlist_exp);
printf("after destroy the list,its size = %d\n", dlist_size(&dlist_exp));
return 0;
}
通过编译后,程序的执行结果如下所示: