单链表的初始化、销毁、清空、求表长、取值、查找、插入、删除。
首元结点:是指链表中存储第1个数据元素a1的结点
代码采用的是C语言和一点C++语法,只展示了功能代码(有部分注释),main()就不再展示。
#include <stdio.h>
#include <stdlib.h> // 给表分配内存,所需该头文件
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0 // 错误
#define INFEASIBLE -1 // 不可行
#define OVERFLOW -2 // 溢出
typedef int Status; // Status是函数的类型,其值是函数返回结果的类型,此处定义为int型(后面用来返回地址下标)
typedef char ElemType; // 定义线性表中存放数据的类型为 char型
// 单链表的存储结构
typedef struct LNode // 定义结构体类型,命名为LNode
{
ElemType data; // 定义char型的数据域
struct LNode *next; // 定义结点型的指针域,即指针域将存放下一个结点的地址
}LNode,*LinkList; // LNode的类型是指针类型,*LinkList指向结构体(为了提高程序的可读性)
定义链表L:LinkList L;
定义结点指针P:LNode *P; 或 LinkList P;
(两种创建结点P的方式,是为了好辨别创建的是个结点还是链表,其本质是相同的)
// 单链表的初始化(构造空表)
status InitList_L (LinkList &L) // LinkList就是单链表结构体类型,LinkList &L即声明L的类型
{
L = new LNode; // C++方法,new LNode即生成新结点作为头结点,L = new LNode即用头指针L指向头结点
// L = (LinkList)malloc(sizeof(LNode)); C方法
L->next = NULL; // 将头结点的指针域置空
return OK; // 创建成功,返回真值
}
单链表是由头指针唯一确定,因此单链表可以用头指针的名字来命名
// 判断单链表是否为空 空表:无元素,头指针和头结点仍存在
int isEmpty(LinkList L) // 同样声明L的类型
{
if(L->next) // L->next为空,返回0,非空,返回1
return 0; // 0表示空表
else
return 1; // 1表示非空表
}
// 销毁单链表(从头结点开始,一次释放所有结点)
Status DestroyList(LinkList &L)
{
LNode *p; // 创建一个用于记录的结点
while(L) // 结束条件 L==NULL
{
p = L; // p指向待删除的结点,指谁删谁
L = L->next; // 待删除的结点已被记录,即可指向链表中的下一个结点
delete p; // 删除结点
}
return OK;
}
// 删除指针,编译器只会释放该指针所指向的内存空间,而不会删除这个指针本身
// 清空单链表(无元素,头指针头结点仍存在)
Status ClearList(LinkList &L)
{
LNode *p, *q; // 创建两个工具结点,p相当于销毁算法中的L,q记录待删除的结点
p = L->next; // p指向首元结点,从首元结点依次向尾部删除
while(p) // 循环条件:p指针没有移到表尾
{
q = p->next; // 循环部分相当于销毁算法
delete p;
p = q;
}
L->next = NULL; // 头结点指针域置空
return OK;
}
销毁与清空比较:
销毁的L一直跟着p移动,边移动边释放内存(可理解为过河拆桥,最终,L相当于指向了最后一个元素的指针域,也为空)
清空的L不动,看着p和q过河拆桥,最终,p和q都为空,不存在,L也直接置空
// 返回单链表的表长(从首元结点开始,依次计数所有结点)
int ListLength_L(LinkList L)
{
LNode *p;
p = L->next; // p指向第一个结点
int i = 0;
while(p) // 只要p不为空,就累加结点数量
{
i++;
p = p->next; // 依次往后移
}
return i;
}
// 单链表取值————取单链表中第i个元素的内容
Status GetElem(LinkList L, int i, ElemType &e) // i要找的位置,&e即通过e返回
{
p = L->next; // 初始化,p指向首元结点
int j = 1; // 计数器
while(p && j<i) // 当j==i时,p所指向的节点既是第i个结点
{
p = p->next; // p指向下一个结点
++j;
}
if(!p && j>i) return ERROR; //第i个元素不存在,!p即超过了链表,j>i即计数的值超过了i值
e = p->data; // 取值
return OK;
}
// 单链表按值查找————根据指定数据获取该数据所在的位置(地址)
LNode *LocateElem(LinkList L, ElemType e) // 在带头结点的单链表L中查找值为e的元素
{
p = L->next; // 初始化,p指向首元结点
while(p && p->data != e) // 结束条件:链表扫描完了或找到了指定数据
p = p->next; // 没找到继续往后扫描
return p; // 返回p。若查找成功,p此时即为结点的地址值,若查找失败,p的值即为NULL
}
// 按值查找————根据指定数据获取该数据位置序号
int LocateElem(LinkList L, ElemType e)
{
p = L->next;
int j = 1;
while(p && p->data != e) // ①找到p!=NULL,有j值;②未找到,p==NULL,无j值
{
p = p->next;
j++;
}
if(p) return j; // 如果没有if(p) j的情况:①返回正确的值;②返回链表长度
else return 0;
}
// 插入————在第i个结点前插入值为e的新结点
Status ListInsert(LinkList &L, int i, ElemType e) // 在带头结点的单链表L中第i个位置插入值为e的新结点
{
p = L;
int j = 0;
while(p && j<i-1) // ①
{
p = p->next; // 查找第i-1个结点,p指向该(i-1)结点
j++;
}
if(!p || j>i-1) return ERROR; // i>n+1 或者 i<1 --> i大于表长+1 或者 小于1,位置非法
s = new LNode; // ②生成新结点*s
s->data = e; // ③将结点*s的数据域置为e
s->next = p->next; // ④将结点*s的指针域指向p的后面一个结点
p->next = s; // ⑤将结点*p的指针域指向结点*s
return OK;
}
// 删除————删除第i个结点
Status ListDelete(LinkList &L, int i) //在带头结点的单链表L中,删除第i个元素
{
p = L;
int j = 0;
while(p->next && j<i-1) //①
{
p = p->next; // 寻找第i个结点,并令p指向其前驱
++j;
}
if(!p->next || j>i-1) return ERROR; // 删除位置不合理
q = p->next; // ②临时保存被删结点,以备释放
p->next = q->next; // ③改变删除结点前驱结点的指针域
delete q; // ④释放删除结点的空间
return OK;
}
时间效率分析:
- 查找O(n)
- 插入删除O(1)
- 如果要在单链表中进行前插或删除操作,就要从头查找前驱结点,所耗时间复杂度为O(n)