线性表的链式表示——单链表(带头结点)

目录

前言

单链表中结点类型的描述

整体代码

关于表长

关于遍历单链表


前言

链表分为:单链表、双链表、循环链表和静态链表,本篇主要是关于带头结点的单链表的基本操作。

链式存储线性表时,不需要使用地址连续的存储单元,它通过“链”建立元素之间的逻辑关系。它的插入和删除操作不需要移动元素,只需要修改指针即可。

单链表中结点类型的描述

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更新到下一个结点
	} 
}

如果有错误,欢迎大家在评论区指出,谢谢~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值