介于作者水平的问题,文中可能会有这样或者那样的错误或者漏洞,欢迎指正,
提示:我相信把这篇文章要是你要是能独立打出来会更棒,这也是考研的基本要求吧! 宝
文章目录
- 前言
- 一、单链表须知
- 二、代码分部(带头节点)
- 1.引入库
- 1、InitList(&L):初始化一个空的线性表
- 2、Length(L):求表长,返回线性表L的长度,即即L中数据元素的个数
- 3、LocateElem(L,e):按值查找操作,即获取表L中具有给定关键字值的元素
- 4、GetElem(L,i):按位查找操作,获取L中第i个位置上的元素的值
- 5、PrintList(L):输出操作,按照前后 顺序输出线性表L的所有元素的值
- 6、Empty(L):判空操作:若是L为空表,返回true,否则返回false
- 7、DeleteLite(&L,i);删除给定位置的结点
- 8、DeleteListElemType(LNode* &L,ElemType e):删除某一个元素值
- 9、DeleteLite(&L,*ptr) 删除某个给定的结点
- 10、DestoryList(&l):销毁操作,销毁线性表,并释放线性表L占用的存储空间
- 11、ModifyLocation(LNode* &L,int i,ElemType e)修改位置i上的元素为e
- 12、ModifyElemType(LNode* &L,ElemType e,ElemType value)
- 13、ListHeadInsert(LNode* &L)头插法
- 14ListTailInsert(LNode* &L) 尾插法建立
- 三、可执行代码汇总
- 上面十四种功能都使用了一遍,实现了增删改查
- 总结
前言
再读我这篇文章之前,你对链表理解可能只停留在理论阶段,什么用的不是一个连续的空间呀,什么方便插入不方便查找呀,至于什么头结点 首结点 更是一知半解,不过这些对于初学者确实不易理解,这里就让我们大家一起再来学习一下 ps(代码我都调试过,但是有的截图就没有截图,十四个功能我都试验过可以执行 汇总代码中使用他们实现表的增删改查)
提示:以下是本篇文章正文内容,
考研的话 其中下表这些是最基本的
还是放一个图吧,(高情商)为了大家先知道自己要学什么(低情商)毕竟自己辛辛苦苦打的 不放感觉怪亏得,同样附上一个高清链接
一、单链表须知
1、链表的结点
*单链表是由一个一个结点node构成,正如你之前就知道的,它存放数据的地址可以是不连续的,所以你可以想象每一个结点存放的不仅有自己本身需要携带的数据,而且要包含如何找到下一个数据的线索,也就是指针,这个指针指向的是下一个数据的(ptr指针数据)位置!,位置!,位置 若是我们对这个位置解引用(prt)也就会得到其中的信息,这个信息依然是数据信息,以及位置信息(题外话:这里我就要将一个故事嘞,非常形象,从前一座山山里一座庙,庙里一个老和尚对小和尚说”从前一座山,山上一座庙…“)记住每一个结点就是闲 就是惦记别人家的东西,所以结点存放的是别人家的地址
2、链表中几个概念
头指针:指向第一个结点的位置,所以若是存在头节点则指向的是 头节点,若是不存在头节点,则指向的是首结点
头结点:添加它的目的是为了让包括空表在内的所有链表都有一个头指针。这样对是否为空的链表的操作就能统一起来,其中的数据域有时存放表的长度,有时什么都不放 所以若是带头节点使用的就是,删除,,插入的时候就不需要判断是否是第一个元素了操作就统一起来了(因为若是没有头节点 而你删除的又是第一个元素你的操作是L=frist->next, free(first); 若是删除的不是第一个元素pre->next=pre->next->next) ;
首结点:存放第一个有效数据的结点(不是头节点)
尾结点:存放最后一个有效数据的结点,它的指针域不为空 而是指向NULL
尾指针:指向尾结点的指针
重要(我之前也看了许多人的但是好像大家都不讲关于这方面的)
综上:单链表可以没有头节点,但是不能没有头指针 题目中的L等价于head, 还有一个点,我感觉你可能搞不清的一点就是上表中为什么没有头节点的时候判空条件是 L== null 有头结点的时候L->next== NULL( 若是确实是像我所说,我接下来的解释 我认为是值得一个点赞的呦)
首先来看有无头节点的对比
InitList(LNode* L){//所谓初始化就是给结构体中变量赋值,分为带头结点与不带头节点 ,数据不需要赋值,因为一赋值就相当于不是一个空表了
//带头节点,就需要申请一个头结点L此时就是头结点
L=(LNode*)malloc(sizeof(LNode));
L->next=NULL; L->data=0;//头指针中的数据域可以用来存放链表的长度;
//因为使用了malloc所以就要加一个判断是否申请成功
if(NUll==L){
cout<<"空间申请失败"<<endl;
exit(1);
}
else{
cout<<"空间申请成功";
}
//若是不带头节点
//L=NULL;
}
二、代码分部(带头节点)
主要是因为带头节点会方便很多
在上一篇的功能的基础上添加了
1.引入库
代码如下(示例):
#include<bits/stdc++.h>
#define OK 1;
#define ERROR 0;
#define status int
#define ElemType int
using namespace std;
1、InitList(&L):初始化一个空的线性表
status InitList(LinkList &L){
/*所谓初始化就是给结构体中变量赋值,分为带头结点与不带头节点
数据域不需要赋值 ,这里用的是引用符号,因为要对其中的L进行修改 */
//带头节点,就需要申请一个头结点L此时就是头结点
L=(LNode*)malloc(sizeof(LNode));
L->next=NULL; L->data=0;
/*头指针中的数据域可以用来存放链表的长度;
因为使用了malloc所以就要加一个判断是否申请成功*/
if(NULL==L){
cout<<"空间申请失败"<<endl;
exit(1);
}
else{
cout<<"空间申请成功"<<endl;;
}
//若是不带头节点
//L=NULL;
return OK;
}
2、Length(L):求表长,返回线性表L的长度,即即L中数据元素的个数
int Length(LNode* L){//判断长度要设置一个计数器
int count=0;
LNode* P=L;//P初始化为头指针
while(P->next!=NULL){
P=P->next;//这一句话相当于是将P的下一个位置的信息给了P 也就实现了P向后移动的操作
++count;
}
PrintList(L);
cout<<"此时的链表长度是"<<count<<endl;
/*加上这样一句话方便判断代码的问题*/
return count;
}
3、LocateElem(L,e):按值查找操作,即获取表L中具有给定关键字值的元素
LNode* LocateElem(LNode* L,ElemType e){//这个时候返回位置就没有意义了,当然是返回指针,
LNode* P=L;
int count=0;//从头开始数的所以这里初始化就为0;
//尾结点的P也不是NULL ,tail->next==NULL
while(P&&P->data!=e){
P=P->next;//向后移动
++count;
}
if(P==NULL){
cout<<"未发现元素"<<endl;
return NULL;
}
else printf("在第%d个位置发现了元素\n",count);
PrintList(L);
return P;
}
4、GetElem(L,i):按位查找操作,获取L中第i个位置上的元素的值
ElemType GetElem(LNode* L,int i){
LNode* P=L->next;
while((--i)&&P){
P=P->next;
}
PrintList(L);
if(P==NULL) {
cout<<"你输入的位置不正确"<<endl;
}
else {
return P->data;
}
}
5、PrintList(L):输出操作,按照前后 顺序输出线性表L的所有元素的值
status PrintList(LNode* L){
cout<<"此时的链表中的数据是"<<endl;
LNode *P=L->next;
while(P!=NULL){
cout<<P->data<<" ";
P=P->next;
}
cout<<endl;
return OK;
}
6、Empty(L):判空操作:若是L为空表,返回true,否则返回false
bool Empty(LNode *L){
if(L->next==NULL) {
return true;
}
else {
return false;
}
}
7、DeleteLite(&L,i);删除给定位置的结点
status DeleteListLocation(LNode* L,int i){
LNode* P;
LNode* Pre=L->next;LNode* Next=L;
while((--i)&&Pre!=NULL){
Pre=Pre->next;
Next=Next->next;
}
if(Pre==NULL){
cout<<"你输入的数字不对"<<endl;
}
else{//此时这两个Pre一个指向的是待删除结点,一个指向的是待删除结点的前驱
P=Next->next;
Next->next=Pre->next;
free(P);
cout<<"删除成功"<<endl;
}
PrintList(L);
return OK
}
8、DeleteListElemType(LNode* &L,ElemType e):删除某一个元素值
status DeleteListElemType(LNode* &L,ElemType e){
LNode* Pre=L->next;//用来遍历链表 找到要删除的元素
LNode* Next=L;// 走的慢一步,用来保存要删除元素的前驱,
LNode* Seve=NULL;
while(Pre!=NULL&&Pre->data!=e){//没有到末尾且没有找到e
cout<<Pre->data<<" ";
Pre=Pre->next;//遍历指针向后移动
Next=Next->next;
}
if(Pre==NULL){
printf("没有找到元素\n");
cout<<e;
return ERROR;
}
else{//否则就是找到了 ,我们要删除的是pre先走的
Seve=Next->next;
Next->next=Pre->next;
free(Seve);
cout<<"成功删除!"<<endl;
}
PrintList(L);
return OK;
}
9、DeleteLite(&L,*ptr) 删除某个给定的结点
DeleteListLNode(LNode *L,LNode* Ptr){//这里我们使用第二种方式
LNode* P=L;
if(Ptr->next==NULL){//如果是尾结点,需要找到尾结点的前驱
cout<<"删除的是尾结点"<<endl;
while(P->next->next){
P=P->next;
}
P->next=NULL;
free(Ptr);
}
else{//不是尾结点,值覆盖便可 实质上删除的是所给结点的后继结点
cout<<"不是尾结点"<<endl;
Ptr->data=Ptr->next->data;
P=Ptr->next;//保存是为了便于释放,要不然下一步时候 ,你就找不到原来要删除结点的后继 有如何来释放呢
Ptr->next=Ptr->next->next;
free(P);
}
PrintList(L);
}
10、DestoryList(&l):销毁操作,销毁线性表,并释放线性表L占用的存储空间
status DestoryListL(LNode* &L){//销毁一个链表
if(L->next==NULL){
cout<<"为空链表"<<endl;
}
LNode* Pre=L->next;LNode* Next=L;
while(Pre){
Next=Pre;
Pre=Pre->next;
free(Next);
}
L->next=NULL;
cout<<"成功销毁"<<L->next;
return OK;
}
11、ModifyLocation(LNode* &L,int i,ElemType e)修改位置i上的元素为e
status ModifyLocation(LNode* &L,int i,ElemType e){
if(Length(L)<i||i<0){
cout<<"你输入的位置信息不对"<<endl;
return ERROR;
}
LNode *P=L->next;
while(--i){
P=P->next;
}
P->data=e;
cout<<"修改之后表是";
PrintList(L);
return OK
}
12、ModifyElemType(LNode* &L,ElemType e,ElemType value)
//修改所有值为e的改成value
status ModifyElemType(LNode* &L,ElemType e,ElemType value){
if(LocateElem(L,e)==NULL){
printf("你输入的值表中不存在\n");
return ERROR;
}
LNode *P=L->next;
while(P){
if(P->data==e) P->data=value;
P=P->next;
}
cout<<"修改之后的表是";
PrintList(L);
return OK;
}
13、ListHeadInsert(LNode* &L)头插法
status ListHeadInsert(LNode* &L){
cout<<"进入Head中"<<endl;
ElemType e;
LNode* P=NULL;//作为新插入的结点
cout<<"请输入一系列值"<<endl;
while(scanf("%d",&e)!=EOF){
//若是其中使用cin>>e;第一遍走程序还没有问题,但是后面走就有问题了 ,不知道原因
//要插入数据就要重新搞一个结点,结点里面放数据
P=(LNode*)malloc(sizeof(LNode));
P->data=e;
/*将P这结点插入的链表中,头结点存储的信息交给P中存储
再将P的地址信息交给头结点给*/
P->next=L->next;
L->next=P;
/*可能你会问我,这两句话,能不能反过来,当然不能,因为要是翻过来
L原来首结点的地址没有被保存,就被断开了 这里的第一步既是连接新生成的
结点 同样的也是保存了首结点的地址*/
}
PrintList(L);
return OK;
}
头插入有一个很重要的应用就是链表的逆置,
14ListTailInsert(LNode* &L) 尾插法建立
//尾插法建立单链表
status ListTailInsert(LNode* &L){
cout<<"进入Tail中"<<endl;
ElemType e;
LNode* P=NULL;//P作为新插入的结点
LNode* T=L; //T的作用找到 yi巴并标记;
cout<<"开始输入"<<endl;
while(T->next!=NULL) T=T->next;
cout<<"请输入一系列的值"<<endl;
while(scanf("%d",&e)!=EOF){
//要插入数据就要重新搞一个结点,结点里面放数据
P=(LNode*)malloc(sizeof(LNode));
P->data=e;
P->next=NULL;
T->next=P;
T=T->next;
}
PrintList(L);
return OK;
}
三、可执行代码汇总
上面十四种功能都使用了一遍,实现了增删改查
//InitList(&L):初始化一个空的线性表
//Length(L):求表长,返回线性表L的长度,即即L中数据元素的个数
//LocateElem(L,e):按值查找操作,即获取表L中具有给定关键字值的元素
//GetElem(L,i):按位查找操作,获取L中第i个位置上的元素的值
//ListHeadInsert(&L,e):插入操作,使用头插法建立单链表
//PrintList(L):输出操作,按照前后 顺序输出线性表L的所有元素的值
//Empty(L):判空操作:若是L为空表,返回true,否则返回false
//DeleteLite(&L,i);删除结点,其中有两种,一种是删除某个位置
//DeleteLite(&L,*ptr) 删除某个给定的结点
//DestoryList(&l):销毁操作,销毁线性表,并释放线性表L占用的存储空间
//考试的时候最好也是使用这些名称
#include<bits/stdc++.h>
#define OK 1;
#define ERROR 0;
#define status int
#define ElemType int
using namespace std;
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;//这两个是等价的
//打印链表
status PrintList(LNode* L){
cout<<"此时的链表中的数据是"<<endl;
LNode *P=L->next;
while(P!=NULL){
cout<<P->data<<" ";
P=P->next;
}
cout<<endl;
return OK;
}
status InitList(LinkList &L){
/*所谓初始化就是给结构体中变量赋值,分为带头结点与不带头节点
数据域不需要赋值 ,这里用的是引用符号,因为要对其中的L进行修改 */
//带头节点,就需要申请一个头结点L此时就是头结点
L=(LNode*)malloc(sizeof(LNode));
L->next=NULL; L->data=0;
/*头指针中的数据域可以用来存放链表的长度;
因为使用了malloc所以就要加一个判断是否申请成功*/
if(NULL==L){
cout<<"空间申请失败"<<endl;
exit(1);
}
else{
cout<<"空间申请成功"<<endl;;
}
//若是不带头节点
//L=NULL;
return OK;
}
int Length(LNode* L){//判断长度要设置一个计数器
int count=0;
LNode* P=L;//P初始化为头指针
while(P->next!=NULL){
P=P->next;//这一句话相当于是将P的下一个位置的信息给了P 也就实现了P向后移动的操作
++count;
}
PrintList(L);
cout<<"此时的链表长度是"<<count<<endl;
/*加上这样一句话方便判断代码的问题*/
return count;
}
LNode* LocateElem(LNode* L,ElemType e){//这个时候返回位置就没有意义了,当然是返回指针,
LNode* P=L;
int count=0;//从头开始数的所以这里初始化就为0;
//尾结点的P也不是NULL ,tail->next==NULL
while(P&&P->data!=e){
P=P->next;//向后移动
++count;
}
if(P==NULL){
cout<<"未发现元素"<<endl;
return NULL;
}
else printf("在第%d个位置发现了元素\n",count);
PrintList(L);
return P;
}
//获取链表某一个位置的值
ElemType GetElem(LNode* L,int i){
LNode* P=L->next;
while((--i)&&P){
P=P->next;
}
PrintList(L);
if(P==NULL) {
cout<<"你输入的位置不正确"<<endl;
}
else {
return P->data;
}
}
//头插法建立单链表
status ListHeadInsert(LNode* &L){
cout<<"进入Head中"<<endl;
ElemType e;
LNode* P=NULL;//作为新插入的结点
cout<<"请输入一系列值"<<endl;
while(scanf("%d",&e)!=EOF){
//若是其中使用cin>>e;第一遍走程序还没有问题,但是后面走就有问题了 ,不知道原因
//要插入数据就要重新搞一个结点,结点里面放数据
P=(LNode*)malloc(sizeof(LNode));
P->data=e;
/*将P这结点插入的链表中,头结点存储的信息交给P中存储
再将P的地址信息交给头结点给*/
P->next=L->next;
L->next=P;
/*可能你会问我,这两句话,能不能反过来,当然不能,因为要是翻过来
L原来首结点的地址没有被保存,就被断开了 这里的第一步既是连接新生成的
结点 同样的也是保存了首结点的地址*/
}
PrintList(L);
return OK;
}
//尾插法建立单链表
status ListTailInsert(LNode* &L){
cout<<"进入Tail中"<<endl;
ElemType e;
LNode* P=NULL;//P作为新插入的结点
LNode* T=L; //T的作用找到 yi巴并标记;
cout<<"开始输入"<<endl;
while(T->next!=NULL) T=T->next;
cout<<"请输入一系列的值"<<endl;
while(scanf("%d",&e)!=EOF){
//要插入数据就要重新搞一个结点,结点里面放数据
P=(LNode*)malloc(sizeof(LNode));
P->data=e;
P->next=NULL;
T->next=P;
T=T->next;
}
PrintList(L);
return OK;
}
//判断链表是否为空
bool Empty(LNode *L){
if(L->next==NULL) {
return true;
}
else {
return false;
}
}
//删除某一个元素(给元素)
status DeleteListElemType(LNode* &L,ElemType e){
LNode* Pre=L->next;//用来遍历链表 找到要删除的元素
LNode* Next=L;// 走的慢一步,用来保存要删除元素的前驱,
LNode* Seve=NULL;
while(Pre!=NULL&&Pre->data!=e){//没有到末尾且没有找到e
cout<<Pre->data<<" ";
Pre=Pre->next;//遍历指针向后移动
Next=Next->next;
}
if(Pre==NULL){
printf("没有找到元素\n");
cout<<e;
return ERROR;
}
else{//否则就是找到了 ,我们要删除的是pre先走的
Seve=Next->next;
Next->next=Pre->next;
free(Seve);
cout<<"成功删除!"<<endl;
}
PrintList(L);
return OK;
}
//删除某一个给定的结点
/*方法有两种,第一个跟上面差不多 也是通过寻找前驱来删除元素,
另外一种通过赋值,也就是找到要删除的结点,但是不删除它,而是而是将其后继的数值域保留下来
删除其后继,将这个数值赋值给原来要删除的 ,但这个方法也就需要是要有待删除结点的后继是要存在的
若是使用这个方法来删尾结点 的话 还要修改它前驱的指针域置为NULL
*/
DeleteListLNode(LNode *L,LNode* Ptr){//这里我们使用第二种方式
LNode* P=L;
if(Ptr->next==NULL){//如果是尾结点,需要找到尾结点的前驱
cout<<"删除的是尾结点"<<endl;
while(P->next->next){
P=P->next;
}
P->next=NULL;
free(Ptr);
}
else{//不是尾结点,值覆盖便可 实质上删除的是所给结点的后继结点
cout<<"不是尾结点"<<endl;
Ptr->data=Ptr->next->data;
P=Ptr->next;//保存是为了便于释放,要不然下一步时候 ,你就找不到原来要删除结点的后继 有如何来释放呢
Ptr->next=Ptr->next->next;
free(P);
}
PrintList(L);
}
//这里使用的也是快慢指针的方式
status DeleteListLocation(LNode* L,int i){
LNode* P;
LNode* Pre=L->next;LNode* Next=L;
while((--i)&&Pre!=NULL){
Pre=Pre->next;
Next=Next->next;
}
if(Pre==NULL){
cout<<"你输入的数字不对"<<endl;
}
else{//此时这两个Pre一个指向的是待删除结点,一个指向的是待删除结点的前驱
P=Next->next;
Next->next=Pre->next;
free(P);
cout<<"删除成功"<<endl;
}
PrintList(L);
return OK
}
//销毁一个同样的也是需要两个指针 我们这里删除的是Next(慢指针) ,销毁之后就不能在打印了
status DestoryListL(LNode* &L){//销毁一个链表
if(L->next==NULL){
cout<<"为空链表"<<endl;
}
LNode* Pre=L->next;LNode* Next=L;
while(Pre){
Next=Pre;
Pre=Pre->next;
free(Next);
}
L->next=NULL;
cout<<"成功销毁"<<L->next;
return OK;
}
status ModifyLocation(LNode* &L,int i,ElemType e){
if(Length(L)<i||i<0){
cout<<"你输入的位置信息不对"<<endl;
return ERROR;
}
LNode *P=L->next;
while(--i){
P=P->next;
}
P->data=e;
cout<<"修改之后表是";
PrintList(L);
return OK
}
//修改所有值为e的改成value
status ModifyElemType(LNode* &L,ElemType e,ElemType value){
if(LocateElem(L,e)==NULL){
printf("你输入的值表中不存在\n");
return ERROR;
}
LNode *P=L->next;
while(P){
if(P->data==e) P->data=value;
P=P->next;
}
cout<<"修改之后的表是";
PrintList(L);
return OK;
}
/*******************操作函数***************************/
//上述中使用两种方式,一种是头插入,一种是未插入
void Add(LNode* &L){
int flag;
cout<<"请输入你的选择"<<endl;
cout<<"1、头插入 2、yi巴插入"<<endl;
cin>>flag;
if(1==flag) ListHeadInsert(L);
else ListTailInsert(L);
}
void Delete(LNode* &L){
int flag;
int Location;
ElemType value;
LNode* P;
cout<<"1、删除某值,2、删除某一个具体结点 3、删除某一个位置 4、删除全表"<<endl;;
cin>>flag;
switch(flag){
case 1:{
cout<<"请输出某值"<<endl;
cin>>value;
DeleteListElemType(L,value);
break;
}
case 2:{//我们不好找一个具体结点,但是我们可以通过LocateElem(L,e)来返回一个指针来确定一个具体的结点
cout<<"请输入你要删除的结点的值" <<endl;
cin>>value;
P=LocateElem(L,value);
DeleteListLNode(L,P);
break;
}
case 3:{
cout<<"请输入一个具体的位置"<<endl;
cin>>Location;
DeleteListLocation(L,Location);
break;
}
case 4:{
DestoryListL(L);
break;
}
default:break;
}
}
void Modify(LNode* &L){
int choice;int location;ElemType value,value2;
cout<<"1、修改某个位置 2、修改某个值"<<endl;
cin>>choice;
switch(choice){
case 1:{
cout<<"请输入你要修改的位置"<<endl;
cin>>location;
cout<<"请输入你现在要放入的值"<<endl;
cin>>value;
ModifyLocation(L,location,value);
break;
}
case 2:{
cout<<"请输入你修改的值"<<endl;
cin>>value;
cout<<"你现在可以输入要替换的值"<<endl;
cin>>value2;
ModifyElemType(L,value,value2);
break;
}
default:break;
}
}
void Seek(LNode* &L){
int choice;int location;
cout<<"1、表中元素的个数 2、表是否为空 3、某一个位置的值"<<endl;
cin>>choice;
switch(choice){
case 1:{
cout<<"表中的元素个数是"<<Length(L);
break;
}
case 2:{
if(!Empty(L)){
cout<<"表不为空"<<endl;
}
else cout<<"表为空"<<endl;
break;
}
case 3:{
cout<<"请输入你想获取的位置"<<endl;
cin>>location;
cout<<"你想获取的位置上的数是"<<GetElem(L,location);
break;
}
default:break;
}
}
void menu(){
cout<<"请输入你要进行的操作"<<endl;
cout<<"1、增加 2、删除 3、修改 4、查看 5、退出"<<endl;
}
int main()
{
LinkList L;int choice;
InitList(L);
while(1)
{
menu();
printf("请输入菜单序号:\n");
scanf("%d",&choice);
if(choice==5) break;
switch(choice)
{
case 1:Add(L);break;
case 2:Delete(L);break;
case 3:Modify(L);break;
case 4:Seek(L);break;
default:printf("输入错误!!!\n");
}
}
return 0;
}
总结
提示:这里对文章进行总结:我在打这个代码的时候也遇到各种问题 ,特别是在总代码一百行中 不知道为什么while(cin>>e)为什么会循环,
若是文章对你的提升由哪怕一点帮助的话 请答应我 不要吝啬你的点赞评论 转载请告知哪一部分我检查一下