目录
前言
链表分为:单链表、双链表、循环链表和静态链表,本篇主要是关于带头结点的单链表的基本操作。
链式存储线性表时,不需要使用地址连续的存储单元,它通过“链”建立元素之间的逻辑关系。它的插入和删除操作不需要移动元素,只需要修改指针即可。
单链表中结点类型的描述
typedef struct LNode{
int *data; //数据域
struct LNode *next; //指向下一个结点的指针域
}LNode,*LinkList;
LNode *L; 或者:
LinkList L; //声明一个指向单链表第一个结点的指针
这两种声明方式从效果上来看是一模一样的,但是,用第二种方式来声明头指针的可读性更强一点。
第一种更强调这是一个指向结点的指针,而第二种更强调这是一个单链表。
在单链表中,头结点的下一个节点通常被称为首元结点(也称为第一个元素结点或首个数据结点)。
整体代码
由于这是一个带头结点的单链表,因此头结点的数据域是空闲不用的,因此我就让头结点的数据域存储链表的长度(也因为定义的数据域刚好是int型)。
整体代码自我感觉比较冗余,因为首先是创建单链表,里面包含了用头插法建立和用尾插法建立;其次是插入新元素的操作,又包含了用前插操作和用后插操作。
关于使用前插还是后插操作,在 bool ListInsert(LinkList &L,int location,int e); 按位置插入新元素这个函数中,若用前插,则是让p指针指向要插入的位置;若用后插,则让p指针指向要插入位置的前驱结点。
在按位置插入新元素函数以及实现删除元素操作的函数中,运用了封装的按位查找函数。
//2025.3.17 写下 单链表(带头结点)
#include<stdio.h>
#include<stdlib.h> //包含malloc与free函数的头文件
typedef struct LNode{
int data; //数据域
struct LNode *next; //指向下一个结点的指针域
}LNode,*LinkList;
//头结点中的数据可以用来存储结点的长度
//带头结点的单链表的初始化
bool InitList(LinkList &L){
L = (LNode *)malloc(sizeof(LNode)); //创建头结点
if(L == NULL) return false; //内存不足,分配失败
L->next = NULL; //头结点的下一个指针置空
L->data = 0; //最开始,链表的长度为0
return true;
}
LNode *GetElem(LinkList L,int location);
//创建单链表,分为头插法和尾插法
//头插法创建单链表
bool List_HeadInsert(LinkList &L,int length){ //length表明要创建的结点的个数
int i=0,e=0; //e用来接收新元素的值
LNode *p;
printf("请依次输入元素值:\n");
for(i=0;i<length;i++){
scanf("%d",&e);
p = (LNode *)malloc(sizeof(LNode)); //创建一个新的结点
if(p == NULL){ //内存不足,分配失败
printf("创建失败!\n");return false;
}
p->data = e;
p->next = L->next;
L->next = p;
L->data++; //表长加一
}
return true;
}
//尾插法创建单链表,同样可以用来直接按位序插入
bool List_TailInsert(LinkList &L,int length){
int i=0,e=0; //e用来接收新元素的值
LNode *p,*r=L; //r表示表尾指针
printf("请依次输入元素值:\n");
for(i=0;i<length;i++){
scanf("%d",&e);
p = (LNode *)malloc(sizeof(LNode)); //创建一个新的结点
if(p == NULL){ //内存不足,分配失败
printf("创建失败!\n");return false;
}
p->data = e;
r->next = p;
r = p; //r指向新的表尾结点
L->data++; //表长加一
}
r->next = NULL;
return true;
}
//遍历单链表
void traList(LinkList L){
int i=0;
LNode *p = L->next; //指针p指向头结点的下一个结点
for(i=0;i<L->data;i++)
{
printf("%d\t",p->data);
p = p->next; //指针p更新到下一个结点
}
}
//销毁单链表并释放其所占用的内存空间。
void DestoryList(LinkList &L){
free(L);
L->next = NULL;
L->data = 0;
}
//插入元素,又分为前插操作和后插操作
//前插操作
bool InsertPriorNode(LNode *p,int e){ //这里的p指针为要插入位置的前驱结点
if(p == NULL) return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s == NULL) return false; //内存分配失败
s->next = p->next; //将结点s插入到p之后
p->next = s;
s->data = p->data; //将p指针与s指针中的值交换位序
p->data = e;
return true;
}
//后插操作
bool InsertNextNode(LNode *p,int e){ //这里的p指针为要插入位置的前驱结点
if(p == NULL) return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s == NULL) return false; //内存分配失败
s->data = e;
s->next = p->next; //将结点s插入到p之后
p->next = s;
return true;
}
//按位置插入新元素
bool ListInsert(LinkList &L,int location,int e){ //插入新元素,要找到第i个结点的前驱结点
int i=0;
if(location < 1){
printf("插入位置不合法!\n"); return false;
}
/*这两个要配对出现
LNode *p = GetElem(L,location-1); //调用按位查找函数,找第location-1个结点
return InsertNextNode(p,e); //用后插操作插入
*/
//这两个要配对出现
LNode *p = GetElem(L,location);
return InsertPriorNode(p,e); //用前插操作插入
}
//删除元素
bool ListDelete(LinkList &L,int location,int &e){
int i=0;
if(location < 1){
printf("删除位置不合法!\n"); return false;
}
LNode *p = GetElem(L,location-1); //调用按位查找函数,找第location-1个结点
if(p == NULL || p->next == NULL) return false; //删除位置不合法
LNode *s = p->next; //指针e指向要删除的结点
e = s->data; //用e返回删除元素的值
p->next = s->next;
free(s); //释放要删除结点的存储空间
return true;
}
//按值查找,返回的是元素所在的位置
//按值查找肯定要遍历一遍所有的元素,若返回的位置大于现在的表长,则查找失败
int LocateElem(LinkList L,int e){
int i=1;
LNode *p =L->next; //头结点为第0个结点
while(p!=NULL){ //循环找值为e的结点
if(p->data == e) break;
p = p->next; i++;
}
return i;
}
//按位查找,返回的是位置对应的指针
LNode *GetElem(LinkList L,int location){
int i=0;
if(location<0) return NULL; //这里设置location<0而不是<1,是因为要兼顾在其他函数中的调用
LNode *p =L; //头结点为第0个结点
while(p!=NULL && i<location){ //循环找到第location个结点
p = p->next; i++;
}
return p;
}
//求表长
int ListLength(LinkList L){ //在这个完整代码中,是这样的
return L->data;
}
//判空
bool isEmpty(LinkList L){
if(L->next == NULL) return true; //判断头结点的下一个结点是否为空
else return false;
}
//主函数
int main(){
LinkList L; //声明一个指向单链表的指针,注意并没有创建一个结点
int n=0,i=0,e=0,len=0; //n用来接收要输入的元素的个数,i用来接收元素位置,e接收元素值,len表示表长
int op = 0; //op表示操作数
LNode *p = NULL; //指针p用来接收返回的指针
if(InitList(L) == false) //单链表的初始化
{
printf("申请内存空间失败!\n");return 0;
}
while(op!=-1){
printf("\n-----------操作目录-----------\n");
printf("1.用头插法创建单链表\n");
printf("2.用尾插法创建单链表\n");
printf("3.遍历单链表\n");
printf("4.显示表长\n");
printf("5.在链表中插入新元素\n");
printf("6.删除元素\n");
printf("7.按值查找\n");
printf("8.按位查找\n");
printf("输入 -1 退出操作\n");
scanf("%d",&op);
switch(op){
case 1: printf("要输入元素的个数:\n"); //用头插法创建单链表
scanf("%d",&n);
List_HeadInsert(L,n);break;
case 2: printf("要输入元素的个数:\n"); //用尾插法创建单链表
scanf("%d",&n);
List_TailInsert(L,n);break;
case 3: traList(L);break; //遍历单链表
case 4: //显示表长
printf("现在表长为:%d\n",ListLength(L));break;
case 5: //插入新元素
printf("请输入要插入的新元素的位置和值:\n");
scanf("%d %d",&i,&e);
if(ListInsert(L,i,e) == false)
printf("插入失败!\n");
break;
case 6: //删除元素
printf("请输入要删除的元素的位置:\n");
scanf("%d",&i);
if(ListDelete(L,i,e) == false) break;
else{
printf("要删除的元素的值:%d\n",e);break;
}
case 7: printf("请输入要查找的元素值:\n"); //按值查找
scanf("%d",&e);
i = LocateElem(L,e);
if(i > ListLength(L))
{
printf("查找失败!\n");
}else{
printf("值为%d的元素所在的位置为%d\n",e,i);
}
break;
case 8: printf("请输入要查找的位置号:\n"); //按位查找
scanf("%d",&i);
p = GetElem(L,i);
//printf("指针的地址为:%p\n",(LNode *)p);
if(p == NULL || i<1)
printf("查找失败!\n");
else printf("要查找的位置号对应的值为%d\n",p->data);break;
}
}
DestoryList(L);
return 0;
}
关于表长
在上面的代码中,求表长直接返回头结点的data值就行。
将一般求表长的代码放在下面:
int ListLength(LinkList L){ //求表长
int i = 0; //初值为0,对应最开始p指针指向头结点
LNode *p=L; //p指针指向头结点
while(p->next != NULL){ //这里注意,若写成p != NULL,则会让i的值多加一
p = p->next;
i++;
}
return i;
}
关于遍历单链表
若是头结点的数据域没有存储表长,可以这样写:
//遍历单链表
void traList(LinkList L){
int i=0;
LNode *p = L->next; //指针p指向头结点的下一个结点
while(p != NULL){
printf("%d\t",p->data);
p = p->next; //指针p更新到下一个结点
}
}
如果有错误,欢迎大家在评论区指出,谢谢~~