上次我们已经看过顺序表的实现方式,这次来看看单链表的实现。
还是老规矩:
程序在码云上可以下载。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git
单链表的ADT与顺序表一样:
ADT List{
数据对象:D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 }
数据关系:R1={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n }
基本操作:
(1)线性表的初始化操作
InitList(&L,n)
操作结果:将L初始化为空表,申请空间的大小为n。
(2)线性表元素输入操作
ListInput(&L)
初始条件:线性表L已存在 。
操作结果:线性表中的部分元素或全部元素已被赋值。
(3)线性表的置空操作
ClearList(&L)
初始条件:线性表L已存在且不为空 。
操作结果:将表L置为空表。
(4)线性表的判空操作
ListIsEmpty(&L)
初始条件:线性表L已存在。
操作结果:如果L为空表则返回1,否则返回0。
(5)获取线性表元素个数的操作
ListLength(L)
初始条件:线性表L已存在。
操作结果:如果L为空表则返回0,否则返回表中的元素个数。
(6)获取线性表第i个元素的操作(用元素的位序查找元素的值)
GetItem(L,i,&e)
初始条件:表L已存在且不为空,1<=i<=ListLength(L)。
操作结果:用e返回L中第i个元素的值。
(7)确定线性表元素位置的操作 (用元素的值确定元素位置)
LocateElem(L,e)
初始条件:表L已存在且不为空,e为合法元素值。
操作结果:如果L中存在元素e,则将“当前指针”指向第一个这样的元素e所在位置并返回真,否则返回假。
(8)线性表插入元素操作
ListInsert(&L,i,e)
初始条件:表L已存在且不为空,e为合法元素值且1≤i≤ListLength(L)+1。
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1。
(9)输出线性表元素操作
PrintList(&L)
初始条件:线性表L已存在且不为空。
操作结果:线性表L中的所有元素已输出。
(10)销毁线性表操作
DestroyList(&L)
初始条件:线性表L已存在。
操作结果:将L销毁。
}ADT List
单链表采用了链式存储结构,不再要求存储空间必须是连续的(我没说链表的存储空间不可以连续,只是不强制要求连续而已(连续不连续都可以),比如后面要介绍的静态链表的存储空间就是连续的)。这就决定了单链表的插入删除效率高(不需要来回移动元素,只要改改指针就行了),但是查询效果却不理想(只能一个个地扫描式的向后查找,不能像顺序表一样使用位置计算公式直接定位)。
一起来看看单链表的代码实现吧:
//*******************************************引入头文件*********************************************
#include <stdio.h> //使用了标准库函数
#include <stdlib.h> //使用了动态内存分配函数
//******************************************自定义符号常量*******************************************
#define OVERFLOW -2 //内存溢出错误常量
#define ILLEGAL -1 //非法操作错误常量
#define OK 1 //表示操作正确的常量
#define ERROR 0 //表示操作错误的常量
//******************************************自定义数据类型********************************************
typedef int Status; //用typedef给int起个别名,也便于程序的维护
typedef float ElemType; //用typedef给float起个别名,也便于程序的维护
typedef struct LNode { //用C语言描述线性单链表的结构,声明结构体的同时声明一个结构体指针
ElemType data; //数据域
struct LNode *next; //指针域
}LNode, * LinkList;
/*说明:该操作等价于:
struct LNode //先声明一个结构体
{
ElemType data; //数据域
struct LNode *next; //指针域
};
typedef LNode * LinkList; //声明一个结构体指针
*/
//******************************************线性表的主要操作******************************************
//1.-------------------------------------线性单链表的初始化操作---------------------------------------
/*
函数:MallocList_L
参数:LinkList &L 带回创建的节点
返回值:状态码,OK表示操作成功
作用:申请一个节点的内存空间
*/
Status MallocList_L(LinkList &L) {
//为线性表L开辟内存空间,只申请一个结点的内存空间
L = (LinkList)malloc(sizeof(LNode));
if(!L) { //if(!L) <=> if(L == NULL)
printf("申请内存失败!\n");
exit(OVERFLOW); //退出程序,并提示用户内存分配失败的原因是内存泄露
}//if
//操作成功
return OK;
} //MallocList_L
/*
函数:InitList_L
参数:LinkList &L 单链表的头指针
int n 线性表元素的个数
返回值:状态码,OK表示操作成功
作用:构造一个线性表并将其初始化
*/
Status InitList_L(LinkList &L, int n){
//i声明在这里不是好的写法,应该将i写在for循环中,有利于缩短i的作用域,及早回收其内存空间。
//但是由于C语言不兼容这种写法,考虑到兼容性所以将i写在外面。(C++支持这种写法)
int i;
//工作指针
LinkList p;
//先构造一个空的单链表,完成头结点的创建
MallocList_L(L);
//将头结点的指针域设置为NULL
L->next = NULL;
//->初始化单链表的方法有多种,这里写了两种方法,根据需要保留一个,另一个注释掉即可
/*
//1.使用头插法创建带头结点的单链线性表L,要求输入元素时“逆位序 ”输入
for(i = n; i > 0; --i){
//生成新结点
p = (LinkList)malloc(sizeof(LNode));
//从键盘接收元素值,并存入p指向结点的数据域
printf("请输入第%d个元素的值:(逆位序输入)\n", i);
scanf("%f", &p->data);
//将线性表头结点后面的所有节点链接到p后面
p->next = L->next;
//将p链接到头结点后面,也就是把p插入到了线性表的表头
L->next = p;
}
*/
//2.使用尾插法创建带头结点的单链线性表L,按照正常位序输入元素
printf("请依次输入元素的值(用空格隔开):\n");
LinkList tail = L;
for(i = 0; i < n; i++){
//生成一个新结点,使p指向此结点
MallocList_L(p);
//从键盘接收元素值,并存入p指向结点的数据域
scanf("%f", &p->data);
//将新申请的结点的指针域设为NULL,因为p结点要作为新的表尾结点使用,所以后面没有后继
p->next = NULL;
//将p节点链接到当前表尾元素的后面,成为新的表尾结点
tail->next = p;
//p成为了新的表尾结点,修改tail使其指向p
tail = p;
}//for
//头结点数据域记录了单链表的长度,由于初始化了n个元素,所以赋值n
L->data = n;
//操作成功
return OK;
} //InitList_L
//2.-------------------------------------判断线性单链表是否为空------------------------------------
/*
函数:ListIsEmpty_L
参数:LinkList &L 单链表的头指针
返回值:如果线性表是空表返回1,否则返回0
作用:判断线性表L是否为空
*/
int ListIsEmpty_L(LinkList &L) {
//如果头结点后面有节点,那么单链表就不为空
//L->next == NULL表示头结点后面没有节点
return L->next == NULL;
} //ListIsEmpty_L
//3.------------------------------------线性单表中插入元素的操作-----------------------------------
/*
函数:ListInsert_L
参数:LinkList &L 单链表的头指针
int i 插入位置i
ElemType e 插入元素e
返回值:状态码,OK表示操作成功,ERROR表示操作失败
作用:在带头结点的线性单链表L中第i个位置之前插入元素e
*/
Status ListInsert_L(LinkList &L, int i, ElemType e){
//工作指针
LinkList p = L,s;
//计数器,临时变量,用于记录查找插入位置的下标情况
int