雙向循環鏈表是基於雙向鏈表的基礎上實現的,和雙向鏈表的操作差不多,唯一的區別就是它是個循環的鏈表,通過每個節點的兩個指針把它們扣在一起組成一個環狀。所以呢,每個節點都有前驅節點和后繼節點(包括頭節點和尾節點)這是和雙向鏈表不同的地方。我們看下雙向循環鏈表的示意圖(我在網上找了張圖片,自己畫的實在難看,有時間真的要去學下怎么畫圖了,然后可以寫出更好的博客):
在程序的編寫方面呢,雙向循環鏈表有點向前面知識的組合,雙向循環鏈表在“雙向”方面和雙向鏈表一樣,在“循環方面”和單向循環鏈表一樣。以前的知識消化了呢,這個雙向循環鏈表也就簡單了。上面這個圖比較形象的體現出了雙向循環鏈表的含義:簡單說呢,就是當前節點的一個指針指向前置節點一個指針指向后繼節點,每個節點都重復這樣,就形成了一個環了。
DbCcLinkList.h 頭文件——包含了節點結構的定義和鏈表相關操作函數的聲明
- #ifndef DOUBLE_CIRCULAR_LINKED_LIST_H
- #define DOUBLE_CIRCULAR_LINKED_LIST_H
- typedef struct Node
- {
- int data;
- struct Node *pNext;
- struct Node *pPre;
- }NODE, *pNODE;
- //創建雙向循環鏈表
- pNODE CreateDbCcLinkList(void);
- //打印鏈表
- void TraverseDbCcLinkList(pNODE pHead);
- //判斷鏈表是否為空
- int IsEmptyDbCcLinkList(pNODE pHead);
- //計算鏈表的長度
- int GetLengthDbCcLinkList(pNODE pHead);
- //向鏈表中插入節點
- int InsertEleDbCcLinkList(pNODE pHead, int pos, int data);
- //從鏈表中刪除節點
- int DeleteEleDbCcLinkList(pNODE pHead, int pos);
- //刪除整個鏈表,釋放內存
- void FreeMemory(pNODE *ppHead);
- #endif
DbCcLinkList.cpp 雙向循環鏈表的源文件——包含了鏈表相關操作函數的定義
(1)這部分是用來創建鏈表的,雙向循環鏈表每插入一個節點就要控制4個指針,第一,插入位置的上一個節點有一個指針,它要指向插入節點;第二,插入的節點有兩個指針,一個指向上一個節點,一個指向下一個節點;第三,插入位置的下一個節點有一個指針,它是指着插入節點的。寫程序的關鍵也就是控制好這四個指針,不要弄錯了,要不然會有奇怪的結果(程序出不來結果,無線循環等)
- #include <stdio.h>
- #include <stdlib.h>
- #include "DbCcLinkList.h"
- //創建雙向循環鏈表
- pNODE CreateDbCcLinkList(void)
- {
- int i, length = 0, data = 0;
- pNODE p_new = NULL, pTail = NULL;
- pNODE pHead = (pNODE)malloc(sizeof(NODE));
- if (NULL == pHead)
- {
- printf("內存分配失敗!\n");
- exit(EXIT_FAILURE);
- }
- pHead->data = 0;
- pHead->pNext = pHead;
- pHead->pPre = pHead;
- pTail = pHead;
- printf("請輸入想要創建鏈表的長度:");
- scanf("%d", &length);
- for (i=1; i<length+1; i++)
- {
- p_new = (pNODE)malloc(sizeof(NODE));
- if (NULL == p_new)
- {
- printf("內存分配失敗!\n");
- exit(EXIT_FAILURE);
- }
- printf("請輸入第%d個節點元素值:", i);
- scanf("%d", &data);
- p_new->data = data;
- p_new->pPre = pTail;
- p_new->pNext = pHead;
- pTail->pNext = p_new;
- pHead->pPre = p_new;
- pTail = p_new;
- }
- return pHead;
- }
(2)這部分是獲得雙向鏈表的信息,和單向循環鏈表一樣,鏈表結束的限制條件是判斷是否等於頭結點。意思就是從頭節點的下一個節點開始如果又到了頭節點說明已經遍歷一圈了。
- //打印鏈表
- void TraverseDbCcLinkList(pNODE pHead)
- {
- pNODE pt = pHead->pNext;
- printf("鏈表打印如:");
- while (pt != pHead)
- {
- printf("%d ", pt->data);
- pt = pt->pNext;
- }
- putchar('\n');
- }
- //判斷鏈表是否為空
- int IsEmptyDbCcLinkList(pNODE pHead)
- {
- pNODE pt = pHead->pNext;
- if (pt == pHead)
- return 1;
- else
- return 0;
- }
- //計算鏈表的長度
- int GetLengthDbCcLinkList(pNODE pHead)
- {
- int length = 0;
- pNODE pt = pHead->pNext;
- while (pt != pHead)
- {
- length++;
- pt = pt->pNext;
- }
- return length;
- }
(3)這部分是雙向鏈表的插入、刪除節點操作,但是它不需要和雙向鏈表一樣判斷最后一個節點是否為空(因為此時要用到節點的指針),雙向循環鏈表不存在這樣的情況,這是由於它不光是雙向的,而且是循環的,所以每個節點都不可能為空。
- //向鏈表中插入節點
- int InsertEleDbCcLinkList(pNODE pHead, int pos, int data)
- {
- pNODE p_new = NULL, pt = NULL;
- if (pos > 0 && pos < GetLengthDbCcLinkList(pHead) + 2)
- {
- p_new = (pNODE)malloc(sizeof(NODE));
- if (NULL == p_new)
- {
- printf("內存分配失敗!\n");
- exit(EXIT_FAILURE);
- }
- while (1)
- {
- pos--;
- if (0 == pos)
- break;
- pHead = pHead->pNext;
- }
- p_new->data = data;
- pt = pHead->pNext;
- p_new->pNext = pt;
- p_new->pPre = pHead;
- pHead->pNext = p_new;
- pt->pPre = p_new;
- return 1;
- }
- else
- return 0;
- }
- //從鏈表中刪除節點
- int DeleteEleDbCcLinkList(pNODE pHead, int pos)
- {
- pNODE pt = NULL;
- if (pos > 0 && pos < GetLengthDbCcLinkList(pHead) + 1)
- {
- while (1)
- {
- pos--;
- if (0 == pos)
- break;
- pHead = pHead->pNext;
- }
- pt = pHead->pNext->pNext;
- free(pHead->pNext);
- pHead->pNext = pt;
- pt->pPre = pHead;
- return 1;
- }
- else
- return 0;
- }
(4)這是釋放內存操作,和上面講的一樣,不需要判斷最后一個節點是否為空,每次釋放一個節點的內存的以后它還是保持環狀的結構,所以沒有節點為空。
- //刪除整個鏈表,釋放內存空間
- void FreeMemory(pNODE *ppHead)
- {
- pNODE pt = NULL;
- while (*ppHead != NULL)
- {
- pt = (*ppHead)->pNext->pNext;
- if ((*ppHead)->pNext == *ppHead)
- {
- free(*ppHead);
- *ppHead = NULL;
- }
- else
- {
- free((*ppHead)->pNext);
- (*ppHead)->pNext = pt;
- pt->pPre = *ppHead;
- }
- }
- }
maincpp 測試程序——通過簡單的交互界面判斷函數的功能是否正確
- #include <stdio.h>
- #include <stdlib.h>
- #include "DbCcLinkList.h"
- int main(void)
- {
- int flag = 0, length = 0;
- int position = 0, value = 0;
- pNODE head = NULL;
- head = CreateDbCcLinkList();
- flag = IsEmptyDbCcLinkList(head);
- if (flag)
- printf("雙向循環鏈表為空!\n");
- else
- {
- length = GetLengthDbCcLinkList(head);
- printf("雙向循環鏈表的長度為:%d\n", length);
- TraverseDbCcLinkList(head);
- }
- printf("請輸入要插入節點的位置和元素值(兩個數用空格隔開):");
- scanf("%d %d", &position, &value);
- flag = InsertEleDbCcLinkList(head, position, value);
- if (flag)
- {
- printf("插入節點成功!\n");
- TraverseDbCcLinkList(head);
- }
- else
- printf("插入節點失敗!\n");
- flag = IsEmptyDbCcLinkList(head);
- if (flag)
- printf("雙向循環鏈表為空,不能進行刪除操作!\n");
- else
- {
- printf("請輸入要刪除節點的位置:");
- scanf("%d", &position);
- flag = DeleteEleDbCcLinkList(head, position);
- if (flag)
- {
- printf("刪除節點成功!\n");
- TraverseDbCcLinkList(head);
- }
- else
- printf("刪除節點失敗!\n");
- }
- FreeMemory(&head);
- if (NULL == head)
- printf("已成功刪除雙向循環鏈表,釋放內存完成!\n");
- else
- printf("刪除雙向循環鏈表失敗,釋放內存未完成!\n");
- return 0;
- }
PS:到這里為止,鏈表的知識就寫到這里了,后面開始就是隊列和棧了。其實線性表方面的知識都大同小異,只要原理清楚之后,它的形式的變化可以是多鍾多樣的,當然本人水平有限,希望能和同道之人繼續深入探討,共同進步。