上一节我们讲述了数据结构的基本概念,这一节让我们来讨论下单链表的概念和实现
我从书中简单摘录下单链表概念
简单而言单链表的是通过许多节点构成,每个节点包含2个重要元素:该节点数据(数据域)和指向下个节点的地址(指针域)
这样说太枯燥了,让我们直接用c#来一步步实现
既然一个节点是由(数据域)和(指针域)构成,那我们简单DIY一个LinkNod类
///summary///单链表的节点////summarypublicclassLinkNod{//节点数据域publicobjctLinkNodData{gt;st;}//自己节点的地址publicGuidSlfAddrss{gt;st;}//下个节点的地址指针(存储位置)publicGuidNxtAddrss{gt;st;}}
继续来了解概念了,既然节点准备好了,那我们要了解节点是怎么通过指针域连接在一起的,看图
图中节点就是一个小矩形,数据域是姓名,指针域就是那个箭头所表示的指向它的后继,头节点h-zhao-Qian-....Wang
这样连接起来就是一个完整的单链表,头结点的数据域可以是任何信息,尾节点的地址域是空(他没有后继节点了)
好,代码中我们只有nod没有LinkTabl,那我们就按照上图来建立一个LinkTabl类
publicclassLinkTabl
{
//定义一个LinkNod集合
ListLinkNodlist=nwListLinkNod();
publicLinkTabl()
{
}
///summary
///进行单向链表初始化
////summary
publicvoidInitialList()
{
//添加5个节点拥有唯一的guid作为自身地址,后继节点地址为guid.mpty
for(inti=0;i5;i++)
{
list.Add
(
nwLinkNod{LinkNodData=string.Format(第{0}个节点,i+1),SlfAddrss=Guid.NwGuid(),NxtAddrss=Guid.Empty}
);
}
s
varj=0;
//将节点的指针域指向下一个节点的地址
list.ForEach
(
(linkNod)=
{
if(jlist.Count-1)
{
linkNod.NxtAddrss=list.Skip(++j).FirstOrDfault().SlfAddrss;
}
}
);
}
}
LinkTabl类包含一个LinkNod集合和一个初始方法,这个方法是先添加节点数据到集合中,然后将节点的地址域一一连接起来
肯定会有朋友问我,那么你怎么在单链表中插入数据或删除数据呢?
非常棒的问题,看图:
图中可以看出a节点的后继是b节点,a节点的指针域指向b节点,那如果在a节点和b节点中添加一个新的节点那情况又如何?
其实图中已经表达出来了,将a的指针域指向新节点,然后将新节点的指针域指向b节点
马上看代码理解
既然是添加节点那我们在LinkTabl类中添加方法就行
///summary
///添加一个新节点
////summary
///paramnam=nod节点/param
///paramnam=addIndx在indx处添加节点/param
publicvoidAddNod(LinkNodnod,intaddIndx)
{
if(this.list==null
nod==null)
{
//如果链表为空则初始链表并且添加节点
this.InitialList();
}
if(addIndx0
addIndxlist.Count)
{
thrownwIndxOutOfRangExcption(rmovIndx超出范围);
}
varlistCount=list.Count;
//注意,得到新插入节点的前一个索引位置
varprv=addIndx-1=0
listCount=0?0:addIndx-1;
//注意,得到新插入节点的后一个索引位置
varaftr=listCount=0?0:
addIndxlistCount-1?listCount-1:addIndx;
//插入后前一个节点
varprvNod=list[prv];
//插入后后一个节点
varaftrNod=list[aftr];
//将前一个节点的指针域指向新节点
nod.NxtAddrss=aftrNod.SlfAddrss;
//将新节点的指针域指向后一个节点
prvNod.NxtAddrss=nod.SlfAddrss;
//判断是否插入到最后一个位置
if(addIndx==list.Count)
{
nod.NxtAddrss=Guid.Empty;
list.Add(nod);
}
ls
{
//插入新节点
list.Insrt(addIndx,nod);
}
}
代码注释能够帮助你了解下添加节点的具体过程,请大家仔细消化下
最后是删除一个节点的情况:
和添加节点正好逆向思维,当我们删除b节点时,我们要将a节点的指针域指向c节点保证我们的单链表不被破坏
删除方法同样写在LinkTabl类中
///summary
///通过索引删除
////summary
///paramnam=rmovIndx/param
///rturns/rturns
publicvoidRmov(intrmovIndx)
{
if(this.list==null)
{
//如果链表为空则初始链表并且添加节点
this.InitialList();
}
if(rmovIndxthis.list.Count-1
rmovIndx0)
{
thrownwIndxOutOfRangExcption(rmovIndx超出范围);
}
varprIndx=rmovIndx-1;
varaftrIndx=rmovIndx+1;
varprNod=list[prIndx];
varaftrNod=list[aftrIndx];
//将被删除节点前后的指针域进行整理
prNod.NxtAddrss=aftrNod.SlfAddrss;
//删除该节点
list.Rmov(list[rmovIndx]);
}
ok,这就是单链表的一个简单理解,请大家务必牢记,因为后章的循环列表将更复杂,单链表只是一个链表的基础(以下是完整代码及输出情况)
classProgram
{
staticvoidMain(string[]args)
{
LinkTabltabl=nwLinkTabl();
//初始化
tabl.InitialList();
//尝试添加一个新节点
tabl.AddNod
(
nwLinkNod{LinkNodData=新节点,NxtAddrss=Guid.Empty,SlfAddrss=Guid.NwGuid()},
);
//删除一个节点
tabl.Rmov(1);
vari=0;
//循环显示
tabl.list.ForEach
(
(linkNod)=
{
if(i==tabl.list.Count-1)
{
Consol.Writ({0}-{1},linkNod.LinkNodData,Null);
}
ls
{
Consol.Writ({0}-,linkNod.LinkNodData);
}
i++;
}
);
Consol.RadLin();
}
}
///summary
///单链表的节点
////summary
publicclassLinkNod
{
//节点数据域
publicobjctLinkNodData{gt;st;}
//自己节点的地址
publicGuidSlfAddrss{gt;st;}
//下个节点的地址指针(存储位置)
publicGuidNxtAddrss{gt;st;}
}
///summary
///单链表
////summary
publicclassLinkTabl
{
//定义一个LinkNod集合
publicListLinkNodlist=nwListLinkNod();
publicLinkTabl()
{
}
///summary
///进行单向链表初始化
////summary
publicvoidInitialList()
{
//添加10个节点拥有唯一的guid作为自身地址,后继节点地址为guid.mpty
for(inti=0;i5;i++)
{
list.Add
(
nwLinkNod{LinkNodData=string.Format(第{0}个节点,i+1),SlfAddrss=Guid.NwGuid(),NxtAddrss=Guid.Empty}
);
}
varj=0;
//将节点的指针域指向下一个节点的地址
list.ForEach
(
(linkNod)=
{
if(jlist.Count-1)
{
linkNod.NxtAddrss=list.Skip(++j).FirstOrDfault().SlfAddrss;
}
}
);
}
///summary
///添加一个新节点
////summary
///paramnam=nod节点/param
///paramnam=addIndx在indx处添加节点/param
publicvoidAddNod(LinkNodnod,intaddIndx)
{
if(this.list==null
nod==null)
{
//如果链表为空则初始链表并且添加节点
this.InitialList();
}
if(addIndx0
addIndxlist.Count)
{
thrownwIndxOutOfRangExcption(rmovIndx超出范围);
}
varlistCount=list.Count;
//注意,得到新插入节点的前一个索引位置
varprv=addIndx-1=0
listCount=0?0:addIndx-1;
//注意,得到新插入节点的后一个索引位置
varaftr=listCount=0?0:
addIndxlistCount-1?listCount-1:addIndx;
//插入后前一个节点
varprvNod=list[prv];
//插入后后一个节点
varaftrNod=list[aftr];
//将前一个节点的指针域指向新节点
nod.NxtAddrss=aftrNod.SlfAddrss;
//将新节点的指针域指向后一个节点
prvNod.NxtAddrss=nod.SlfAddrss;
//判断是否插入到最后一个位置
if(addIndx==list.Count)
{
nod.NxtAddrss=Guid.Empty;
list.Add(nod);
}
ls
{
//插入新节点
list.Insrt(addIndx,nod);
}
}
///summary
///通过索引删除
////summary
///paramnam=rmovIndx/param
///rturns/rturns
publicvoidRmov(intrmovIndx)
{
if(this.list==null)
{
//如果链表为空则初始链表并且添加节点
this.InitialList();
}
if(rmovIndxthis.list.Count-1
rmovIndx0)
{
thrownwIndxOutOfRangExcption(rmovIndx超出范围);
}
varprIndx=rmovIndx-1;
varaftrIndx=rmovIndx+1;
varprNod=list[prIndx];
varaftrNod=list[aftrIndx];
//将被删除节点前后的指针域进行整理
prNod.NxtAddrss=aftrNod.SlfAddrss;
//删除该节点
list.Rmov(list[rmovIndx]);
}
}
输出:
希望大家对单链表有比较深的理解,其实在效率性能上这样的单链表不及数组,因为数组更本没有那么繁琐,
大家在实际项目还是用数组比较好,下章会和大家先补充下c#中的LinkList类和Array类的区别(*数组和链表的区别(很重要)),
然后简单说下循环链表。
北京最权威治疗白癜风医院怎样防治白癜风