註:本文的主要目的是為了記錄自己的學習過程,也方便與大家做交流。轉載請註明來自:
http://blog.csdn.net/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;}
通過編譯後,程式的執行結果如下所示: