一 单链表的概念
不同于在内存空间中连续存放线性表的顺序存储方式,链式存储为线性表的另一种存储方式,在链式存储中,表中逻辑上相邻的元素在物理地址空间上不一定相邻,只要元素的指针域中存放的指针指向下一个元素结点即可。
二 单链表的定义
单链表的每个结点都是一个结构体变量LNode,其中数据域变量存储结点数据(这里,以double类型的变量为结点数据的类型),指针域变量为LNode*类型的指针变量。
#include<stdio.h>
#include<stdlib.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
int main(void) {
LinkList Head = (LinkList)malloc(sizeof(LNode));
//动态分配头结点,并将指向头结点的指针赋值给头指针Head
if (!Head) {
printf("Allocation Failure!\n");
exit(EXIT_FAILURE);
} //检验存储头结点的内存空间是否分配成功,若失败则退出程序
return 0;
}
三 单链表的基本操作
与顺序表一样,同为线性表,单链表常见基本操作如下:
①初始化;
②求表长;
③插入操作;
④删除操作;
⑤按位查找;
⑥按值查找;
⑦输出操作;
⑧判空操作;
⑨销毁操作
下面一一讨论上述列举的九种单链表基本操作。
①初始化
我们将初始化操作交由函数InitList完成,而非在main函数中完成。
#include<stdio.h>
#include<stdlib.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
void InitList(LinkList* list) {
*list = (LinkList)malloc(sizeof(LNode));
//动态分配头结点,并将指向头结点的指针的值赋给头指针
if (!*list) {
printf("Allocation Failure!\n"); //输出提示分配错误
exit(EXIT_FAILURE);
} //若存放头结点的存储空间分配失败,则退出程序
(*list)->next = NULL; //头结点指针域置空
}
//初始化链表
注意头指针置空这一步操作不能少,否则(*list)->next将为野指针。
②求表长
沿着指针域中的指针,一个一个结点地移动,每移动一个结点,计数器变量便加1,直到访问到末尾一个结点的空指针域,此时便可将计数器的值作为求表长函数的返回值。
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
int ListLength(LinkList head) {
LinkList mover = head;
//声明在计算表长过程中往前移动的指针
int Counter = 0;
//表长的计数器,初始值置为0
while (mover->next) {
mover = mover->next; //向下一个结点的指针域移动
Counter++; //表长计数器加1
} //表长计数
return Counter;
}
//求链表的表长
③插入操作
单链表插入操作示意图如下:
先将指针移动到相应位序,再进行上图所示的插入操作。
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
bool ListInsert(LinkList head, double value, int Loc) {
LinkList move = head, insert;
//用于在寻找相应位序的结点时移动的指针,及接收新插入的结点的指针
int length = ListLength(head);
//声明并计算操作对象链表的长度
if (Loc<1 || Loc>length + 1) {
printf("Input valid location!\n"); //提示位序非有效位序
return false;
} //检查位序参数Loc是否在有效位序范围1~length+1之间
for (int count = 1; count <= Loc - 1; count++) {
move = move->next;
} //移动到相应的位序处进行操作
insert = (LinkList)malloc(sizeof(LNode));
//为新插入的结点分配内存空间
if (!insert) {
printf("Allocation Failure!\n"); //输出提示分配错误
exit(EXIT_FAILURE);
} //若存放新插入的结点的存储空间分配失败,则退出程序
insert->Data = value; //为新结点的数据域赋值
insert->next = move->next; //新结点指针域指向原先的第Loc个结点
move->next = insert; //第Loc-1个结点指针域指向新结点
return true;
}
//结点插入操作
④删除操作
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
bool ListDelete(LinkList head, int Loc, double* deleted) {
LinkList mover = head, reduce; //声明移动的指针,及指向被删除的结点的指针
int length = ListLength(head); //声明并计算操作对象链表的长度
if (Loc<1 || Loc>length) {
printf("Input valid location!\n"); //提示位序非有效位序
return false;
} //检查位序参数Loc是否在有效位序范围1~length之间
for (int count = 1; count <= Loc-1; count++) {
mover = mover->next;
} //移动到相应的位序处进行操作
reduce = mover->next; //将指向即将被删除的结点的指针赋给指针变量reduce
mover->next = reduce->next; //指针移动,指向后一个结点
*deleted = reduce->Data; //将被删除的结点的数据域的值返回给*deleted
free(reduce); //释放被删除的结点所占内存空间
return true;
}
//结点删除操作
⑤按位查找操作
#include<stdio.h>
#include<stdbool.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
bool GetElem(LinkList head, int Loc, double* Elem) {
LinkList mover = head; //声明移动的指针
int length = ListLength(head); //声明并计算操作对象链表的长度
if (Loc<1 || Loc>length) {
printf("Input valid location!\n"); //提示位序非有效位序
return false;
} //检查位序参数Loc是否在有效位序范围1~length之间
for (int count = 1; count <= Loc; count++) {
mover = mover->next;
} //移动到相应的位序处进行操作
*Elem = mover->Data; //将第Loc个结点数据域的值赋给*Elem
return true;
}
//按位查找操作
⑥按值查找操作
#include<stdio.h>
#include<stdbool.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
bool LocateElem(LinkList head, double value, int* result) {
LinkList mover = head; //声明移动的指针
int Counter = 0;
//计数器初值为0,搜索目标值的过程中,指针mover每往前移动一步,计数器Counter便加1,直到寻找到目标值
*result = Counter; //结果值*result首先被赋值为初始无效位序0
while (mover->next) {
mover = mover->next; //若指针域不为空,则指针mover向下一个结点移动
Counter++; //指针mover每往前移动一步,计数器加1
if (mover->Data == value) {
*result = Counter; //查找成功,将目标值第一次出现的位序返回给*result
return true;
}
} //查找操作
printf("Selection Failure!\n"); //查找失败,屏幕输出失败提示
return false;
}
//按值查找操作,得到目标值第一次出现时的位序
⑦输出操作
#include<stdio.h>
#include<stdbool.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
void PrintList(LinkList head) {
LinkList mover = head->next;
//声明在计算表长过程中往前移动的指针
while (mover) {
printf("%.3lf", mover->Data); //指针mover不为空则输出其指向的结点的数据域中存储的值
if (mover->next) {
printf(", "); //格式设定,除最后一个元素外每一个元素输出后都要在后面打个逗号
}
mover = mover->next; //向下一个结点的指针域移动
} //按顺序输出链表各元素
}
//屏幕输出线性表
⑧判空操作
算法非常简单,头结点指针域中存储的指针指向NULL则为空表,反之则不是。
#include<stdbool.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
bool Empty(LinkList head) {
if (!head->next) {
return true;
} //是空表则返回true
return false; //非空表则返回false
}
//判空操作,若为空表则返回true,反之则返回false
⑨销毁操作
#include<stdlib.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
void DestroyList(LinkList head) {
LinkList mover = head, destroy = head;
//声明用于移动的指针mover,及用于销毁的指针destroy
while (mover) {
destroy = mover; //将前一个结点交付给destroy
mover = mover->next; //指针向后移动
free(destroy); //前一个结点被销毁
}
}
//销毁操作,将链表在内存中所占用的存储空间全部释放
四 自选操作游戏
将上述函数略作改进(增加了对未定义头结点的非法情形的处理),最终可由用户操作的程序如下。
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef struct LNode {
double Data; //数据域
struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点
void InitList(LinkList* list); //初始化链表
int ListLength(LinkList head); //求链表的表长
bool ListInsert(LinkList head, double value, int Loc);
//结点插入操作
bool ListDelete(LinkList head, int Loc, double* deleted);
//结点删除操作
bool GetElem(LinkList head, int Loc, double* Elem);
//按位查找操作
bool LocateElem(LinkList head, double value, int* result);
//按值查找操作,得到目标值第一次出现时的位序
bool PrintList(LinkList head); //屏幕输出线性表
bool Empty(LinkList head); //判空操作,若为空表则返回true,反之则返回false
void DestroyList(LinkList* list); //销毁操作,将链表在内存中所占用的存储空间全部释放
void ClearBuffer(void); //清除缓冲区操作
int main(void) {
LinkList Head = NULL; //声明头指针
char ch; //用于识别对线性表的操作类型的字符变量
int loc, selectResult; //用于传参的位序,及按值查找操作所返回的位序
double element, elementDeleted, elementDetected;
//用于传参的元素值,删除操作返回的元素值,及按位查找操作所返回的元素值
printf("There are 8 operations for the list:\n"
"s: InitList\n"
"i: ListInsert\n"
"d: ListDelete\n"
"l: GetElem\n"
"v: LocateElem\n"
"p: PrintList\n"
"e: Empty\n"
"h: DestroyList\n"
"Please input the codes to choose different operations.\n");
//操作指南
while (1) {
printf("Input an operation code: ");
ch = getchar(); //输入操作所对应的字符
if (ch == 's') {
getchar(); //吞掉换行符
InitList(&Head);
printf("The sequence list has been successfully initialized.\n");
//输出提示初始化成功
}
//初始化线性表
else if (ch == 'i') {
getchar(); //吞掉换行符
printf("Input the inserted element: ");
while (scanf("%lf", &element) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
printf("Input the location: ");
while (scanf("%d", &loc) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
getchar(); //吞掉换行符
if (ListInsert(Head, element, loc)) {
PrintList(Head);
}
}
//插入操作
else if (ch == 'd') {
getchar(); //吞掉换行符
printf("Input the location of the deleted element: ");
while (scanf("%d", &loc) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
getchar(); //吞掉换行符
if (ListDelete(Head, loc, &elementDeleted)) {
printf("%.3lf, the No.%d element of the list, has been deleted.\n", elementDeleted, loc);
PrintList(Head);
}
}
//删除操作
else if (ch == 'l') {
getchar(); //吞掉换行符
printf("Input the location which will be detected: ");
while (scanf("%d", &loc) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
getchar(); //吞掉换行符
if (GetElem(Head, loc, &elementDetected)) {
printf("The element detected on the location %d is %.3lf!\n", loc, elementDetected);
}
}
//按位查找
else if (ch == 'v') {
getchar(); //吞掉换行符
printf("Input the value which will be selected: ");
while (scanf("%lf", &element) != 1) {
printf("Input valid data!\n");
ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
} //排除非法输入
getchar(); //吞掉换行符
if (LocateElem(Head, element, &selectResult)) {
printf("The element %.3lf firstly appeared on the location %d!\n", element, selectResult);
}
}
//按值查找
else if (ch == 'p') {
getchar(); //吞掉换行符
PrintList(Head);
}
//输出线性表
else if (ch == 'e') {
getchar(); //吞掉换行符
if (Empty(Head)) {
printf("The list is null!\n");
}
else {
printf("The list is not null.\n");
}
}
//线性表判空
else if (ch == 'h') {
getchar(); //吞掉换行符
DestroyList(&Head);
printf("The list has been destroyed!\n");
}
//销毁线性表
else if (ch == '\n') {
}
//若从输入缓冲区读取到'\n',则什么也不执行
else {
printf("Input valid code!\n");
ClearBuffer(); //清除stdin输入缓冲区
}
//排除非法输入的字符
printf("\n");
//为了在格式上区分每一步操作,每一步循环的末尾都要换行
}
return 0;
}
void InitList(LinkList* list) {
if (*list) {
free(*list);
printf("The last list has been destroyed!\n");
} //若原表还未被销毁,则先将原表销毁,以防内存泄漏
*list = (LinkList)malloc(sizeof(LNode));
//动态分配头结点,并将指向头结点的指针的值赋给头指针
if (!*list) {
printf("Allocation Failure!\n"); //输出提示分配错误
exit(EXIT_FAILURE);
} //若存放头结点的存储空间分配失败,则退出程序
(*list)->next = NULL; //头结点指针域置空
}
//初始化链表
int ListLength(LinkList head) {
LinkList mover = head;
//声明在计算表长过程中往前移动的指针
int Counter = 0;
//表长的计数器,初始值置为0
if (!mover) {
printf("Head is NULL!\n");
return Counter;
} //头指针为空的处理,此时默认表的长度为0
while (mover->next) {
mover = mover->next; //向下一个结点的指针域移动
Counter++; //表长计数器加1
} //表长计数
return Counter;
}
//求链表的表长
bool ListInsert(LinkList head, double value, int Loc) {
LinkList mover = head, insert;
//用于在寻找相应位序的结点时移动的指针,及接收新插入的结点的指针
if (!mover) {
printf("Head is NULL!\n");
return false;
} //头指针为空的处理
int length = ListLength(head);
//声明并计算操作对象链表的长度
if (Loc<1 || Loc>length + 1) {
printf("Input valid location!\n"); //提示位序非有效位序
return false;
} //检查位序参数Loc是否在有效位序范围1~length+1之间
for (int count = 1; count <= Loc - 1; count++) {
mover = mover->next;
} //移动到相应的位序处进行操作
insert = (LinkList)malloc(sizeof(LNode));
//为新插入的结点分配内存空间
if (!insert) {
printf("Allocation Failure!\n"); //输出提示分配错误
exit(EXIT_FAILURE);
} //若存放新插入的结点的存储空间分配失败,则退出程序
insert->Data = value; //为新结点的数据域赋值
insert->next = mover->next; //新结点指针域指向原先的第Loc个结点
mover->next = insert; //第Loc-1个结点指针域指向新结点
return true;
}
//结点插入操作
bool ListDelete(LinkList head, int Loc, double* deleted) {
LinkList mover = head, reduce; //声明移动的指针,及指向被删除的结点的指针
if (!mover) {
printf("Head is NULL!\n");
return false;
} //头指针为空的处理
int length = ListLength(head); //声明并计算操作对象链表的长度
if (Loc<1 || Loc>length) {
printf("Input valid location!\n"); //提示位序非有效位序
return false;
} //检查位序参数Loc是否在有效位序范围1~length之间
for (int count = 1; count <= Loc-1; count++) {
mover = mover->next;
} //移动到相应的位序处进行操作
reduce = mover->next; //将指向即将被删除的结点的指针赋给指针变量reduce
mover->next = reduce->next; //指针移动,指向后一个结点
*deleted = reduce->Data; //将被删除的结点的数据域的值返回给*deleted
free(reduce); //释放被删除的结点所占内存空间
return true;
}
//结点删除操作
bool GetElem(LinkList head, int Loc, double* Elem) {
LinkList mover = head; //声明移动的指针
if (!mover) {
printf("Head is NULL!\n");
return false;
} //头指针为空的处理
int length = ListLength(head); //声明并计算操作对象链表的长度
if (Loc<1 || Loc>length) {
printf("Input valid location!\n"); //提示位序非有效位序
return false;
} //检查位序参数Loc是否在有效位序范围1~length之间
for (int count = 1; count <= Loc; count++) {
mover = mover->next;
} //移动到相应的位序处进行操作
*Elem = mover->Data; //将第Loc个结点数据域的值赋给*Elem
return true;
}
//按位查找操作
bool LocateElem(LinkList head, double value, int* result) {
LinkList mover = head; //声明移动的指针
if (!mover) {
printf("Head is NULL!\n");
return false;
} //头指针为空的处理
int Counter = 0;
//计数器初值为0,搜索目标值的过程中,指针mover每往前移动一步,计数器Counter便加1,直到寻找到目标值
*result = Counter; //结果值*result首先被赋值为初始无效位序0
while (mover->next) {
mover = mover->next; //若指针域不为空,则指针mover向下一个结点移动
Counter++; //指针mover每往前移动一步,计数器加1
if (mover->Data == value) {
*result = Counter; //查找成功,将目标值第一次出现的位序返回给*result
return true;
}
} //查找操作
printf("Selection Failure!\n"); //查找失败,屏幕输出失败提示
return false;
}
//按值查找操作,得到目标值第一次出现时的位序
bool PrintList(LinkList head) {
if (!head) {
printf("Head is NULL!\n");
return false;
} //头指针为空的处理
LinkList mover = head->next;
//声明在计算表长过程中往前移动的指针
while (mover) {
printf("%.3lf", mover->Data); //指针mover不为空则输出其指向的结点的数据域中存储的值
if (mover->next) {
printf(", "); //格式设定,除最后一个元素外每一个元素输出后都要在后面打个逗号
}
mover = mover->next; //向下一个结点的指针域移动
} //按顺序输出链表各元素
return true;
}
//屏幕输出线性表
bool Empty(LinkList head) {
if (!head) {
return true;
}
else if (!head->next) {
return true;
} //是空表则返回true(只有头结点,或连头指针都为空)
return false; //非空表则返回false
}
//判空操作,若为空表则返回true,反之则返回false
void DestroyList(LinkList* list) {
LinkList mover = *list, destroy = *list;
//声明用于移动的指针mover,及用于销毁的指针destroy
while (mover) {
destroy = mover; //将前一个结点交付给destroy
mover = mover->next; //指针向后移动
free(destroy); //前一个结点被销毁
}
*list = NULL; //注意将头指针置零,否则头指针将成为野指针
}
//销毁操作,将链表在内存中所占用的存储空间全部释放
void ClearBuffer(void) {
char ch;
while ((ch = getchar())!='\n' && ch!=EOF) {
}
} //清除缓冲区操作