链表初始化,头插法创建链表,尾插法创建链表,取值,查找(第i个节点的数据域或者数据域为某个特定值的节点),有顺序的合并两个链表,无顺序的合并两个链表,插入,删除,逆置链表,就地逆置链表求链表的节点个数,删除数据域重复的节点,输出链表,判空,置空链表,使用快慢指针高效找到中间节点,使用快慢指针高效找到倒数第k个节点,有序链表的合并。
动态存储,不需要预先分配最大空间,插入删除时不需要移动元素
每次动态分配一个节点,每个节点的地址是不连续的,需要有指针记录下一个节点的地址,指针域需要占用一个int的空间,存取元素必须从头到尾按顺序查找
#ifndef DANLIANBIAO_H
#define DANLIANBIAO_H
#endif
#include
#include
#include
#define ElemType int
//头结点是链表里面第一个结点,他的数据域可以不存放任何信息(有时候也会存放链表的长度等等信息),他的指针区域存放的是链表中第一个数据元素的结点(就是传说中的首元结点)存放的地址。
//指向第一个结点(可能是头节点也可能是首元节点)的指针称为头指针
using namespace std;
typedef struct Lnode{
ElemType data;//数据域
//elemtype 定义元素类型,使元素种类可以多样
struct Lnode *next;//指针域,指向下一个节点的指针
}Lnode,*Linklist;
// LNode是一个具象的结构体类型,指向的是包含某个数据类型的数据域和指针域的结构体类型。
// 而LinkList是LNode的指针类型,它占用一定内存空间,内存空间中的值是一个LNode类型结构体的地址
//用typedef将结构体等价于类型名Lnode,指针Linklist
//1,单链表初始化
bool InitList_L(Linklist &L){
//创建了一个带有头节点的空链表
L=new Lnode;//头节点L,其数值域为空,L->next指向第一个存有数据的节点即首节点
if(!L)
return false;
L->next=NULL;
return true;
}
//2,头插法创建单链表
void CreateList_H(Linklist &L)
{
int n;//输入n个元素的值,建立到头节点的单链表L
Linklist s;//定义指针变量s
L=new Lnode;//头节点L,其数值域为空,L->next指向第一个存有数据的节点即首节点
L->next=NULL;
cout<<"请输入元素个数n:"<<endl;
cin>>n;
cout<<"头插法创建单链表"<<endl;
cout<<"请依次输入n个元素:"<<endl;
while(n--)
//先运算 :把n的值作为判断条件,即非 0 都为真;后递减 :在判断为真后,将n递减
{
s=new Lnode;//linklist是指针变量,存储s的地址,随着后面 L->next=s;,s的地址不断改变,linklist指针也在不断后移
cin>>s->data;
s->next=L->next;//先修改没有指针标记的一端,要不然原来L后面的节点就找不到了
L->next=s;
}
}
//3,尾插法创建单链表
void CreateList_R(Linklist & L)
{
int n;
Linklist s,r;
L=new Lnode;
L->next=NULL;
r=L;//将头节点L的地址存储于r中即尾指针指向头节点
cout<<"请输入元素个数 n:"<<endl;
cin>>n;
cout<<"尾插法创建单链表 "<<endl;
cout<<"请依次输入n个元素:"<<endl;
while(n--){
s=new Lnode;//生成新节点
cin>>s->data;
s->next=NULL;
r->next=s;//r原来存贮的是L的地址,此步相当于 L->next=s,但是后来随着节点的不断插入,r->next=s就代表是尾节点的指针指向新插入的s,即尾插法
r=s;//r存储s的地址,
}
}
//4,输出链表的元素
void Show_L(Linklist &L){
Linklist s;
s=L->next;
while(s){
cout<<s->data<<" ";
s=s->next;
}
cout<<endl;
cout<<"输出完毕"<<endl;
}
//5,取值
int GetElem_L(Linklist &L,int i)
{
//在带头节点的单链表L中查找第i个节点存放的data值
//用e记录L中第i 个数据元素的值
int j,e;
Linklist p;
p=L->next;//p指向第一个数据节点
j=1;//j为计数器
while(j<i&&p)//顺着链表向后扫描,直到p指向第i个元素或p为空
{
p=p->next;//p指向下一个节点
j++;//计数器加1
}
if(!p||j>i)//i值不合法,i<=0,i>n
return -1;
e=p->data;//取第i 个节点的数据域
return e;
}
//6,查找,在带头节点的单链表L中查找值为e的元素
int LocateElem_L(Linklist L,int e)
{
Linklist p;
p=L;
int j=0;
while(p&&p->data!=e){//沿着链表向后扫描直到p为空或p所指节点数据域等于e
p=p->next;
j++;
}
if(!p)如果!p不为空,即如果p为空
//if(),while(),条件判断都是()里的东西不为空
return -1;
else
return j;
}
//7,插入
bool ListInsert_L(Linklist &L,int i,int e)
{
int j;
Linklist p,s;
p=L;
j=0;
while(p&&j<i-1)
{
p=p->next;
j++;
}
if(!p||j>i-1)//i值不合法
return false;
else
{
s=new Lnode;//生成新节点
s->data=e;//将要插入的元素e放入新节点的数据域中
s->next=p->next;//新节点的指针指向第i个节点
p->next=s;
return true;
}
}
//8,删除
bool ListDelete_L(Linklist &L,int i)
{
//删除第i 个位置
Linklist p,q;//q指向被删除的节点
int j;
p=L;//p指向头指针L
j=0;
while((p->next)&&(j<i-1))//查找第i-1个节点,p指向该节点
//while((p->next)&&(j
//while(p&&jnext,所以while(p)while(p->next)其实本质是一样的
//while(p->next)使进了循环p就能赋值,但是如果while(p)的话,进了循环p不一定能赋值,因为可能p->next为空
{
p=p->next;
j++;//j=0 第一次循环:第一个节点,j=1,第二个节点,j=2......
}
if(!(p->next)||(j>i-1))//删除位置i取值不合适
return false;
else
{
q=p->next;// 临时保存被删节点的地址即第i个节点的地址以备释放空间
p->next=q->next;//将i 节点的下一个节点的地址赋值给p节点的指针域
delete q;//释放被删除节点的空间
return true;
}}
//9,就地逆置单链表(简单)
void reverseLinklist(Linklist &L)
{
Linklist p,q;
p=L->next;//p指向L的第一个元素,下一步要置空头节点,防止后面节点丢失
L->next=NULL;// 置空头节点
while(p)
{
q=p->next;//q指向p的下一个节点,记录断点
p->next=L->next;//此步和下一步是运用头插法,因为前面把链表的头节点置空了,相当于不断从后面链表里取元素,不断运用头插法把它重新创建一个链表,只不过是在原有链表的基础上创建的
//因为头插法创建链表,本身就是和输入的顺序是相反的,正好是逆序!
L->next=p;
p=q;//p指针后移
}
}
//10,单链表逆置(较复杂)
void Reverse_L(Linklist &L){
Linklist r,p,q;//r代表前驱节点,p当前节点,q后继节点;Linklist r,p,q分别指向前驱节点,当前节点,后继节点
if(!L->next)//如果当前链表为空,则不需要逆置,结束
return;
//反转每个节点的指针域,从单链表的第一个节点开始,沿着链表滑动,直到最后一个节点,逐一反转
r=NULL;p=L->next;q=p->next;
while(p)
{
p->next=r;//修改当前节点的指针域next,把它反转指向其前驱节点
//把指针r,p,q依次后移一个结点
r=p;
p=q;
if(q)//保证指向后继节点的指针q不会移出表尾
q=q->next;
}
//当从循环出来时说明p为空,则r就是最后一个节点,此时把最后一个节点作为首节点
L->next=r;
}
//11,把单链表置空
void clear_L(Linklist &L)
{
Linklist p,q;//指向代删除节点及其后继节点
//回收每个节点的存储空间即从单链表的第一个节点开始,沿着链表滑动,直到最后一个节点,逐一回收
p=NULL;
q=L->next;
while(q){
p=q;//把指针p指向代删除的节点q,便于后面清空储存空间
q=q->next;//指针q,指向下一个代删除的节点
delete p;//回收代删除节点的空间
}
L->next=NULL;
}
//12,删除单链表中数据域为e的第一个节点
bool DeleteElem_L(Linklist &L,ElemType e){
Linklist r ,p;//分别指向待删除节点的前驱和待删除节点的指针
//L是头指针,是不能变换指向的,要不然会导致整个链表丢失,L->next;就是指向首节点的(即第一个data不为空的节点)
r=L;//使r指向头节点,有头节点的好处是当要删除的元素是第一个节点时,不用再单独处理首节点
// 即省去了这两步操作 if(r==NULL); L->next=L->next->next;单独对首节点处理
p=L->next;
while(p&&(p->data)!=e)//查找待删除节点
{
r=p;// 指向前驱节点
p=p->next;//指向当前节点
}
if(p==NULL)//滑动到表尾,没找到代删除节点,返回false
return false;
//if(r==NULL)
// L->next=L->next->next;
else
r->next=p->next;//修改待删除节点的前驱节点,使其指向待删除节点的下一个节点
free(p);//回收待删除节点的存储空间
return true;
}
//13,删除单链表中的重复值
//查找当前节点p是否与前面的每个节点有重复值,用指针s来实现,s从链表的第一个节点直到p所指的当前节点为止,逐一判断是否有与p->data相等的节点
//若有相同则指针p所指的当前节点, p=p->next;,重复判断是否有重复操作直到链表尾
void DeleteRepeat(Linklist &L){
Linklist r,p;//分别指向前驱节点和当前节点的指针
Linklist s;
r=L;p=L->next;//从单链表的第一个节点开始,直到最后一个节点,逐一判断
while(p){
s=L->next;//s指针的作用从第一个节点开始,直到p所指的当前节点,逐一判断看是否有重复
while(s!=p)
{
if(s->data==p->data)//如果有重复,使前驱节点绕过当前p节点即删掉p节点
{
r->next=p->next;//如果有重复,使前驱节点绕过当前p节点即删掉p节点
delete p;//回收指针p所指当前节点的存储空间,不是删掉p指针
p=r;//p指针原本指向的节点被删除,使p指针指向前驱节点r,后面的操作 p=p->next;,就可使p后移一位,而前驱节点仍保持不变,因为删除了一个节点的原因,可以代入样例试一下
break;
}
s=s->next;//若没有重复,则下移一个节点,继续比较,直到s指向p出循环
}
//前驱节点和当前节点都后移一个节点,直到p移到表尾
r=p;
if(p)//判断指针p是否已经到达表尾
p=p->next;
}
}
// 14,求单链表节点个数,头节点个数不计算在内
int GetLength(Linklist &L){
int n=0;
Linklist p;
p=L;
while(p->next)//while(p->next)头节点不计算在个数内;while(p)头节点计算在个数内
{
p=p->next;
n++;
}
return n;
}
//15,判断单链表是否为空
bool IsEmpty(Linklist &L){
if(L->next)//如果头节点不为空,则链表不是空链表
return false;
else
return true;//如果头节点为空,则链表是空链表
}
//16,将两个链表(不要求事先是排好序的两个链表)合并,合并成有顺序的
void GuiblingList(Linklist & A,Linklist& B)
//将B插入到A中合适位置,从而得到一个有顺序的合并链表
{
Linklist p,q,r;
q=A;p=B->next;
//此处因为要把B链表插入到A链表中去,q指针指向的是A的头指针,这样当B链表中的值小于A链表中的值,需要插入到A链表中时(进行插入操作需要直到插入位置的前驱节点)q指针指向总是能指向要插入位置的前驱节点
//比较的是q->next->data和p->data的值,但是q的指向是q->next的前驱节点,这样便于B链表的插入
while(p)//当B链表不为空时
{ //看不懂,可以自己手动迭代一下代码,就清楚了
//从A链表第一个节点的值开始和B的第一个节点的值比较,如果A的第一个节点的值A1next;A链表的下一个节点A2再和B1进行比较
//如果A1>B1,则使用另一个指针r,r指针指向B1,p指针指向B2(保证B1后面的节点不会丢失),再让r指针指向A1r->next=q->next;(把A1插到B1后面)
//再让A链表的头指针指向r(即B1)从而达到把B1插入到A链表中去
while((q->next)&&(q->next->data<p->data))//从A链表第一个节点的值开始和B的第一个节点的值比较
{
q=q->next;//当q->next->data小于p->data,q就指向链表A的下一个节点,再和p->data比较
}
//如果q->next->data大于p->data,需要把B链表的节点插入到A中
r=p;
p=p->next;//p指向当前节点的后继节点防止当前节点的后面节点丢失
r->next=q->next;//将q->next(因为此时q指向的是前驱节点)的节点插入到r指向节点的后面
q->next=r;//再将前面排好序好的节点重新插入到A链表中去
}
Show_L(A);
}
//17,合并链表,物理直接拼接
void CoalitionLinkList(Linklist& A,Linklist &B)
{
Linklist p;
for(p=A;p->next!=NULL;p=p->next)//找到A链表的尾节点
{};
p->next=B->next;//出循环后,将B链表的首节点接到A链表的尾节点
free (B);//释放B的存储空间
Show_L(A);
}
//18,有序(两个非递减单链表)合并链表法2穿针引线,不需要再创建空间
//要求事先排好链表的顺序,再对两个排好序的链表进行合并排序
void mergelinklist (Linklist &La,Linklist &Lb,Linklist &Lc){
Linklist p,q,r;
p=La->next;//p指向La的第一个数据元素节点
q=Lb->next;//p指向Lb的第一个数据元素节点
Lc=La;//Lc指向La的头节点
r=Lc ;//r指向新链表Lc的尾部
while(p&&q)//当p,q不为空的时候
{
if(p->data<=q->data)
{
r->next=p;
r=p;
p=p->next;
}
else
{
r->next=q;
r=q;
q=q->next;
}
}
while(p&&(q==NULL))//p不为空,p有剩余
{
r->next=p;
r=p;
p=p->next;
}
while (q&&(p==NULL))//q不为空,q有剩余
{
r->next=q;
r=q;
q=q->next;
}
delete Lb;
}
//19,查找链表的中间节点,使用快指针和慢指针,高效!
int findmiddle(Linklist &L)
{
Linklist p,q;
p=L;//p为快指针,一次走两个节点,初始时指向L
q=L;//q为慢指针,一次走一个节点,初始时指向L
while(p!=NULL&&(p->next)!=NULL){
p=p->next->next;//p为快指针,一次走两个节点
//如果链表长度为偶数时,p指针刚好走到链表最后一个节点,p->next->next=尾节点,出循环
//如果链表长度为奇数时,p指针走出链表了,p->next->next=NULL,出循环
q=q->next;//q为慢指针,一次走一个节点
//当p为空时即出循环时,q指针指向的就是中间节点
}
return q->data;
}
//20,单链表找到倒数第k个节点,快速方法,慢指针和快指针
int findk(Linklist &L,int k){
Linklist p,q;
p=L->next;//p为快指针,初始时指向第一个数据元素节点
q=L->next;//q为慢指针,初始时指向第一个数据元素节点
while(p->next!=NULL)//判断p指针是否指向到尾指针
{
if(--k<=0)//--k是先-再赋值
{q=q->next;}//q是慢指针,等p先指针走完k-1步,q指针再出发,并和快指针p以相同速率前进
p=p->next;
}
//出while循环,慢指针q指的就是所求的倒数第k个节点,因为快指针p和慢指针q之间的距离始终保持的是k-1,当快指针p走到链表尾时,向前数k-1个节点即慢指针q所指向的节点就是所求的倒数第k个节点
if(k>0)// 快指针p还没走完k-1步,就到头了,即k>n(链表长度)
return 0;
else
return q->data;
}