“单链表能够独立实现,那么双链表的实现也就干脆利落了”,这是我实现完单链表之后的想法。于是受惯性思维的驱使,大部分都是照搬单链表,甚至可以说是直接复制过来。
后来我仔细一想:单链表一个数据域,一个指针域(指向后继结点);双链表一个数据域,两个指针域,一个指向前驱结点,一个指向后继结点。双链表的功能是实现了,可其前指针域貌似没怎么使用。
于是我把双链表代码推倒重来,在实现双链表的过程中,尽可能多的使用前指针域。不这样做的话,那双链表跟单链表有什么区别?双链表的存在意义何在?
一、谈谈链表的定义及个人认识
前指针域:双向链表中指向前驱结点的指针
后指针域:双向链表中指向后继结点的指针
首结点:。。。
尾结点:。。。
起源结点:首结点之前的那个结点;或者说确定首结点的结点。起源结点的数据类型和首结点的数据类型相同,不存放有效数据,只是为了方便对链表的操作,其数据域可用来存放有效结点个数。
断链:解除两个结点之间的联系
牵链:让两个结点产生联系,通过其中一个结点确定另一个结点
首指针:指向首节点的指针变量
尾指针:指向尾节点的指针变量
起源指针:指向起源节点的指针变量
首领/头领,这两个词意思是相同的,斩首/砍头,这两个词也是一个意思,那么我的问题就来了,首和头是不是相同的意思?书中讲到的头节点/首元节点,我感觉很怪异,并没有去使用,而是另取名字。它们和首结点从词义上来讲,听起来有些冲突,所以我把所谓的头结点/首元结点称作起源节点。
确定一个双链表需要的参数:起源指针或者尾指针,我们通过这两个指针任意一个都可以推算出链表中的其他结点。
二、运行效果
1.初始化双链表
2.遍历双链表
3.追加结点
4.删除结点
输入有效下标删除结点
输入无效下标删除结点:
等于0的下标,无效
大于结点个数的下标,无效
小于0的下标,无效
5.插入结点
输入有效下标插入结点
输入无效下标插入结点:
下标等于0,无效
下标大于结点个数,无效
下标等于0,无效
6.修改结点
输入有效下标修改结点
输入无效下标修改结点:
输入下标等于0,无效
输入下标小于0,无效
输入下标大于结点个数,无效
7.按结点下标取值
输入有效下标取值
输入无效下标取值:
下标大于结点个数,无效
下标小于0,无效
下标等于0,无效
8.按结点值取其下标
输入值不存在,则给出提示
输入值存在,则给出下标
9.逆转双链表
逆转一次
再逆转一次
10.为双链表排序
11.清空双链表,保留其起源结点
从尾结点向首结点,依次释放每个结点
在双链表为空的情况下清空双链表,提示双链表为空
11.重新初始化双链表
①在双链表为空的情况下重新初始化双链表,先提示双链表为空,再释放起源结点,最后再重新初始化双链表。
②在双链表不为空的情况下,先释放链表中的有效结点,再释放起源结点,最后初始化双链表。
重新初始化后的双链表
12.在双链表为空情况下,无法进行的操作均会提示“DoubleLinkedList Is Empty!”
逆转
插入
排序
按下标取值
按值取下标
删除
修改
清空双链表
三、一字一句的血汗代码
干净利落无注释版:
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<malloc.h>
#define OK 1
#define ERROR -1
#define status int
#define SPAN 35
#define VerticalGap 1
typedef struct doubleNode
{
struct doubleNode * prior;
int data;
struct doubleNode * next;
}* DoubleNodePointer,DoubleNode;
DoubleNodePointer initialize();
void manifest(DoubleNodePointer origin);
status isEmpty(DoubleNodePointer origin);
void prompt(char* hint);
void inputData(char* hint,char* type,void* object);
status inverse(DoubleNodePointer origin);
status delete(DoubleNodePointer origin, int position);
void append(DoubleNodePointer origin, int value);
status insert(DoubleNodePointer origin, int position, int value);
DoubleNodePointer getNode(DoubleNodePointer origin, int position);
status locateNode(DoubleNodePointer origin, int value,int* position);
status modify(DoubleNodePointer origin, int position, int value);
status sort(DoubleNodePointer origin);
void clear(DoubleNodePointer origin);
void menu(char **menu, int length);
void duplicate(int size, char token);
void main()
{
char *operatingMenu[] = {"DoubleLinkedList",
"1.reinitialize list", "2.present list", "3.append element",
"4.delete element", "5.insert element", "6.modify element",
"7.get element", "8.locate element", "9.sort", "10.inverse",
"11.clear list", "12.exit"};
int length = sizeof(operatingMenu) / sizeof(char *);
int indicator, position, value;
DoubleNodePointer origin=initialize();
while (1)
{
system("cls");
menu(operatingMenu, length);
if(!isEmpty(origin))
{
printf("CURRENT TOTAL:\t%d\n",origin->data);
manifest(origin);
}
else
{
prompt("Data are required!\n");
}
inputData("type in number:\t","%d",&indicator);
switch (indicator) {
case 1:
clear(origin);
free(origin);
prompt("The Origin Node Is Released completely!\n");
prompt("...\treinitializing\t...\n");
origin=initialize();
break;
case 2:
manifest(origin);
system("pause");
break;
case 3:
inputData("VALUE:","%d",&value);
append(origin, value);
manifest(origin);
system("pause");
break;
case 4:
inputData("POSITION:","%d",&position);
(delete(origin, position)== OK) ? manifest(origin) : prompt("check out and try again!\n");
system("pause");
break;
case 5:
inputData("POSITION:","%d",&position);
inputData("VALUE:","%d",&value);
(insert(origin,position, value)==OK) ? manifest(origin) : prompt("check out and try again!\n");
system("pause");
break;
case 6:
inputData("POSITION:","%d",&position);
inputData("VALUE:","%d",&value);
(modify(origin,position,value)==OK)?manifest(origin) : prompt("check out and try again!\n");
system("pause");
break;
case 7:
inputData("POSITION:","%d",&position);
DoubleNodePointer dnp=getNode(origin,position);
dnp? printf("VALUE: %d\n", dnp->data) : prompt("check out and try again!\n");
system("pause");
break;
case 8:
inputData("VALUE:","%d",&value);
(locateNode(origin,value,&position)==OK) ? printf("POSITION:%d\n", position):prompt("check out and try again!\n");
system("pause");
break;
case 9:
(sort(origin)==OK)?manifest(origin):prompt("check out and try again!\n");
system("pause");
break;
case 10:
(inverse(origin)== OK) ? manifest(origin) : prompt("check out and try again!\n");
system("pause");
break;
case 11:
clear(origin);
system("pause");
break;
case 12:
clear(origin);
free(origin);
prompt("The Origin Node Is Released completely!\n");
system("pause");
exit(-1);
break;
}
}
}
status isEmpty(DoubleNodePointer origin)
{
if(origin->next==NULL)
{
prompt("DoubleLinkedList Is Empty!\n");
return 1;
}
return 0;
}
void inputData(char* hint,char* type,void* object)
{
printf("%s",hint);
scanf(type,object);
}
void prompt(char* hint)
{
printf("%s",hint);
}
DoubleNodePointer initialize()
{
DoubleNodePointer origin=(DoubleNodePointer)malloc(sizeof(DoubleNode));
if(origin==NULL)
{
prompt("FAILED TO ALLOCATE MEMORY");
exit(-1);
}
origin->prior=NULL;
origin->next=NULL;
origin->data=0;
inputData("The Length Of DoubleLinkedList:","%d",&(origin->data));
DoubleNodePointer transition=origin;
DoubleNodePointer newborn;
for(int i=0;i<origin->data;i++)
{
newborn=(DoubleNodePointer)malloc(sizeof(DoubleNode));
if(newborn==NULL)
{
prompt("FAILED TO ALLOCATE MEMORY");
exit(-1);
}
printf("element\t%d:\t",i+1);
scanf("%d",&(newborn->data));
newborn->prior=transition;
newborn->next=NULL;
transition->next=newborn;
transition=newborn;
}
return origin;
}
void manifest(DoubleNodePointer origin)
{
if(isEmpty(origin))
return;
printf("{\t");
while(origin->next!=NULL)
{
origin=origin->next;
if(origin->next!=NULL)
printf("%d\t",origin->data);
else
printf("%d",origin->data);
}
printf("\t}\n");
}
status inverse(DoubleNodePointer origin)
{
if(isEmpty(origin))
return ERROR;
if(origin->data==1)
return OK;
DoubleNodePointer begin=origin->next;
DoubleNodePointer end=origin;
while(end->next!=NULL)
{
end=end->next;
}
int temp;
while(begin->prior!=end && begin!=end)
{
temp=end->data;
end->data=begin->data;
begin->data=temp;
begin=begin->next;
end=end->prior;
}
return OK;
}
void append(DoubleNodePointer origin, int value)
{
DoubleNodePointer newborn=(DoubleNodePointer)malloc(sizeof(DoubleNode));
if(newborn==NULL)
{
prompt("FAILED TO ALLOCATE MEMORY\n");
return;
}
origin->data=origin->data+1;
newborn->data=value;
newborn->next=NULL;
for(;origin->next!=NULL;origin=origin->next);
newborn->prior=origin;
origin->next=newborn;
}
status delete(DoubleNodePointer origin, int position)
{
if(isEmpty(origin))
return ERROR;
int* length=&(origin->data);
int i;
for(i=1;i<position && origin->next!=NULL;i++,origin=origin->next);
if(i>position || origin->next==NULL)
{
prompt("INVALID POSITION\n");
return ERROR;
}
--*length;
DoubleNodePointer destroyedNode=origin->next;
int value=destroyedNode->data;
origin->next=destroyedNode->next;
if(destroyedNode->next!=NULL)
destroyedNode->next->prior=origin;
free(destroyedNode);
printf("The element < %d > is deleted successfully!\n",value);
return OK;
}
status insert(DoubleNodePointer origin, int position, int value)
{
if(isEmpty(origin))
return ERROR;
DoubleNodePointer spring=origin;
int i=1;
for(;i<position && origin->next!=NULL;i++,origin=origin->next);
if(i>position || origin->next==NULL)
{
prompt("INVALID POSITION\n");
return ERROR;
}
spring->data++;
DoubleNodePointer newborn=(DoubleNodePointer)malloc(sizeof(DoubleNode));
newborn->data=value;
newborn->prior=origin;
newborn->next=origin->next;
origin->next->prior=newborn;
origin->next=newborn;
return OK;
}
DoubleNodePointer getNode(DoubleNodePointer origin, int position)
{
if(isEmpty(origin))
return NULL;
int i=1;
for(;i<position && origin->next!=NULL;i++,origin=origin->next);
if(origin->next==NULL || i>position)
{
prompt("INVALID POSITION\n");
return NULL;
}
return origin->next;
}
status locateNode(DoubleNodePointer origin, int value,int* position)
{
*position=-1;
if(isEmpty(origin))
return ERROR;
int i=1;
for(;origin->next!=NULL && origin->next->data!=value;i++,origin=origin->next);
if(origin->next==NULL)
{
prompt("There Is No Such Node!!!\n");
return ERROR;
}
*position=i;
return OK;
}
status modify(DoubleNodePointer origin, int position, int value)
{
if(isEmpty(origin))
return ERROR;
if(position<1 || position>origin->data)
{
prompt("INVALID POSITION\n");
return ERROR;
}
for(int i=0;i<position;i++,origin=origin->next);
origin->data=value;
prompt("The modification is completed!\n");
return OK;
}
status sort(DoubleNodePointer origin)
{
if(isEmpty(origin))
return ERROR;
if(origin->data==1)
return OK;
DoubleNodePointer begin=origin->next->next;
DoubleNodePointer end=NULL;
int rival;
for(;begin!=NULL;begin=begin->next)
{
rival=begin->data;
end=begin->prior;
for(;end!=origin && rival<end->data;end=end->prior)
{
end->next->data=end->data;
}
end->next->data=rival;
}
prompt("Sorting is finished!\n");
return OK;
}
void clear(DoubleNodePointer origin)
{
if(isEmpty(origin))
return;
DoubleNodePointer terminal=origin;
DoubleNodePointer temporary=NULL;
int value;
for(;terminal->next!=NULL;terminal=terminal->next);
while(terminal!=origin)
{
temporary=terminal;
value=terminal->data;
terminal=terminal->prior;
free(temporary);
printf("The element < %d > is released completely!\n",value);
origin->data--;
}
origin->next=NULL;
}
void menu(char **origin, int length)
{
int span = SPAN;
int gapLength;
duplicate(span, '*');
duplicate(1, '\n');
for (int i = 0; i < length; ++i) {
duplicate(1, '*');
gapLength = span - (strlen(*(origin + i))) - 2;
duplicate(gapLength / 2, ' ');
printf("%s", origin[i]);
if (gapLength % 2 == 0) {
duplicate(gapLength / 2, ' ');
} else {
duplicate(gapLength / 2 + 1, ' ');
}
duplicate(1, '*');
duplicate(VerticalGap, '\n');
}
duplicate(span, '*');
duplicate(1, '\n');
}
void duplicate(int size, char token) {
for (int i = 0; i < size; ++i) {
printf("%c", token);
}
}
呕心沥血有注释版:
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<malloc.h>
#define OK 1
#define ERROR -1
#define status int
#define SPAN 35//菜单的水平跨度
#define VerticalGap 1//菜单的垂直间距,1就是一个换行,2就是两个换行
typedef struct doubleNode //定义双向链表的结点
{
struct doubleNode * prior; //前指针域
int data; //数据域
struct doubleNode * next; //后指针域
}* DoubleNodePointer,DoubleNode; //DoubleNodePointer 等于struct doubleNode* ,DoubleNode等于struct doubleNode
DoubleNodePointer initialize();
//初始化双链表,
//由用户输入双向链表的长度,长度大于0,再依次输入每个结点的值,生成一个带起源结点的双向链表,并返回起源指针
void manifest(DoubleNodePointer origin);
//遍历双向链表的所有结点,不包括起源结点
//这里可能有人会很好奇,函数名不是说好的show吗?
//我之所以把函数名改了,是因为这个单词我接触到无数次了,内心有种抵触
status isEmpty(DoubleNodePointer origin);
//判断双向链表是否为空,若空返回1,否则返回0
//若起源结点后指针域为空,则双向链表为空,否则双向链表不为空
void prompt(char* hint);
//自己封装的一个函数,运用了字符串和指针的小知识,用来输出提示信息
//只需给函数形参传递一个字符串起始指针,或者直接传递字符串常量
void inputData(char* hint,char* type,void* object);
//自己封装的一个函数,运用了指针和字符串的小知识,用来输入数据
//形参主要有:
// char* hint 数据输入前给出的提示信息,提示用户要输入什么数据。给其传递一个字符串常量,或者字符串起始指针即可
// char* type 输入的数据是什么类型,事先定好。可以为其传递 %d、%c、%s、%f %lf等
// void* object 用什么变量接收数据,可为其传递一个变量的指针,该变量的类型要与type保持一致
// 举例1 char name[20];inputData("Your Name","%s",name);
// 举例2 float price;inputData("apple:","%f",&price);
status inverse(DoubleNodePointer origin);
//将双向链表中的所有有效结点进行头尾逆转,如:头尾逆转前 {14,13,2020,52,952,} 头尾逆转后 {952,52,2020,13,14}
//需要为函数形参传递双向链表的起源指针,函数返回值为双向链表逆转是否完成的状态值,OK或者ERROR
//若双向链表为空,返回ERROR;若双向链表只有一个有效结点,返回OK;
//若双向链表存在多个有效结点就进行一次逆转,返回OK
status delete(DoubleNodePointer origin, int position);
//删除双向链表中某一指定下标的结点
//需要为函数形参传递双向链表的起源指针,双向链表的结点下标,返回值为删除结点是否完成的状态值,OK或者ERROR
//若输入下标小于等于0,下标无效,返回ERROR;若输入下标大于有效结点个数,下标无效。返回ERROR
//若输入有效下标,则删除该下标对应的结点,返回OK
void append(DoubleNodePointer origin, int value);
//为双向链表追加新结点
//链表没有长度限度,只要你内存够用,随便追加结点
//需要为函数形参传递双向链表的起源指针,新结点的数据
//首先生成新结点,然后为其数据域和后指针域初始化,最后利用其前指针域与双向链表尾结点相互牵链
status insert(DoubleNodePointer origin, int position, int value);
//为双向链表插入一个新结点
//为函数形参传递起源指针,新结点插入的下标,新结点的数据
//首先判断双向链表是否为空,为空则无法进行插入新结点操作
//然后判断插入的下标是否为有效下标,若为有效下标则插入新结点,否则无法插入新结点,返回ERROR
//下标小于等于0、大于有效结点个数均为无效下标
DoubleNodePointer getNode(DoubleNodePointer origin, int position);
//按下标取结点
//为函数形参传递起源指针,结点下标,返回对应的结点
//若双向链表为空,返回NULL;若下标为无效下标,返回NULL。下标小于等于0、下标大于结点个数均视为无效
//下标有效时,返回对应的结点
status locateNode(DoubleNodePointer origin, int value,int* position);
//按值取结点下标
//需要给形参传递起源指针,结点数据,待赋值的下标变量指针,返回查询结点后的状态值 OK 或者 ERROR
//若双向链表为空,为下标变量赋值-1,返回ERROR;
//若结点存在,则为下标变量赋上对应的下标值 ,返回OK;否则为下标变量赋值-1,返回 ERROR
status modify(DoubleNodePointer origin, int position, int value);
//根据下标,修改对应结点的数据
//为函数形参传递起源指针,结点下标,结点数据,返回修改后的状态值 OK 或者 ERROR
//若双向链表为空,返回ERROR
//若输入的下标无效,返回ERROR
//下标有效,修改结点的数据,返回OK
status sort(DoubleNodePointer origin);
//为双向链表进行升序排序,返回排序后的状态值OK 或者 ERROR
//若双向链表为空,返回ERROR
//若双向链表的长度为1,返回OK
//若双向链表存在多个结点则进行排序,返回OK
void clear(DoubleNodePointer origin);
//清空双向链表,但保留起源结点,起源结点的后指针域置空
void menu(char **menu, int length);//动态地输出菜单
void duplicate(int size, char token);//给出一个整数size和一个字符型符号token,输出size个token,不换行
void main()
{
char *operatingMenu[] = {"DoubleLinkedList",
"1.reinitialize list", "2.present list", "3.append element",
"4.delete element", "5.insert element", "6.modify element",
"7.get element", "8.locate element", "9.sort", "10.inverse",
"11.clear list", "12.exit"};
int length = sizeof(operatingMenu) / sizeof(char *);
int indicator, position, value;
DoubleNodePointer origin=initialize();
while (1)
{
system("cls");
menu(operatingMenu, length);
if(!isEmpty(origin))
{
printf("CURRENT TOTAL:\t%d\n",origin->data);
manifest(origin);
}
else
{
prompt("Data are required!\n");
}
inputData("type in number:\t","%d",&indicator);
switch (indicator) {
case 1:
clear(origin);
free(origin);
prompt("The Origin Node Is Released completely!\n");
prompt("...\treinitializing\t...\n");
origin=initialize();
break;
case 2:
manifest(origin);
system("pause");
break;
case 3:
inputData("VALUE:","%d",&value);
append(origin, value);
manifest(origin);
system("pause");
break;
case 4:
inputData("POSITION:","%d",&position);
(delete(origin, position)== OK) ? manifest(origin) : prompt("check out and try again!\n");
system("pause");
break;
case 5:
inputData("POSITION:","%d",&position);
inputData("VALUE:","%d",&value);
(insert(origin,position, value)==OK) ? manifest(origin) : prompt("check out and try again!\n");
system("pause");
break;
case 6:
inputData("POSITION:","%d",&position);
inputData("VALUE:","%d",&value);
(modify(origin,position,value)==OK)?manifest(origin) : prompt("check out and try again!\n");
system("pause");
break;
case 7:
inputData("POSITION:","%d",&position);
DoubleNodePointer dnp=getNode(origin,position);
dnp? printf("VALUE: %d\n", dnp->data) : prompt("check out and try again!\n");
system("pause");
break;
case 8:
inputData("VALUE:","%d",&value);
(locateNode(origin,value,&position)==OK) ? printf("POSITION:%d\n", position):prompt("check out and try again!\n");
system("pause");
break;
case 9:
(sort(origin)==OK)?manifest(origin):prompt("check out and try again!\n");
system("pause");
break;
case 10:
(inverse(origin)== OK) ? manifest(origin) : prompt("check out and try again!\n");
system("pause");
break;
case 11:
clear(origin);//清空链表,保留起源结点,以便后续操作
system("pause");
break;
case 12:
clear(origin);//清空链表所有有效节点
free(origin);//起源结点再单独释放
prompt("The Origin Node Is Released completely!\n");
system("pause");
exit(-1);//退出系统
break;
}
}
}
status isEmpty(DoubleNodePointer origin)
{
if(origin->next==NULL)//起源结点的next指针域无指向,那么双向链表即为空
{
prompt("DoubleLinkedList Is Empty!\n");
return 1;
}
return 0;
}
void inputData(char* hint,char* type,void* object)
{
printf("%s",hint);
scanf(type,object);
}
void prompt(char* hint)
{
printf("%s",hint);
}
DoubleNodePointer initialize()
{
DoubleNodePointer origin=(DoubleNodePointer)malloc(sizeof(DoubleNode));
//初始化起源结点,将其指针保存在指针变量origin中
if(origin==NULL)//若起源指针为空的话,则分配内存失败,直接退出当前系统
{
prompt("FAILED TO ALLOCATE MEMORY");
exit(-1);
}
origin->prior=NULL;//起源结点前指针域初始化为空
origin->next=NULL;//起源结点后指针域初始化为空
origin->data=0;//起源结点数据域初始化为0
inputData("The Length Of DoubleLinkedList:","%d",&(origin->data));//起源结点的数据域存储有效结点的个数,该数由用户输入
DoubleNodePointer transition=origin;
DoubleNodePointer newborn;
for(int i=0;i<origin->data;i++)
{
newborn=(DoubleNodePointer)malloc(sizeof(DoubleNode));//创建新结点
if(newborn==NULL)//若内存分配失败,则弹出提示信息,不再往下执行
{
prompt("FAILED TO ALLOCATE MEMORY");
exit(-1);
}
printf("element\t%d:\t",i+1);//提示输入信息,
scanf("%d",&(newborn->data));//由用户输入新结点的值
newborn->prior=transition;//新结点与其前驱结点牵链
newborn->next=NULL;//新结点的后指针域初始化为空
transition->next=newborn;//新结点的前驱结点与其牵链
transition=newborn;//将结点指针指向新结点,进行下一次循环
}
return origin;
}
void manifest(DoubleNodePointer origin)
{
if(isEmpty(origin))//链表为空的话,后续操作不再执行
return;
printf("{\t");
while(origin->next!=NULL)
{
origin=origin->next;
if(origin->next!=NULL)
printf("%d\t",origin->data);
else
printf("%d",origin->data);
}
printf("\t}\n");
}
status inverse(DoubleNodePointer origin)
{
if(isEmpty(origin))
return ERROR;
if(origin->data==1)
return OK;
DoubleNodePointer begin=origin->next;//保存双向链表的首结点指针
DoubleNodePointer end=origin;//事先将end置为起源结点指针
while(end->next!=NULL)
{
end=end->next;//将end定位到双向链表的尾结点指针
}
int temp;
while(begin->prior!=end && begin!=end)
{
//若结点总数为偶数,在首指针前进,尾指针后退相遇之后结束循环
//若结点总数为奇数,首指针和尾指针达中间结点后结束循环
temp=end->data;
end->data=begin->data;
begin->data=temp;
begin=begin->next;//首指针向后继结点前进
end=end->prior;//尾指针向前驱结点后退
}
return OK;
}
void append(DoubleNodePointer origin, int value)
{
DoubleNodePointer newborn=(DoubleNodePointer)malloc(sizeof(DoubleNode));//创建新结点,并分配堆内存
if(newborn==NULL)//若内存分配失败,则弹出提示信息,不再往下执行
{
prompt("FAILED TO ALLOCATE MEMORY\n");
return;
}
origin->data=origin->data+1;//追加结点时,起源结点的数据域加1
newborn->data=value;
newborn->next=NULL;
for(;origin->next!=NULL;origin=origin->next);//定位到双向链表的尾结点
newborn->prior=origin;//为新结点与尾结点牵链
origin->next=newborn;//为尾结点与新结点牵链
}
status delete(DoubleNodePointer origin, int position)
{
if(isEmpty(origin))
return ERROR;
//从逻辑上,为双向链表所有结点人为设定下标。起源结点下标为0,首结点下标为1,第二个结点下标为2,,,
//小于等于0的下标为非法下标,大于结点个数的下标为非法下标
//有效结点的个数存储在起源结点的数据域
int* length=&(origin->data);//获取起源结点的数据域指针
int i;
for(i=1;i<position && origin->next!=NULL;i++,origin=origin->next);//直接定位到第position-1个结点
//要删除第position个结点,先瞧它前面的position-1个结点是否都存在
if(i>position || origin->next==NULL)//这里i>position的意思是"若输入的元素下标position小于0,则i自然大于position"
{ //若第position个结点不存在 ,则输入下标position非法
prompt("INVALID POSITION\n");
return ERROR;
}
--*length;//删除一结点,起源结点的数据域减1
DoubleNodePointer destroyedNode=origin->next;//事先保存待销毁结点指针,origin现为待销毁结点的前驱结点
int value=destroyedNode->data;//事先保存待销毁结点的数据域值
origin->next=destroyedNode->next;//待销毁结点的前驱结点与其后继结点牵链
if(destroyedNode->next!=NULL)//若待销毁结点存在后继结点
destroyedNode->next->prior=origin;//则待销毁结点的后继结点与其前驱结点牵链
//若待销毁结点不存在后继结点,即待销毁结点为尾结点,
//则直接释放待销毁结点,其前驱结点的后指针域直接赋值为尾指针的后指针域,尾指针的后指针域自身就是空
free(destroyedNode);//释放待销毁结点
printf("The element < %d > is deleted successfully!\n",value);
return OK;
}
status insert(DoubleNodePointer origin, int position, int value)
{
if(isEmpty(origin))
return ERROR;
//从逻辑上,为双向链表所有结点人为设定下标。起源结点下标为0,首结点下标为1,第二个结点下标为2,,,
//插入结点时,小于等于0的下标为非法下标,大于等于结点个数的下标为非法下标
DoubleNodePointer spring=origin;//将起源结点指针保存在指针变量spring中
int i=1;
for(;i<position && origin->next!=NULL;i++,origin=origin->next);//遍历到第position-1个结点
if(i>position || origin->next==NULL)
{
prompt("INVALID POSITION\n");
return ERROR;
}
spring->data++;//插入一个新结点后起源结点数据域加1
DoubleNodePointer newborn=(DoubleNodePointer)malloc(sizeof(DoubleNode));//创建待插入结点,并分配堆内存
newborn->data=value;//为待插入结点的数据域赋值
//----------------------------------------------------插入新结点
newborn->prior=origin;//为待插入结点与其前驱结点牵链
newborn->next=origin->next;//为待插入结点与其后继结点牵链
origin->next->prior=newborn;//为待插入结点的后继结点与待插入结点牵链 //双链表插入结点的次序不断乱
origin->next=newborn;//为待插入结点的前驱结点与待插入结点牵链
//----------------------------------------------------插入新结点结束
return OK;
}
DoubleNodePointer getNode(DoubleNodePointer origin, int position)
{
if(isEmpty(origin))
return NULL;
int i=1;
for(;i<position && origin->next!=NULL;i++,origin=origin->next);//定位到双向链表的第position-1个结点
if(origin->next==NULL || i>position)
{
prompt("INVALID POSITION\n");
return NULL;
}
return origin->next;
}
status locateNode(DoubleNodePointer origin, int value,int* position)
{
*position=-1;
if(isEmpty(origin))
return ERROR;
int i=1;
for(;origin->next!=NULL && origin->next->data!=value;i++,origin=origin->next);
//上述循环需要认真仔细的思考一番,才能体会其中味道
//i变量用来确定循环链表中的下标,
//当结点指针指向首结点时,i=1;当结点指针指向第二个结点时,i=2;i当结点指针指向第三个结点时,i=3;...
//请注意循环内的结点都用其前驱结点表示的。i=1,1号结点用起源结点表示;i=2,2号结点用1号结点表示;...
//假设我们有六个有效结点,起源结点算在内,那么按照上述规律
//4号结点,必是用3号结点表示,i=4
//5号结点,即尾结点,必是用4号结点表示,i=5
//若继续执行,6号结点,必是5号结点表示,
//但5号结点已经是尾结点了,所以5号结点的后指针域为NUll,无法推算出6号结点,退出循环
if(origin->next==NULL)
{
prompt("There Is No Such Node!!!\n");
return ERROR;
}
*position=i;
return OK;
}
status modify(DoubleNodePointer origin, int position, int value)
{
if(isEmpty(origin))
return ERROR;
if(position<1 || position>origin->data)
{
prompt("INVALID POSITION\n");
return ERROR;
}
for(int i=0;i<position;i++,origin=origin->next);//定位到第position个结点
origin->data=value;
prompt("The modification is completed!\n");
return OK;
}
status sort(DoubleNodePointer origin)//选用插入排序
{
if(isEmpty(origin))
return ERROR;
if(origin->data==1)
return OK;
DoubleNodePointer begin=origin->next->next;//从第2个结点开始,进行插入排序
//默认第一个结点就是排序的基准,
//第二个结点与第一个结点相比较,进行一次排序,
//第三个结点与第二个结点、第一个结点相比较,进行一次排序
//第四个结点与第三个结点、第二个结点、第一个结点相比较,进行一次排序,
DoubleNodePointer end=NULL;
int rival;
for(;begin!=NULL;begin=begin->next)
{
rival=begin->data;//将待排序结点的数据域值事先保存起来
end=begin->prior;//将待排序结点的前驱结点保存起来
for(;end!=origin && rival<end->data;end=end->prior)
//从其前驱结点往回倒,在倒的过程中,每个结点与待排序结点进行比较
{
end->next->data=end->data;
}
end->next->data=rival;//这一步需要认真思考,思考的点在循环条件rival<end->data
}
prompt("Sorting is finished!\n");
return OK;
}
void clear(DoubleNodePointer origin)//清空链表,保留起源结点
{
if(isEmpty(origin))
return;
//双链表的清空和单链表的清空完全相同,可以把单链表的清空代码照抄过来,功能实现简单地不要不要的了
//为了突显出双链表的前驱和后继指针域的属性,双链表的清空就从其尾结点向首结点遍历,逐个进行释放
DoubleNodePointer terminal=origin;
DoubleNodePointer temporary=NULL;
int value;
for(;terminal->next!=NULL;terminal=terminal->next);//定位到双向链表的尾结点
while(terminal!=origin)
{
temporary=terminal;//记下当前结点
value=terminal->data;//记下当前结点数据域值
terminal=terminal->prior;//记下当前结点的前驱结点
free(temporary);//释放当前结点
printf("The element < %d > is released completely!\n",value);
origin->data--;//起源结点数据域值减1
}
origin->next=NULL;//起源结点的后指针域置空
}
void menu(char **origin, int length)
{
int span = SPAN;
int gapLength;
duplicate(span, '*');
duplicate(1, '\n');
for (int i = 0; i < length; ++i) {
duplicate(1, '*');
gapLength = span - (strlen(*(origin + i))) - 2;
duplicate(gapLength / 2, ' ');
printf("%s", origin[i]);
if (gapLength % 2 == 0) {
duplicate(gapLength / 2, ' ');
} else {
duplicate(gapLength / 2 + 1, ' ');
}
duplicate(1, '*');
duplicate(VerticalGap, '\n');
}
duplicate(span, '*');
duplicate(1, '\n');
}
void duplicate(int size, char token) {
for (int i = 0; i < size; ++i) {
printf("%c", token);
}
}
三、总结
这篇博客,算是我有史以来用时间最长的一篇博客了,内容比较详细丰富。在写博客和写代码注释的过程中,培养了自己的耐心和毅力,锻炼了自己的文字表达能力,还发现了代码中存在的些许逻辑错误,顿时感悟良多。这性子还是太急了,慢一点,不要着急,时间都来得及!如有发现代码中存在的错误,欢迎指正和批评,我一定及时纠正!