程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 雙向鏈表(C語言)詳解 及 例程

雙向鏈表(C語言)詳解 及 例程

編輯:關於C語言
 

雙(向)鏈表中有兩條方向不同的鏈,即每個結點中除next域存放後繼結點地址外,還增加一個指向其直接前趨的指針域prior。雙向鏈表在查找時更方便 特別是大量數據的遍歷

雙向鏈表(C語言)詳解 及 例程 - 海邊風 - 鴨梨柵搭

雙向鏈表(C語言)詳解 及 例程 - 海邊風 - 鴨梨柵搭

注意:
     ①雙鏈表由頭指針head惟一確定的。
     ②帶頭結點的雙鏈表的某些運算變得方便。
     ③將頭結點和尾結點鏈接起來,為雙(向)循環鏈表。

2、雙向鏈表的結點結構和形式描述
①結點結構(見上圖a)
  
②形式描述 
    typedef struct dlistnode{
         DataType data;(數據類型按自己要求更改)
         struct dlistnode *prior,*next;
      }DListNode;
    typedef DListNode *DLinkList;
    DLinkList head;

     由於雙鏈表的對稱性,在雙鏈表能能方便地完成各種插入、刪除操作。
①雙鏈表的前插操作

雙向鏈表(C語言)詳解 及 例程 - 海邊風 - 鴨梨柵搭

ps:注意箭頭 沒有直入框內 而是整體 代表指向的是整個結點包括 prior data next

    void DInsertBefore(DListNode *p,DataType x)
      {//在帶頭結點的雙鏈表中,將值為x的新結點插入*p之前,設p≠NULL
        DListNode *s=malloc(sizeof(DListNode));//①(為鏈表結點動態分配內存)
        s->data=x;//② (將數據X的值賦給s->data)
        s->prior=p->prior;//③ (將結點p的前驅的值賦給s的前驅 使s的前驅指向原來p之前的結點)
        s->next=p;//④ (使s的後驅指向p 經過2.3.4步結點s各個部分賦值完畢)
        p->prior->next=s;//⑤ (原來p之前的結點的後驅指向s)
        p->prior=s;//⑥  (使p的前驅指向s)
       }

   PS: 第⑤⑥步的順序不能改變,想想為什麼呢?

  ②雙鏈表上刪除結點*p自身的操作

 雙向鏈表(C語言)詳解 及 例程 - 海邊風 - 鴨梨柵搭

    void DDeleteNode(DListNode *p)
      {//在帶頭結點的雙鏈表中,刪除結點*p,設*p為非終端結點
          p->prior->next=p->next;//① (使p的前一個結點的後驅直接指向 原來的p的後驅)
          p->next->prior=p->prior;//② (使p的後一個結點的前驅 直接為原來p的前一個結點)
          free(p);//③ (釋放p的內存 這個很重要 特別是處理大量數據時)
      } 
注意:
     與單鏈表上的插入和刪除操作不同的是,在雙鏈表中插入和刪除必須同時修改兩個方向上的指針。
     上述兩個算法的時間復雜度均為O(1)。









雙向鏈表是一種基本的數據結構,在許多場合都會使用到。最近正在編寫 一個基於Linux的GUI系統,裡邊許多地方都要使用到雙向鏈表,於是寫了一個通用的雙向鏈表。代碼如下:

  1. #ifndef __STDLIST_H__
  2. #define __STDLIST_H__

  3.  
  4. typedef struct tagSTDNODE       STDNODE, *LPSTDNODE;
  5. typedef struct tagSTDLIST       STDLIST, *LPSTDLIST;

  6.  
  7. // 鏈表數據結構
  8. struct tagSTDNODE {
  9.     LPSTDNODE           lpPrev;
  10.     LPSTDNODE           lpNext;
  11. };

  12.  
  13. // 鏈表節點結構
  14. struct tagSTDLIST {
  15.     LPSTDNODE           lpHead;
  16.     LPSTDNODE           lpTail;
  17. };

  18.  
  19. // 用於鏈表擴展的宏
  20. #define NODE_INITIALIZER    ((STDNODE){ .lpPrev = NULL, .lpNext = NULL, })
  21. #define LIST_INITIALIZER    ((STDLIST){ .lpHead = NULL, .lpTail = NULL, })
  22. #define DECLARELISTNODE()   STDNODE __snStdNode
  23. #define INIT_LISTNODE()     __snStdNode = NODE_INITIALIZER

  24.  
  25. // 初始化鏈表
  26. STATIC INLINE
  27. LPSTDLIST       TWINAPI     StdListInit(
  28.                             LPSTDLIST lpList
  29.                             ) {
  30.     lpList->lpHead  = NULL;
  31.     lpList->lpTail  = NULL;
  32.     return lpList;
  33. }

  34.  
  35. // 獲取鏈表頭部節點
  36. STATIC INLINE
  37. LPSTDNODE       TWINAPI     StdListGetHeadNode(
  38.                             LPSTDLIST lpList
  39.                             ) {
  40.     return lpList->lpHead;
  41. }

  42.  
  43. // 獲取鏈表尾部節點
  44. STATIC INLINE
  45. LPSTDNODE       TWINAPI     StdListGetTailNode(
  46.                             LPSTDLIST lpList
  47.                             ) {
  48.     return lpList->lpTail;
  49. }

  50.  
  51. // 獲取給定節點的下一個節點
  52. STATIC INLINE
  53. LPSTDNODE       TWINAPI     StdListGetNextNode(
  54.                             LPSTDNODE lpNode
  55.                             ) {
  56.     return lpNode->lpNext;
  57. }

  58.  
  59. // 獲取給定節點的上一個節點
  60. STATIC INLINE
  61. LPSTDNODE       TWINAPI     StdListGetPrevNode(
  62.                             LPSTDNODE lpNode
  63.                             ) {
  64.     return lpNode->lpPrev;
  65. }

  66.  
  67. // 在鏈表頭部插入一個節點
  68. STATIC INLINE
  69. VOID            TWINAPI     StdListPushFront(
  70.                             LPSTDLIST lpList,
  71.                             LPSTDNODE lpNode
  72.                             ) {
  73.     lpNode->lpPrev  = NULL;
  74.     lpNode->lpNext  = lpList->lpHead;
  75.     if(lpList->lpHead)
  76.         lpList->lpHead->lpPrev  = lpNode;
  77.     else
  78.         lpList->lpTail  = lpNode;
  79.     lpList->lpHead  = lpNode;
  80. }

  81.  
  82. // 在鏈 表尾部插入一個節點
  83. STATIC INLINE
  84. VOID            TWINAPI     StdListPushBack(
  85.                             LPSTDLIST lpList,
  86.                             LPSTDNODE lpNode
  87.                             ) {
  88.     lpNode->lpNext  = NULL;
  89.     lpNode->lpPrev  = lpList->lpTail;
  90.     if(lpList->lpTail)
  91.         lpList->lpTail->lpNext  = lpNode;
  92.     else
  93.         lpList->lpHead  = lpNode;
  94.     lpList->lpTail  = lpNode;
  95. }

  96.  
  97. // 在指定節點後插入一個節點
  98. STATIC INLINE
  99. VOID            TWINAPI     StdListInsert(
  100.                             LPSTDLIST lpList,
  101.                             LPSTDNODE lpAfter,
  102.                             LPSTDNODE lpNode
  103.                             ) {
  104.     if(lpAfter) {
  105.         if(lpAfter->lpNext)
  106.             lpAfter->lpNext->lpPrev = lpNode;
  107.         else
  108.             lpList->lpTail  = lpNode;
  109.         lpNode->lpPrev  = lpAfter;
  110.         lpNode->lpNext  = lpAfter->lpNext;
  111.         lpAfter->lpNext = lpNode;
  112.     } else {
  113.         StdListPushFront(lpList, lpNode);
  114.     }
  115. }

  116.  
  117. // 從鏈表頭部彈出一個節點
  118. STATIC INLINE
  119. LPSTDNODE       TWINAPI     StdListPopFront(
  120.                             LPSTDLIST lpList
  121.                             ) {
  122.     if(lpList->lpHead) {
  123.         LPSTDNODE lpNode    = lpList->lpHead;
  124.         if(lpList->lpHead->lpNext)
  125.             lpList->lpHead->lpNext->lpPrev  = NULL;
  126.         else
  127.             lpList->lpTail  = NULL;
  128.         lpList->lpHead  = lpList->lpHead->lpNext;
  129.         lpNode->lpPrev  = lpNode->lpNext    = NULL;
  130.         return lpNode;
  131.     } else {
  132.         return NULL;
  133.     }
  134. }

  135.  
  136. // 從鏈 表尾部彈出一個節點
  137. STATIC INLINE
  138. LPSTDNODE       TWINAPI     StdListPopBack(
  139.                             LPSTDLIST lpList
  140.                             ) {
  141.     if(lpList->lpTail) {
  142.         LPSTDNODE lpNode    = lpList->lpTail;
  143.         if(lpList->lpTail->lpPrev)
  144.             lpList->lpTail->lpPrev->lpNext  = NULL;
  145.         else
  146.             lpList->lpHead  = NULL;
  147.         lpList->lpTail  = lpList->lpTail->lpPrev;
  148.         lpNode->lpPrev  = lpNode->lpNext    = NULL;
  149.         return lpNode;
  150.     } else {
  151.         return NULL;
  152.     }
  153. }

  154.  
  155. // 從鏈表中刪除給定節點
  156. STATIC INLINE
  157. LPSTDNODE       TWINAPI     StdListRemove(
  158.                             LPSTDLIST lpList,
  159.                             LPSTDNODE lpNode
  160.                             ) {
  161.     if(lpNode->lpPrev)
  162.         lpNode->lpPrev->lpNext  = lpNode->lpNext;
  163.     else
  164.         lpList->lpHead  = lpNode->lpNext;
  165.     if(lpNode->lpNext)
  166.         lpNode->lpNext->lpPrev  = lpNode->lpPrev;
  167.     else
  168.         lpList->lpTail  = lpNode->lpPrev;
  169.     return lpNode;
  170. }

  171.  
  172. // 檢查 鏈表是否為空
  173. STATIC INLINE
  174. BOOL            TWINAPI     StdListIsEmpty(
  175.                             LPSTDLIST lpList
  176.                             ) {
  177.     if(lpList->lpHead || lpList->lpTail)
  178.         return FALSE;
  179.     else
  180.         return TRUE;
  181. }

  182.  
  183. // 獲取鏈表中的節點數
  184. STATIC INLINE
  185. LONG            TWINAPI     StdListGetSize(
  186.                             LPSTDLIST lpList
  187.                             ) {
  188.     LONG lnSize = 0;
  189.     LPSTDNODE lpNode    = StdListGetHeadNode(lpList);
  190.     while(lpNode) {
  191.         ++ lnSize;
  192.         lpNode  = StdListGetNextNode(lpNode);
  193.     }
  194.     return lnSize;
  195. }

  196.  
  197. // 合並兩個鏈表
  198. STATIC INLINE
  199. LPSTDLIST       TWINAPI     StdListCombine(
  200.                             LPSTDLIST lpList1,
  201.                             LPSTDLIST lpList2
  202.                             ) {
  203.     if(!StdListIsEmpty(lpList2)) {
  204.         if(!StdListIsEmpty(lpList1)) {
  205.             lpList1->lpTail->lpNext = lpList2->lpHead;
  206.             lpList2->lpHead->lpPrev = lpList1->lpTail;
  207.             lpList1->lpTail = lpList2->lpTail;
  208.         } else {
  209.             lpList1->lpHead = lpList2->lpHead;
  210.             lpList1->lpTail = lpList2->lpTail;
  211.         }
  212.         lpList2->lpHead = lpList2->lpTail   = NULL;
  213.     }
  214.     return lpList1;
  215. }

  216.  
  217. #endif//__STDLIST_H__

  218.  

算法本身沒有什麼特別的地方,為什麼說這是一個通用雙向鏈表呢?奧妙就在那四個 用於擴展的宏。利用這幾個宏你可以將這個雙向鏈表擴展到任意結構。

例如,我們要實現一個消息隊列,那麼我們可以這樣定義消息節點:

    • typedef struct tagMSGNODE {
    •     DECLARELISTNODE();
    •     HWND        hWnd;
    •     UINT        uMsg;
    •     WPARAM      wParam;
    •     LPARAM      lParam;
    • } MSGNODE, *LPMSGNODE;

    •  

而消息隊列則可以直接用LISTNODE結構來表示:

  1. STDLIST     lstMsgQueue;

  2.  

這樣我們就可以用剛剛定義的函數來對這個消息隊列進行操作了:

  1. // 向消息隊列插入一個消息
  2. LPMSGNODE lpMsgNode = MemAlloc(SIZEOF(MSGNODE));
  3. lpMsgNode->INIT_LISTNODE();
  4. lpMsgNode->hWnd     = hWnd;
  5. lpMsgNode->uMsg     = WM_PAINT;
  6. lpMsgNode->wParam   = NULL;
  7. lpMsgNode->lParam   = NULL;
  8. StdListPushBack(&lstMsgQueue, (LPSTDNODE)lpMsgNode);

  9.  
  10. // 從消 息隊列取出一個消息
  11. LPMSGNODE lpMsgNode = (LPMSGNODE)StdListPopFront(&lstMsgQueue);

  12.  
  13. // 刪除所有屬於hWnd的消息
  14. LPMSGNODE lpTemp    = (LPMSGNODE)StdListGetHeadNode(&lstMsgQueue);
  15. while(lpTemp) {
  16.     LPMSGNODE lpNext    = (LPMSGNODE)StdListGetNextNode(&lstMsgQueue, (LPSTDNODE)lpTemp);
  17.     if(lpTemp->hWnd == hWnd) {
  18.         StdListRemove(&lstMsgQueue, (LPSTDNODE)lpTemp);
  19.                 MemFree(lpTemp);
  20.         }
  21.     lpTemp  = lpNext;
  22. }

  23.  

是不是很方便呢^_^。

 

備注:

由於正在編寫的GUI是參考Windows的體系結構,所以代碼風 格采用了微軟的風格。

 

代碼中的MemAlloc,MemFree函數以及各種數據結構均 在其他地方定義

 

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved