1. 程式人生 > 實用技巧 >C++ 資料結構 1:線性表

C++ 資料結構 1:線性表

1 資料結構

1.1 資料結構中基本概念

資料:程式的操作物件,用於描述客觀事物。

資料的特點:

  • 可以輸入到計算機

  • 可以被計算機程式處理

資料是一個抽象的概念,將其進行分類後得到程式設計語言中的型別。如:int,float,char等等。

資料元素:組成資料的基本單位。

資料項:一個數據元素由若干資料項組成。

資料物件:性質相同的資料元素的集合(比如:陣列,連結串列)。

示例程式碼:

//宣告一個結構體型別
struct _MyTeacher   //一種資料型別
{
	char	name[32];
	char	tile[32];
	int	age;
	char	addr[128];
};

int main()
{
	struct _MyTeacher  t1;        //資料元素
        struct _MyTeacher tArray[30]; //資料物件
	memset(&t1, 0, sizeof(t1));

	strcpy(t1.name, "name"); //資料項
	strcpy(t1.addr, "addr"); //資料項
	strcpy(t1.tile, "addr"); //資料項
	t1.age = 1;
}

資料元素之間不是獨立的,存在特定的關係,這些關係即結構。

資料結構 指 資料物件中資料元素 之間的關係。

1.2 資料的邏輯結構

資料元素之間的 邏輯關係。即 從邏輯關係上描述資料,它與資料的儲存無關,是獨立於計算機的。邏輯結構可細分為4類:

  • 集合:資料元素間除 “同屬一個結合”外,無其他關係。

  • 線性結構:一個對一個,如線性表、棧、佇列。

  • 樹形結構:一個對多個,如樹。

  • 圖狀結構:多個對多個,如圖。

1.3 資料的物理結構

資料的物理結構也稱儲存結構,是 資料的邏輯結構在計算機儲存器內的表示(或映像)。它依賴於計算機。

儲存結構可分為四大類:順序、鏈式、索引、雜湊。

最常用的儲存結構:

  • 順序儲存結構:藉助元素在儲存器中的相對位置來表示資料元素間的邏輯關係。

  • 鏈式儲存結構:藉助指示元素儲存地址的指標表示資料元素間的邏輯關係。

1.4 資料的邏輯結構與儲存結構的關係

  • 演算法設計 -> 邏輯結構

  • 演算法實現 -> 儲存結構

2 線性表

2.1 線性表基本概念

2.1.1 線性表定義

定義:線性表(List)是零個或多個數據元素的有限序列。

特點:

  • 資料元素之間是 有順序的

  • 資料元素個數是 有限的

  • 資料元素的 型別必須相同

2.1.2 數學定義

線性表是具有相同型別的 n(≥ 0)個數據元素的有限序列(a0, a1, a2, …, an)。

ai是表項,n 是表長度。

2.1.3 性質

a0 為線性表的第一個元素,只有一個後繼。

an 為線性表的最後一個元素,只有一個前驅。

除 a0 和 an 外的其它元素 ai,既有前驅,又有後繼。

線性表能夠逐項訪問和順序存取。

2.1.4 線性表操作

  • 建立線性表

  • 銷燬線性表

  • 清空線性表

  • 將元素插入線性表

  • 將元素從線性表中刪除

  • 獲取線性表中某個位置的元素

  • 獲取線性表的長度

2.1.5 線性表的抽象資料型別定義

ADT線性表(List)

Data
線性表的資料物件集合為{a1,a2,……,an},每個元素的型別均為 DataType。其中,除第一個元素 a1 外,每個元素有且只有一個直接前驅元素,除了最後一個元素 an 外,每個元素有且只有一個直接後繼元素。資料元素之間的關係是一一對應的。

Operation(操作)
	// 初始化,建立一個空的線性表L。
	InitList(*L);
	// 若線性表為空,返回true,否則返回false
	ListEmpty(L);
	// 將線性表清空
	ClearList(*L);
	// 將線性表 L 中的第 i 個位置的元素返回給 e
	GetElem(L, i, *e);
	// 線上性表L中的第 i 個位置插入新元素 e
	ListInsert(*L, i, e);
	// 刪除線性表L中的第 i 個位置元素,並用 e 返回其值
	ListDelete(*L,i,*e);
	// 返回線性表 L 的元素個數
	ListLength(L);
	// 銷燬線性表
	DestroyList(*L);

endADT

2.2 順序儲存的線性表

2.2.1 基本概念

線性表的順序儲存結構,指的是 用一段地址連續的儲存單元依次儲存線性表的資料元素

線性表 (a1,a2,……,an)的順序儲存示意圖如下:

2.2.2 設計與實現

部分操作演算法如下所示:

插入元素演算法:

  1. 判斷線性表是否合法

  2. 判斷插入位置是否合法

  3. 把最後一個元素到插入位置的元素後移一個位置

  4. 將新元素插入

  5. 線性表長度加1

獲取元素演算法:

  1. 判斷線性表是否合法

  2. 判斷位置是否合法

  3. 直接通過陣列下標的方式獲取元素

刪除元素演算法:

  1. 判斷線性表是否合法

  2. 判斷刪除位置是否合法

  3. 將元素取出

  4. 將刪除位置後的元素分別向前移動一個位置

  5. 線性表長度減1

實現程式碼

SqList.h

#define MAXSIZE 50	// 最大容量
typedef int ElemType;	// 資料型別


// 定義線性表結構體
typedef struct SQLIST
{
    ElemType data[MAXSIZE];	// 陣列
    int length;			//陣列長度 
}SqList;

// 初始化,建立一個空的線性表L
void InitList(SqList *L);

// 若線性表為空,返回true,否則返回false
int ListEmpty(SqList L);

// 將線性表清空
void ClearList(SqList *L);

// 將線性表L 中的第 pos 個位置的元素返回給 e
void GetElem(SqList L, int pos, ElemType *e);

// 線上性表L 中的第 pos 個位置插入新元素 e
void ListInsert(SqList *L, int pos, ElemType e);

// 刪除線性表L 中的第 pos 個位置元素,並用 e 返回其值
void ListDelete(SqList *L, int pos, ElemType *e);

// 返回線性表L 的元素個數
int ListLength(SqList L);

SqList.c

#include "SqList.h"
#include <string.h>

void InitList(SqList *L)
{
	// 初始化
	L->length = 0;
	
	memset(L->data, 0, sizeof(L->data));
}

int ListEmpty(SqList L)
{
	if (L.length == 0)
	{
		return 1;
	}

	return 0;
}

void ClearList(SqList *L)
{
	InitList(L);
}

void GetElem(SqList L, int pos, ElemType *e)
{
	// pos值是否合法
	if (pos < 0 || pos >= L.length)
	{
		return;
	}

	// 如果為空
	if (L.length == 0)
	{
		return;
	}

	// 賦值
	*e = L.data[pos];
}

void ListInsert(SqList *L, int pos, ElemType e)
{
	int i = -1;

	// 錯誤處理
	if (pos < 0 || pos > L->length)
	{
		return;
	}

	if (L->length >= MAXSIZE)
	{
		// 已滿
		return;
	}

	// 移動
	// 從length-1 到 pos 依次後移
	for (i = L->length - 1; i >= pos; --i)
	{
		L->data[i + 1] = L->data[i];
	}

	// 賦值 -- 資料的拷貝
	L->data[pos] = e;
	
	L->length++;
}

void ListDelete(SqList *L, int pos, ElemType *e)
{
	int i = -1;

	// 錯誤處理
	if (pos < 0 || pos >= L->length)
	{
		return;
	}

	if (L->length == 0)
	{
		// 空表
		return;
	}

	// 儲存要刪除的位置的值
	*e = L->data[pos];

	// pos+1 --> length-1 
	for (i = pos + 1; i < L->length; ++i)
	{
		// 前移操作
		L->data[i - 1] = L->data[i];
	}

	// 長度
	L->length--;
}

int ListLength(SqList L)
{
	return L.length;
}

main.c

#include "SqList.h"

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

int main()
{
	SqList list;

	int i = -1;

	// 初始化
	InitList(&list);

	printf("初始化順序線性表成功\n");

	printf("\n");

	// 插入操作
	for (i = 0; i < 10; ++i)
	{
		ListInsert(&list, i, i + 1);

		printf("InsertElem value = %d\n",i + 1);
	}

	printf("\n");

	// 遍歷
	for (i = 0; i < ListLength(list); ++i)
	{
		int tmp;

		GetElem(list, i, &tmp);

		printf("GetElem value = %d\n", tmp);
	}

	printf("\n");

	// 刪除順序表元素
	while (ListEmpty(list) != 1)
	{
		int tmp;

		ListDelete(&list, 0, &tmp);

		printf("DeleteElem value = %d\n", tmp);
	}

	printf("\n");

	system("pause");

	return 0;
}

執行結果

2.2.3 優點和缺點

優點

  • 無需為線性表中的邏輯關係增加額外的空間

  • 可以快速的獲取表中合法位置的元素

缺點

  • 插入和刪除操作需要移動大量元素

  • 當線性表長度變化較大時難以確定儲存空間的容量

2.3 鏈式儲存的線性表

2.3.1 基本概念

鏈式儲存定義:為了表示每個資料元素與其直接後繼元素之間的邏輯關係,每個元素除了儲存本身的資訊外,還需要儲存指示其直接後繼的資訊。

單鏈表:線性表的鏈式儲存結構中,每個節點中只包含一個指標域,這樣的連結串列叫單鏈表。

表頭結點:連結串列中的第一個結點,包含指向第一個資料元素的指標以及連結串列自身的一些資訊

資料結點:連結串列中代表資料元素的結點,包含指向下一個資料元素的指標和資料元素的資訊

尾結點:連結串列中的最後一個數據結點,其下一元素指標為空,表示無後繼。

2.3.2 設計與實現

插入操作

node->next = current->next;
current->next = node;

刪除操作

current->next = ret->next

實現程式碼

Linklist.h

#ifndef _LINKLIST_H
#define _LINKLIST_H

typedef int ElemType;

#if 1	// 傳統連結串列

typedef struct NODE
{
	ElemType data;	// 資料
	struct NODE *next;	// 指向後繼節點的指標
}Node;

typedef struct LINKLIST
{
	int length;
	Node header;
}LinkList;


#else	// 企業連結串列

// 小連結串列節點
typedef struct NODE
{
	struct NODE *next;	// 指向後繼節點的指標
}Node;

// 業務節點(包含一個小連結串列節點)
typedef struct _LISTNODE
{
	Node node;	// 小連結串列節點
	void *data;	// 指向資料地址的指標
}ListNode;

typedef struct LINKLIST
{
	int length;
	ListNode header;
}LinkList;

#endif

// 初始化,建立一個空的線性表L。
void InitList(LinkList *L);
// 若線性表為空,返回true,否則返回false
int ListEmpty(LinkList L);
// 將線性表清空
void ClearList(LinkList *L);
// 將線性表L中的第pos個位置的元素返回給e
void GetElem(LinkList L, int pos, ElemType *e);
// 線上性表L中的第pos個位置插入新元素e
void ListInsert(LinkList *L, int pos, ElemType e);
// 刪除線性表L中的第pos個位置元素,並用e返回其值
void ListDelete(LinkList *L, int pos, ElemType *e);
// 返回線性表L的元素個數
int ListLength(LinkList L);
// 銷燬線性表
void DestroyList(LinkList *L);

#endif	// _LINKLIST_H

Linklist.c

#include "LinkList.h"

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

void InitList(LinkList *L)
{
	// 初始化
	L->length = 0;
	L->header.next = NULL;
}

int ListEmpty(LinkList L)
{
	if (L.length == 0)
	{
		return 1;
	}
	return 0;
}

void ClearList(LinkList *L)
{
	// 刪除並釋放所有節點
	while (L->length)
	{
		int tmp;
		ListDelete(L, 0, &tmp);
	}
}

void GetElem(LinkList L, int pos, ElemType *e)
{
	// 輔助指標 指向頭結點
	Node* pCur = &L.header;

	int i = -1;


	// 錯誤檢查
	if (pos < 0 || pos >= L.length)
	{
		return;
	}

	// 遍歷連結串列, 找到pos位置
	for (i = 0; i <= pos; ++i)
	{
		// 輔助指標後移
		pCur = pCur->next;
	}
	// 取值
	*e = pCur->data;
}

void ListInsert(LinkList *L, int pos, ElemType e)
{
	// 輔助指標 指向頭結點
	Node* pCur = &L->header;

	int i = -1;

	// 建立新節點
	Node* pNew = (Node*)malloc(sizeof(Node));

	// 錯誤檢查
	if (pos < 0 || pos > L->length)
	{
		return;
	}

	// 遍歷連結串列, 找到pos位置
	for (i = 0; i < pos; ++i)
	{
		// 輔助指標後移
		pCur = pCur->next;
	}
	
	// 初始化
	pNew->data = e;

	// 插入節點操作
	// 先連線後半部分
	pNew->next = pCur->next;
	// 前半部分
	pCur->next = pNew;

	// 長度+1
	L->length++;
}

void ListDelete(LinkList *L, int pos, ElemType *e)
{
	// 輔助指標 指向頭結點
	Node* pCur = &L->header;

	// 輔助指標
	Node* pDel = pCur->next;

	int i = -1;

	// 錯誤檢查
	if (pos < 0 || pos >= L->length)
	{
		return;
	}

	if (L->length == 0)
	{
		return;
	}

	// 遍歷連結串列, 找到pos位置
	for (i = 0; i < pos; ++i)
	{
		// 輔助指標後移
		pCur = pCur->next;
	}
	// 儲存pos位置節點的值
	*e = pDel->data;

	// 刪除節點從連結串列中
	pCur->next = pDel->next;
	// 釋放記憶體
	free(pDel);

	// 長度-1
	L->length--;
}

int ListLength(LinkList L)
{
	return L.length;
}

void DestroyList(LinkList *L)
{
	ClearList(L);
}

main.c

#include "LinkList.h"

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

void main()
{
	LinkList ls;

	int i = -1;

	// init
	InitList(&ls);

	printf("初始化鏈式線性表成功\n");

	printf("\n");

	// insert data
	for (i = 0; i < 10; ++i)
	{
		ListInsert(&ls, i, i + 1);

		printf("InsertElem value = %d\n",i + 1);
	}

	printf("\n");

	// 遍歷
	for (i = 0; i < ListLength(ls); ++i)
	{
		int tmp;

		GetElem(ls, i, &tmp);

		printf("GetElem value = %d\n", tmp);
	}
	printf("\n");

	// 刪除全部
	while (ListEmpty(ls) != 1)
	{
		int tmp;

		// 逐個刪除
		ListDelete(&ls, 0, &tmp);

		printf("DeleteElem value = %d\n", tmp);
	}

	printf("\n");

	// 銷燬連結串列
	DestroyList(&ls);

	system("pause");
}

執行結果

2.3.3 優點和缺點

優點

  • 無需一次性定製連結串列的容量

  • 插入和刪除操作無需移動資料元素

缺點

  • 資料元素必須儲存後繼元素的位置資訊

  • 獲取指定資料的元素操作需要順序訪問之前的元素

2.3.4 連結串列技術領域推演

2.4 迴圈連結串列

2.4.1 基本概念

迴圈連結串列的定義:單鏈表中最後一個結點的指標域指向頭結點,整個連結串列形成一個環。

迴圈連結串列的操作

迴圈連結串列擁有單鏈表的所有操作:

  • 建立連結串列

  • 銷燬連結串列

  • 獲取連結串列長度

  • 清空連結串列

  • 獲取第pos個元素操作

  • 插入元素到位置pos

  • 刪除位置pos處的元素

迴圈連結串列新操作

  • 將遊標重置指向連結串列中的第一個資料元素
    CircleListNode* CircleList_Reset(CircleList* list);

  • 獲取當前遊標指向的資料元素
    CircleListNode* CircleList_Current(CircleList* list);

  • 將遊標移動指向到連結串列中的下一個資料元素
    CircleListNode* CircleList_Next(CircleList* list);

  • 直接指定刪除連結串列中的某個資料元素
    CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node);

遊標的定義:在迴圈連結串列中可以定義一個“當前”指標,這個指標通常稱為遊標,可以通過這個遊標來遍歷連結串列中的所有元素。

2.4.2 設計與實現

迴圈連結串列插入元素的分析

  1. 普通插入元素

  1. 尾插法

輔助指標向後跳length次,指向最後面那個元素(length-1位置),因為是迴圈連結串列,所以length位置元素即為第一個資料節點。

CircleList_Insert(list, (CircleListNode*)&v1, CircleList_Length(list));

  1. 頭插法

注意:

  • 要進行頭插法,需要求出尾節點,連線新的0號位置節點

  • 第一次插入元素時,讓遊標指向0號結點(即第一個資料節點)

CircleList_Insert(list, (CircleListNode*)&v1, 0);

  1. 第一次插入元素(相當於頭插法)

要求出尾節點,尾節點指標指向第一個資料節點(即自己指向自己)

迴圈連結串列刪除結點分析

  1. 刪除普通結點

  1. 刪除頭結點(刪除0號位置處元素)

要求出尾結點,連線新的零號位置節點

2.4.2.1 迴圈連結串列基本功能實現程式碼

實現程式碼:

CircleList.h

#ifndef _CIRCLE_LIST_H
#define _CIRCLE_LIST_H

//自定義迴圈連結串列資料型別
typedef void CircleList;
//自定義迴圈連結串列節點資料型別
typedef struct tag_CirclListNode
{
	struct tag_CirclListNode *next;
}CircleListNode;

//建立結構體管理連結串列
typedef struct tag_CircleList
{
	//迴圈連結串列頭結點
	CircleListNode	header;
	//迴圈連結串列遊標
	CircleListNode	*slider;
	//迴圈連結串列長度
	int		length;
}TCircleList;

//建立迴圈連結串列
CircleList* CircleList_Create();

//銷燬迴圈連結串列
void CircleList_Destroy(CircleList* list);

//清空迴圈連結串列
void CircleList_Clear(CircleList* list);

//獲取迴圈連結串列長度
int CircleList_Length(CircleList* list);

//在迴圈連結串列中插入新節點
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos);

//獲取迴圈連結串列中的指定位置的節點
CircleListNode* CircleList_Get(CircleList* list, int pos);

//刪除迴圈連結串列中的指定位置的節點
CircleListNode* CircleList_Delete(CircleList* list, int pos);

//------------------ new add ------------------

//直接指定刪除連結串列中的某個資料元素
CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node);

//將遊標重置指向連結串列中的第一個資料元素
CircleListNode* CircleList_Reset(CircleList* list);

//獲取當前遊標指向的資料元素
CircleListNode* CircleList_Current(CircleList* list);

//將遊標移動指向到連結串列中的下一個資料元素
CircleListNode* CircleList_Next(CircleList* list);

#endif //_CIRCLE_LIST_H

CircleList.c

#include "CircleList.h"

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

//建立迴圈連結串列
CircleList* CircleList_Create()
{
	//定義TCircleList指標變數,並分配記憶體空間
	TCircleList* tlist = (TCircleList*)malloc(sizeof(TCircleList));

	if (tlist == NULL)
	{
		printf("error: TCircleList* tlist = (TCircleList*)malloc(sizeof(TCircleList)) \n");
		return NULL;
	}

	//資料初始化
	tlist->header.next = NULL;
	tlist->slider = NULL;
	tlist->length = 0;

	return (CircleList*)tlist;
}

//銷燬迴圈連結串列
void CircleList_Destroy(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("Destory error: list 為無效指標\n");

		return;
	}

	free(list);
}

//清空迴圈連結串列
void CircleList_Clear(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("Clear error: list 為無效指標\n");

		return;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;
	//將長度重置為0
	tlist->length = 0;
	//頭結點指標域指向空
	tlist->header.next = NULL;
	//遊標指向空
	tlist->slider = NULL;
}

//獲取迴圈連結串列長度
int CircleList_Length(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("Length error: list 為無效指標\n");

		return -1;
	}
	//型別轉換並賦值
	tlist = (TCircleList*)list;

	return tlist->length;
}

//在迴圈連結串列中插入新節點
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos)
{
	int i;

	//定義TCircleList指標變數
	TCircleList *tlist = NULL;
	//定義輔助指標變數
	CircleListNode	*currentNode = NULL;

	//判斷list是否為有效指標
	if (list == NULL || node == NULL || pos < 0)
	{
		printf("Insert error: if (list == NULL || node == NULL || pos < 0)\n");

		return -1;
	}
	//型別轉換並賦值
	tlist = (TCircleList*)list;

	//元素插入
	//step 1: 使用輔助指標變數,指向頭結點
	currentNode = &tlist->header;
	//step 2: 找到pos-1位置節點
	for (i = 0; i < pos; ++i)
	{
		//判斷是否有後繼節點
		if (currentNode->next != NULL)
		{
			//指標後移
			currentNode = currentNode->next;
		}
		else
		{
			//沒有後繼節點跳出迴圈
			break;
		}
	}

	//step 3: 將node節點的指標指向當前節點(pos-1)的後繼節點(pos)
	node->next = currentNode->next;

	//step 4: 當前節點的指標指向node節點的地址
	currentNode->next = node;

	//step 5: 如果是第一次插入節點
	if (tlist->length == 0)
	{
		//將遊標指向新插入節點
		tlist->slider = node;
	}

	//step 6: 連結串列長度加1
	tlist->length++;

	//step 7:若頭插法 currentNode仍然指向頭部
	//原因: 跳0步, 沒有跳走
	if (currentNode == &tlist->header)
	{
		CircleListNode* lastNode = CircleList_Get(list, tlist->length - 1);

		//最後一個節點的指標,指向第一個資料節點
		lastNode->next = currentNode->next;
	}

	return 0;
}

//獲取迴圈連結串列中的指定位置的節點
CircleListNode* CircleList_Get(CircleList* list, int pos)
{
	int i;

	//定義TCircleList指標變數
	TCircleList *tlist = NULL;
	//定義輔助指標變數
	CircleListNode	*currentNode = NULL;

	//判斷list是否為有效指標
	if (list == NULL || pos < 0)
	{
		printf("CircleList_Get error: if (list == NULL || pos < 0)\n");
		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;

	//step 1: 使用輔助指標變數,指向頭結點
	currentNode = &tlist->header;

	//step 2: 找到pos位置節點
	for (i = 0; i <= pos; ++i)
	{
		//判斷是否有後繼節點
		if (currentNode->next != NULL)
		{
			//指標後移
			currentNode = currentNode->next;
		}
		else
		{
			//沒有後繼節點跳出迴圈
			printf("error: 沒找到指定位置的元素\n");

			return NULL;
		}
	}
	return currentNode;
}


//刪除迴圈連結串列中的指定位置的節點
//-------------------------------
CircleListNode* CircleList_Delete(CircleList* list, int pos)
{
	int i;

	//定義TCircleList指標變數
	TCircleList *tlist = NULL;
	//定義連結串列節點指標,儲存要刪除的節點地址
	CircleListNode	*deleteNode = NULL;
	//定義連結串列節點指標,儲存最後一個節點
	CircleListNode  *lastNode = NULL;
	//定義輔助指標變數
	CircleListNode  *currentNode = NULL;

	//判斷list是否為有效指標
	if (list == NULL || pos < 0)
	{
		printf("CircleList_Delete error: if (list == NULL || pos < 0)\n");

		return NULL;
	}
	//型別轉換並賦值
	tlist = (TCircleList*)list;

	//判斷連結串列中是否有節點
	if (tlist->length <= 0)
	{
		printf("error: 連結串列為空,不能刪除\n");

		return NULL;
	}

	//元素刪除

	//step 1: 輔助指標變數,指向頭結點
	currentNode = &tlist->header;

	//step 2: 找到pos-1位置節點
	for (i = 0; i < pos; ++i)
	{
		//指標後移
		currentNode = currentNode->next;
	}

	//step 3: 儲存要刪除的節點的地址
	deleteNode = currentNode->next;

	//step 4-1: 判斷刪除的元素是否為第一個元素
	if (currentNode == &tlist->header)
	{
		//step 4-2: 找到最後一個節點
		lastNode = CircleList_Get(list, tlist->length - 1);
	}

	//step 4-3: 判斷lastNode是否為空
	if (lastNode != NULL)
	{
		//step 4-4: 將最後一個節點的地址指向要刪除節點的後繼節點
		lastNode->next = deleteNode->next;
	}

	//step 4-5: 將頭結點的指標指向要刪除節點的後繼節點
	currentNode->next = deleteNode->next;

	//step 5: 連結串列長度減1
	tlist->length--;

	//step 6-1: 判斷刪除的元素是否為遊標指向的元素
	if (tlist->slider == deleteNode)
	{
		//step 6-2: 遊標後移
		tlist->slider = deleteNode->next;
	}

	//step 7-1: 判斷刪除元素後,連結串列長度是否為零
	if (tlist->length == 0)
	{
		//step 7-2: 連結串列頭結點指標域指向空
		tlist->header.next = NULL;
		//step 7-3: 連結串列遊標指向空
		tlist->slider = NULL;
	}

	return deleteNode;
}


//------------------ new add ------------------

//直接指定刪除連結串列中的某個資料元素
CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node)
{
	int i;
	int nPos = 0;

	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//定義輔助指標變數
	CircleListNode* currentNode = NULL;
	//定義輔助指標變數,用來儲存要刪除的節點地址
	CircleListNode* delNode = NULL;

	//判斷list是否為有效指標
	if (list == NULL || node == NULL)
	{
		printf("CircleList_DeleteNode error: if (list == NULL || node == NULL)\n");

		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;
        // 輔助指標變數,指向頭結點
        currentNode = &tlist->header;

	//查詢node節點在迴圈連結串列中的位置
	for (i = 0; i < tlist->length; ++i)
	{
		//從第一個資料節點開始判斷,查詢等於node的節點
		if (currentNode->next == node)
		{
			//儲存與node節點相等的節點的位置
			nPos = i;
			//儲存要刪除的節點地址
			delNode = currentNode->next;

			//跳出迴圈
			break;
		}
		//當前節點指標後移
		currentNode = currentNode->next;
	}

	//如果找到delNode,根據nPos刪除該節點
	if (delNode != NULL)
	{
		//刪除指定位置的元素
		CircleList_Delete(list, nPos);
	}

	return delNode;
}


//將遊標重置指向連結串列中的第一個資料元素
CircleListNode* CircleList_Reset(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("CircleList_Reset error: if (list == NULL)\n");

		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;
	//重置遊標位置
	tlist->slider = tlist->header.next;

	return tlist->slider;
}


//獲取當前遊標指向的資料元素
CircleListNode* CircleList_Current(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("CircleList_Current error: if (list == NULL)\n");

		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;

	return tlist->slider;
}


//將遊標移動指向到連結串列中的下一個資料元素
CircleListNode* CircleList_Next(CircleList* list)
{
	//定義連結串列節點指標變數
	CircleListNode	*currNode = NULL;
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("CircleList_Next error: if (list == NULL)\n");

		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;
	//儲存當前遊標位置
	currNode = tlist->slider;

	//判斷當前遊標是否指向空
	if (tlist->slider != NULL)
	{
		//遊標後移
		tlist->slider = currNode->next;
	}

	return currNode;
}

迴圈連結串列基本功能測試.c

#include "CircleList.h"

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

typedef struct tag_value
{
	CircleListNode circleNode;
	int v;
}Value;

#if 1

int main()
{
	int i;

	//定義Value結構體陣列
	Value val[10];	
	//建立迴圈連結串列
	CircleList* list = CircleList_Create();

	Value *pVal;

	//迴圈初始化陣列
	for (i = 0; i < sizeof(val) / sizeof(Value); ++i)
	{
		val[i].v = i + 20;

		//往迴圈連結串列中插入資料
		CircleList_Insert(list, (CircleListNode*)&val[i], i);
	}

	//遍歷迴圈連結串列
	//************* 怎麼證明是迴圈連結串列? *************
	for (i = 0; i < CircleList_Length(list) * 2; ++i)	//列印兩遍
	{
		pVal = (Value*)CircleList_Get(list, i);

		printf("Value %d = %d\n", i, pVal->v);
	}

	//刪除節點
	while (CircleList_Length(list) > 0)
	{
		pVal = (Value*)CircleList_Delete(list, 0);

		printf("Delete Value: val = %d\n", pVal->v);
	}

	//銷燬迴圈連結串列
	CircleList_Destroy(list);

	system("pause");

	return 0;
}

#endif

執行結果:

2.4.2.2 約瑟夫問題求解

實現程式碼:

CircleList.h

#ifndef _CIRCLE_LIST_H
#define _CIRCLE_LIST_H

//自定義迴圈連結串列資料型別
typedef void CircleList;
//自定義迴圈連結串列節點資料型別
typedef struct tag_CirclListNode
{
	struct tag_CirclListNode *next;
}CircleListNode;

//建立結構體管理連結串列
typedef struct tag_CircleList
{
	//迴圈連結串列頭結點
	CircleListNode	header;
	//迴圈連結串列遊標
	CircleListNode	*slider;
	//迴圈連結串列長度
	int				length;
}TCircleList;

//建立迴圈連結串列
CircleList* CircleList_Create();

//銷燬迴圈連結串列
void CircleList_Destroy(CircleList* list);

//清空迴圈連結串列
void CircleList_Clear(CircleList* list);

//獲取迴圈連結串列長度
int CircleList_Length(CircleList* list);

//在迴圈連結串列中插入新節點
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos);

//獲取迴圈連結串列中的指定位置的節點
CircleListNode* CircleList_Get(CircleList* list, int pos);

//刪除迴圈連結串列中的指定位置的節點
CircleListNode* CircleList_Delete(CircleList* list, int pos);

//------------------ new add ------------------

//直接指定刪除連結串列中的某個資料元素
CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node);

//將遊標重置指向連結串列中的第一個資料元素
CircleListNode* CircleList_Reset(CircleList* list);

//獲取當前遊標指向的資料元素
CircleListNode* CircleList_Current(CircleList* list);

//將遊標移動指向到連結串列中的下一個資料元素
CircleListNode* CircleList_Next(CircleList* list);

#endif //_CIRCLE_LIST_H

CircleList.c

#include "CircleList.h"

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

//建立迴圈連結串列
CircleList* CircleList_Create()
{
	//定義TCircleList指標變數,並分配記憶體空間
	TCircleList* tlist = (TCircleList*)malloc(sizeof(TCircleList));

	if (tlist == NULL)
	{
		printf("error: TCircleList* tlist = (TCircleList*)malloc(sizeof(TCircleList)) \n");
		return NULL;
	}

	//資料初始化
	tlist->header.next = NULL;
	tlist->slider = NULL;
	tlist->length = 0;

	return (CircleList*)tlist;
}

//銷燬迴圈連結串列
void CircleList_Destroy(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("Destory error: list 為無效指標\n");

		return;
	}

	free(list);
}

//清空迴圈連結串列
void CircleList_Clear(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("Clear error: list 為無效指標\n");

		return;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;
	//將長度重置為0
	tlist->length = 0;
	//頭結點指標域指向空
	tlist->header.next = NULL;
	//遊標指向空
	tlist->slider = NULL;
}

//獲取迴圈連結串列長度
int CircleList_Length(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("Length error: list 為無效指標\n");

		return -1;
	}
	//型別轉換並賦值
	tlist = (TCircleList*)list;

	return tlist->length;
}

//在迴圈連結串列中插入新節點
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos)
{
	int i;

	//定義TCircleList指標變數
	TCircleList *tlist = NULL;
	//定義輔助指標變數
	CircleListNode	*currentNode = NULL;

	//判斷list是否為有效指標
	if (list == NULL || node == NULL || pos < 0)
	{
		printf("Insert error: if (list == NULL || node == NULL || pos < 0)\n");

		return -1;
	}
	//型別轉換並賦值
	tlist = (TCircleList*)list;

	//元素插入
	//step 1: 使用輔助指標變數,指向頭結點
	currentNode = &tlist->header;
	//step 2: 找到pos-1位置節點
	for (i = 0; i < pos; ++i)
	{
		//判斷是否有後繼節點
		if (currentNode->next != NULL)
		{
			//指標後移
			currentNode = currentNode->next;
		}
		else
		{
			//沒有後繼節點跳出迴圈
			break;
		}
	}

	//step 3: 將node節點的指標指向當前節點(pos-1)的後繼節點(pos)
	node->next = currentNode->next;

	//step 4: 當前節點的指標指向node節點的地址
	currentNode->next = node;

	//step 5: 如果是第一次插入節點
	if (tlist->length == 0)
	{
		//將遊標指向新插入節點
		tlist->slider = node;
	}

	//step 6: 連結串列長度加1
	tlist->length++;

	//step 7:若頭插法 currentNode仍然指向頭部
	//原因: 跳0步, 沒有跳走
	if (currentNode == &tlist->header)
	{
		CircleListNode* lastNode = CircleList_Get(list, tlist->length - 1);

		//最後一個節點的指標,指向第一個資料節點
		lastNode->next = currentNode->next;
	}

	return 0;
}

//獲取迴圈連結串列中的指定位置的節點
CircleListNode* CircleList_Get(CircleList* list, int pos)
{
	int i;

	//定義TCircleList指標變數
	TCircleList *tlist = NULL;
	//定義輔助指標變數
	CircleListNode	*currentNode = NULL;

	//判斷list是否為有效指標
	if (list == NULL || pos < 0)
	{
		printf("CircleList_Get error: if (list == NULL || pos < 0)\n");
		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;

	//step 1: 使用輔助指標變數,指向頭結點
	currentNode = &tlist->header;

	//step 2: 找到pos位置節點
	for (i = 0; i <= pos; ++i)
	{
		//判斷是否有後繼節點
		if (currentNode->next != NULL)
		{
			//指標後移
			currentNode = currentNode->next;
		}
		else
		{
			//沒有後繼節點跳出迴圈
			printf("error: 沒找到指定位置的元素\n");

			return NULL;
		}
	}
	return currentNode;
}


//刪除迴圈連結串列中的指定位置的節點
//-------------------------------
CircleListNode* CircleList_Delete(CircleList* list, int pos)
{
	int i;

	//定義TCircleList指標變數
	TCircleList *tlist = NULL;
	//定義連結串列節點指標,儲存要刪除的節點地址
	CircleListNode	*deleteNode = NULL;
	//定義連結串列節點指標,儲存最後一個節點
	CircleListNode  *lastNode = NULL;
	//定義輔助指標變數
	CircleListNode  *currentNode = NULL;

	//判斷list是否為有效指標
	if (list == NULL || pos < 0)
	{
		printf("CircleList_Delete error: if (list == NULL || pos < 0)\n");

		return NULL;
	}
	//型別轉換並賦值
	tlist = (TCircleList*)list;

	//判斷連結串列中是否有節點
	if (tlist->length <= 0)
	{
		printf("error: 連結串列為空,不能刪除\n");

		return NULL;
	}

	//元素刪除

	//step 1: 輔助指標變數,指向頭結點
	currentNode = &tlist->header;

	//step 2: 找到pos-1位置節點
	for (i = 0; i < pos; ++i)
	{
		//指標後移
		currentNode = currentNode->next;
	}

	//step 3: 儲存要刪除的節點的地址
	deleteNode = currentNode->next;

	//step 4-1: 判斷刪除的元素是否為第一個元素
	if (currentNode == &tlist->header)
	{
		//step 4-2: 找到最後一個節點
		lastNode = CircleList_Get(list, tlist->length - 1);
	}

	//step 4-3: 判斷lastNode是否為空
	if (lastNode != NULL)
	{
		//step 4-4: 將最後一個節點的地址指向要刪除節點的後繼節點
		lastNode->next = deleteNode->next;
	}

	//step 4-5: 將頭結點的指標指向要刪除節點的後繼節點
	currentNode->next = deleteNode->next;

	//step 5: 連結串列長度減1
	tlist->length--;

	//step 6-1: 判斷刪除的元素是否為遊標指向的元素
	if (tlist->slider == deleteNode)
	{
		//step 6-2: 遊標後移
		tlist->slider = deleteNode->next;
	}

	//step 7-1: 判斷刪除元素後,連結串列長度是否為零
	if (tlist->length == 0)
	{
		//step 7-2: 連結串列頭結點指標域指向空
		tlist->header.next = NULL;
		//step 7-3: 連結串列遊標指向空
		tlist->slider = NULL;
	}

	return deleteNode;
}


//------------------ new add ------------------

//直接指定刪除連結串列中的某個資料元素
CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node)
{
	int i;
	int nPos = 0;

	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//定義輔助指標變數
	CircleListNode* currentNode = NULL;
	//定義輔助指標變數,用來儲存要刪除的節點地址
	CircleListNode* delNode = NULL;

	//判斷list是否為有效指標
	if (list == NULL || node == NULL)
	{
		printf("CircleList_DeleteNode error: if (list == NULL || node == NULL)\n");

		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;

        //輔助指標變數,指向頭結點
        currentNode = &tlist->header;

	//查詢node節點在迴圈連結串列中的位置
	for (i = 0; i < tlist->length; ++i)
	{
		//從第一個資料節點開始判斷,查詢等於node的節點
		if (currentNode->next == node)
		{
			//儲存與node節點相等的節點的位置
			nPos = i;
			//儲存要刪除的節點地址
			delNode = currentNode->next;

			//跳出迴圈
			break;
		}
		//當前節點指標後移
		currentNode = currentNode->next;
	}

	//如果找到delNode,根據nPos刪除該節點
	if (delNode != NULL)
	{
		//刪除指定位置的元素
		CircleList_Delete(list, nPos);
	}

	return delNode;
}


//將遊標重置指向連結串列中的第一個資料元素
CircleListNode* CircleList_Reset(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("CircleList_Reset error: if (list == NULL)\n");

		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;
	//重置遊標位置
	tlist->slider = tlist->header.next;

	return tlist->slider;
}


//獲取當前遊標指向的資料元素
CircleListNode* CircleList_Current(CircleList* list)
{
	//定義TCircleList指標變數
	TCircleList *tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("CircleList_Current error: if (list == NULL)\n");

		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;

	return tlist->slider;
}


//將遊標移動指向到連結串列中的下一個資料元素
CircleListNode* CircleList_Next(CircleList* list)
{
	//定義連結串列節點指標變數
	CircleListNode	*currNode = NULL;
	//定義TCircleList指標變數
	TCircleList		*tlist = NULL;

	//判斷list是否為有效指標
	if (list == NULL)
	{
		printf("CircleList_Next error: if (list == NULL)\n");

		return NULL;
	}

	//型別轉換並賦值
	tlist = (TCircleList*)list;
	//儲存當前遊標位置
	currNode = tlist->slider;

	//判斷當前遊標是否指向空
	if (tlist->slider != NULL)
	{
		//遊標後移
		tlist->slider = currNode->next;
	}

	return currNode;
}

約瑟夫問題求解.c

#include "circlelist.h"

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

/*
	約瑟夫問題-迴圈連結串列典型應用
	n 個人圍成一個圓圈,首先第 1 個人從 1 開始一個人一個人順時針報數,
	報到第 m 個人,令其出列。然後再從下一 個人開始從 1 順時針報數,報
	到第 m 個人,再令其出列,…,如此下去,求出列順序。

	求解: 假設 m = 3, n = 8 (1 2 3 4 5 6 7 8)
	結果: 3 6 1 5 2 8 4 7
*/


// 定義資料結構
// 業務節點的定義
typedef struct _Value
{
	// 內部的連結串列節點
	CircleListNode node;
	int v;	// 資料
}Value;

void joseph_question()
{
	Value val[8];

	int i = -1;

	Value* p;

	// 建立迴圈連結串列
	CircleList *list = CircleList_Create();

	// 初始化陣列
	for (i = 0; i < 8; ++i)
	{
		val[i].v = i + 1;

		// 節點插入到連結串列
		CircleList_Insert(list, &val[i].node, i);
	}

	// 遍歷
	for (i = 0; i < CircleList_Length(list); ++i)
	{
		p = (Value*)CircleList_Get(list, i);

		printf("%d  ", p->v);
	}

	printf("\n");

	// 出連結串列操作
	printf("刪除的節點的次序\n");

	// 遊標重置, 指向第一個資料節點
	CircleList_Reset(list);

	while (CircleList_Length(list))
	{
		// 遊標後移的步長
		for (i = 0; i < 2; ++i)
		{
			// 遊標後移
			CircleList_Next(list);
		}

		// 獲取當前遊標指向的節點
		p = (Value*)CircleList_Current(list);
		
		printf("%d  ", p->v);

		// 刪除當前節點
		CircleList_DeleteNode(list, (CircleListNode*)p);
	}

	printf("\n");

	CircleList_Destroy(list);
}

#if 1

void main()
{
	joseph_question();

	system("pause");
}

#endif

執行結果:

2.4.3 優點和缺點

優點:

  • 迴圈連結串列可以完全取代單鏈表的使用

  • 迴圈連結串列的Next和Current操作可以高效的遍歷連結串列中的所有元素

缺點:

  • 程式碼複雜度提高了

2.5 雙向連結串列

2.4.1 基本概念

雙向連結串列的定義:在單鏈表的結點中增加一個指向其前驅的 pre 指標

雙向連結串列的操作

雙向連結串列的擁有單向連結串列的所有操作:

  • 建立連結串列

  • 銷燬連結串列

  • 獲取連結串列長度

  • 清空連結串列

  • 獲取第 pos 個元素操作

  • 插入元素到位置 pos

  • 刪除位置 pos 處的元素

雙向連結串列的新操作:

  • 獲取當前遊標指向的資料元素
    DLinkListNode* DLinkList_Current(DLinkList* list);

  • 將遊標重置指向連結串列中的第一個資料元素
    DLinkListNode* DLinkList_Reset(DLinkList* list);

  • 將遊標移動指向到連結串列中的下一個資料元素
    DLinkListNode* DLinkList_Next(DLinkList* list);

  • 將遊標移動指向到連結串列中的上一個資料元素
    DLinkListNode* DLinkList_Pre(DLinkList* list);

  • 直接指定刪除連結串列中的某個資料元素
    DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node);

2.4.2 設計與實現

插入操作

current->next = node;
node->next = next;
next->pre = node;
node->pre = current;

刪除操作

current->next = next;
next->pre = current;

示例程式碼:

Dlinklist.h

#ifndef _DLINK_LIST_H
#define _DLINK_LIST_H

//自定義雙向連結串列資料型別
typedef void DLinkList;

//自定義雙向連結串列節點資料型別
typedef struct tag_dLinkListNode
{
	struct tag_dLinkListNode *prev;
	struct tag_dLinkListNode *next;
}DLinkListNode;

//定義資料結構體
typedef struct tag_value
{
	//包含雙向連結串列的一個節點
	DLinkListNode head;
	int value;
}Value;

//定義管理雙向連結串列的結構體
typedef struct _tag_dlinklist
{
	DLinkListNode head;
	DLinkListNode *slider;
	int length;
}TDLinkList;
 
//建立連結串列
DLinkList* DLinkList_Create();

//銷燬連結串列
void DLinkList_Destroy(DLinkList* list);

//清空連結串列
void DLinkList_Clear(DLinkList* list);

//獲取連結串列長度
int DLinkList_Length(DLinkList* list);

//獲取第pos個元素操作
DLinkListNode* DLinkList_Get(DLinkList* list, int pos);

//插入元素到位置pos
int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos);

//刪除位置pos處的元素
DLinkListNode* DLinkList_Delete(DLinkList* list, int pos);

//獲取當前遊標指向的資料元素
DLinkListNode* DLinkList_Current(DLinkList* list);

//將遊標重置指向連結串列中的第一個資料元素
DLinkListNode* DLinkList_Reset(DLinkList* list);

//將遊標移動指向到連結串列中的下一個資料元素
DLinkListNode* DLinkList_Next(DLinkList* list);

//將遊標移動指向到連結串列中的上一個資料元素
DLinkListNode* DLinkList_Prev(DLinkList* list);

//直接指定刪除連結串列中的某個資料元素
DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node);

#endif //_DLINK_LIST_H

Dlinklist.c

#include "DLinkList.h"

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


//建立連結串列
DLinkList* DLinkList_Create()
{
	//定義結構體型別指標變數,並分配記憶體空間
	TDLinkList* dlist = (TDLinkList*)malloc(sizeof(TDLinkList));

	//如果分配記憶體成功,則初始化變數
	if (dlist != NULL)
	{
		dlist->head.next = NULL;
		dlist->slider = NULL;
		dlist->length = 0;

		return (DLinkList*)dlist;
	}

	//失敗返回空
	printf("分配記憶體失敗\n");

	return NULL;
}

//銷燬連結串列
void DLinkList_Destroy(DLinkList* list)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		//釋放記憶體空間
		free(list);
	}
}

//清空連結串列
void DLinkList_Clear(DLinkList* list)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;

		//資料重置
		dlist->length = 0;
		dlist->head.next = NULL;
		dlist->slider = NULL;
	}
}

//獲取連結串列長度
int DLinkList_Length(DLinkList* list)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;

		return dlist->length;
	}

	printf("DLinkList_Length error: list 指標無效\n");

	return -1;
}

//獲取第pos個元素操作
DLinkListNode* DLinkList_Get(DLinkList* list, int pos)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;
		//定義輔助指標變數, 並初始化,指向頭節點
		DLinkListNode* currNode = &dlist->head;

		int i = -1;

		//迴圈查詢pos位置元素
		for (i = 0; i <= pos; ++i)
		{
			currNode = currNode->next;
		}

		return currNode;
	}

	printf("DLinkList_Get error: list 指標無效\n");

	return NULL;
}

//插入元素到位置pos
int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;
		//定義輔助指標變數, 並初始化,指向頭節點
		DLinkListNode* currNode = &dlist->head;
		//定義輔助指標變數
		DLinkListNode* posNode = NULL;

		int i = -1;

		//迴圈查詢pos-1位置元素
		for (i = 0; i < pos; ++i)
		{
			//判斷是否有後繼節點
			if (currNode->next != NULL)
			{
				//指標後移
				currNode = currNode->next;
			}
			else
			{
				//沒有後繼節點,結束迴圈
				break;
			}
		}
		//賦值,輔助指標變數指向pos位置節點
		posNode = currNode->next;

		//開始插入元素
		//step1: 將新節點的next域指標指向pos位置節點的地址
		node->next = posNode;

		//step2: 將當前節點的next域指標指向新插入節點的地址
		currNode->next = node;

		//step3: 將pos位置的節點的prev域指標指向新插入節點的地址
		//********** 特殊處理 **********
		if (posNode != NULL)	//當連結串列插入第一個元素需要特殊處理
		{
			posNode->prev = node;
		}

		//step4: 將新插入節點的地址指向當前節點的地址
		node->prev = currNode;
		//********** 特殊處理 **********
		if (currNode == &dlist->head)	//如果連結串列為空
		{
			//將第一個節點的前驅節點設為空
			node->prev = NULL;
			//遊標指向第一個節點
			dlist->slider = node;
		}

		//step4: 連結串列長度加1
		dlist->length++;

		return 0;
	}

	printf("DLinkList_Insert error: list 指標無效\n");

	return -1;
}

//刪除位置pos處的元素
DLinkListNode* DLinkList_Delete(DLinkList* list, int pos)
{
	//判斷list是否為有效指標
	if (list != NULL && pos >= 0)
	{
		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;

		//定義輔助指標變數, 並初始化,指向頭節點
		DLinkListNode* currNode = &dlist->head;
		//定義輔助指標變數
		DLinkListNode* delNode = NULL;
		DLinkListNode* nextNode = NULL;

		int i = -1;

		//迴圈查詢pos-1位置元素
		for (i = 0; i < pos; ++i)
		{
			currNode = currNode->next;
		}

		//賦值
		delNode = currNode->next;
		nextNode = delNode->next;

		//開始刪除元素

		//step1: 將當前節點的next域指標指向被刪除節點的後繼節點
		currNode->next = nextNode;
		//****** 需要特殊處理 ******
		if (nextNode != NULL)
		{
			//step2: nextNode節點的prev域指標指向當前節點的地址
			nextNode->prev = currNode;

			//****** 需要特殊處理 ******
			if (currNode == &dlist->head)	//如果當前節點為頭結點
			{
				//將nextNode節點指向空
				nextNode->prev = NULL;
			}
		}

		//step 3: 連結串列長度減1
		dlist->length--;

		//判斷刪除的元素是不是當前遊標指向的位置
		if (dlist->slider == delNode)
		{
			//如果是,遊標後移
			dlist->slider = nextNode;
		}

		return delNode;
	}

	printf("DLinkList_Delete error: list指標 或 pos位置無效\n");
	return NULL;
}

//獲取當前遊標指向的資料元素
DLinkListNode* DLinkList_Current(DLinkList* list)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;

		return dlist->slider;
	}

	printf("DLinkList_Current error: list 指標無效\n");

	return NULL;
}

//將遊標重置指向連結串列中的第一個資料元素
DLinkListNode* DLinkList_Reset(DLinkList* list)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;

		dlist->slider = dlist->head.next;

		return dlist->slider;
	}

	printf("DLinkList_Reset error: list 指標無效\n");

	return NULL;
}

//將遊標移動指向到連結串列中的下一個資料元素
DLinkListNode* DLinkList_Next(DLinkList* list)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;
		//定義連結串列節點指標儲存當前遊標地址
		DLinkListNode* currSlider = dlist->slider;

		//遊標後移
		if (dlist->slider->next != NULL)
		{
			dlist->slider = dlist->slider->next;

			return currSlider;
		}
		else
		{
			return NULL;
		}
	}

	printf("DLinkList_Next error: list 指標無效\n");

	return NULL;
}

//將遊標移動指向到連結串列中的上一個資料元素
DLinkListNode* DLinkList_Prev(DLinkList* list)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;
		//定義連結串列節點指標儲存當前遊標地址
		DLinkListNode* currSlider = dlist->slider;

		//遊標前移
		dlist->slider = dlist->slider->prev;

		return currSlider;
	}

	printf("DLinkList_Prev error: list 指標無效\n");

	return NULL;
}

//直接指定刪除連結串列中的某個資料元素
DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node)
{
	//判斷list是否為有效指標
	if (list != NULL)
	{
		int nPos = 0;

		//定義結構體型別指標,並給其賦值
		TDLinkList* dlist = (TDLinkList*)list;

		int i = -1;

		DLinkListNode* delNode = NULL;

		//查詢與node節點相等的節點
		for (i = 0; i < dlist->length; ++i)
		{
			if (node == DLinkList_Get(list, i))
			{
				//儲存位置
				nPos = i;

				//跳出迴圈
				break;
			}
		}
		//刪除指定nPos位置節點
		delNode = DLinkList_Delete(list, nPos);

		return delNode;
	}

	printf("DLinkList_DeleteNode error: list or node 指標無效\n");

	return NULL;
}

雙向連結串列基本功能測試

#include "DLinkList.h"

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


//雙向連結串列測試程式
void dLinkListTest()
{
	int i = -1;

	//定義Value結構體陣列
	Value val[10];
	//當前遊標指向的節點
	Value* pVal = NULL;

	//建立雙向連結串列
	DLinkList *dlist = DLinkList_Create();

	//判斷是否建立成功
	if (dlist == NULL)
	{
		printf("雙向連結串列建立失敗\n");

		return;
	}
	
	//初始化並向連結串列中插入資料
	for (i = 0; i < 10; ++i)
	{
		val[i].value = i + 10;

		//向尾部插入元素
		DLinkList_Insert(dlist, (DLinkListNode*)&val[i], i);
	}

	printf("初始化雙向連結串列成功\n");
	printf("\n");

	//遍歷雙向連結串列
	printf("雙向連結串列為:\n");

	for (i = 0; i < DLinkList_Length(dlist); ++i)
	{
		//獲取指定位置元素
		pVal = (Value*)DLinkList_Get(dlist, i);

		printf("%d\t", pVal->value);
	}

	printf("\n");

	printf("刪除最後一個節點成功\n");
	printf("\n");

	//刪除最後一個節點
	DLinkList_Delete(dlist, DLinkList_Length(dlist) - 1);

	//刪除第一節點
	DLinkList_Delete(dlist, 0);

	printf("刪除第一節點成功\n");
	printf("\n");

	//再次遍歷連結串列
	printf("刪除最後一個節點和第一節點的雙向連結串列為:\n");
	for (i = 0; i < DLinkList_Length(dlist); ++i)
	{
		//獲取指定位置元素
		pVal = (Value*)DLinkList_Get(dlist, i);

		printf("%d\t", pVal->value);
	}

	printf("\n");

	//重置遊標
	DLinkList_Reset(dlist);

	//遊標後移
	DLinkList_Next(dlist);
	//獲取當前遊標指向的節點
	pVal = (Value*)DLinkList_Current(dlist);
	//列印當前節點的value值
	printf("遊標後移後,列印當前遊標指向的節點的value值: value = %d\n", pVal->value);
	printf("\n");

	//刪除遊標指向的當前節點
	DLinkList_DeleteNode(dlist, (DLinkListNode*)pVal);
	//再次獲取當前遊標指向的節點
	pVal = (Value*)DLinkList_Current(dlist);
	//再次列印當前節點的value值
	printf("刪除遊標指向的當前節點,列印當前遊標指向的節點的value值: value = %d\n", pVal->value);
	printf("\n");

	//向前移動遊標
	DLinkList_Prev(dlist);
	//第三次獲取當前遊標指向的節點
	pVal = (Value*)DLinkList_Current(dlist);
	//第三次列印當前節點的value值
	printf("向前移動遊標,列印當前遊標指向的節點的value值: value = %d\n", pVal->value);
	printf("\n");

	//列印連結串列的長度
	printf("連結串列的長度, Length = %d\n", DLinkList_Length(dlist));
	printf("\n");

	//銷燬雙向連結串列
	DLinkList_Destroy(dlist);
}

void main()
{
	dLinkListTest();

	system("pause");
}

執行結果:

2.4.3 優點和缺點

優點:

  • 雙向連結串列在單鏈表的基礎上增加了指向前驅的指標

  • 功能上雙向連結串列可以完全取代單鏈表的使用

  • 雙向連結串列的 Next,Pre 和 Current 操作可以高效的遍歷連結串列中的所有元素

缺點:

  • 程式碼複雜