Java系列(九)__链表

Java系列(九)__链表

1、链表

学习到现在有两个概念没有讲解透彻:

                   · this表示当前对象,只是在对象比较中简单的应用了一次;

                   · 内部类做什么用?

         在之前所编写的第二个代码模型利用的是对象数组完成的,但是成是对象数组,败也是对象数组,因为对象数组有着严格的顺序索引同时不可修改大小。所以必须利用自己的代码进行问题的解决。

         链表 = 动态对象数组,是利用引用关系的组合实现的,所以来讲本处都是引用关系的操作。 

1.1、基本操作形式

         在现实之中有可能出现这样一种情况:从的领导是顾,顾的领导是谢、谢的领导是刁,刁的领导是金。现在如果由我们个人来表示这样的关系,那么类该如何设计?

范例:问题的引出

class Emp {

         private String name ;

         private Emp mgr ;         // 领导

         public Emp(String name) {

                   this.name = name ;

         }

         public void setMgr(Emp mgr) {

                   this.mgr = mgr ;

         }

         public Emp getMgr() {

                   return this.mgr ;

         }

         public String getName() {

                   return this.name ;

         }

}

public class LinkDemo {

         public static void main(String args[]) {

                   Emp ea = new Emp("从") ;

                   Emp eb = new Emp("顾") ;

                   Emp ec = new Emp("谢") ;

                   Emp ed = new Emp("刁") ;

                   Emp ee = new Emp("金") ;

                   ea.setMgr(eb) ;

                   eb.setMgr(ec) ;

                   ec.setMgr(ed) ;

                   ed.setMgr(ee) ;

         }

}

         现在通过如上的设计关系,可以发现,所有的对象就可以像一个个节点一样,进行连接。


范例:定义节点类

class Node {        // 只是作为一个数据的载体

         private String data ;       // 假设现在存放的是String型

         private Node next ;       // 下一个节点

         public Node(String data) {     // 有数据才应该有节点

                   this.data = data ;

         }

         public String getData() {

                   return this.data ;

         }

         public void setNext(Node next) {

                   this.next = next ;

         }

         public Node getNext() {

                   return this.next ;

         }

}

范例:设置节点的关联

public class LinkDemo {

         public static void main(String args[]) {

                   // 第一层:设置节点关系

                   Node root = new Node("数据A") ;

                   Node n1 = new Node("数据B") ;

                   Node n2 = new Node("数据C") ;

                   root.setNext(n1) ;

                   n1.setNext(n2) ;

                   // 第二层:取出节点关系

                   Node currentNode = root ;     // 当前操作节点

                   while(currentNode != null) { // 有节点

                            System.out.println(currentNode.getData()) ;

                            currentNode = currentNode.getNext() ;    // 改变当前节点

                   }

         }

}

         但是很明显以上的代码通过循环的方式并不好,最好的方式是使用递归进行输出。

范例:通过递归实现输出

public class LinkDemo {

         public static void main(String args[]) {

                   // 第一层:设置节点关系

                   Node root = new Node("数据A") ;

                   Node n1 = new Node("数据B") ;

                   Node n2 = new Node("数据C") ;

                   root.setNext(n1) ;

                   n1.setNext(n2) ;

                  print(root) ;       // 从root开始输出

         }

         public static void print(Node currentNode) {

                   if (currentNode != null) {

                            System.out.println(currentNode.getData()) ;

                            print(currentNode.getNext()) ;

                   }

         }

}

         以上通过设置节点保存,就可以发现可以保存无数的信息,没有长度限制。

1.2、链表的基本概念

         通过以上的分析可以发现现在的问题:用户需要自己准备好数据,需要自己定义节点,需要自己设置节点关系,取得的时候要分析节点组成后取出数据,这些实在是太麻烦了,用户关心的只是数据往那里保存,从那里得到。

         所以应该有一个类专门进行节点的操作,即:用户不应该去关心Node类之间的操作。于是现在要准备出一个Link类,这个类负责操作Node。

         在整个的Link类之中最需要关心的就是第一个节点(根节点)只有存在了根节点,才可以进行后期的所有操作。


范例:实现代码

class Node {        // 只是作为一个数据的载体

         private String data ;       // 假设现在存放的是String型

         private Node next ;       // 下一个节点

         public Node(String data) {     // 有数据才应该有节点

                   this.data = data ;

         }

         public String getData() {

                   return this.data ;

         }

         public void setNext(Node next) {

                   this.next = next ;

         }

         public Node getNext() {

                   return this.next ;

         }

         // 第一次(Link):this = Link.root

         // 第二次(Node):this = Link.root.next

         // 第三次(Node):this = Link.root.next.next

         public void addNode(Node newNode) {

                   if (this.next == null) {  // 当前的下一个节点为null

                            this.next = newNode;   // 保存新节点

                   } else {

                            this.next.addNode(newNode) ;

                   }

         }

         // 第一次(Link):this = Link.root

         // 第二次(Node):this = Link.root.next

         public void printNode() {

                   System.out.println(this.data) ;

                   if (this.next != null) {

                            this.next.printNode() ;

                   }

         }

}

class Link {         // 专门操作节点

         private Node root ;        // 根节点

         public void add(String data) {         // 保存数据

                   // 将数据封装在节点之中,因为只有封装之后才可以设置节点关系

                   Node newNode = new Node(data) ;        

                   if (this.root == null) {   // 现在没有根节点

                            this.root = newNode ;   // 第一个作为根节点

                   } else {

                            this.root.addNode(newNode) ;

                   }

         }

         public void print() {

                   if (this.root != null) {    // 有数据

                            this.root.printNode() ;   // 交给Node类处理

                   }

         }

}

public class LinkDemo {

         public static void main(String args[]) {

                   Link all = new Link() ;

                   all.add("货物A") ;

                   all.add("货物B") ;

                   all.add("货物C") ;

                   all.print() ;

         }

}

         以上就是一个最最最最最简单的链表操作,也包含了所有链表的基本操作形式。

1.3、开发可用链表

         之前的代码只能够说是满足了链表的基本形式要求,但是代码不可用。最核心的问题在于,所有的数据都是直接进行输出的,这在实际的工作上根本就无法去使用。但是通过之前的程序也可以发现一点问题:

· Node类是整个链表节点配置关系的核心部分,但是用户不需要知道Node类存在,但是在之前的程序,发现Node类用户可以直接去使用,不合法;

                   · Link类作为节点排列的操作类,一定要建立好根节点。

范例:首先修改程序的基本组成

class Link {         // 用户最需要关注的是这个类

         // Node类不希望被其它类使用,但是Link类要使用

         private class Node {     // 存储数据和配置节点关系

                   private String data ;       // 假设现在保存的是String

                   private Node next ;       // 下一个节点

                   public Node(String data) {     // 节点要有数据

                            this.data = data ; 

                   }

         }

         // **********************************

         private Node root ;        // 根节点

}

         而在本结构之中最重要的几个操作:增加、删除、查找、输出全部。

1.3.1、增加数据:public void add(数据类型 对象)

1、   首先需要在Link类里面增加一个add()方法;

2、   将传入的数据封装为一个Node类对象,封装的好处是可以进行节点的排列,此方法中的数据类型一定要与Node类之中data属性的类型相同;

class Link {         // 用户最需要关注的是这个类

         // Node类不希望被其它类使用,但是Link类要使用

         private class Node {     // 存储数据和配置节点关系

                   private String data ;       // 假设现在保存的是String

                   private Node next ;       // 下一个节点

                   public Node(String data) {     // 节点要有数据

                            this.data = data ; 

                   }

                   public void addNode(Node newNode) {

                            if (this.next == null) {  // 当前节点的下一个为null

                                     this.next = newNode ;

                            } else {

                                     this.next.addNode(newNode) ;

                            }

                   }

         }

         // **********************************

         private Node root ;        // 根节点

         public void add(String data){

                   if (data == null) {

                            return ;       // 保存的数据不为null

                   }

                   Node newNode = new Node(data) ;         // 将数据封装为节点

                   if (this.root == null) {   // 没有根节点

                            this.root = newNode     ;        // 新节点作为根节点

                   } else {       // 交给Node类进行处理

                            this.root.addNode(newNode) ;

                   }

         }

}

1.3.2、取得保存的对象个数:public int size()

         每当一个新数据保存成功之后,应该进行一个数量的修改。

1、   由于需要针对于所有的节点操作,那么首先应该在Link类里面增加一个count的属性;

         private int count ; // 统计个数

2、   在add()方法执行完毕之后表示增加成功,所以保存的个数要增加

                   this.count ++ ;     // 增加个数

3、   在Link类里面增加一个size()方法,此方法就返回count这个属性的内容;

         public int size() {

                   return this.count ;

         }

1.3.3、判断是否是空链表:public boolean isEmpty()

         如果说现在链表之中没有保存任何数据(root是null或者count为0),那么就应该返回true,否则返回false。

1、   在Link类之中增加isEmpty()方法

         public boolean isEmpty() {

                   return this.count == 0 ;

         }

         如果个数为0返回true,否则返回false。

1.3.4、清空链表:public void clean()

         整个链表就是靠着Link类中的root属性的,如果root的内容是null,那么链表一定都不会保存。

         public void clean() {

                   this.root = null ;   // 清空

                   this.count = 0 ;    // 归零

         }

1.3.5、取得指定索引位置的数据:public 数据类型 get(int index)


         索引号是动态生成的,那么这些节点的索引生成代码应该由Link类负责。

1、   首先在Link类之中增加一个用于标记索引的属性:foot

         private int foot ;   // 用于标记索引顺序

2、   在Link类之中增加一个get()方法,查询数据。

         public String get(int index) {

                   if (index > this.count) {

                            return null ;

                   }

                   this.foot = 0 ;       // 让脚标从0开始

                   return this.root.getNode(index) ;     }

3、   在Node类里面如果要进行查询,肯定要修改foot属性的内容,而幸运的是,在内部类之中可以方便的访问外部类的私有数据。

                   public String getNode(int index) {

                            if(index == Link.this.foot ++) {      // 符合查找索引

                                     return this.data ;   // 返回当前节点数据

                            } else {

                                     return this.next.getNode(index) ;

                            }                 }

1.3.6、判断指定数据是否存在:public boolean contains(数据类型 对象)


1、   首先需要在Link类里面增加一个contains()方法,这个方法里面要先判断查询的数据是否为null。

         public boolean contains(String data) {

                   if (data == null) {

                            return false ;        // 没有查询数据

                   }

                   return this.root.containsNode(data) ;

         }

2、   将查询过程交给Node类进行处理,就是将每一个节点进行比较,如果查询到了返回true,否则返回false。

                   public boolean containsNode(String data) {

                            if (data.equals(this.data)) {     // 查询数据与当前节点吻合

                                     return true ;

                            } else {

                                     if (this.next != null) {

                                               return this.next.containsNode(data) ;

                                     } else {

                                               return false ;

                                     }

                            }

                   }

1.3.7、删除数据:public void remove(数据类型 对象)

         如果要想进行数据的删除操作,必须考虑两种情况:

情况一:要删除的数据是根节点(应该由Link类完成,而且在Link类里面一定已经判断完了根节点)


情况二:要删除的节点不是根节点(Node类进行,至少从第二个节点开始)


1、   首先在Link类之中增加一个remove()方法,而删除数据之前应该执行一个查询,查询要删除的数据是否存在,如果不存在就没必要删除了。如果查询到数据存在,则需要在Link类里面判断要删除的数据是否是根节点数据。

         public void remove(String data) {

                   if(this.contains(data)) { // 查找到数据了

                            if (this.root.data.equals(data)) {

                                     this.root = this.root.next ;       // 改变根节点

                            } else {       // 要删除的不是根节点

                                     this.root.next.removeNode(this.root,data) ;

                            }

                            this.count -- ;

                   }

         }

2、   在Node类之中进行删除操作(现在是从第二个节点开始的判断)

                   public void removeNode(Node previous,String data) {

                            if(this.data.equals(data)) {      // 当前节点满足删除数据

                                     previous.next = this.next ;      // 空出当前节点

                            } else {       // 向下继续判断

                                     this.next.removeNode(this,data) ;

                            }

                   }

1.3.8、返回全部数据:public 数据类型 [] toArray()


1、   此对象数组所有节点都应该可以看见,那么只能够定义在Link类之中。

         private String [] retData ;       // 保存的对象数组

2、   在Link类之中增加一个toArray()方法,此方法一定是要返回retData这个属性内容,但是retData的内容应该交给Node类来处理,而开辟的大小应该是由count决定的;

         public String [] toArray() {

                   if(this.count == 0) {

                            return null ;          // 没数据

                   }

                   this.foot = 0 ;       // 通过foot设置数组索引

                   this.retData = new String [this.count] ;     // 开辟数组

                   this.root.toArrayNode() ;       // 交给Node类处理

                   return this.retData ;

         }

3、   在Node类之中增加一个toArrayNode()方法

                   public void toArrayNode() {

                            Link.this.retData[Link.this.foot ++] = this.data ;

                            if (this.next != null) {

                                     this.next.toArrayNode() ;

                            }

                   }

         所有的链表程序不要求你会写(会写最好了),但是这些方法的作用一定要知道。

1.4、保存其它对象

         以上的代码为了讲解方便,是采用了String数据类型操作的,但是在实际之中有可能需要操作的是用户定义的对象,那么如果要想将其修改为用户定义对象需要修改以下部分:

                   · 所有的数据类型应该是用户自定义对象;

· 查询数据和删除数据依靠的是String的equals()方法,这个方法的实际功能是对象比较,那么如果是用户对象,则对象所在的类一定要编写对象比较操作(compare());

第一步:定义自己的类,提供好对象比较方法

class Person {

         private String name ;

         private int age ;

         public Person(String name,int age) {

                   this.name = name ;

                   this.age = age ;

         }

         public boolean compare(Person person) {

                   if (this == person) {

                            return true ;

                   }

                   if (person == null) {

                            return false ;

                   }

                   if (this.name.equals(person.name) && this.age == person.age) {

                            return true ;

                   }

                   return false ;

         }

         public String getInfo() {

                   return "姓名:" + this.name + ",年龄:" + this.age ;

         }

}

第二步:修改Link类,此时保存的类型是Person,比较的方法是compare()

class Link {         // 用户最需要关注的是这个类

         // Node类不希望被其它类使用,但是Link类要使用

         private class Node {     // 存储数据和配置节点关系

                   private Person data ;     // 假设现在保存的是String

                   private Node next ;       // 下一个节点

                   public Node(Person data) {   // 节点要有数据

                            this.data = data ; 

                   }

                   public void addNode(Node newNode) {

                            if (this.next == null) {  // 当前节点的下一个为null

                                     this.next = newNode ;

                            } else {

                                     this.next.addNode(newNode) ;

                            }

                   }

                   public Person getNode(int index) {

                            if(index == Link.this.foot ++) {      // 符合查找索引

                                     return this.data ;   // 返回当前节点数据

                            } else {

                                     return this.next.getNode(index) ;

                            }

                   }

                   public boolean containsNode(Person data) {

                            if (data.compare(this.data)) { // 查询数据与当前节点吻合

                                     return true ;

                            } else {

                                     if (this.next != null) {

                                               return this.next.containsNode(data) ;

                                     } else {

                                               return false ;

                                     }

                            }

                   }

                   public void removeNode(Node previous,Person data) {

                            if(this.data.compare(data)) {  // 当前节点满足删除数据

                                     previous.next = this.next ;      // 空出当前节点

                            } else {       // 向下继续判断

                                     this.next.removeNode(this,data) ;

                            }

                   }

                   public void toArrayNode() {

                            Link.this.retData[Link.this.foot ++] = this.data ;

                            if (this.next != null) {

                                     this.next.toArrayNode() ;

                            }

                   }

         }

         // **********************************

         private Node root ;        // 根节点

         private int count ; // 统计个数

         private int foot ;   // 用于标记索引顺序

         private Person [] retData ;      // 保存的对象数组

         public void add(Person data){

                   if (data == null) {

                            return ;       // 保存的数据不为null

                   }

                   Node newNode = new Node(data) ;         // 将数据封装为节点

                   if (this.root == null) {   // 没有根节点

                            this.root = newNode     ;        // 新节点作为根节点

                   } else {       // 交给Node类进行处理

                            this.root.addNode(newNode) ;

                   }

                   this.count ++ ;     // 增加个数

         }

         public int size() {

                   return this.count ;

         }

         public boolean isEmpty() {

                   return this.count == 0 ;

         }

         public void clean() {

                   this.root = null ;   // 清空

                   this.count = 0 ;    // 归零

         }

         public Person get(int index) {

                   if (index > this.count) {

                            return null ;

                   }

                   this.foot = 0 ;       // 让脚标从0开始

                   return this.root.getNode(index) ;

         }

         public boolean contains(Person data) {

                   if (data == null) {

                            return false ;        // 没有查询数据

                   }

                   return this.root.containsNode(data) ;

         }

         public void remove(Person data) {

                   if(this.contains(data)) { // 查找到数据了

                            if (this.root.data.compare(data)) {

                                     this.root = this.root.next ;       // 改变根节点

                            } else {       // 要删除的不是根节点

                                     this.root.next.removeNode(this.root,data) ;

                            }

                            this.count -- ;

                   }

         }

         public Person [] toArray() {

                   if(this.count == 0) {

                            return null ;          // 没数据

                   }

                   this.foot = 0 ;       // 通过foot设置数组索引

                   this.retData = new Person [this.count] ;   // 开辟数组

                   this.root.toArrayNode() ;       // 交给Node类处理

                   return this.retData ;

         }

}

public class LinkDemo {

         public static void main(String args[]) {

                   Link all = new Link() ;

                   all.add(new Person("张三",20)) ;

                   all.add(new Person("李四",30)) ;

                   all.add(new Person("王五",50)) ;

                   Person temp [] = all.toArray() ;

                   for (int x = 0 ; x < temp.length ; x ++) {

                            System.out.println(temp[x].getInfo()) ;

                   }

                   System.out.println(all.contains(new Person("王五",50))) ;

         }

}

         对于此部分的内容会修改使用即可,不要求从无到有编写。




2、链表习题讲解_A

之所以不再使用对象数组而使用链表进行替代,很大一个关系在于,链表是一种动态的顺序结构,可以实现轻松的数据删除与查询功能,这一点是对象数组所不具备的功能。

0、   利用链表来修改之前的部门和雇员关系(一对多)

1、   多对多映射


         配置完关系之后,要求可以实现如下的功能:

                   · 可以根据一个管理员取得此管理员所在的所有管理员组,以及每个管理员组所具备的权限;

                   · 可以根据一个管理员组取得此管理员组下的所有管理员和权限信息;

                   · 可以根据一个权限取得具备此权限的所有管理员组,以及每个管理员组的管理员信息。

         那么到现在为止就可以清楚的发现,如果继续按照之前所学习的代码进行项目的编写,最麻烦的莫过于数据的统一问题,就拿链表为例,换一种数据类型就需要去修改链表结构,这种代码是不可使用的。所以后面的内容就是进行参数类型的统一。

         本程序只是一个简单的多对多映射。




3、链表习题讲解_B

多对多映射(能理解就理解)


 

         最麻烦的地方在于下载记录,此时的下载记录之中发现存在有多个数据,一个用户有多个下载记录,一个软件有多个下载记录。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值