实验3、链表的基本操作

实验3、链表的基本操作

(1)实验目的
通过该实验,深入理解链表的逻辑结构、物理结构等概念,掌握链表基本操作的编程实现,熟练掌握C语言中指针的操作。和实验3对比,掌握线性结构两种不同存储方式的区别。
(2)实验内容
编程实现链表下教材第二章定义的线性表的基本操作,最好用菜单形式对应各个操作,使其编程一个完整的小软件。
(3)参考界面
在这里插入图片描述

(4)验收/测试用例

 没有初始化前进行其他操作,程序是否能控制住;
 初始化一个顺序表;
 插入数据(位置, 数据),要测插入位置不合法的情况(0,1)、(2,1),正确插入4个数据(1,2)、(1,1)、(3,3);
 显示顺序表中的数据,屏幕输出1, 2, 3;
 判空,屏幕输出顺便表非空;
 顺便表长度,屏幕输出3;
 获取指定位置元素,要测指定位置在【1,3】范围之外的情况和之内的情况;
 定位,输入:4, 输出:不存在,输入2,输出位置为2;
 求直接前驱,要测求第一个元素的前驱、不存在顺序表中的元素的直接前驱,其他元素的直接前驱;
 求直接后继,要测最后一个元素的后继、不存在顺序表中的元素的直接后继,其他元素的直接后继;
 删除,要测位置在【1,3】范围之外的情况和之内的情况;
 清空操作后再测长度;
 销毁顺序表

个人感觉这个实验是所有数据结构里面最难的一个实验,主要难在函数的构造和里面用到了大量的指针,初学数据结构感觉指针就是一生之敌。有的时候很容易忘了加*号。一定要仔细检查有没有添加。不然就是疯狂报错,如下是我第一次将程序完整写出来的时候:
在这里插入图片描述
不用怀疑了,电脑下方的黄皮桌子正是华苑桌子经典皮肤
在这里插入图片描述

然后就会因为找不到错误,而陷入无尽的找bug环节
在这里插入图片描述
这个程序我写了整整一个星期,先搞懂链表基本操作的理论原理,然后再去读代码看怎么去实现它,坚持住,过来链表实验后,你会发现之后的栈、队列和链表都不在一个层次,程序可以运行!这个代码写出来实属不易,如果帮助到你请点一个免费的赞👍吧
代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;

#define ok 1
#define error -4
#define false -1
#define overflow -2
#define yuejie -3 

typedef int Status;
typedef int ElemType;

typedef struct Lnode{
	ElemType data;
	struct Lnode *next;
}Lnode,*LinkList;

//初始化函数
Status InitList(LinkList &L)
{
	L = (LinkList)malloc(sizeof(Lnode));//申请空间 
	if(L == 0)
	{
		exit(overflow);
	}
	L->next = NULL;//建立一个头结点 
	return ok;
} 

//销毁链表
Status DestoryList(LinkList &L)
{
	if(L == NULL)
	{
		return error;
	}
	Lnode *p;
	p = L;   //可以把P = L,放到循环中去 
	while(L->next != NULL)
	{
		L = L->next;
		free(p);
		p = L;
	}
	L = NULL;
	return ok;
} 

//清空线性表

Status ClearList(LinkList &L)
{
	if(L == NULL)
	{
		return error;
	}
	Lnode *p;
	Lnode *q;
	p = L->next;
	//q = p -> next;
	while(q == NULL)
	{
	    q = p -> next;
	    free(p);
	    p = q;//每一次循环过后,pq都在同一个位置,当q == NULL时, 其上一个循环中pq已经到达最后一个元素了 
	}
	L->next = NULL;//将头结点置空 
	return ok;
} 

//链表长度
//Status ListLength(LinkList &L,int *count)
Status ListLength(LinkList &L)
{
	int count;
	if(L == NULL)
	{
		return error;
	}
	count = 0;
	//Londe *p;
	LinkList p;
	p = L->next;
	while(p != NULL)
	{
		p = p->next;
		count++;
	}
	return count;
} 

//指定位置的元素值
Status LocalElem(LinkList &L,int i,int *e)
{
	if(L == NULL)
	{
		return error;//链表未初始化 
	}
	
	int j = 1;
	//Londe *p;
	LinkList p;
	p = L->next;
	while((p != NULL)&&(j < i))
	{
		p = p->next;
		j++;
	}
	if((p == NULL)||(j > i))
	{
		return false;//链表中不存在该位置 
	}
	*e = p->data;
	return ok;
}
//链表已存在元素的位序
Status Local_Elem(LinkList &L,int e,int *i)
{
	if(L == NULL)
	{
		return error;//链表未初始化 
	}
	int j = 1;
	//Londe *p;
	LinkList p;
	p = L->next;
	while((p != NULL)&&(p->data != e))
	{
		p = p->next;
		j++;
	}
	if(p != NULL)
	{
		*i = j;
		return ok;
	}
	else
	{
		return false;//链表中没有该元素e 
	}
} 
// 求输入元素的直接前驱
Status PriorElem(LinkList &L,int e,int *pri_e)
{
	if(L == NULL)
	{
		return error;//链表未初始化 
	}
	/*Londe *p;
	Londe *q;*/
	LinkList p;
	LinkList q;
	p = L->next;
	while(p != NULL)
	{
		//p = p->next;
		if(p->data == e)//只在第一次循环中起作用,其他循环中结果均与下方的q循环结果相同不再起作用 
		{
			return false;
		}
		if(p->next != NULL)
		{
		q = p->next;
		if(q->data == e)//如果不加判断可能会导致 q指向空值,直接调用q->data就会导致程序崩溃 
		{
			*pri_e = p->data;
			return ok;
		}
	    }
		p = p->next;
	}
	if(p == NULL)
	{
		return yuejie;//所求元素不在该链表中 
	}
}
//求输入元素的直接后继
Status nextElem(LinkList &L,int e,int *next_e)
{
	if(L == NULL)
	{
		return error;//链表未初始化 
	}
	//Londe *p;
	LinkList p;
	p = L;
	while(p != NULL)
	{
		p = p->next;
		if(p != NULL)
		{
		if(p->data == e)
		{
			if(p->next != NULL)
			{
				*next_e = p->next->data;
				return ok;
			}
			else
			{
				return false;//最后一位元素没有后继 
			}
		}
	    }
	    else{
	    	break;
		}
	}
	if(p == NULL)
	{
		return yuejie;//所求元素不在该链表中 
	}
} 
//在第i个位置插入一个元素
Status InserElem(LinkList &L,int i,int e)
 {
 	if(L == NULL)
	{
		return error;//链表未初始化 
	}
	/*Londe *p;
	Londe *s;*/
	LinkList p;
	LinkList s;
	int j = 0;
	p = L;
	while((p != NULL)&&(j < i - 1))//遍历的目的是为了找到第i-1个位置便于插入 
	{
		p = p->next; 
		j++;
	}
	if((p == NULL)||(j > i - 1))
	{
		return false;//i值不合理,大于链表长度加一或者小于1 
	}
	s = (LinkList)malloc(sizeof(Lnode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return ok;
 }
//删除第i个元素 
Status DeleteElem(LinkList &L,int i)
{
	if(L == NULL)
	{
		return error;//链表未初始化 
	}
	/*Londe *p;
	Londe *q;*/
	LinkList p;
	LinkList q;
	int j = 0;
	p = L;
	while((p->next != NULL)&&(j < i - 1))//让 p->next != NULL保证p后面还有元素可以删除 
	{
		p = p->next;
		j++;
	}
	if((p->next == NULL)||(j > i -1))
	{
		return false;//i值不合法,或者p已经指向最后一个元素已经无法再调用指针删除一个元素 
	}
	q = p->next;
	p->next = q->next;
	//free(q);
	return ok;
} 
//输出有的链表元素
Status VisitElem(LinkList L)
{
	if(L == NULL)
	{
		return error;//链表未初始化 
	}
	//Londe *p;
	LinkList p;
	p = L->next;
	while(p != NULL)
	{
		cout<<p->data<<" ";
		p = p->next;
	}
	return ok;
}
//初始化并用头插法(或尾插法)输入元素
Status CreatList(LinkList &L,int n)
{
	if(L == NULL)
	{
		return error;//链表未初始化 
	}
	//Londe *p;
	LinkList p;
	int i;
	for(i = n;i > 0;i--)
	{
		p = (LinkList)malloc(sizeof(Lnode));
		cin>>p->data;
		p->next = L->next;
		L->next = p;
	}
	return ok;
}
//实现单链表的逆序存放
Status NiList(LinkList &L,int n)
{
	int i;
	if(L == NULL)
	{
		return error;//链表未初始化 
	}
	Lnode *p;
	Lnode *q;
	LinkList L1;
	InitList(L1);
	p = L;
	for(i = n;i > 0;i--)
	{
		p = p->next;
		q = (LinkList)malloc(sizeof(Lnode));
		q->data = p->data;
		q->next = L1->next;
		L1->next = q; 
	}
	VisitElem(L1);
	return ok;
}
//两个非递减有序表的去重合并
void MergeList(LinkList &LA,LinkList &LB,LinkList &LC)
{
	Lnode *pa;
	Lnode *pb;
	Lnode *pc;
    pa = LA->next;
    pb = LB->next;
    LC = LA;
	pc = LC;
	while((pa != NULL)&&(pb != NULL))
	{
		if((pa->data) <= (pb->data))
		{
			pc->next = pa;
			pc = pa;
			pa = pa->next;
		}
		else
		{
			pc->next = pb;
			pc = pb;
			pb = pb->next;
		}
	}
	if(pa != NULL)
	{
		pc->next = pa;
	}
	 if(pb != NULL)
	{
		pc->next = pb;
	}
	free(LB);
	//链表查重功能 
	Lnode *p;
	Lnode *q;
	Lnode *r;//保留删除后指针p的地址 
	int count1 = 1;
	//int i = false;
	p = LC -> next;
    q = LC -> next;
   while(p != NULL)//p遍历LC 
   {
   	while(q != p)//p = q 时不查重,第一次循环不进入二层循环,剩下的查重q运行到p的前一个元素 
   	{
   		if(p -> data == q -> data)
   		{
   			r = p;//r保留p的地址 
   			DeleteElem(LC,count1);//相同则删除该元素p -> data退出内层循环 
   			p = r;//把r的地址重新传给p 
   			break;
		}
		q = q -> next;//否则q运行到p的前一个元素
	}
	p = p -> next;//每一次循环p前移 
	count1++;
	q = LC -> next;//q 回到 第一个结点位置再重新运行到p的前一个元素进行查重 
	/*if(i == false)
	{
	p = p -> next;//每一次循环p前移 
	count1++;
	q = LC -> next;//q 回到 第一个结点位置再重新运行到p的前一个元素进行查重 
	}
	if(i == ok)
	{
		p = r->next;//r在删除之前保留p的地址 
		count1++;
		q = LC -> next;//q 回到 第一个结点位置再重新运行到p的前一个元素进行查重
	}
   } */ 
   } 
	VisitElem(LC);
} 
//主函数输出
int main()
{
	int i,j,n,e,pri_e,next_e,A,B,C;
	int m;
	//int count;
	LinkList L;
	LinkList LA;
	LinkList LB;
	LinkList LC;
	L = NULL;
	cout<<"*********************"<<endl;
		cout<<"h e n u - 于 笨 笨"<<endl;
		cout<<"1.初始化链表"<<endl;
		cout<<"2.销毁链表"<<endl;
		cout<<"3.清空链表"<<endl;
		cout<<"4.求链表长度"<<endl;
		cout<<"5.指定位置的元素值"<<endl;
		cout<<"6.链表已存在元素的位序"<<endl;
		cout<<"7.求输入元素的直接前驱"<<endl;
		cout<<"8.求输入元素的直接后继"<<endl;
		cout<<"9.在第i个位置插入一个元素"<<endl;
		cout<<"10.删除第i个元素"<<endl;
		cout<<"11.输出有的链表元素"<<endl;
		cout<<"12.头插法创建链表"<<endl;
		cout<<"13.逆序输出链表"<<endl;
		cout<<"14.初始化线性表LA,LB,LC"<<endl;
		cout<<"15.插入构建线性表LA"<<endl;
		cout<<"16.插入构建线性表LB"<<endl;
		cout<<"17.合并两个非递减链表"<<endl;
		cout<<"输入一个负数退出"<<endl;
		cout<<"**********************"<<endl;
	do{
		//cout<<"输出空值:"<<NULL<<endl; 
		cout<<"请输入你的选择:"<<endl;
		cin>>n;
		switch(n)
		{
			case 1:
				m = InitList(L);
				if(m == overflow)
				{
					cout<<"链表初始化失败!"<<endl;
				}
				if(m == ok)
				{
					cout<<"链表初始化成功!"<<endl;
				}
				break;
			case 2:
				m = DestoryList(L);
				if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == ok)
				{
					cout<<"线性表销毁成功!"<<endl;
				} 
				break;
			case 3:
				m = ClearList(L);
				if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == ok)
				{
					cout<<"线性表清空成功!"<<endl;
				} 
				break;
			case 4:
				m = ListLength(L);
				if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				//if(m == ok)
				else
				{
					cout<<"线性表的长度为:"<<m<<endl;
				} 
				break;
			case 5:
				cout<<"请输入你要查询的位置:"<<endl;
				cin>>i;
				m = LocalElem(L,i,&e);
				if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == false)
				{
					cout<<"链表中不存在该位置!"<<endl;
					//cout<<"链表中不存在该元素!"<<endl;
				}
				if(m == ok)
				{
					cout<<"该位置的元素为:"<<e<<endl;
				} 
				break;
			case 6:
				cout<<"请输入你要查询的元素:"<<endl;
				cin>>e;
				m = Local_Elem(L,e,&i);
				if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == false)
				{
				    cout<<"链表中不存在该元素!"<<endl;
				}
				if(m == ok)
				{
					cout<<"该元素的位置为:"<<i<<endl;
				} 
				break;
			case 7:
				cout<<"请输入你要求前驱的元素:"<<endl;
				cin>>e;
				m = PriorElem(L,e,&pri_e);
				cout<<"输出此时的m值:"<<m<<endl;
				if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				else if(m == false)
				{
				    cout<<"该元素没有前驱!"<<endl;
				}
				
				else if(m == ok)
				{
					cout<<"该元素的前驱为:"<<pri_e<<endl;
				} 
				//if(m == yuejie)
				else 
				{
				    cout<<"链表中不存在该元素!"<<endl;
				}
				break;
			case 8:
				cout<<"请输入你要求后继的元素:"<<endl;
				cin>>e;
				m = nextElem(L,e,&next_e); 
				if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == false)
				{
				    cout<<"该元素没有后继!"<<endl;
				}
				if(m == yuejie)
				{
				    cout<<"链表中不存在该元素!"<<endl;
				}
				if(m == ok)
				{
					cout<<"该元素的后继为:"<<next_e<<endl;
				} 
				break;
			case 9:
				cout<<"请输入要插入的位置:"<<endl;
				cin>>i;
				cout<<"请输入要插入的元素:"<<endl;
				cin>>e;
				m = InserElem(L,i,e);
				if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == false)
				{
				    cout<<"在链表中的插入位置不合理!"<<endl;
				}
				if(m == ok)
				{
					cout<<"该元素插入成功!"<<endl;
				} 
				break;
			case 10:
				cout<<"请输入要删除元素的位置:"<<endl;
				cin>>i;
				m = DeleteElem(L,i);
				if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == false)
				{
				    cout<<"在链表中的删除位置不合理!"<<endl;
				}
				if(m == ok)
				{
					cout<<"该元素删除成功!"<<endl;
				} 
				break;
			 case 11:
			 	m = VisitElem(L);//函数里面有输出会不会干扰return的返回??? 不行就直接调用函数不再进行if语句判断!! 
			 	if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == ok)
				{
					cout<<"该链表的元素为:"<<endl;
					VisitElem(L);
				}
				cout<<endl;
				break;
			 case 12:
			 	cout<<"请输入要创建链表中元素的个数:"<<endl;
			 	cin>>n;
			 	m = CreatList(L,n);// 
			 	if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == ok)
				{
					cout<<"链表创建成功"<<endl;
				}
				break;
			 case 13:
			 	n = ListLength(L);
			 	cout<<"输出此时链表的长度为:"<<n<<endl;
			 	m = NiList(L,n);
			 	if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == ok)
				{
					cout<<"该链表逆序后的元素为:"<<endl;
					NiList(L,n);
				}
				cout<<endl;
				break;
			 case 14:
			   A = InitList(LA);
				if(A == overflow)
				{
					cout<<"链表初始化失败!"<<endl;
				}
				if(A == ok)
				{
					cout<<"链表初始化成功!"<<endl;
				}
			  B = InitList(LB);
				if(B == overflow)
				{
					cout<<"链表初始化失败!"<<endl;
				}
				if(B == ok)
				{
					cout<<"链表初始化成功!"<<endl;
				}
			  C = InitList(LC);
				if(C == overflow)
				{
					cout<<"链表初始化失败!"<<endl;
				}
				if(C == ok)
				{
					cout<<"链表初始化成功!"<<endl;
				}
				break;
			case 15:
				cout<<"请输入要创建链表中元素的个数:"<<endl;
			 	cin>>n;
			 	m = CreatList(LA,n);// 
			 	if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == ok)
				{
					cout<<"链表创建成功"<<endl;
				}
				break;
			case 16:
				cout<<"请输入要创建链表中元素的个数:"<<endl;
			 	cin>>n;
			 	m = CreatList(LB,n);// 
			 	if(m == error)
				{
					cout<<"请先初始化链表!"<<endl;
				}
				if(m == ok)
				{
					cout<<"链表创建成功"<<endl;
				}
				break;
			 case 17:
			   cout<<"输出合并后的链表:"<<endl;
			   MergeList(LA,LB,LC);
			   cout<<endl;
			   break;
			  default:
			  cout<<"请输入1~17内的数"<<endl;
			  break;	 	
				
		}
		
	}while(n > 0);
	
}

下面手把手展示一下程序中数据如何输入的问题:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个代码写出来实属不易,如果帮助到你请点一个免费的赞👍吧
在这里插入图片描述

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

henu-于笨笨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值