數據結構:二級指針與不含表頭的單鏈表

【簡介】css

對於最基礎的數據結構,單鏈表,一般看到的作法是在存儲鏈表的全部元素之時,除了有一個指向鏈表的變量以外,每每還存在一個指向表頭的元素。一般兩者其實都會指向鏈表的第一個元素。這麼作的緣由是爲了鏈表操做方便,當進行鏈表的插入操做時,若在某一元素後面插入新元素時沒什麼問題,但當要求在某一元素的前面插入新元素時就會存在問題。即,若須要在表頭的前面插入新元素,會比較麻煩。因此前面存在兩個變量,一個指向鏈表,一個指向表頭時的這種操做會比較方便,可是若只有一個指向鏈表的變量存在的時候就不那麼方便了。html

在表頭的前面插入新元素,所以表頭的位置會發生變化,所以一個簡單的作法是將指向表頭的指針做爲參數傳入到插入函數中,返回一個新的指向表頭的指針便可。但其實這種方法徹底能夠用另外一種參數傳遞形式代替。若一個參數傳遞到函數內部,這個參數會在函數執行事後發生變化,則咱們能夠將該參數的地址傳入到函數,函數內部操做這個地址便可。這裏也同樣,對於這種表頭前面插入新元素,而致使表頭發生變化的狀況,能夠很簡單的將該表頭指針的地址傳入函數就能夠了,函數傳入參數就會出現二級指針。node

下面給出兩種方法實現,兩者本質都是同樣的,只不過第一種的鏈表內數據是一個簡單的int型變量,而第二種方法的數據使用了void*指針,將數據與鏈表結構分離開來。這種手段也是我比較推崇的,我之後發佈的數據結構算法儘可能都採起void*或void**來代替簡單的數據,分析代碼就能夠看出這種作法的靈活之處了。另外,代碼中均利用了Opaque Structure的手段實現了面向對象OO的風格,雖然這種方法有些人爭論它並非完整意義上的OO,但姑且我這裏就這樣稱呼他。全部代碼採用gcc編譯,能夠參考個人這篇文章下載window平臺下配置好的gcc(即MinGW)開發環境。算法

總結下兩種方法的對比:編程

方法一:鏈表中的數據類型爲int,新節點採用malloc分配,OO。數組

方法二:鏈表中的數據類型爲void*,新節點採用靜態數組分配,OO。(在嵌入式編程中我更喜歡用這種)數據結構

【方法一】app

我直接將前面提到的關鍵函數,在某一節點的前面插入新元素的方法貼出來:函數

   1:  void   List_Insert_Front(Node ** pp_list, Node *p_pos, Node * p_new)
   2:  {
   3:      Node * p_cur;
   4:      Node ** pp_cur;
   5:      Node * p_prev;
   6:      if( (!pp_list) || (!p_pos)  || (!p_new)   )     // *pp_list will be assert in the while loop
   7:          return ;
   8:          
   9:      
  10:      p_new->next =  *pp_list;          // Store the list head in the p_new->next,
  11:      p_prev      =   p_new;            // Then we can find the next p_prev simple use p_prev = p_prev->next
  12:      
  13:      pp_cur      =   pp_list;
  14:      while(*pp_cur)
  15:      {
  16:          p_cur = *pp_cur;
  17:          if(p_cur == p_pos)
  18:          {
  19:              p_prev->next = p_new;
  20:              p_new->next  = p_cur;
  21:              *pp_cur      = p_new;     // Dereference current node
  22:              /* 
  23:              *  When insert point is head, code above will update the head pointer of the list
  24:              *  But when insert point is not head, code above actually will do nothing, 
  25:              *  just update the nth pointer(but nothing will happen indeed)
  26:              */
  27:              return ;
  28:          }
  29:          pp_cur = &p_cur->next;
  30:          p_prev = p_prev ->next;
  31:      }
  32:      return ;
  33:  }

能夠看到前插入的關鍵就是傳入表頭指針的地址,在函數內部修改該指針。oop

我這種while循環方式的關鍵點在於二:

一是,第10行,初始化時將表頭暫時存放在新節點p_new的next中。

二是,第21行,當插入點找到時,作一次當前節點二級指針pp_cur的取內容賦值爲新節點值p_new。

對於在某一節點後面插入新元素,理論上能夠不用採用二級指針,不過爲了跟前插入的風格統一,一樣採用二級指針傳入。

   1:  void   List_Insert_Back(Node ** pp_list, Node *p_pos, Node * p_new)
   2:  {
   3:      Node * p_cur;
   4:      //if( (!pp_list) || (!*pp_list)  || (!p_pos)  || (!p_new)   )
   5:      if( (!pp_list) || (!p_pos)  || (!p_new)   )     // *pp_list will be assert in the while loop
   6:          return ;
   7:      //find the p_position
   8:      p_cur = * pp_list;                  // Get the head of the list
   9:      
  10:      //In the loop below, pp_list will never be dereferrenced
  11:      while(p_cur)
  12:      {
  13:          if(p_cur == p_pos)
  14:          {
  15:              p_new->next  = p_cur->next;     // Save current next to new next
  16:              p_cur->next  = p_new;           // Add new to current next
  17:              return ;
  18:          }
  19:          p_cur = p_cur->next;                // Get the next pointer
  20:      }
  21:      return ;
  22:  }

完整的代碼,包括測試用main函數,在文章末尾的壓縮包下載中有包含。Single_linke_list_Two_Star.7zip

 

【方法二】

這種方法跟方法一本質上沒有區別,但鑑於我是第一次在博客中介紹這種方法,仍是簡單說明一下。

節點的struct聲明和存放地點存在於list.c的全局變量中。固然爲了限制訪問最好添加static修飾。

   1:  #include  "list.h"
   2:   
   3:  struct _Node{
   4:      struct _Node * next   ;       // point to next
   5:      void         * value  ;       // point to actual value
   6:  };
   7:   
   8:  static    Node    Node_Array[MAX_NODE];
   9:  static    int     Node_Entry = 0;

以後能夠調用函數,返回這個Node_Array數組中的一個成員。這個數組的最大值能夠經過修改MAX_NODE肯定。

   1:  Node  *   Node_New(void)
   2:  {
   3:      Node  *   p_new;
   4:      if(Node_Entry>=MAX_NODE)
   5:          return (Node *)0;
   6:      
   7:      p_new = &Node_Array[Node_Entry];
   8:      p_new->value  =  (void *)0;
   9:      p_new->next   =  (Node *)0;
  10:      Node_Entry++;
  11:      
  12:      return p_new;
  13:  }
  14:  Node  *    Node_New_P_Value(void* value)
  15:  {
  16:      Node  * p_new_value;
  17:      p_new_value = Node_New();
  18:      if(p_new_value)
  19:          p_new_value->value = value;
  20:      return p_new_value;
  21:  }

注意,這裏的節點Node中的實際數據是void*,即指向任何類型的指針,因此若使用Node_New_P_Value新建一個節點時須要傳入一個實際指向數據存儲地方的指針。固然若數據指針不賦值就保持void* 0,就只能後果自負了。在個人測試主函數中,實際存儲區域任然是int類型的數組:

   1:  #include  "stdio.h"
   2:  #include  "list.h"
   3:  #define  MAX_NODE_VALUE     20
   4:  static int Node_Value_Pool [MAX_NODE_VALUE];
   5:  static int Node_Value_Entry = 0;
   6:  int * Node_Value_Get(int value);
   7:   
   8:   
   9:   
  10:   
  11:  Node * p_list_1;
  12:  int main(void)
  13:  {
  14:      Node * p_pos;
  15:      Node * p_new;
  16:      
  17:      p_list_1 = Node_New_P_Value(Node_Value_Get(0x2));       // 0x2

這裏的數據存儲區Node_Value_Pool數組我任然作了一個函數叫Node_Value_Get()來進行賦初值的調用。這樣的結果是,當p_list_1調用函數Node_New_P_Value()時,其獲得了一個next爲Null指針,數據成員void*爲指向Node_Value_Pool數組中的一個的指針。

這種方法跟方法一沒有本質區別,可是我跟推薦第二種方法,之後的數據結構若是可能我會盡可能採用第二種方法,即,不使用malloc新建一個類型實例的方法來編寫。

能夠看到,一旦將Node中的數據成員使用void*帶來的好處是,能夠將任何數據作成鏈表,不論是int仍是你本身typedef struct作的一個新類型,均可以用這種OO的方式,新建一個抽象的鏈表對象,List,來管理你本身的數據類型。這個鏈表中實際上並不存聽任何東西,實際存放區域在其餘地方,好比,個人測試程序中的一個int類型數組中。這種方法就簡單的將數據類型抽象出來,與實際存儲區域進行了分離。

 

【測試代碼下載】

Single_linke_list_Two_Star.7z

Single_linked_list_No_Malloc.7z

 

apollius

Jul 28, 2013

相關文章
相關標籤/搜索