完整代码:
c
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6013)
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#include<time.h>
// 一个具有独立头节点的双向循环链表,
// 区别在于将头节点和数据区域分开;
// 而且有独立头节点的链表为空的条件是:L->next等于L->prior等于NULL;
// 没有独立头节点的链表未空的条件是:L->next等于L->prior等于L;
// 还有一点要注意的是如何让p=L,指针转一圈之后不会回到L;
typedef int ElemType;
typedef int Status;
typedef struct Node {
ElemType data;
struct Node* next; //直接后继指针
struct Node* prior; //直接前驱指针
}Node;
typedef Node* DuLinkList;
// 函数声明
Status ListCreate(DuLinkList* L);
Status LinkEmpty(DuLinkList L);
Status ClearList(DuLinkList L);
Status GetElem(DuLinkList L, int i, ElemType* e);
Status LocateElem(DuLinkList L, ElemType e);
Status ListInsert(DuLinkList L, int i, ElemType e);
Status ListDelete(DuLinkList L, int i, ElemType* e);
void printAll(DuLinkList L);
void randList(DuLinkList L, int i);
Status insertHead(DuLinkList L, ElemType e);
Status insertEnd(DuLinkList L, ElemType e);
Status deleteEnd(DuLinkList L);
void ListReverse(DuLinkList L);
void test(DuLinkList L);
/// <summary>
/// 创建一个空的链表,并创建头节点
/// </summary>
Status ListCreate(DuLinkList* L) { //传递的是指针,要用指针接收
*L = (DuLinkList)malloc(sizeof(Node));
if (!L) {
printf("创建失败!\n");
return 0;
}
(*L)->data = 0;
(*L)->next = NULL;
(*L)->prior = NULL;
printf("创建成功!\n");
return 1;
}
/// <summary>
/// 判断链表是否为空
/// </summary>
Status LinkEmpty(DuLinkList L) {
int flag;
flag = (L->next == NULL) ? 0 : 1;
return flag;
}
/// <summary>
/// 将链表清空。
/// </summary>
Status ClearList(DuLinkList L) {
DuLinkList p;
p = L->next;
int j = 1;
while (1) {
if (p == L->prior) {
free(p);
break;
}
p = p->next;
free(p->prior);
}
L->next = NULL;
L->prior = NULL;
L->data = 0;
return 1;
}
/// <summary>
/// 将链表L中的第i个位置元素值返回给e。
/// </summary>
Status GetElem(DuLinkList L, int i, ElemType* e) {
DuLinkList p;
p = L->next;
int j = 1;
while (j < i) {
if (p == L->prior && j != 1 || p == NULL) { return 0; } //指针转了一圈,没找到第i个元素;
p = p->next;
j++;
}
if (j > i || p == NULL) { return 0; }
*e = p->data;
return 1;
}
/// <summary>
/// 在链表L中查找与给定值e相等的元素,
/// 如果查找成功,返回该元素在表中序号表示成功;
/// 否则,返回0表示失败。
/// </summary>
Status LocateElem(DuLinkList L, ElemType e) {
DuLinkList p;
p = L->next;
int i;
i = 1;
while (p) {
if (p->data == e) { return i; }
if (p == L->prior) { break; }
p = p->next;
i++;
}
return 0;
}
/// <summary>
/// 在链表L中的第i个位置插入新元素e。
/// </summary>
Status ListInsert(DuLinkList L, int i, ElemType e) {
DuLinkList p, s;
p = L->next;
s = NULL;
int j = 1;
if (i < 1) {
printf("第%d个元素不存在。\n", i);
return 0;
}
while (j < i) {
if (p == L->next && j != 1) {
printf("第%d个元素不存在。\n", i);
return 0;
}
p = p->next;
j++;
}
s = (DuLinkList)malloc(sizeof(Node));
if (!s) {
printf("内存申请失败!\n");
}
s->data = e;
// 考虑极端情况
if (L->next == NULL) { //链表中没有数据元素;
s->next = s;
s->prior = s;
L->next = s;
L->prior = s;
}
else { // 链表中有数据元素;
s->next = p;
s->prior = p->prior;
p->prior->next = s;
p->prior = s;
if (i == 1) { //插入的位置是首元结点;
L->next = s;
}
else if (i == L->data + 1) { //插入的位置是尾节点;
L->prior = s;
}
}
L->data++;
return 1;
}
/// <summary>
/// 删除链表L中第i个位置元素,并用e返回其值。
/// </summary>
Status ListDelete(DuLinkList L, int i, ElemType* e) {
DuLinkList p;
p = L->next;
int j = 1;
if (i < 1 || i>L->data) {
printf("第%d个元素不存在。\n", i);
return 0;
}
while (j < i) {
p = p->next;
j++;
}
*e = p->data;
if (L->next == L->prior) { // 链表中只有一个元素
L->next = NULL;
L->prior = NULL;
}
else if (L->next == p) { // 删除第一个元素
L->next = p->next;
}
else if (L->prior == p) { // 删除最后一个元素
L->prior = p->prior;
}
// 正常情况
p->next->prior = p->prior;
p->prior->next = p->next;
free(p);
L->data--;
return 1;
}
/// <summary>
/// 输出链表中所有元素。
/// </summary>
void printAll(DuLinkList L) {
DuLinkList p;
p = L->next;
int i = 0;
while (p) {
printf("%d\t", p->data);
if (p == L->prior)break;
p = p->next;
}
printf("\n");
}
/// <summary>
/// 向链表中添加i个随机元素。
/// </summary>
void randList(DuLinkList L, int i) {
int min = 0;
int max = 1000;
int range = max - min + 1;
for (int j = 0; j < i; j++) {
ElemType e = min + rand() % range;
insertEnd(L, e);
}
printf("\n");
}
/// <summary>
/// 在链表头部插入一个元素(头插法)
/// </summary>
Status insertHead(DuLinkList L, ElemType e) {
DuLinkList p, s;
p = L->next;
s = (DuLinkList)malloc(sizeof(Node));
if (s == NULL) {
return 0;
}
s->data = e;
L->next = s;
if (!p) { //链表中没有一个元素
L->prior = s;
s->next = s;
s->prior = s;
}
else { //链表中又元素
s->next = p;
s->prior = L->prior;
L->prior->next = s;
p->prior = s;
}
L->data++;
return 1;
}
/// <summary>
/// 在链表末尾插入一个元素(尾插法)
/// </summary>
Status insertEnd(DuLinkList L, ElemType e) {
DuLinkList p, s;
p = L->prior;
s = (DuLinkList)malloc(sizeof(Node));
if (!s) {
return 0;
}
s->data = e;
L->prior = s;
if (!p) { //链表中没有一个元素
L->next = s;
s->next = s;
s->prior = s;
}
else { //链表中有元素
s->prior = p;
s->next = L->next;
L->next->prior = s;
p->next = s;
}
L->data++;
return 1;
}
/// <summary>
/// 在链表末尾删除一个元素
/// </summary>
Status deleteEnd(DuLinkList L) {
DuLinkList p;
p = L->prior;
if (!p) { //链表中没有一个元素
return 0;
}
if (L->prior == L->next) { //链表中的元素只有一个
L->prior = NULL;
L->next = NULL;
}
else { //链表中的元素大于一个
L->prior = p->prior;
L->prior->next = L->next;
L->next->prior = L->prior;
}
L->data--;
free(p);
return 1;
}
/// <summary>
/// 将所有元素反转(交换每一个结点的next和prior指针)\
/// (还有一种实现方法是:创建一个新的头结点,将原链表的的结点根据一定顺序插进去)
/// </summary>
void ListReverse(DuLinkList L) {
DuLinkList p, agent;
p = L->next;
agent = NULL;
while (p) {
agent = p->next;
p->next = p->prior;
p->prior = agent;
if (p == L->prior) {
break;
}
p = agent;
}
agent = L->next;
L->next = L->prior;
L->prior = agent;
}
// 另一种实现方式
void Reverse(DuLinkList L) {
DuLinkList p, s;
p = L->next;
s = NULL;
ListCreate(&s);
if (!s) {
return;
}
while (p) {
insertHead(s, p->data);
if (p == L->prior) {
break;
}
p = p->next;
}
//printf("%p\n", &L);
//printf("%p\n", L);
ClearList(L);
*L = *s;
//printf("%p\n", L);
//test(L);
//printf("---------\n");
//test(s);
free(s);
}
/// <summary>
/// 获得链表长度(同ListEmpty)
/// </summary>
int ListLight(DuLinkList L) {
return L->data;
}
void menu() {
printf("=============================菜单-双向循环链表===================================\n");
printf("(1) 显示菜单\t\t\t\t(2) 判空\t\t\t\t|\n");
printf("(3) 清空链表\t\t\t\t(4) 按位删除\t\t\t\t|\n");
printf("(5) 按位查找\t\t\t\t(6) 按值查找\t\t\t\t|\n");
printf("(7) 按位插入\t\t\t\t(8) 元素个数\t\t\t\t|\n");
printf("(9) 输出链表\t\t\t\t(10) 随机生成\t\t\t\t|\n");
printf("(11) 头插入\t\t\t\t(12) 尾插入\t\t\t\t|\n");
printf("(13) 尾删除\t\t\t\t(14) 反转链表\t\t\t\t|\n");
printf("(0) 结束程序\t\t\t\t\t\t\t\t\t|\n");
printf("=================================================================================\n");
}
void test(DuLinkList L) {
int i = 1;
printf("[0] data: %d\tself: %p next: %p prior: %p\n", L->data, L, L->next, L->prior);
DuLinkList p;
p = L->next;
while (p) {
printf("[%d] data: %d\tself: %p next: %p prior: %p\n", i++, p->data, p, p->next, p->prior);
if (p == L->prior) { break; }
p = p->next;
}
}
// 简化后的main函数
int main() {
DuLinkList L = NULL;
ListCreate(&L);
int choice;
ElemType e, * p_e = (ElemType*)malloc(sizeof(ElemType));
int i;
menu();
while (1) {
printf("请输入菜单序号:");
scanf("%d", &choice);
switch (choice) {
case 999:
test(L);
break;
case 0:
free(L);
printf("链表已经销毁。\n");
return 0;
case 1:
menu();
break;
case 2:
if (LinkEmpty(L)) {
printf("非空。\n");
}
else {
printf("空\n");
}
break;
case 3:
ClearList(L);
printf("Ok\n");
break;
case 4:
printf("要删除的位置是:");
scanf("%d", &i);
if (ListDelete(L, i, p_e)) {
printf("Success:删除第%d个位置上的元素 %d\n", i, *p_e);
}
else {
printf("Error:检查输入是否正确。\n");
}
break;
case 5:
printf("要查找的位置为:(下标从1开始)");
scanf("%d", &i);
if (GetElem(L, i, p_e)) {
printf("Success:第%d个元素为:%d\n", i, *p_e);
}
else {
printf("Error:无法找到第%d个元素,检查输入是否正确。\n", i);
}
break;
case 6:
printf("要查找的值为:");
scanf("%d", &e);
i = LocateElem(L, e);
if (i) {
printf("Success:第%d个位置\n", i);
}
else {
printf("没有发现这个值。\n");
}
break;
case 7:
printf("输入插入的位置(下标从1开始)以及插入的数值,用空格隔开:\n");
scanf("%d %d", &i, &e);
if (ListInsert(L, i, e)) {
printf("Success\n");
}
else {
printf("Error:插入失败!\n");
}
break;
case 8:
printf("表中元素个数为:%d\n", ListLight(L));
break;
case 9:
printAll(L);
break;
case 10:
printf("随机生成几个数?");
scanf("%d", &i);
randList(L, i);
break;
case 11:
printf("输入要插入的数:");
scanf("%d", &e);
if (insertHead(L, e)) {
printf("Success:插入成功。\n");
}
else {
printf("Error:插入失败!\n");
}
break;
case 12:
printf("输入要插入的数:");
scanf("%d", &e);
if (insertEnd(L, e)) {
printf("Success:插入成功。\n");
}
else {
printf("Error:插入失败!\n");
}
break;
case 13:
if (deleteEnd(L)) {
printf("Success:删除成功。\n");
}
else {
printf("Error:删除失败\n");
}
break;
case 14:
/* printf("%p\n", &L);
printf("%p\n", L);*/
Reverse(L);
break;
default:
printf("Error:错误输入。\n");
break;
}
}
free(p_e);
return 0;
}
一、双向循环链表简介
双向循环链表是一种在链表基础上拓展而来的数据结构。与普通链表不同的是,它的每个节点都有两个指针,一个指向前驱节点(prior
),一个指向后继节点(next
)。而且,它是循环的,即链表的尾节点的后继指针指向头节点,头节点的前驱指针指向尾节点,形成一个闭合的环。
我们这里讨论的双向循环链表还有一个独立的头节点。这个独立头节点不存储数据,主要用于方便对链表的操作和维护。这种链表为空的条件是头节点的next
和prior
指针都指向NULL
。
二、代码结构和数据类型定义
(一)数据类型定义
在代码中,首先定义了链表节点的数据类型。通过以下结构体来表示一个节点:
c
typedef struct Node {
ElemType data;
struct Node* next;
struct Node* prior;
} Node;
typedef Node* DuLinkList;
这里,ElemType
被定义为int
类型,用于存储节点的数据。Node
结构体包含了数据域data
以及next
和prior
这两个指针,分别用于构建链表的双向连接。DuLinkList
则是指向Node
类型的指针,它代表了整个链表(实际上是从独立头节点开始)。
(二)函数声明
代码中声明了一系列操作双向循环链表的函数,包括创建链表、判断链表是否为空、清空链表、按位获取元素、按值查找元素、插入元素(按位插入、头插法、尾插法)、删除元素(按位删除、尾删除)以及链表的反转等功能。这些函数为我们全面操作链表提供了丰富的接口。
三、主要函数实现分析
(一)创建链表 - ListCreate
函数
c
Status ListCreate(DuLinkList* L) {
*L = (DuLinkList)malloc(sizeof(Node));
if (!L) {
printf("创建失败!\n");
return 0;
}
(*L)->data = 0;
(*L)->next = NULL;
(*L)->prior = NULL;
printf("创建成功!\n");
return 1;
}
这个函数用于创建一个空的链表。它通过malloc
函数为链表的头节点分配内存空间。如果分配成功,将头节点的data
初始化为0
,next
和prior
指针都设为NULL
,并返回1
表示创建成功;如果malloc
失败,则返回0
。
(二)判断链表是否为空 - LinkEmpty
函数
c
Status LinkEmpty(DuLinkList L) {
int flag;
flag = (L->next == NULL)? 0 : 1;
return flag;
}
通过检查链表头节点的next
指针是否为NULL
来判断链表是否为空。如果next
为NULL
,说明链表没有数据节点,返回0
;否则返回1
。
(三)清空链表 - ClearList
函数
c
Status ClearList(DuLinkList L) {
DuLinkList p;
p = L->next;
int j = 1;
while (1) {
if (p == L->prior) {
free(p);
break;
}
p = p->next;
free(p->prior);
}
L->next = NULL;
L->prior = NULL;
L->data = 0;
return 1;
}
这个函数用于清空链表。它从链表的第一个数据节点开始,依次释放每个节点的内存。在遍历过程中,通过free(p->prior)
释放当前节点的前驱节点,直到到达尾节点。最后,将头节点的next
和prior
指针设为NULL
,data
设为0
。
(四)按位获取元素 - GetElem
函数
c
Status GetElem(DuLinkList L, int i, ElemType* e) {
DuLinkList p;
p = L->next;
int j = 1;
while (j < i) {
if (p == L->prior && j!= 1 || p == NULL) { return 0; }
p = p->next;
j++;
}
if (j > i || p == NULL) { return 0; }
*e = p->data;
return 1;
}
该函数用于获取链表中指定位置i
的元素值。通过遍历链表,从头节点的下一个节点开始,逐个移动指针p
,直到找到第i
个节点。如果找到了,将该节点的数据存储到e
所指向的变量中,并返回1
;如果在遍历过程中出现指针转了一圈还没找到或者p
为NULL
等情况,则返回0
。
(五)按值查找元素 - LocateElem
函数
c
Status LocateElem(DuLinkList L, ElemType e) {
DuLinkList p;
p = L->next;
int i;
i = 1;
while (p) {
if (p->data == e) { return i; }
if (p == L->prior) { break; }
p = p->next;
i++;
}
return 0;
}
在链表中查找值为e
的元素。从链表的第一个数据节点开始,逐个比较节点的数据值与e
是否相等。如果找到相等的值,返回该元素在链表中的序号;如果遍历完整个链表都没找到,则返回0
。
(六)按位插入元素 - ListInsert
函数
c
Status ListInsert(DuLinkList L, int i, ElemType e) {
DuLinkList p, s;
p = L->next;
s = NULL;
int j = 1;
if (i < 1) {
printf("第%d个元素不存在。\n", i);
return 0;
}
while (j < i) {
if (p == L->next && j!= 1) {
printf("第%d个元素不存在。\n", i);
return 0;
}
p = p->next;
j++;
}
s = (DuLinkList)malloc(sizeof(Node));
if (!s) {
printf("内存申请失败!\n");
}
s->data = e;
if (L->next == NULL) {
s->next = s;
s->prior = s;
L->next = s;
L->prior = s;
}
else {
s->next = p;
s->prior = p->prior;
p->prior->next = s;
p->prior = s;
if (i == 1) {
L->next = s;
}
else if (i == L->data + 1) {
L->prior = s;
}
}
L->data++;
return 1;
}
这个函数实现在链表的指定位置i
插入新元素e
。首先,通过遍历找到要插入位置的前一个节点p
。然后创建一个新节点s
,并根据链表是否为空以及插入位置是否为首元节点或尾节点等情况,调整节点之间的指针关系,最后增加链表长度计数器L->data
的值。
(七)按位删除元素 - ListDelete
函数
c
Status ListDelete(DuLinkList L, int i, ElemType* e) {
DuLinkList p;
p = L->next;
int j = 1;
if (i < 1 || i>L->data) {
printf("第%d个元素不存在。\n", i);
return 0;
}
while (j < i) {
p = p->next;
j++;
}
*e = p->data;
if (L->next == L->prior) {
L->next = NULL;
L->prior = NULL;
}
else if (L->next == p) {
L->next = p->next;
}
else if (L->prior == p) {
L->prior = p->prior;
}
p->next->prior = p->prior;
p->prior->next = p->next;
free(p);
L->data--;
return 1;
}
用于删除链表中指定位置i
的元素,并将删除元素的值存储到e
所指向的变量中。先找到要删除的节点p
,然后根据链表中节点的数量情况(如只有一个元素、删除第一个元素、删除最后一个元素等)调整指针关系,最后释放被删除节点的内存,并减少链表长度计数器的值。
(八)头插法 - insertHead
函数
c
Status insertHead(DuLinkList L, ElemType e) {
DuLinkList p, s;
p = L->next;
s = (DuLinkList)malloc(sizeof(Node));
if (s == NULL) {
return 0;
}
s->data = e;
L->next = s;
if (!p) {
L->prior = s;
s->next = s;
s->prior = s;
}
else {
s->next = p;
s->prior = L->prior;
L->prior->next = s;
p->prior = s;
}
L->data++;
return 1;
}
实现将一个元素插入到链表头部。创建一个新节点s
,将其数据域设为e
,然后调整头节点与新节点以及原第一个节点(如果存在)之间的指针关系,最后增加链表长度。
(九)尾插法 - insertEnd
函数
c
Status insertEnd(DuLinkList L, ElemType e) {
DuLinkList p, s;
p = L->prior;
s = (DuLinkList)malloc(sizeof(Node));
if (!s) {
return 0;
}
s->data = e;
L->prior = s;
if (!p) {
L->next = s;
s->next = s;
s->prior = s;
}
else {
s->prior = p;
s->next = L->next;
L->next->prior = s;
p->next = s;
}
L->data++;
return 1;
}
将元素插入到链表的末尾。创建新节点s
,设置其数据值为e
,然后根据链表是否为空来调整新节点与链表尾节点(如果存在)以及头节点之间的指针关系,最后更新链表长度。
(十)尾删除 - deleteEnd
函数
c
Status deleteEnd(DuLinkList L) {
DuLinkList p;
p = L->prior;
if (!p) {
return 0;
}
if (L->prior == L->next) {
L->prior = NULL;
L->next = NULL;
}
else {
L->prior = p->prior;
L->prior->next = L->next;
L->next->prior = L->prior;
}
L->data--;
free(p);
return 1;
}
用于删除链表的尾节点。首先判断链表是否为空,如果不为空,根据链表中节点数量情况(只有一个节点或多个节点)调整指针关系,释放尾节点内存并减少链表长度。
(十一)链表反转 - ListReverse
函数
c
void ListReverse(DuLinkList L) {
DuLinkList p, agent;
p = L->next;
agent = NULL;
while (p) {
agent = p->next;
p->next = p->prior;
p->prior = agent;
if (p == L->prior) {
break;
}
p = agent;
}
agent = L->next;
L->next = L->prior;
L->prior = agent;
}
这个函数通过交换每个节点的next
和prior
指针来实现链表的反转。使用一个临时指针agent
来辅助交换过程,遍历链表,逐个交换节点的指针,最后调整头节点的next
和prior
指针,完成链表的反转。
(十二)另一种链表反转 - Reverse
函数
c
void Reverse(DuLinkList L) {
DuLinkList p, s;
p = L->next;
s = NULL;
ListCreate(&s);
if (!s) {
return;
}
while (p) {
insertHead(s, p->data);
if (p == L->prior) {
break;
}
p = p->next;
}
ClearList(L);
*L = *s;
free(s);
}
这是另一种实现链表反转的方法。它先创建一个新的链表s
,然后通过遍历原链表L
,将原链表的元素以头插法插入到新链表s
中。接着清空原链表L
,再将新链表s
的内容复制到原链表L
中(通过*L = *s
),最后释放新链表s
的头节点内存。