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 操作可以高效的遍历链表中的所有元素

缺点:

  • 代码复杂

C++ 数据结构 1:线性表的更多相关文章

  1. 用C#学习数据结构之线性表

    什么是线性表 线性表是最简单.最基本.最常用的数据结构.线性表是线性结构的抽象(Abstract),线性结构的特点是结构中的数据元素之间存在一对一的线性关系.这种一对一的关系指的是数据元素之间的位置关 ...

  2. 数据结构之线性表(python版)

    数据结构之线性表(python版) 单链表 1.1  定义表节点 # 定义表节点 class LNode(): def __init__(self,elem,next = None): self.el ...

  3. C语言数据结构-顺序线性表的实现-初始化、销毁、长度、查找、前驱、后继、插入、删除、显示操作

    1.数据结构-顺序线性表的实现-C语言 #define MAXSIZE 100 //结构体定义 typedef struct { int *elem; //基地址 int length; //结构体当 ...

  4. javascript实现数据结构:线性表--简单示例及线性表的顺序表示和实现

    线性表(linear list)是最常用且最简单的一种数据结构.一个线性表是n个数据元素的有限序列.在稍复杂的线性表中,一个数据元素可以由若干个数据项(item)组成. 其中: 数据元素的个数n定义为 ...

  5. javascript实现数据结构:线性表--线性链表(链式存储结构)

    上一节中, 线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻,因此可以随机存取表中任一元素,它的存储位置可用一个简单,直观的公式来表示.然后,另一方面来看,这个特点也造成这种存储 ...

  6. Java数据结构之线性表(2)

    从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...

  7. Java数据结构之线性表

    从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...

  8. C 数据结构1——线性表分析(顺序存储、链式存储)

    之前是由于学校工作室招新,跟着大伙工作室招新训练营学习数据结构,那个时候,纯碎是小白(至少比现在白很多)那个时候,学习数据结构,真的是一脸茫然,虽然写出来了,但真的不知道在干嘛.调试过程中,各种bug ...

  9. 算法与数据结构(一) 线性表的顺序存储与链式存储(Swift版)

    温故而知新,在接下来的几篇博客中,将会系统的对数据结构的相关内容进行回顾并总结.数据结构乃编程的基础呢,还是要不时拿出来翻一翻回顾一下.当然数据结构相关博客中我们以Swift语言来实现.因为Swift ...

  10. 【Java】 大话数据结构(1) 线性表之顺序存储结构

     本文根据<大话数据结构>一书,实现了Java版的顺序存储结构. 顺序存储结构指的是用一段地址连续的存储单元一次存储线性表的数据元素,一般用一维数组来实现. 书中的线性表抽象数据类型定义如 ...

随机推荐

  1. linux的安装3.7python

    centos安装python3 首先安装依赖包 yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-dev ...

  2. vue 项目打包后静态资源加载不到

    1, 2,

  3. 多测师_肖sir _python 练习题(一)100以内奇数,偶数,质数胡计算

    (1)求1~100的和方法: 方法一:print(sum(range(1,101))) 方法二: sum1 = 0 i = 1 while True: sum1 = sum1 + i if i == ...

  4. MeteoInfoLab脚本示例:多坐标系

    绘图的时候首先要有坐标系(Axes),可以用axes命令创建,如果没有创建在绘图时会自动创建一个.参数里的position是用来置顶坐标系的图形(figure)中的位置的,通过位置置顶,可以将多个坐标 ...

  5. Asp常见函数

    ASP语言的特点: 1.允许使用VBscript或java script简易Script语言,并可在文件中结合Html: 2.无需编译,由WEBserver执行产生: 3.与任何ActiveX Scr ...

  6. Android HandlerThread 详解

    概述 HandlerThread 相信大家都比较熟悉了,从名字上看是一个带有 Handler 消息循环机制的一个线程,比一般的线程多了消息循环的机制,可以说是Handler + Thread 的结合, ...

  7. linux(centos8):安装java jdk 15 (java 15)

    一,下载jdk15 官方网站: https://www.oracle.com/java/ 下载页面: https://www.oracle.com/cn/java/technologies/javas ...

  8. spring boot:actuator的安全配置:使用spring security做ip地址限制(spring boot 2.3.2)

    一,actuator有哪些环节要做安全配置? actuator是应用广泛的监控工具, 但在生产环境中使用时,需要做严格的安全保障, 避免造成信息泄露等严重的安全问题 actuator可以采取的安全措施 ...

  9. CentOS7下RabbitMQ服务安装配置 (亲测有效)

    erlang 21.3 rabbitmq-server 3.7.14 下载地址 链接: https://pan.baidu.com/s/1g_T1Q_6zpyO3AepS0ZPgYQ 提取码: abq ...

  10. allure测试报告

    首先如果你没有安装 pytest 库的话,先使用 pip 安装一下: pip install pytest 另外还需要安装 pytest 支持 allure 报告的插件库: pip install a ...