1. 程式人生 > >(C語言版)連結串列(四)——實現雙向迴圈連結串列建立、插入、刪除、釋放記憶體等簡單操作

(C語言版)連結串列(四)——實現雙向迴圈連結串列建立、插入、刪除、釋放記憶體等簡單操作

雙向迴圈連結串列是基於雙向連結串列的基礎上實現的,和雙向連結串列的操作差不多,唯一的區別就是它是個迴圈的連結串列,通過每個節點的兩個指標把它們扣在一起組成一個環狀。所以呢,每個節點都有前驅節點和後繼節點(包括頭節點和尾節點)這是和雙向連結串列不同的地方。我們看下雙向迴圈連結串列的示意圖(我在網上找了張圖片,自己畫的實在難看,有時間真的要去學下怎麼畫圖了,然後可以寫出更好的部落格):

在程式的編寫方面呢,雙向迴圈連結串列有點向前面知識的組合,雙向迴圈連結串列在“雙向”方面和雙向連結串列一樣,在“迴圈方面”和單向迴圈連結串列一樣。以前的知識消化了呢,這個雙向迴圈連結串列也就簡單了。上面這個圖比較形象的體現出了雙向迴圈連結串列的含義:簡單說呢,就是當前節點的一個指標指向前置節點一個指標指向後繼節點,每個節點都重複這樣,就形成了一個環了。

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:到這裡為止,連結串列的知識就寫到這裡了,後面開始就是佇列和棧了。其實線性表方面的知識都大同小異,只要原理清楚之後,它的形式的變化可以是多鍾多樣的,當然本人水平有限,希望能和同道之人繼續深入探討,共同進步。