032204链表数据结构实现资料.docx
《032204链表数据结构实现资料.docx》由会员分享,可在线阅读,更多相关《032204链表数据结构实现资料.docx(23页珍藏版)》请在冰豆网上搜索。
![032204链表数据结构实现资料.docx](https://file1.bdocx.com/fileroot1/2022-11/25/5c8b65b4-faf5-41f2-a46a-f81c0435d033/5c8b65b4-faf5-41f2-a46a-f81c0435d0331.gif)
032204链表数据结构实现资料
1、课程名称:
链表数据结构实现
2、知识点
2.1、上次课程的主要知识点
包装类的使用。
2.2、本次预计讲解的知识点
1、链表的基本形式;
2、单向链表的完整实现。
3、具体内容(★★☆☆☆)
3.1、认识链表
链表=可变长的对象数组,属于动态对象数组的范畴。
对象数组有那些问题呢?
·对象数组可以保存一组对象方便开发;
·对象数组的长度固定,而且数据的删除、修改、增加处理麻烦。
所有的开发之中都100%不可能避免掉对象数组的使用。
正因为如此现在如果要想让其可以编写出便于维护的代码,那么就需要实现一个动态对象数组,那么就可以使用链表完成。
但是现在如果要想实现动态的对象数组,要考虑两个问题:
·为了适应于所有的开发要求,此对象数组要求可以保存所有的数据类型,那么一定首选Object类型;
·为了可以保存多个数据,需要采用引用的方式来进行保存,但是数据本身是不可能保存顺序的,所以需要有一个可以负责保存顺序的类来包装这个数据。
通过以上的分析就可以得出如下的结论:
·保存数据为了方便使用Object;
·数据本身不包含有先后的逻辑关系,所以将数据封装在一个Node类,负责关系的维护。
范例:
定义出如下的一个类
classNode{//表示定义的节点
privateObjectdata;//要保存的数据
privateNodenext;//保存下一个节点
publicNode(Objectdata){//有数据才可以保存节点
this.data=data;
}
publicvoidsetNext(Nodenext){//设置节点
this.next=next;
}
publicNodegetNext(){//取得节点
returnthis.next;
}
publicObjectgetData(){
returnthis.data;
}
}
完成节点之后,下面就可以进行节点的基本使用了。
范例:
采用循环的方式操作节点
publicclassTestDemo{
publicstaticvoidmain(Stringargs[]){
//1、定义各自独立的操作节点
Noderoot=newNode("火车头");
Noden1=newNode("车厢1");
Noden2=newNode("车厢2");
//2、设置彼此间的关系
root.setNext(n1);
n1.setNext(n2);
//3、输出
NodecurrentNode=root;//从根节点开始取出数据
while(currentNode!
=null){//当前有节点
System.out.println(currentNode.getData());//取出数据
currentNode=currentNode.getNext();//下一个节点
}
}
}
以上的操作如果使用循环并不方便。
最好的做法是递归调用。
范例:
利用递归的方式实现内容的取得
publicclassTestDemo{
publicstaticvoidmain(Stringargs[]){
//1、定义各自独立的操作节点
Noderoot=newNode("火车头");
Noden1=newNode("车厢1");
Noden2=newNode("车厢2");
//2、设置彼此间的关系
root.setNext(n1);
n1.setNext(n2);
print(root);
}
publicstaticvoidprint(Nodenode){
if(node==null){
return;//结束方法调用
}
System.out.println(node.getData());
print(node.getNext());
}
}
通过以上的结构讲解,应该已经清楚了链表在整个实现的关键就是Node类,Node类要保存数据与下一个节点。
3.2、链表开发入门
虽然以上的代码已经实现了链的形式,但是以上的代码之中存在有两个问题:
·用户需要自己手工定义Node类,但是实际上Node类对用户没用;
·Node类的先后关系如果交由用户处理,那么整个代码就乱了。
所以现在发现,需要有一个类,这个类可以负责所有的Node的关系匹配,而用户只需要通过这个类保存数据或取得数据即可,那么就可以编写一个Link类完成。
范例:
基本结构
classNode{//表示定义的节点
privateObjectdata;//要保存的数据
privateNodenext;//保存下一个节点
publicNode(Objectdata){//有数据才可以保存节点
this.data=data;
}
publicvoidsetNext(Nodenext){//设置节点
this.next=next;
}
publicNodegetNext(){//取得节点
returnthis.next;
}
publicObjectgetData(){
returnthis.data;
}
}
classLink{//表示一个链表操作类,利用此类来隐藏Node的节点匹配
publicvoidadd(Objectobj){//向链表里面追加数据
}
publicvoidprint(){//输出链表中的全部数据
}
}
publicclassTestDemo{
publicstaticvoidmain(Stringargs[]){
Linkall=newLink();
all.add("商品1");
all.add("商品2");
all.add("商品3");
all.print();
}
}
用户不关心Node,用户只关心通过Link操作完成后可以取得数据。
范例:
完善程序
classNode{//表示定义的节点
privateObjectdata;//要保存的数据
privateNodenext;//保存下一个节点
publicNode(Objectdata){//有数据才可以保存节点
this.data=data;
}
publicvoidsetNext(Nodenext){//设置节点
this.next=next;
}
publicNodegetNext(){//取得节点
returnthis.next;
}
publicObjectgetData(){
returnthis.data;
}
//第一次调用:
this=Link.root
//第二次调用:
this=Link.root.next
//第三次调用:
this=Link.root.next.next
publicvoidaddNode(NodenewNode){
if(this.next==null){//当前节点之后没有节点
this.next=newNode;
}else{//如果现在当前节点后有节点
this.next.addNode(newNode);
}
}
//第一次调用:
this=Link.root
//第二次调用:
this=Link.root.next
publicvoidprintNode(){
System.out.println(this.data);//当前节点数据
if(this.next!
=null){//还有后续的节点
this.next.printNode();
}
}
}
classLink{//表示一个链表操作类,利用此类来隐藏Node的节点匹配
privateNoderoot;//需要有一根元素
publicvoidadd(Objectobj){//向链表里面追加数据
//将要操作的数据包装为Node类对象,这样才可以进行先后关系的排列
NodenewNode=newNode(obj);
//现在没有根节点
if(this.root==null){//this出现在Link类,表示Link类的当前对象
this.root=newNode;//将第一个节点作为根节点
}else{//根节点存在了,交由Node类负责处理
this.root.addNode(newNode);//由根节点负责调用
}
}
publicvoidprint(){//输出链表中的全部数据
if(this.root!
=null){//现在有数据
this.root.printNode();//输出节点数据
}
}
}
publicclassTestDemo{
publicstaticvoidmain(Stringargs[]){
Linkall=newLink();
all.add("商品1");
all.add("商品2");
all.add("商品3");
all.print();
}
}
此时的代码就实现了链表的基本操作,整个过程之中,用户不关心Node的处理,只关心数据的保存和输出。
3.3、开发可用链表
以上的代码只能够说是基本的链表结构形式,但是从另外一个方面,以上的代码给我们提供了链表的实现思路,可是如何才能够设计一个好的链表呢?
链表的实现必须依靠于节点类Node类来实现,但是在整个的过程之中一定要清楚,用户不需要操作Node。
而且通过现在的代码可以发现,Node类里的操作有其特定的需要。
但是这个时候Node类写在了外面,那么就表示用户可以直接操作Node类的对象。
所以程序现在的问题在于:
如何可以让Node类只为Link类服务,但是又不让其被其他类所访问,那么自然就要想到使用内部类来完成,而且内部类的好处在于:
可以与外部类直接进行私有属性的访问。
“
范例:
合理的结构规划
classLinkImpl{//外部的程序只关心此类
privateclassNode{//使用私有内部类,防止外部使用此类
privateObjectdata;
privateNodenext;
publicNode(Objectdata){
this.data=data;
}
}
//***************************************************
privateNoderoot;//根元素
}
如果要开发程序,那么一定要创建出自己的操作标准,那么一旦说到标准,就应该想到使用接口来完成。
interfaceLink{
}
classLinkImplimplementsLink{//外部的程序只关心此类
privateclassNode{//使用私有内部类,防止外部使用此类
privateObjectdata;
privateNodenext;
publicNode(Objectdata){
this.data=data;
}
}
//***************************************************
privateNoderoot;//根元素
}
在随后完善代码的过程之中,除了功能的实现之外,实际上也属于接口功能的完善。
3.3.1、实现数据增加操作:
publicvoidadd(Objectdata)
1、需要在接口里面定义好数据增加的操作方法;
interfaceLink{
publicvoidadd(Objectdata);//数据增加
}
2、进行代码的实现,同样实现的过程之中LinkImpl类只关心根节点,而具体的子节点的排列都交由Node类负责处理;
·在Link类中实现add()方法:
publicvoidadd(Objectdata){
if(data==null){//现在没有要增加的数据
return;//结束调用
}
NodenewNode=newNode(data);//创建新的节点
if(this.root==null){//保留有根节点
this.root=newNode;
}else{//应该交由Node类负责处理
this.root.addNode(newNode);
}
}
·在Node类中进行数据的追加操作;
publicvoidaddNode(NodenewNode){
if(this.next==null){
this.next=newNode;
}else{
this.next.addNode(newNode);
}
}
此时的代码实现过程与基本的实现是完全一样的。
3.3.2、取得保存元素个数:
publicintsize()
每一个Link接口的对象都要保存各自的内容,所以为了方便控制保存个数,可以增加一个Link类中的属性,并且利用此属性在数据成功追加之后实现自增操作。
·在Link类中定义一个count属性,默认值为0;
privateintcount=0;
·当数据已经成功添加完毕之后实现计数的统计;
publicvoidadd(Objectdata){
if(data==null){//现在没有要增加的数据
return;//结束调用
}
NodenewNode=newNode(data);//创建新的节点
if(this.root==null){//保留有根节点
this.root=newNode;
}else{//应该交由Node类负责处理
this.root.addNode(newNode);
}
this.count++;
}
而在Link接口里面追加size()的方法,同时在LinkImpl子类里进行方法的覆写。
interfaceLink{
publicvoidadd(Objectdata);//数据增加
publicintsize();//取得保存元素的个数
}
publicintsize(){
returnthis.count;
}
此操作直接与最后的输出有关。
3.3.3、判断是否为空集合:
publicbooleanisEmpty()
如果要想判断集合是否为空,有两种方式:
长度为0,另外一个就是判断根元素是否为null。
范例:
在Link接口中追加一个新的方法:
isEmpty
interfaceLink{
publicvoidadd(Objectdata);//数据增加
publicintsize();//取得保存元素的个数
publicbooleanisEmpty();//判断是否为空集合
}
范例:
在LinkImpl类中实现此方法
publicbooleanisEmpty(){
returnthis.root==null;
}
实际上此操作与size()几乎一脉相承。
3.3.4、数据查询:
publicbooleancontains(Objectdata)
任何情况下,Link类只负责与根元素操作有关的内容,而所有的其他元素的数据的变更、查找、关系的匹配都应该交由Node类来负责处理。
1、在Link接口里面创建一个新的方法;
interfaceLink{
publicvoidadd(Objectdata);//数据增加
publicintsize();//取得保存元素的个数
publicbooleanisEmpty();//判断是否为空集合
publicbooleancontains(Objectobj);//判断是否有指定的元素
}
2、在LinkImpl子类里面要通过根元素开始调用查询,所有的查询交由Node类负责;
·在LinkImpl发出具体的查询要求之前,必须要保证有集合数据;
publicbooleancontains(Objectdata){
if(this.root==null){//没有集合数据
returnfalse;
}
returnthis.root.containsNode(data);//根元素交给Node类完成
}
·在Node类中实现数据的查询;
//第一次:
this=LinkImpl.root;
//第二次:
this=LinkImpl.root.next;
publicbooleancontainsNode(Objectdata){
if(this.data.equals(data)){//该节点数据符合于查找数据
returntrue;
}else{//继续向下查找
if(this.next!
=null){//当前节点之后还有下一个节点
returnthis.next.containsNode(data);
}else{
returnfalse;
}
}
}
这种查询的模式实质上也属于逐行的判断扫描。
3.3.5、根据索引取得数据:
publicObjectget(intindex)
既然链表属于动态的对象数组,所以数组本身一定会提供有根据索引取得数据的操作支持,那么在链表中也可以定义与之类似的方法。
但是在进行数据保存的时候并没有设置索引,那么现在有两种方案:
·修改Node类的结构,为每一个节点自动匹配一个索引,数据删除不方便;
·在操作索引时动态生成索引,适合于集合的修改。
1、修改LinkImpl类,为其增加一个foot的属性,之所以将foot属性定义在LinkImpl类之中,主要的目的是方便多个Node共同进行属性的操作使用的,同时内部类可以方便的访问外部类中的私有成员;
privateintfoot=0;//操作的索引脚标
2、在Link接口里面首先定义出新的操作方法;
publicObjectget(intindex);//根据索引取得内容,索引从0开始
3、在LinkImpl类里面定义功能实现:
·在Node类中应该提供有一个getNode()的方法,那么这个方法的功能是依次判断每一个索引值的操作形式。
publicObjectgetNode(intindex){//传递索引的序号
if(LinkImpl.this.foot++==index){//当前的索引为要查找的索引
returnthis.data;//返回当前节点对象
}else{
returnthis.next.getNode(index);
}
}
·在Link类中实现get()方法;
publicObjectget(intindex){
if(index>=this.count){//索引不存在
returnnull;
}
this.foot=0;//查询之前执行一次清零操作
returnthis.root.getNode(index);
}
这种查询的模式与contains()最大的不同一个是数字索引,一个是内容。
3.3.6、修改数据:
publicvoidset(intindex,Objectobj)
与get()相比,set()方法依然需要进行循环的判断,只不过get()索引判断成功之后会返回数据,而set只需要用新的数据更新已有节点数据即可。
1、在Link接口里面定义新的方法:
publicvoidset(intindex,Objectobj);
2、修改LinkImpl子类,流程与get()差别不大;
·在Node类里面追加一个新的setNode()方法;
publicvoidsetNode(intindex,Objectdata){
if(LinkImpl.this.foot++==index){
this.data=data;//重新保存数据
}else{
this.next.setNode(index,data);
}
}
·在LinkImpl子类里面覆写set()方法,在set()方法编写的时候也需要针对于给定的索引进行验证;
publicvoidset(intindex,Objectdata){
if(index>=this.count){//索引不存在
return;
}
this.foot=0;//查询之前执行一次清零操作
this.root.setNode(index,data);
}
set()与get()方法实际上在使用时都有一个固定的条件:
集合中的保存数据顺序应该为添加顺序。
3.3.7、数据删除:
publicvoidremove(Objectobj)
如果要进行数据的删除,那么对于整个链表而言就是节点的删除操作,而节点的删除操作过程之中需要考虑的问题:
要删除的是根节点还是子