中午寫了一篇關於單鏈表的博客。好吧,我並無搜到我寫的這篇文章。但我仍是要寫下去,萬一有人看到了呢……不過,呵呵。。。node
雙鏈表和單鏈表的操做大同小異,只是多了一個前驅指針,我是這樣定義的。函數
typedef struct node { int data; struct node * previous; struct node * next; } Node; typedef struct dlist { Node * head; } Dlist;
與單鏈表相比,它只多了一個前驅節點。請看圖示!spa
這裏用prev表明previous,我在第一次寫雙鏈表時,previous與next到底指向哪裏糾結了半天,從圖上來看,previous好像指向了前驅的next,然而並非。設計
previous和next指向的都是節點的首地址,並非previous或者next。也就是說previous和next裏面存的是前驅節點和後繼節點的地址值。指針
爲了操做方便,咱們設計了一個頭節點。調試
void d_init(Dlist * list) { list->head = make_node(INT_MIN);//建立頭節點,並賦值一個整形的最小值 }
由於雙鏈表具備前驅指針,因此咱們能夠方便的找到要操做的節點的前驅和後繼。code
插入操做,要實現順序鏈表,因此第一步仍是找到插入位置,第二步進行插入。請看圖示!blog
先作一、2步,再作三、4步,這裏主要是想先講新節點連接到鏈表上,一、2誰先並無關係。而後將3節點的後繼與5節點的前驅連接到新節點上。內存
這裏還有一種狀況,就是在最後的位置插入新節點。這樣只需將將新節點與前驅節點連接就能夠了。就是不作一、4步,先作二、再作3就能夠了。rem
if ( current->next == NULL )//在鏈表的尾端插入和中間插入不一樣 { current->next = node; node->previous = current; } else { node->previous = current->previous; //將新節點的前驅指向當前節點的前驅 node->next = current;//將新節點的後繼指向當前節點,因此這時一個前插方式 current->previous->next = node;//將當前節點的前驅的後繼指向新節點 current->previous = node;//將當前節點的前驅指向新節點 }
刪除操做,仍是先找到刪除節點。而後將刪除節點從鏈表中摘出來,釋放內存。請看圖示!
因爲咱們只知道current指針,因此咱們找到3節點的後繼指針的辦法是current->previous->next。而找5節點前驅指針也是相似的,current->next->privous。而後就能夠釋放刪除節點了。
主要的操做已經講完了,接下來看源碼吧。
仍是有兩部分組成,dlist.h和dlist.c
dlist.h
#ifndef _DLIST_H #define _DLIST_H typedef struct node { int data; struct node * previous; struct node * next; } Node; typedef struct dlist { Node * head; } Dlist; void d_init(Dlist * list);//初始化 bool d_insert(Dlist * list, const int data);//插入 bool d_remove(Dlist * list, const int key);//移除 bool d_modify(Dlist * list, const int key, const int data);//修改 Node* d_find(Dlist * list, const int key);//查找 void d_treaverse(Dlist * list, void (*func)(void * pointer) );//遍歷 void d_destroy(Dlist * list);//銷燬 #endif //_DLIST_H
dlist.c
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <limits.h> #include <stdbool.h> #include "dlist.h" static Node* make_node(const int data);//建立節點 static void destroy_node(void * pointer);//銷燬節點 static void treaverse_list(Node * current, void (*func)(void * pointer) );//遍歷鏈表,包括頭節點 void d_init(Dlist * list) { list->head = make_node(INT_MIN);//建立頭節點,並賦值一個整形的最小值 } bool d_insert(Dlist * list, const int data) { Node * current = list->head;//當前指向頭節點 Node * node;//新節點指針 while ( current->next != NULL && current->data < data) //找到插入位置 current = current->next;//在不是尾節點結束時,當前指針的值要大於新值,因此插入在當前節點以前 if ( current->data == data )//若是數據重複,插入失敗 return false; node = make_node(data); if ( current->next == NULL )//在鏈表的尾端插入和中間插入不一樣 { current->next = node; node->previous = current; } else { node->previous = current->previous; //將新節點的前驅指向當前節點的前驅 node->next = current;//將新節點的後繼指向當前節點,因此這時一個前插方式 current->previous->next = node;//將當前節點的前驅的後繼指向新節點 current->previous = node;//將當前節點的前驅指向新節點 } return true; } bool d_remove(Dlist * list, const int key) { Node * current = list->head;//當前指針指向頭節點 while ( (current = current->next) != NULL && current->data < key) ;//找到關鍵元素,因爲是順序鏈表,因此不用所有遍歷所有鏈表,只要找到第一不小於關鍵key的位置 if ( current->next == NULL && current->data == key )//刪除最後一個節點和中間節點方法不一樣 current->previous->next = NULL;//刪除最後一個節點 else if ( current->data == key )//刪除中間節點 { current->previous->next = current->next;//當前節點的前驅的後繼指向當前節點的後繼 current->next->previous = current->previous;//當前節點的後繼的前驅指向當前節點的前驅 } else return false;//沒有找到關鍵字,返回false free(current);//釋放當前指針內存 return true; } bool d_modify(Dlist * list, const int key, const int data)//修改元素,由於是有序鏈表,因此要先刪除後插入 { if ( d_remove(list, key) ) if ( d_insert(list, data) ) return true; return false; } Node* d_find(Dlist * list, const int key) { Node * current = list->head;//當前指針指向頭節點 while ( (current = current->next) != NULL && current->data < key );//找到關鍵字位置 if (current->data == key)//判斷查找的元素是否存在 return current; else return NULL; } void d_treaverse(Dlist * list, void (*func)(void * pointer) ) { treaverse_list(list->head->next, func);//遍歷,不包括頭節點 } void d_destroy(Dlist * list) { treaverse_list(list->head, destroy_node); //遍歷,銷燬鏈表 list->head = NULL; } static void treaverse_list(Node * current, void (*func)(void * pointer) )//這裏使用函數指針,是爲了程序的可擴展行,本程序中,d_treaverse 和 d_destroy 函數都用該函數實現不一樣功能 { while ( current != NULL )//遍歷,包括頭節點 { func(current); current = ( (Node *)current)->next; //強制類型轉換,因爲->的優先級高於(),因此用括號改變結合順序 } } static void destroy_node(void * pointer)//釋放內存 { free((Node *)pointer); } static Node* make_node(const int data)//建立節點 { Node * node = (Node *)malloc( sizeof(Node) ); if ( node == NULL ) { exit(1); printf("動態分配內存失敗,程序結束."); } node->data = data; node->previous = NULL; node->next = NULL; return node; }
還有一件事忘說了,這些代碼都是在gcc下調試過的,我電腦沒裝Windows,因此就沒有實驗,不過問題應該不大,若是哪裏出現問題,請聯繫我,歡迎你們指正。。。
2016-01-10