带有头结点的单链表的实现
一、链表概述
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
二、线性表的单链表存储数据
以存储数据为整型为例
typedef int ElemType; /* 定义抽象数据类型ElemType为整型 */
三、线性表的单链表存储结构
线性表的单链表存储结构图示
代码示
//线性表的单链表存储结构
typedef struct Node
{
ElemType data; //代表数据域
struct Node * next; //代表指针域,指向直接后继元素;定义指向Node类型的指针
}Node, *pNode;
四、线性表的单链表操作定义13个
//1、创建头结点返回头指针
pNode InitList();
//2、判断链表是否为空
bool ListEmpty(pNode L);
//3、向链表中追加元素
bool ListAddElem(pNode L, ElemType e);
//4、向链表pos位置前插入元素
bool ListInsert(pNode L, int pos, ElemType e);
//5、遍历链表
void ListTraverse(pNode L);
//6、删除指定位置的元素
bool ListDelete(pNode L, int i, ElemType &e);
//7、获取链表的长度
int ListLength(pNode L);
//8、获取第i个结点的值
bool GetElem(pNode L, int i, ElemType &e);
//9、若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱
bool PriorElem(pNode L, ElemType cur_e, ElemType &pre_e);
//10、若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继
bool NextElem(pNode L, ElemType cur_e, ElemType &next_e);
//11、查询满足相应条件的结点元素,并返回序位
int LocateElem(pNode L, ElemType e, bool(*compare)(ElemType, ElemType));
//11.1比较俩个元素是否相等
bool compare(ElemType e1, ElemType e2);
//12、将链表清空
void ClearList(pNode L); // 不改变L
//13、销毁链表
void DestroyList(pNode &L);
1、创建头结点返回头指针
pNode InitList()
{
pNode pHead = (pNode)malloc(sizeof(Node));//申请一个结点空间作为头结点,头指针指向头结点
if (!pHead) //pHead为空 存储分配失败
exit(-1);
//pHead->data 因为是头结点所以头结点的数据域可以不用管,是一个垃圾直值
pHead->next = NULL; // 头结点指针域初始化为空
return pHead;
}
2、判断链表是否为空
当头指针的指针域为空时,链表为空
bool ListEmpty(pNode L)
{ // 初始条件:线性表L已存在。
//操作结果:若L为空表,则返回true,否则返回false
if (L->next) // 非空
return false;
else
return true;
}
3、向链表中追加元素
bool ListAddElem(pNode L, ElemType e)
{
// 初始条件:线性表L已存在。
//操作结果:在链表最后一个结点后追加一个结点
pNode pEnd = L, pNew, pProEnd = NULL;
while (pEnd != NULL)//让pProEnd指向链表的尾结点
{
pProEnd = pEnd;//保存倒数第二个结点的next值,这个值指向尾结点
pEnd = pEnd->next;
}
pNew = (pNode)malloc(sizeof(Node));//生成新节点
if (pNew == NULL)//为空则节点分配失败,程序终止
{
printf("分配失败,程序终止!\n");
exit(-1);
}
pNew->data = e; //新结点值域赋值
pNew->next = NULL; //新结点指针域赋值,由于是尾结点所以赋值为空
pProEnd->next = pNew; //新结点挂载到尾结点
return true;
}
4、向链表插入结点元素
插入结点图示 pos = 2
bool ListInsert(pNode L, int pos, ElemType e)
{ // 初始条件:线性表L已存在。
// 在带头结点的单链线性表L中第pos个位置之前插入元素e
int i = 0;
pNode p = L;
while (p != NULL && i < pos - 1)//用p指向第pos-1个结点,将指针移动到插入元素位置之前。pos<=1,则不执行while
{
p = p->next; //p指向头结点,此语句执行第一次后p指向首结点,第二次p指向第二个结点....
i++; //计数直到pos-1位置处的结点
}
if (p == NULL || i > pos - 1) //如果p指向的结点为空则p指向了尾结点或者链表没有元素 pos小于1或者大于表长
return false;
pNode Pnew = (pNode)malloc(sizeof(Node)); // 生成新结点
Pnew->data = e; // 将值赋给要插入的结点
Pnew->next = p->next;//新结点指针域指向下一个结点
p->next = Pnew;//第pos-1个结点指向新节点
return true;
}
5、遍历链表
void ListTraverse(pNode L)
{// 初始条件:线性表L已存在。
//操作结果:依次对L的每个结点值进行输出
if (L == NULL)
{
printf("链表不存在\n");
exit(-1);
}
pNode p = L->next;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
6、删除指定位置的元素
bool ListDelete(pNode L, int i, ElemType &e) // 不改变L
{ // 初始条件:线性表L已存在。
//操作结果:在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
int j = 0;
pNode p = L, q;
while (p != NULL && j < i - 1) // 链表不为空,用p指向第i-1个结点
{
p = p->next;
j++;
}
if (p == NULL || j > i - 1) // p初始指针指向为空或者i为链表长度+1 删除位置不合理
return false;
q = p->next; //保存要删除结点指针
p->next = q->next;//要删除结点的指针域的值赋值给要删除结点前的结点的指针域
e = q->data;//保存要删除结点的值
free(q);//释放删除结点的占用的内存空间
return true;
}
7、获取链表的长度
int ListLength(pNode L)
{ // 初始条件:线性表L已存在。操作结果:返回L中数据元素个数
int i = 0;
pNode p = L->next; // p指向第一个结点
while (p != NULL) // 没到表尾
{
i++;
p = p->next;
}
return i;
}
8、获取第i个结点的值
bool GetElem(pNode L, int i, ElemType &e)
{ // L为带头结点的单链表的头指针。当第i个元素存在时,其值赋给e并返回true,否则返回false
int j = 1; // j为计数器
pNode p = L->next; // p指向第一个结点
while (p != NULL && j < i) // 顺指针向后查找,直到p指向第i个元素或p为空
{
p = p->next;
j++;
}
if (p == NULL || j > i) // 第i个元素不存在
return false;
e = p->data; // 取第i个元素
return true;
}
9、若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱
找到第一个结点值为cur_e的结点,返回其前一个节点值
bool PriorElem(pNode L, ElemType cur_e, ElemType &pre_e)
{
// 初始条件: 线性表L已存在
// 操作结果: 若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 返回true;否则操作失败,pre_e无定义,返回false
pNode q = NULL, p = L->next;// p指向第一个结点
if (p->data == cur_e)
{
printf("第一个结点没有前驱\n");
return false;
}
while (p != NULL)
{
q = p;//q用来保存p的前驱
p = p->next;
if (p != NULL && p->data == cur_e)//找到与cur_e相等的结点,当p ==NULL时 p是最后一个结点的指针域的值。则p->data是非法的
break;//终止循环
}
if (p == NULL)//防止链表为空
{
return false;
}
pre_e = q->data;//保存前驱结点值
return true;
}
10、若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继
bool NextElem(pNode L, ElemType cur_e, ElemType &next_e)
{ // 初始条件:线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,
// 返回true;否则操作失败,next_e无定义,返回false
pNode p = L, q = L;
if (q == NULL)
return false;
int i = 0;
while (p != NULL && i<ListLength(L))//将指针指向最后一个结点
{
p = p->next;
i++;
}
while (q != NULL && q != p) // p所指结点有后继,即q最多只会指向最后一个结点之前的结点
{
if (q->data == cur_e)
{
next_e = q->next->data;//保存后驱结点值
return true;
}
q = q->next;
}
return false;
}
11、查询满足相应条件的结点元素,并返回序位
int LocateElem(pNode L, ElemType e, bool(*compare)(ElemType, ElemType))
{ // 初始条件: 线性表L已存在,compare()是数据元素判定函数(满足为ture,否则为false)
// 操作结果: 返回L中第1个与e满足关系compare()的数据元素的位序。
// 若这样的数据元素不存在,则返回值为0
int i = 0;
pNode p = L->next;//p指向首节点
while (p)
{
i++;
if (compare(p->data, e)) // 找到这样的数据元素
return i;
p = p->next;
}
return 0;
}
11.1比较俩个元素是否相等
bool compare(ElemType e1, ElemType e2)
{
if (e1 == e2)
return true;
else
return false;
}
12、将链表清空
void ClearList(pNode L) // 不改变L
{ // 初始条件:线性表L已存在。
//操作结果:将L重置为空表,从第首节点开始释放
pNode p = NULL, q = NULL;
p = L->next;//p指向首节点
while (p)
{
q = p->next;//q保存下一个结点的地址
free(p);//释放当前结点内存
p = q;//将下一节点的地址给p,保证循环
}
L->next = NULL; //头结点指针域重置为空
}
13、销毁链表
void DestroyList(pNode &L)
{ // 初始条件:线性表L已存在。
//操作结果:销毁线性表L,从头结点开始释放(free(L)释放L指向的内存地址)
pNode q;
while (L)
{
q = L->next;
free(L);
L = q;
}
}
最后给出完整代码
/*带有头结点的单链表*/
# include<stdio.h>
# include<stdlib.h>
# include<malloc.h>
typedef int ElemType; /* 定义抽象数据类型ElemType为整型 */
//线性表的单链表存储结构
typedef struct Node
{
ElemType data; //代表数据域
struct Node * next; //代表指针域,指向直接后继元素;定义指向Node类型的指针
}Node, *pNode;
//1、创建头结点返回头指针
pNode InitList();
//2、判断链表是否为空
bool ListEmpty(pNode L);
//3、向链表中追加元素
bool ListAddElem(pNode L, ElemType e);
//4、向链表pos位置前插入元素
bool ListInsert(pNode L, int pos, ElemType e);
//5、遍历链表
void ListTraverse(pNode L);
//6、删除指定位置的元素
bool ListDelete(pNode L, int i, ElemType &e);
//7、获取链表的长度
int ListLength(pNode L);
//8、获取第i个结点的值
bool GetElem(pNode L, int i, ElemType &e);
//9、若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱
bool PriorElem(pNode L, ElemType cur_e, ElemType &pre_e);
//10、若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继
bool NextElem(pNode L, ElemType cur_e, ElemType &next_e);
//11、查询满足相应条件的结点元素,并返回序位
int LocateElem(pNode L, ElemType e, bool(*compare)(ElemType, ElemType));
//11.1比较俩个元素是否相等
bool compare(ElemType e1, ElemType e2);
//12、将链表清空
void ClearList(pNode L); // 不改变L
//13、销毁链表
void DestroyList(pNode &L);
int main(void)
{
pNode L = NULL; //创建头指针L,空指向
L = InitList();//创建一个链表头结点,L指向头结点
if (ListEmpty(L))
printf("链表为空\n");
else
printf("链表不为空\n");
if (ListAddElem(L, 22))
printf("追加成功\n");
int i = 0;
while (i < 5)
{
ListAddElem(L, 2 * i);
i++;
}
ListTraverse(L);
ListInsert(L, 1, 100);
ListTraverse(L);
ElemType e = 0;
ListDelete(L, 5, e);
ListTraverse(L);
printf("删除的元素是 %d\n", e);
printf("链表的长度为 %d\n", ListLength(L));
if (GetElem(L, 5, e))
printf("第5个结点值为 %d\n", e);
else
printf("获取第5个结点失败\n");
ElemType pre_e, cur_e = 2, next_e;
if (PriorElem(L, cur_e, pre_e))
printf("第一个值 %d 的结点的前驱结点值为 %d \n", cur_e, pre_e);
else
printf("前驱获取失败\n");
if (NextElem(L, cur_e, next_e))
printf("第一个值 %d 的结点的后继结点值为 %d \n", cur_e, next_e);
else
printf("后继获取失败\n");
printf("链表中与值%d 相等的值的序位为 %d\n", cur_e, LocateElem(L, cur_e, compare));
ClearList(L);
ListTraverse(L);
DestroyList(L);
ListTraverse(L);
return 0;
}
//1、创建头结点返回头指针
pNode InitList()
{
pNode pHead = (pNode)malloc(sizeof(Node));//申请一个结点空间作为头结点,头指针指向头结点
if (!pHead) //pHead为空 存储分配失败
exit(-1);
//pHead->data 因为是头结点所以头结点的数据域可以不用管,是一个垃圾直值
pHead->next = NULL; // 头结点指针域初始化为空
return pHead;
}
//2、判断链表是否为空
//当头指针的指针域为空时,链表为空
bool ListEmpty(pNode L)
{ // 初始条件:线性表L已存在。
//操作结果:若L为空表,则返回true,否则返回false
if (L->next) // 非空
return false;
else
return true;
}
// 3、向链表中追加元素
bool ListAddElem(pNode L, ElemType e)
{
pNode pEnd = L, pNew, pProEnd = NULL;
while (pEnd != NULL)//让pProEnd指向链表的尾结点
{
pProEnd = pEnd;//保存倒数第二个结点的next值,这个值指向尾结点
pEnd = pEnd->next;
}
pNew = (pNode)malloc(sizeof(Node));//生成新节点
if (pNew == NULL)//为空则节点分配失败,程序终止
{
printf("分配失败,程序终止!\n");
exit(-1);
}
pNew->data = e; //新结点值域赋值
pNew->next = NULL; //新结点指针域赋值,由于是尾结点所以赋值为空
pProEnd->next = pNew; //新结点挂载到尾结点
return true;
}
//4、向链表插入元素
bool ListInsert(pNode L, int pos, ElemType e)
{
// 在带头结点的单链线性表L中第pos个位置之前插入元素e
int i = 0;
pNode p = L;
while (p != NULL && i < pos - 1)//用p指向第pos-1个结点,将指针移动到插入元素位置之前。pos<=1,则不执行while
{
p = p->next; //p指向头结点,此语句执行第一次后p指向首结点,第二次p指向第二个结点....
i++; //计数直到pos-1位置处的结点
}
if (p == NULL || i > pos - 1) //如果p指向的结点为空则p指向了尾结点或者链表没有元素 pos小于1或者大于表长
return false;
pNode Pnew = (pNode)malloc(sizeof(Node)); // 生成新结点
Pnew->data = e; // 将值赋给要插入的结点
Pnew->next = p->next;//新结点指针域指向下一个结点
p->next = Pnew;//第pos-1个结点指向新节点
return true;
}
//5、遍历链表
void ListTraverse(pNode L)
{// 初始条件:线性表L已存在。
//操作结果:依次对L的每个结点值进行输出
if (L == NULL)
{
printf("链表不存在\n");
exit(-1);
}
pNode p = L->next;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
//6、删除指定位置的元素
bool ListDelete(pNode L, int i, ElemType &e) // 不改变L
{ // 在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
int j = 0;
pNode p = L, q;
while (p != NULL && j < i - 1) // 链表不为空,用p指向第i-1个结点
{
p = p->next;
j++;
}
if (p == NULL || j > i - 1) // p初始指针指向为空或者i为链表长度+1 删除位置不合理
return false;
q = p->next; //保存要删除结点指针
p->next = q->next;//要删除结点的指针域的值赋值给要删除结点前的结点的指针域
e = q->data;//保存要删除结点的值
free(q);//释放删除结点的占用的内存空间
return true;
}
//7、获取链表的长度
int ListLength(pNode L)
{ // 初始条件:线性表L已存在。操作结果:返回L中数据元素个数
int i = 0;
pNode p = L->next; // p指向第一个结点
while (p != NULL) // 没到表尾
{
i++;
p = p->next;
}
return i;
}
//8、获取第i个结点的值
bool GetElem(pNode L, int i, ElemType &e) // 算法2.8
{ // L为带头结点的单链表的头指针。当第i个元素存在时,其值赋给e并返回true,否则返回false
int j = 1; // j为计数器
pNode p = L->next; // p指向第一个结点
while (p != NULL && j < i) // 顺指针向后查找,直到p指向第i个元素或p为空
{
p = p->next;
j++;
}
if (p == NULL || j > i) // 第i个元素不存在
return false;
e = p->data; // 取第i个元素
return true;
}
//9、若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱
bool PriorElem(pNode L, ElemType cur_e, ElemType &pre_e)
{
// 初始条件: 线性表L已存在
// 操作结果: 若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 返回true;否则操作失败,pre_e无定义,返回false
pNode q = NULL, p = L->next;// p指向第一个结点
if (p->data == cur_e)
{
printf("第一个结点没有前驱\n");
return false;
}
while (p != NULL)
{
q = p;//q用来保存p的前驱
p = p->next;
if (p != NULL && p->data == cur_e)//找到与cur_e相等的结点,当p ==NULL时 p是最后一个结点的指针域的值。则p->data是非法的
break;//终止循环
}
if (p == NULL)//防止链表为空
{
return false;
}
pre_e = q->data;//保存前驱结点值
return true;
}
//10、若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继
bool NextElem(pNode L, ElemType cur_e, ElemType &next_e)
{ // 初始条件:线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,
// 返回true;否则操作失败,next_e无定义,返回false
pNode p = L, q = L;
if (q == NULL)
return false;
int i = 0;
while (p != NULL && i<ListLength(L))//将指针指向最后一个结点
{
p = p->next;
i++;
}
while (q != NULL && q != p) // p所指结点有后继,即q最多只会指向最后一个结点之前的结点
{
if (q->data == cur_e)
{
next_e = q->next->data;//保存后驱结点值
return true;
}
q = q->next;
}
return false;
}
//11.1比较俩个元素是否相等
bool compare(ElemType e1, ElemType e2)
{
if (e1 == e2)
return true;
else
return false;
}
//11、查询满足相应条件的结点元素,并返回序位
int LocateElem(pNode L, ElemType e, bool(*compare)(ElemType, ElemType))
{ // 初始条件: 线性表L已存在,compare()是数据元素判定函数(满足为ture,否则为false)
// 操作结果: 返回L中第1个与e满足关系compare()的数据元素的位序。
// 若这样的数据元素不存在,则返回值为0
int i = 0;
pNode p = L->next;//p指向首节点
while (p)
{
i++;
if (compare(p->data, e)) // 找到这样的数据元素
return i;
p = p->next;
}
return 0;
}
//12、将链表清空
void ClearList(pNode L) // 不改变L
{ // 初始条件:线性表L已存在。
//操作结果:将L重置为空表,从第首节点开始释放
pNode p = NULL, q = NULL;
p = L->next;//p指向首节点
while (p)
{
q = p->next;//q保存下一个结点的地址
free(p);//释放当前结点内存
p = q;//将下一节点的地址给p,保证循环
}
L->next = NULL; //头结点指针域重置为空
}
//13、销毁链表
void DestroyList(pNode &L)
{ // 初始条件:线性表L已存在。
//操作结果:销毁线性表L,从头结点开始释放(free(L)释放L指向的内存地址)
pNode q;
while (L)
{
q = L->next;
free(L);
L = q;
}
}