中午寫了一篇關於單鏈表的博客。好吧,我並沒有搜到我寫的這篇文章。但我還是要寫下去,萬一有人看到了呢……不過,呵呵。。。
雙鏈表和單鏈表的操作大同小異,只是多了一個前驅指針,我是這樣定義的。
typedef struct node { int data; struct node * previous; struct node * next; } Node; typedef struct dlist { Node * head; } Dlist;
與單鏈表相比,它只多了一個前驅節點。請看圖示!
這里用prev代表previous,我在第一次寫雙鏈表時,previous與next到底指向哪里糾結了半天,從圖上來看,previous好像指向了前驅的next,然而並不是。
previous和next指向的都是節點的首地址,並不是previous或者next。也就是說previous和next里面存的是前驅節點和后繼節點的地址值。
為了操作方便,我們設計了一個頭節點。
void d_init(Dlist * list) { list->head = make_node(INT_MIN);//創建頭節點,並賦值一個整形的最小值 }
因為雙鏈表具有前驅指針,所以我們可以方便的找到要操作的節點的前驅和后繼。
插入操作,要實現順序鏈表,所以第一步還是找到插入位置,第二步進行插入。請看圖示!
先做1、2步,再做3、4步,這里主要是想先講新節點鏈接到鏈表上,1、2誰先並沒有關系。然后將3節點的后繼與5節點的前驅鏈接到新節點上。
這里還有一種情況,就是在最后的位置插入新節點。這樣只需將將新節點與前驅節點鏈接就可以了。就是不做1、4步,先做2、再做3就可以了。
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