【數據結構】帶有頭結點的雙向循環鏈表的基本操作

本篇主要實現了帶有頭結點的雙向循環鏈表的基本操作,其中包括增刪改查以及清空銷燬、判空、求結點個數等等。

頭文件

DoubleLinkList.h

# ifndef __DOUBLELINKLIST_H__
# define __DOUBLELINKLIST_H__

# include <stdio.h>
# include <assert.h>
# include <stdlib.h>

enum OPTION
{
	QUIT, // 退出
	ADD, // 添加
	DETELE, // 刪除
	SEARCH, // 查找
	MODIFY, // 修改
	CLEAR, // 清空
	NUM_NODE, // 結點數
	IS_EMPTY, // 判空
	DESTORY, // 銷燬
	SHOW // 顯示
};

enum ADD_DATA
{
	EXIT_ADD, // 退出添加
	PUSH_FRONT, // 頭插
	PUSH_BACK, // 尾插
	PUSH_INSERT // 任意插
};

enum DELETE_DATA
{
	EXIT_DELETE, // 退出刪除
	POP_FRONT, // 頭刪
	POP_BACK, // 尾刪
	POP_ERASE, // 任意刪
	REMOVE_FIRST, // 只刪除第一個要刪除的
	REMOVE_ALL // 刪除所有要刪除的
};

typedef int DataType;

typedef struct DListNode
{
	DataType data; // 數據
	struct DListNode * pPrev; // 指向上一個結點
	struct DListNode * pNext; // 指向下一個結點
}DListNode, * pDListNode;

void DListInit(pDListNode * pHead); // 初始化

void DListPrint(pDListNode pHead); // 打印鏈表內容

void DListPushFront(pDListNode pHead, DataType data); // 頭插

void DListPushBack(pDListNode pHead, DataType data); // 尾插

void DListInsert(pDListNode pHead, pDListNode pPos, DataType data); // 給定結點插入,插入到結點前

void DListAdd(pDListNode pHead); // 雙鏈表添加數據

void DListPopFront(pDListNode pHead); // 頭刪

void DListPopBack(pDListNode pHead); // 尾刪

void DListErase(pDListNode pHead, pDListNode pPos); // 給定結點刪除

void DListSearch(pDListNode pHead); // 查找輸入數據,如果找到該數據則提示找到,反之則提示沒找到

void DListModify(pDListNode pHead); // 修改雙鏈表數據

void DListRemove(pDListNode pHead, DataType data); // 按值刪除,只刪遇到的第一個

void DListRemoveAll(pDListNode pHead, DataType data); // 按值刪除,刪除所有的

void DListDel(pDListNode pHead); // 雙鏈表刪除數據

void DListIsEmpty(pDListNode pHead); // 判空

void DListSize(pDListNode pHead); // 結點數

void DListClear(pDListNode pHead); // 清空

void DListDestroy(pDListNode * pHead); // 銷燬

# endif // __DOUBLELINKLIST_H__

操作說明

首先,分別使用頭插、尾插、任意插三種方式添加 6 個數字,這裏需要說明的是,由於循環鏈表最終形成了一個環,這裏通過兩邊的數字爲 0 來模擬環的實現,實際上就是 頭結點,這裏的 0 毫無實際意義。

接下來刪除第一個 1,

然後刪除所有的 1,

任意刪後,

此時判斷鏈表是否爲空,

計算鏈表的結點數(ps: 包含頭結點,並且頭結點數記爲 1),

頭刪後,

尾刪後,

此時再次插入兩個數字,

清空鏈表,

再插入兩個數字,查找並修改其中的一個數字,

最後銷燬鏈表並退出。

難點剖析

下面通過兩張圖對刪除和插入操作加以分析。

  • 在任意結點前插入結點

需要注意的是:pPrevPos 是 pPos 結點的前驅。

  • 在任意結點前刪除結點

需要注意的是:pPrevNode 是 pPos 結點的前驅,pNextNode 是 pPos 結點的後繼。

源代碼

DoubleLinkList.c

#define _CRT_SECURE_NO_WARNINGS 1

/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 文件名稱:DoubleLinkList.c
* 功能:有頭結點的循環雙鏈表基本操作內部實現細節
*
* 當前版本:V1.0
* 作者:sustzc
* 完成日期:2018年5月29日13:53:31
*/

# include "DoubleLinkList.h"

/*
*	函數名稱:CreateNewNode
*
*	函數功能:創建新節點
*
*	入口參數:data
*
*	出口參數:void
*
*	返回類型:pDListNode
*/

static pDListNode CreateNewNode(DataType data)
{
	pDListNode pNewNode = (pDListNode)malloc(sizeof(DListNode));

	if (NULL == pNewNode)
	{
		perror("內存分配失敗!\n");
		exit(-1);
	}
	else
	{
		pNewNode->data = data;
		pNewNode->pPrev = NULL;
		pNewNode->pNext = NULL;
	}

	return pNewNode;
}

/*
*	函數名稱:DListInit
*
*	函數功能:帶有頭結點的循環雙鏈表的初始化
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListInit(pDListNode * pHead)
{
	int no_data = 0;

	assert(NULL != pHead);

	//頭結點的數據無效(ps:這裏用0表示)
	*pHead = CreateNewNode(no_data);
	(*pHead)->pPrev = *pHead;
	(*pHead)->pNext = *pHead;

	return;
}

/*
*	函數名稱:DListPrint
*
*	函數功能:打印鏈表存儲的內容(ps:本身有頭結點,因此傳一級指針即可)
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListPrint(pDListNode pHead)
{
	pDListNode pCur = NULL;

	assert(NULL != pHead);

	printf("鏈表內容爲:\n");

	printf("%2d -> ", pHead->data);

	for (pCur = pHead->pNext; pHead != pCur; pCur = pCur->pNext)
	{
		printf("%2d -> ", pCur->data);
	}

	printf("%2d\n", pHead->data);

	return;
}


/*
*	函數名稱:AddMenu
*
*	函數功能:添加數據菜單顯示
*
*	入口參數:void
*
*	出口參數:select
*
*	返回類型:int
*/

int AddMenu(void)
{
	int select = 0;

	printf("************************************\n");
	printf("*******        添加數據      ********\n");
	printf("*******  1. 頭插     2. 尾插  *******\n");
	printf("*******  3. 任意插   0. 退出  *******\n");;
	printf("************************************\n");
	printf("select>");

	assert(1 == scanf("%d", &select));

	return select;
}

/*
*	函數名稱:DListPushFront
*
*	函數功能:頭插
*
*	入口參數:pHead, data
*
*	出口參數:void
*
*	返回類型:void
*/

void DListPushFront(pDListNode pHead, DataType data)
{
	pDListNode pNewNode = NULL;
	pDListNode pFirst = NULL;

	assert(NULL != pHead);

	pNewNode = CreateNewNode(data);
	pFirst = pHead->pNext;

	pHead->pNext = pNewNode;
	pNewNode->pPrev = pHead;
	pNewNode->pNext = pFirst;
	pFirst->pPrev = pNewNode;

	return;
}

/*
*	函數名稱:DListPushBack
*
*	函數功能:尾插
*
*	入口參數:pHead, data
*
*	出口參數:void
*
*	返回類型:void
*/

void DListPushBack(pDListNode pHead, DataType data)
{
	pDListNode pNewNode = NULL;
	pDListNode pLast = NULL;

	assert((NULL != pHead));

	pNewNode = CreateNewNode(data);
	pLast = pHead->pPrev;

	pLast->pNext = pNewNode;
	pNewNode->pPrev = pLast;
	pNewNode->pNext = pHead;
	pHead->pPrev = pNewNode;
	
	return;
}

/*
*	函數名稱:DListInsert
*
*	函數功能:給定結點插入,插入到結點前
*
*	入口參數:pHead, pPos, data
*
*	出口參數:void
*
*	返回類型:void
*/

void DListInsert(pDListNode pHead, pDListNode pPos, DataType data)
{
	pDListNode pNewNode = NULL;
	pDListNode pPrevPos = NULL;

	assert((NULL != pHead) && (NULL != pPos));

	pNewNode = CreateNewNode(data);
	pPrevPos = pPos->pPrev;
	
	pPrevPos->pNext = pNewNode;
	pNewNode->pPrev = pPrevPos;
	pNewNode->pNext = pPos;
	pPos->pPrev = pNewNode;

	return;
}

/*
*	函數名稱:DListAdd
*
*	函數功能:雙鏈表的添加
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListAdd(pDListNode pHead)
{
	DataType data = 0;
	int select = AddMenu();

	assert(NULL != pHead);

	switch (select)
	{
		case EXIT_ADD:
		{
			printf("退出添加!\n");
			break;
		}
		case PUSH_FRONT:
		{
			printf("請輸入要添加的數據:>");
			assert(1 == scanf("%d", &data));

			DListPushFront(pHead, data);
			printf("頭插成功!\n");

			break;
		}
		case PUSH_BACK:
		{
			printf("請輸入要添加的數據:>");
			assert(1 == scanf("%d", &data));

			DListPushBack(pHead, data);
			printf("尾插成功!\n");

			break;
		}
		case PUSH_INSERT:
		{	
			pDListNode pCur = pHead->pNext->pNext;

			printf("請輸入要添加的數據:>");
			assert(1 == scanf("%d", &data));

			DListInsert(pHead, pCur, data);
			printf("任意插成功!\n");

			break;
		}
		default:
		{
			printf("輸入有誤!\n");
			break;
		}
	}

	return;
}

/*
*	函數名稱:DelMenu
*
*	函數功能:刪除數據菜單顯示
*
*	入口參數:void
*
*	出口參數:select
*
*	返回類型:int
*/

int DelMenu(void)
{
	int select = 0;

	printf("************************************************************************\n");
	printf("*******                   刪除數據                               ********\n");
	printf("*******    1. 頭刪                  2. 尾刪                      ********\n");
	printf("*******    3. 任意刪                4. 只刪除要刪數據中的第一個     ********\n");;
	printf("*******    5. 刪除所有要刪除的數據    0. 退出                      ********\n");
	printf("************************************************************************\n");
	printf("select>");

	assert(1 == scanf("%d", &select));

	return select;
}

/*
*	函數名稱:DListPopFront
*
*	函數功能:頭刪
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListPopFront(pDListNode pHead)
{
	pDListNode pFirst = NULL;
	pDListNode pFirstNext = NULL;

	assert(NULL != pHead);

	pFirst = pHead->pNext;
	pFirstNext = pHead->pNext->pNext;
	
	pHead->pNext = pFirstNext;
	pFirstNext->pPrev = pHead;

	free(pFirst);

	return;
}

/*
*	函數名稱:DListPopBack
*
*	函數功能:尾刪
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListPopBack(pDListNode pHead)
{
	pDListNode pLast = NULL;
	pDListNode pLastPrev = NULL;

	assert(NULL != pHead);

	pLast = pHead->pPrev;
	pLastPrev = pHead->pPrev->pPrev;

	pLastPrev->pNext = pHead;
	pHead->pPrev = pLastPrev;

	free(pLast);
	
	return;
}

/*
*	函數名稱:DListPopBack
*
*	函數功能:給定結點刪除
*
*	入口參數:pHead, pPos
*
*	出口參數:void
*
*	返回類型:void
*/

void DListErase(pDListNode pHead, pDListNode pPos)
{
	pDListNode pPrevNode = NULL;
	pDListNode pNextNode = NULL;

	assert((NULL != pHead) && (NULL != pPos));

	pPrevNode = pPos->pPrev;
	pNextNode = pPos->pNext;

	pPrevNode->pNext = pNextNode;
	pNextNode->pPrev = pPrevNode;

	free(pPos);

	return;
}

/*
*	函數名稱:DListFind
*
*	函數功能:按值查找,返回第一個找到的結點指針,如果沒找到,返回 NULL
*
*	入口參數:pHead, data
*
*	出口參數:pCur or NULL
*
*	返回類型:pDListNode
*/

pDListNode DListFind(pDListNode pHead, DataType data)
{
	pDListNode pCur = NULL;

	assert(NULL != pHead);

	for (pCur = pHead->pNext; pHead != pCur; pCur = pCur->pNext)
	{
		if (data == pCur->data)
		{
			return pCur;
		}
		else
		{
			;
		}
	}

	return NULL;
}

/*
*	函數名稱:DListSearch
*
*	函數功能:查找輸入數據,如果找到該數據則提示找到,反之則提示沒找到 
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListSearch(pDListNode pHead)
{
	DataType data = 0;
	pDListNode pPosNode = NULL;

	assert(NULL != pHead);

	printf("請輸入要查找的數據:>");
	assert(1 == scanf("%d", &data));
	pPosNode = DListFind(pHead, data);

	if (NULL != pPosNode)
	{
		printf("找到了%d\n", data);
	}
	else
	{
		printf("沒找到%d\n", data);
	}

	return;
}

/*
*	函數名稱:DListModify
*
*	函數功能:修改雙鏈表數據 
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListModify(pDListNode pHead)
{
	pDListNode pPosNode = NULL;
	DataType old_data = 0;
	DataType new_data = 0;

	assert(NULL != pHead);

	printf("請輸入要修改的數據:>");
	assert(1 == scanf("%d", &old_data));

	pPosNode = DListFind(pHead, old_data);

	if (NULL != pPosNode)
	{
		printf("請輸入修改後的數據:>");
		assert(1 == scanf("%d", &new_data));

		pPosNode->data = new_data;

		printf("修改成功!\n");
	}
	else
	{
		printf("沒找到%d\n", old_data);
	}

	return;
}

/*
*	函數名稱:DListRemove
*
*	函數功能:按值刪除,只刪遇到的第一個
*
*	入口參數:pHead, data
*
*	出口參數:void
*
*	返回類型:void
*/

void DListRemove(pDListNode pHead, DataType data)
{
	pDListNode pPosNode = DListFind(pHead, data);

	if (NULL != pPosNode)
	{
		DListErase(pHead, pPosNode);
	}
	else
	{
		printf("要刪除的數據不存在!\n");
	}

	return;
}

/*
*	函數名稱:DListRemoveAll
*
*	函數功能:按值刪除,刪除所有的
*
*	入口參數:pHead, data
*
*	出口參數:void
*
*	返回類型:void
*/

void DListRemoveAll(pDListNode pHead, DataType data)
{
	pDListNode pDel = pHead->pNext;
	pDListNode pNextNode = NULL;
	pDListNode pPrevNode = NULL;

	while (pHead != pDel)
	{
		if (data == pDel->data)
		{
			pNextNode = pDel->pNext;
			pPrevNode = pDel->pPrev;

			//將pDel這個節點空出來
			pPrevNode->pNext = pNextNode;
			pNextNode->pPrev = pPrevNode;

			free(pDel);
			//pDel從pNextNode開始向後遍歷
			pDel = pNextNode;
		}
		else
		{
			pDel = pDel->pNext;
		}
	}

	return;
}

/*
*	函數名稱:DListDel
*
*	函數功能:雙鏈表的刪除
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListDel(pDListNode pHead)
{
	DataType data = 0;
	int select = DelMenu();

	assert(NULL != pHead);

	switch (select)
	{
		case EXIT_DELETE:
		{
			printf("退出刪除!\n");
			break;
		}
		case POP_FRONT:
		{
			DListPopFront(pHead);
			printf("頭刪成功!\n");

			break;
		}
		case POP_BACK:
		{
			DListPopBack(pHead);
			printf("尾刪成功!\n");

			break;
		}
		case POP_ERASE:
		{
			DListErase(pHead, pHead->pNext->pNext);
			printf("任意刪成功!\n");

			break;
		}
		case REMOVE_FIRST:
		{
			printf("請輸入要刪除的數據:>");
			assert(1 == scanf("%d", &data));

			DListRemove(pHead, data);
			printf("刪除第一個要刪除的數據成功!\n");

			break;
		}
		case REMOVE_ALL:
		{
			printf("請輸入要刪除的數據:>");
			assert(1 == scanf("%d", &data));

			DListRemoveAll(pHead, data);
			printf("要刪除的數據刪除成功!\n");

			break;
		}
		default:
		{
			printf("輸入有誤!\n");
			break;
		}
	}

	return;
}

/*
*	函數名稱:DListIsEmpty
*
*	函數功能:雙鏈表是否爲空
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListIsEmpty(pDListNode pHead)
{
	pDListNode pNextNode = NULL;
	pDListNode pPrevNode = NULL;

	assert(NULL != pHead);
	
	pPrevNode = pHead->pPrev;
	pNextNode = pHead->pNext;

	if ((pHead != pPrevNode) && (pHead != pNextNode))
	{
		printf("鏈表不爲空!\n");
	}
	else
	{
		printf("鏈表爲空!\n");
	}

	return;
}

/*
*	函數名稱:DListSize
*
*	函數功能:雙鏈表的結點數(ps:本身有頭結點,因此傳一級指針即可)
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListSize(pDListNode pHead)
{
	//頭結點也算一個結點
	int count = 1;
	pDListNode pCur = NULL;

	assert(NULL != pHead);

	for (pCur = pHead->pNext; pHead != pCur; pCur = pCur->pNext)
	{
		count++;
	}

	printf("雙鏈表的結點數爲: %d\n", count);
		
	return;
}

/*
*	函數名稱:DListClear
*
*	函數功能:清空雙鏈表,頭結點還在
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListClear(pDListNode pHead)
{
	pDListNode pNode = NULL;
	pDListNode pNextNode = NULL;

	assert(NULL != pHead);

	for (pNode = pHead->pNext; pHead != pNode; pNode = pNextNode) 
	{
		pNextNode = pNode->pNext;
		free(pNode);
	}
	
	pHead->pPrev = pHead;
	pHead->pNext = pHead;

	printf("清空雙鏈表成功!\n");

	return;
}

/*
*	函數名稱:DListDestroy
*
*	函數功能:銷燬雙鏈表,頭結點不在
*
*	入口參數:pHead
*
*	出口參數:void
*
*	返回類型:void
*/

void DListDestroy(pDListNode * pHead)
{
	pDListNode pNode = NULL;
	pDListNode pNextNode = NULL;

	assert(NULL != pHead);

	for (pNode = (*pHead)->pNext; (*pHead) != pNode; pNode = pNextNode) 
	{
		pNextNode = pNode->pNext;
		free(pNode);
	}

	free(*pHead);
	*pHead = NULL;

	printf("銷燬雙鏈表成功!\n");

	return;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 文件名稱:test.c
* 功能:測試帶有頭結點的循環雙鏈表基本操作
*
* 當前版本:V1.0
* 作者:sustzc
* 完成日期:2018年5月29日13:55:40
*/

# include "DoubleLinkList.h"

/*
*	函數名稱:MainMenu
*
*	函數功能:雙鏈表主菜單顯示
*
*	入口參數:void
*
*	出口參數:choose
*
*	返回類型:int
*/

int MainMenu(void)
{
	int choose = 0;

	printf("**************************************\n");
	printf("*******     歡迎操作雙鏈表     *******\n");
	printf("*******  1. 添加      2. 刪除  *******\n");
	printf("*******  3. 查找      4. 修改  *******\n");
	printf("*******  5. 清空      6. 數量  *******\n");
	printf("*******  7. 判空      8. 銷燬  *******\n");
	printf("*******  9. 顯示      0. 退出  *******\n");
	printf("**************************************\n");
	printf("choose>");

	assert(1 == scanf("%d", &choose));

	return choose;
}

/*
*	函數名稱:main
*
*	函數功能:主程序
*
*	入口參數:void
*
*	出口參數:0
*
*	返回類型:int
*/

int main(void)
{
	int choose = 0;
	pDListNode dlnode = NULL;
	//初始化雙鏈表
	DListInit(&dlnode);

	do
	{
		choose = MainMenu();

        switch(choose)
		{
			case QUIT:
				printf("退出雙鏈表!\n");
				break;
			case ADD:
				{
					DListAdd(dlnode);
					DListAdd(dlnode);
					DListAdd(dlnode);	
				}
				break;
			case DETELE:
				DListDel(dlnode);
				break;
			case SEARCH:
				DListSearch(dlnode);
				break;
			case MODIFY:
				DListModify(dlnode);
				break;
			case CLEAR:
				DListClear(dlnode);
				break;
			case NUM_NODE:
				DListSize(dlnode);
				break;
			case IS_EMPTY:
				DListIsEmpty(dlnode);
				break;
			case DESTORY:
				DListDestroy(&dlnode);
				break;
			case SHOW:
				DListPrint(dlnode);
				break;
			default:
				printf("輸入有誤,請重新輸入!\n");
				break;
		}
	}while(choose);

	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章