链表>
- 由于无法得知new出新节点的次序,不可以写部分题,例如:link
- 这道题只能使用数组模拟链表来做,故结构体指针链表最好做为一种了解链表的工具,其限制性很大相较数组模拟链表无优点
- 我真是被链表搞的头都大了,一开始就没学好多东西,结构体指针都没咋学,导致做链表类题的时候直接就跳(尽管我没做过多少题)
- 然后我花了好几天时间才勉强看懂大佬们300行代码的简单教程,对我个新手来说就是折磨,搜了大概十几篇博文,三个浏览器换着看换着理解,就在今天,突然大悟(主要是我回去复习了一下指针和结构体),我看懂了,所有在这里我慢慢剖析一下链表
首先你得懂结构体和指针(不懂也没事我尽量讲清楚一点)
- 只需要下面一行代码,就能把代码模拟出来了,注意只是模拟出来了线性结构,赋值和输出还得靠函数来完成
struct node//node是节点,每个节点存储一个数据和指向下个节点的指针
{
int data;
struct node* next;
};
- node就是节点,一个节点包括两部分,数据域和指针域,如下面的图所示,形成线性结构
- 注指针指向下一个节点的指针p->data,代表本节点元素,p->next若为空则代表到达了尾部,因为这种关系就少不了初始定义:
操作一:初始化
Linklist::Linklist()
{
head=new node;
head->data=0;//初始化头节点对应的元素为0
head->next=NULL;//初始化下一个节点为空
}
- 什么?你看不懂Linklist::Linklist() 这是为了多个函数的实现而把所有可能的操作封装在了一个大类里,当然你可以使用结构体,毕竟他们没区别,我把大类给写一下
class Linklist
{
public:
Linklist(); //1.初始化一个单链表
//~LinkList(); //删除一个单链表
//"~"可以理解为与初始化相反,即删除(涉及到类中的析构函数,
//与链表无太大关系,不在这赘述,主要我不会)
//在销毁类中对应对象时使用,即销毁单链表时使用
//但一般使用,故不要定义,直接定义删除函数就行
void deleteLinklist(); //2.销毁单链表,大工程使用
void clearLinlist(); //3.清空单链表,清空后可以使用,销毁后不可以
void EstablishLinkList(int n);//4.创建一个单链表,n为长度
void OutputLinkList(); //5.遍历线性表,即输出
int GetLength(); //6.获取线性表长度
bool IsEmpty(); //7.判断单链表是否为空
node * Find(int data); //8.查找节点,输出节点的位置
void IEnd(int data); //9.在尾部插入指定的元素
void Insert(int data,int n); //10.在指定位置插入指定元素
void IHead(int data); //11.在头部插入指定元素
void DEnd(); //12.在尾部删除元素
void Delete(int data); //13.删除指定的数据
void DHead(); //14.在头部删除数据
private:
node * head; //定义私有变量--头结点
};
- 类中定义的函数不一定要使用,但初始化和销毁的Linklist一定要用,不然会报错,所以最好 ~Linklist 不要定义,直接使用下面的销毁函数
- 现在知道Linklist::Linklist的意思了吧,下面介绍第二种操作
操作二:销毁
void Linklist::deleteLinklist()
{
struct node*p=head->next;
while(p!=NULL)
{
struct node*tmp=p;
p=p->next;
free(tmp);
}
free(head);//释放头节点
}
- 与之差不多的是不删除头节点的清空操作,唯一区别,是否释放了头节点
操作三:清空
void Linklist::clearLinklist()
{
if(head==NULL)
return ;
struct node*p=head->next;
while(p!=NULL)
{
struct node*tmp=p;
p=p->next;
free(tmp);
}
head->next=NULL;//本来head指向就是空,所以此句可有可无,最好带上,我不是太懂
}
- 就像我们给数组赋值一样,要想给单链表赋值,只需要手写一个函数就行有如下操作
操作四:赋值(创建单链表)
void Linklist::EstablishLinklist(int n)//保证n>0
{
node *pnew,*ptemp;//创建两个工作节点
ptemp=head;
for(int i=0;i<n;i++)
{
pnew=new node;//pnew在ptemp节点之后
cin>>pnew->data;//从头插入每个节点的当前数据
pnew->next=NULL;//新节点的下个地址为空
ptemp->next=pnew;//将当前节点设为新节点
ptemp=pnew;
}
}
- 疯狂的创建新节点与老节点建立联系就是链表的精髓
- 就像我们输出数组元素一样,下面介绍手写的输出函数
操作五:遍历(输出单链表)
void Linklist::Output()
{
if(head==NULL||head->next==NULL)
{
cout<<"链表为空"<<endl;
}
node *p=head;
while(p->next!=NULL)
{
p=p->next;
cout<<p->data<<" ";
}
}
- 输出没什么好说的从头开始往后输出就行
操作六:获取链表长度
- 一看就懂代码奉上,不在赘述
int Linklist::GetLength()
{
int count=0;
node*p=head->next;
while(p!=NULL)
{
count++;
p=p->next;
}
return count;
}
操作七:判断线性表是否为空
- 不看都懂,代码奉上,不在赘述
bool Linklist::IsEmpty()
{
if(head->next==NULL)
return true;
else
return false;
}
- 有没有人想知道每个节点都是随机分配的那么它们存储在哪里呢,只要手写一个Find函数就能实现了
操作八:查找节点位置
node*Linklist::Find(int data)
{
node*p=head;
while(p->next!=NULL)
{
if(p->data==data)
{
return p;//不为空返回节点位置
}
p=p->next;
}
return NULL; //为空返回空
}
- Find函数要注意返回的是指针即位置!
下面讲述基操,就是删增等操作也就是链表的优点,终于到实用的地方了,心累,前面全是铺垫
增删元素o(1)是链表唯一的优点,那么老长的代码,终于到了它发挥作用的时候了
操作九:尾插
- 如果为空直接加在头上,不为空则找到第一个空节点加在后面就行,代码奉上
void Linklist::IEnd(int data)
{
node *newnode=new node;//定义一个节点
newnode->next=NULL;
newnode->data=data;//定义其数据与与指针域
node*p=head;
if(head==NULL)//如果空头,设其为头节点
{
head=newnode;
}
else
{
while(p->next!=NULL)//不空则找到第一个空节点放进该节点,即循环到最后一个节点将其放置在最后
{
p=p->next;
}
p->next=newnode;
}
}
- 有尾插必然少不了指插,咳咳!!
操作十:指插
- 就多加了一个节点是否在定义域内,其余大差不差
void Linklist::Insert(int data,int n)
{
if(n<1||n>GetLength())
cout<<"值错误"<<endl;
else
{
node*ptemp=new node;//定义新节点
ptemp->data=data; //给节点的数据域赋值
node*p=head; //从头遍历
int i=1;
while(n>i)
{
p=p->next; //找到要插入的位置之前的位置p,遍历到指定位置
i++;
}
ptemp->next=p->next;
p->next=ptemp; //将p后的第一个位置给它,将新节点插入到指定位置
}
}
操作十一:头插
void Linklist::IHead(int data)
{
node*newnode=new node;
newnode->data=data;
node*p=head;
if(head==NULL)
{
head=newnode;
}
newnode->next=p->next;
p->next=newnode;
}
- 不禁吟诗一首,尾插头插中间插大差不差……
- 转眼到了操作12
操作十二:尾删
void Linklist::DEnd()
{
node*p=head;
node*ptemp=NULL; //创建一个占位节点
if(p->next==NULL)
cout<<"链表为空"<<endl;
else
{
while(p->next!=NULL)
{
ptemp=p; //ptemp指向尾部的前一个节点
p=p->next; //找到要删除的p,循环到尾部的前一个
}
delete p; //删除
p=NULL; //令P为空
ptemp->next=NULL;
}
}
- 同上面,有尾插指插头插就有尾删指删头删
操作十三:指删
void Linklist::Delete(int data)
{
node*ptemp=Find(data);
if(ptemp==head->next)
{
DHead(); //如果是第一个就删去头节点
}
else
{
node*p=head;
while(p->next!=ptemp)
{
p=p->next; //找到要删除节点的前一个位置
}
p->next=ptemp->next;//链接p与ptemp的下个节点把ptemp挤掉,即删除
delete ptemp;//删除被挤掉的ptemp
ptemp=NULL;//令为空
}
}
- 转眼间就说完了,花了我五六个小时,还有最后一步头删
操作十四:头删
void Linklist::DHead()
{
node*p=head;
if(p==NULL||p->next==NULL)
cout<<"该链表为空"<<endl;
else
{
node*ptemp=NULL; //创建占位节点
p=p->next;
ptemp=p->next; //将头节点的下下个节点指向占位节点
delete p; //删去头节点
p=NULL;
head->next=ptemp; //头节点的指针更换为占位节点的指针
}
}
- 好难好难,下面将完整不带注释的代码发出来,加油加油
#include<iostream>
using namespace std;
struct node//node是节点,每个节点存储一个数据和指向下个节点的指针
{
int data;
struct node* next;
};
//定义一个链表,并把需要的操作写在一个类中,方便使用
class Linklist
{
public:
Linklist();//操作一
//~Linklist();
void deleteLinklist();//操作二
void clearLinklist();//操作三
void EstablishLinklist(int n);//操作四
void Output();//操作五
int GetLength();//操作六
bool IsEmpty();//操作七
node*Find(int data);//操作八
//*****************************************************
void IEnd(int data); //操作九、在尾部插入指定的元素
void Insert(int data,int n); //操作十、在指定位置插入指定元素
void IHead(int data); //操作11、在头部插入指定元素
void DEnd(); //操作12、在尾部删除元素
void Delete(int data); //操作13、删除指定的数据
void DHead(); //操作14、在头部删除指定元素
//****************************************************
private:
node *head;//定义头节点
};
Linklist::Linklist()
{
head=new node;
head->data=0;
head->next=NULL;
}
void Linklist::deleteLinklist()
{
if(head==NULL)
return ;
struct node*p=head->next;
while(p!=NULL)
{
struct node*tmp=p;
p=p->next;
free(tmp);
}
free(head);
}
void Linklist::clearLinklist()
{
if(head==NULL)
return ;
struct node*p=head->next;
while(p!=NULL)
{
struct node*tmp=p;
p=p->next;
free(tmp);
}
head->next=NULL;
}
void Linklist::EstablishLinklist(int n)
{
node *pnew,*ptemp;
ptemp=head;
for(int i=0;i<n;i++)
{
pnew=new node;
cin>>pnew->data;
pnew->next=NULL;
ptemp->next=pnew;
ptemp=pnew;
}
}
void Linklist::Output()
{
if(head->next==NULL)
{
cout<<"链表为空"<<endl;
}
node *p=head;
while(p->next!=NULL)
{
p=p->next;
cout<<p->data<<" ";
}
}
int Linklist::GetLength()
{
int count=0;
node*p=head->next;
while(p!=NULL)
{
count++;
p=p->next;
}
return count;
}
bool Linklist::IsEmpty()
{
if(head->next==NULL)
return true;
else
return false;
}
node*Linklist::Find(int data)
{
node*p=head;
while(p->next!=NULL)
{
if(p->data==data)
{
return p;
}
p=p->next;
}
return NULL;
}
void Linklist::IEnd(int data)
{
node *newnode=new node;
newnode->next=NULL;
newnode->data=data;
node*p=head;
if(head==NULL)
{
head=newnode;
}
else
{
while(p->next!=NULL)
{
p=p->next;
}
p->next=newnode;
}
}
void Linklist::Insert(int data,int n)
{
if(n<1||n>GetLength())
cout<<"值错误"<<endl;
else
{
node*ptemp=new node;
ptemp->data=data;
node*p=head;
int i=1;
while(n>i)
{
p=p->next;
i++;
}
ptemp->next=p->next;
p->next=ptemp;
}
}
void Linklist::IHead(int data)
{
node*newnode=new node;
newnode->data=data;
node*p=head;
if(head==NULL)
{
head=newnode;
}
newnode->next=p->next;
p->next=newnode;
}
void Linklist::DEnd()
{
node*p=head;
node*ptemp=NULL;
if(p->next==NULL)
cout<<"链表为空"<<endl;
else
{
while(p->next!=NULL)
{
ptemp=p;
p=p->next;
}
delete p;
p=NULL;
ptemp->next=NULL;
}
}
void Linklist::Delete(int data)
{
node*ptemp=Find(data);
if(ptemp==head->next)
{
DHead();
}
else
{
node*p=head;
while(p->next!=ptemp)
{
p=p->next;
}
p->next=ptemp->next;
delete ptemp;
ptemp=NULL;
}
}
void Linklist::DHead()
{
node*p=head;
if(p==NULL||p->next==NULL)
cout<<"该链表为空"<<endl;
else
{
node*ptemp=NULL;
p=p->next;
ptemp=p->next;
delete p;
p=NULL;
head->next=ptemp;
}
}
int main()
{
Linklist l;
int n;cin>>n;
l.EstablishLinklist(n);
l.IEnd(6);
l.Insert(6,3);
l.IHead(6);
l.Delete(6);
l.Output();
cout<<l.Find(1)<<endl;
cout<<l.IsEmpty()<<endl;
}
- 忘记写一个现在补上,删除指定位置的元素
void Linklist::Delete(int n)
{
node*p=head;
int i=1;
while(n>i)
{
p=p->next;
i++;
}
p->next=p->next->next;
}
- 采用挤掉法