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
獲取元素演算法:
-
判斷線性表是否合法
-
判斷位置是否合法
-
直接通過陣列下標的方式獲取元素
刪除元素演算法:
-
判斷線性表是否合法
-
判斷刪除位置是否合法
-
將元素取出
-
將刪除位置後的元素分別向前移動一個位置
-
線性表長度減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 設計與實現
迴圈連結串列插入元素的分析
- 普通插入元素
- 尾插法
輔助指標向後跳length次,指向最後面那個元素(length-1位置),因為是迴圈連結串列,所以length位置元素即為第一個資料節點。
CircleList_Insert(list, (CircleListNode*)&v1, CircleList_Length(list));
- 頭插法
注意:
-
要進行頭插法,需要求出尾節點,連線新的0號位置節點
-
第一次插入元素時,讓遊標指向0號結點(即第一個資料節點)
CircleList_Insert(list, (CircleListNode*)&v1, 0);
- 第一次插入元素(相當於頭插法)
要求出尾節點,尾節點指標指向第一個資料節點(即自己指向自己)
迴圈連結串列刪除結點分析
- 刪除普通結點
- 刪除頭結點(刪除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 操作可以高效的遍歷連結串列中的所有元素
缺點:
- 程式碼複雜