概述:众所周知,数据对于数据的存储时连续的,也就是说在计算机的内存中是一个整体的、连续的、不间断的ADT数据结构。伴随的问题也会随之出现,这样其实对于内存的动态分配是不灵活的。而链表具备这个优点。因此链表对于数据的插入和删除是方便的,但是对于数据的查询是麻烦的。因为需要遍历链表,而对于链表的遍历确实极度的麻烦。
1 单向链表的定义
链表主要用来存储引用类型的数据。其结构可以由下图清楚的表示:
链表结点的定义
class Node{ // 链表中保存的数据 public Object obj; // 下一个结点的应用 public Node next; // 该结点中保存的数据 public Node(Object obj){ this.obj=obj; } public void setNext(Node next){ this.next=next; } public Node getNext(){ return this.next; } public Object getObj(){ return this.obj; } }
链表的设置和取出
/** * 对链表设置数据和取出数据 */ public class LinkDemo{ public void static main(String[] args){ //设置数据 Node root= new Node("链表头结点"); Node n1=new Node("链表1"); Node n2=new Node("链表2"); Node n3=new Node("链表3"); // 维护链表的关系 root.setNext(n1); n1.setNext(n2); n2.setNext(n3); // 取出链表的数据 Node currentNode=root;// 创建当前结点对象,将根结点赋值给当前结点 // 判断当前结点是不是为null,如果当前结点为null,则终止循环,否则继续输出 while(currentNode != null){ // 打印出当前结点的数据,然后修改当前结点的引用 System.out.println(currentNode.getObj()); // 将下一个结点设置为当前结点 currentNode=currentNode.getNext(); } } }
结果测试如下:
使用递归的方式优化输出:
/** *对链表设置数据和取出数据 */ public class LinkDemo{ public void static main(String[] args){ //设置数据 Node root= new Node("链表头结点"); Node n1=new Node("链表1"); Node n2=new Node("链表2"); Node n3=new Node("链表3"); // 维护链表的关系 root.setNext(n1); n1.setNext(n2); n2.setNext(n3); // 取出链表的数据 Node currentNode=root;// 创建当前结点对象,将根结点赋值给当前结点 // 判断当前结点是不是为null,如果当前结点为null,则终止循环,否则继续输出 print(root); } public static print(Node node){ //递归的结束条件 if(node == null){ return; }else{ System.out.println(node.getObj()); print(node.getNext()); } } }
在实际的开发中,我们希望对于数据的保存和输出应该是由如下的形式出现的:
public class LinkDemo3{ public static void main(){ Link link=new Link(); link.add("A"); link.add("B"); link.add("C"); link.add("D"); // 展示所有的数据 link.print(); } }
此时我们希望让Node结点类来负责对结点的操作,而Link类类负责对数据的操作。
// 节点类对象 class Node{ // 结点中的数据 public Object obj; // 结点的下一个引用 public Node next; // 结点中保存数据 public Node(Object obj){ return this.obj=obj; } public Node getNext(){ return this.next; } public void setNext(Node next){ this.next=next; } public Object getObj(){ return this.obj; } // 添加结点 public void addNode(Node node){ // 对于添加结点来说,首先判断头节点是否存在下一个的引用,如果为null,此时就可以添加 // 第一次调用该方法,this表示的就是Link.root对象 if(this.next==null){ //为空,那么将该结点挂到头结点的下一个引用上 this.next=node; }else{ //头节点的下一个引用不为空,那么应该是将当前结点的下一个引用为此node结点 //递归循环 this.next.addNode(node); } } // 展示所有的结点 public void printNode(){ System.out.println(this.obj); if(this.next!=null){ // 如果当前结点对于下一个结点的引用不为空,那么当前结点对象递归调用打印方法。 // 当前结点的下一个引用this.next this.next.printNode(); } } } // 对链表的操作 class Link{ // 初始化结点对象,即头结点 public Node root; // 添加数据 public void add(Object obj){ //创建当前结点对象,然后保存数据 Node currentNode=new Node(obj); // 如果要添加数据,就要首先判断根结点是否为空 if(root==null){ //头节点为空,则将创建的当前结点赋值给根结点 root=currentNode; }else{ //如果头结点不为空,此时应该由结点自己来判断 //头节点不为空值,要保存的数据应该在之后的结点上,调用添加结点的方法 root.addNode(currentNode); } } // 展示所有的数据 public void print(){ if(root!=null){ root.printNode(); } } }
当我们创建结点对象的时候,对于保存数据和输出输出,我们每次都会判断根节点是不是空值。这个很重要。当前结点不是根节点这个应该注意到。进一步的优化,注意到我们不希望调用者直接操作结点对象,单纯的Node类会被直接操作,这样不符合Java的封装思想,我们可以使用内部类,并且直接对Node内部类私有化
// 对链表的操作 class Link{ // 初始化结点对象,即头结点 public Node root; // 添加数据 public void add(Object obj){ //创建当前结点对象,然后保存数据 Node currentNode=new Node(obj); // 如果要添加数据,就要首先判断根结点是否为空 if(root==null){ //头节点为空,则将创建的当前结点赋值给根结点 root=currentNode; }else{ //如果头结点不为空,此时应该由结点自己来判断 //头节点不为空值,要保存的数据应该在之后的结点上,调用添加结点的方法 root.addNode(currentNode); } } // 展示所有的数据 public void print(){ if(root!=null){ root.printNode(); } } // 节点类对象 private class Node{ // 结点中的数据 public Object obj; // 结点的下一个引用 public Node next; // 结点中保存数据 public Node(Object obj){ return this.obj=obj; } // 添加结点 public void addNode(Node node){ // 对于添加结点来说,首先判断头节点是否存在下一个的引用,如果为null,此时就可以添加 // 第一次调用该方法,this表示的就是Link.root对象 if(this.next==null){ //为空,那么将该结点挂到头结点的下一个引用上 this.next=node; }else{ //头节点的下一个引用不为空,那么应该是将当前结点的下一个引用为此node结点 //递归循环 this.next.addNode(node); } } // 展示所有的结点 public void printNode(){ System.out.println(this.obj); if(this.next!=null){ // 如果当前结点对于下一个结点的引用不为空,那么当前结点对象递归调用打印方法。 // 当前结点的下一个引用this.next this.next.printNode(); } } } }
确定链表的数据结构
class Link { // 需要结点对象 private Node root;//根节点对象 // 结点对象 //***************内部类******************* private class Node { private Object obj;//结点中保存的数据 private Node next;//下一个结点的引用 // 结点中的数据 public Node(Object obj) { this.obj = obj; } } //***************内部类******************* }
添加数据 public void add(Object obj);
class Link { // 需要结点对象 private Node root;//根节点对象 // 结点对象 //***************内部类******************* private class Node { private Object obj;//结点中保存的数据 private Node next;//下一个结点的引用 // 结点中的数据 public Node(Object obj) { this.obj = obj; } //结点的添加 public void addNode(Node node){ if(this.next==null){ //把当前结点赋值给this.next this.next=node; }else{ //添加一个结点 this.next.addNode(node); } } } //***************内部类******************* public void add(Object obj){ //对于数据为null,是可以保存的,在这里我假设数据为null是不可以保存的 if(obj==null){ return; } Node node=new Node(obj); if(this.root==null){ root=node; }else{ //注意:Link类负责根节点的维护和结点的创建,对于结点的具体操作,应该是Node类操作 this.root.addNode(node); } } }
获取链表的长度 public int size();
每一次的数据保存都需要长度加1.因此我们在Link类中添加组成员变量size.保存时让它自增运算;
class Link { // 需要结点对象 private Node root;//根节点对象 private int size;//链表的长度 // 结点对象 //***************内部类******************* private class Node { private Object obj;//结点中保存的数据 private Node next;//下一个结点的引用 // 结点中的数据 public Node(Object obj) { this.obj = obj; } //结点的添加 public void addNode(Node node){ if(this.next==null){ //把当前结点赋值给this.next this.next=node; }else{ //添加一个结点 this.next.addNode(node); } } } //***************内部类******************* /**数据的添加*/ public void add(Object obj){ //对于数据为null,是可以保存的,在这里我假设数据为null是不可以保存的 if(obj==null){ return; } Node node=new Node(obj); if(this.root==null){ root=node; }else{ //注意:Link类负责根节点的维护和结点的创建,对于结点的具体操作,应该是Node类操作 this.root.addNode(node); } this.size++;//长度自增运算 } /** *链表的长度 */ public int size(){ return this.size; } }
判断链表是否为null,public boolean isEmpty();
-
原理1:如果root为null,则链表的长度为null
-
原理2:如果链表的size==0.则链表的长度为空
class Link { // 需要结点对象 private Node root;//根节点对象 private int size;//链表的长度 // 结点对象 //***************内部类******************* private class Node { private Object obj;//结点中保存的数据 private Node next;//下一个结点的引用 // 结点中的数据 public Node(Object obj) { this.obj = obj; } //结点的添加 public void addNode(Node node){ if(this.next==null){ //把当前结点赋值给this.next this.next=node; }else{ //添加一个结点 this.next.addNode(node); } } } //***************内部类******************* /**数据的添加*/ public void add(Object obj){ //对于数据为null,是可以保存的,在这里我假设数据为null是不可以保存的 if(obj==null){ return; } Node node=new Node(obj); if(this.root==null){ root=node; }else{ //注意:Link类负责根节点的维护和结点的创建,对于结点的具体操作,应该是Node类操作 this.root.addNode(node); } this.size++;//长度自增运算 } /** *链表的长度 */ public int size(){ return this.size; } /** *判读链表是否为空 */ public boolean isEmpty(){ return this.size==0?true:flase; } }
判断链表中是否存在某个元素 public boolean contains(Object obj);
对象的匹配可以使用equals()方法,但是对于自定义对象而言,需要写compare()方法来自定义匹配结果。
class Link { // 需要结点对象 private Node root;//根节点对象 private int size;//链表的长度 public void add(Object obj) { // 创建结点对象,并且保存数据 Node newNode = new Node(obj); if (root == null) { root = newNode; } else { //此时根节点不为空,需要添加结点 root.addNode(newNode); } this.size++;//每次保存数据,链表自增 } /** * 链表的长度 * @return */ public int size(){ return this.size; } /** * 判断链表是否为空 * @return */ public boolean isEmpty(){ return this.size==0?true:false; } public boolean contains(Object obj){ if(obj==null || this.root==null){ return false; }else { // 此时的判断应该交给Node结点完成 return root.containsNode(obj); } } public void print() { if (root != null) { root.printNode(); } } private class Node { private Object obj;//结点中保存的数据 private Node next;//下一个结点的引用 // 结点中的数据 public Node(Object obj) { this.obj = obj; } // 结点的保存 public void addNode(Node node) { if (this.next == null) {//当前结点的下一个结点为空,此时就可以添加结点 this.next = node; } else { //否则,此时应该循环递归添加结点 // 当前结点对象应该添加新的结点 this.next.addNode(node); } } // 输出一个结点 public void printNode() { //打印数据 System.out.println(this.obj); if (this.next != null) { this.next.printNode();//递归调用输出 } } /** * 判断链表中受否包含某个元素 * @param obj * @return */ public boolean containsNode(Object obj) { if(this.next==null){ return false; }else { // 链表结点不为空。此时要判断元素是否匹配 if(this.obj.equals(obj)){ return true; }else { return this.next.containsNode(obj); } } } } }
根据索引查询元素
然后定义get方法;
- 查询有多次,但是每一次的查询都要将foot属性设置为0;
- 如果当前查询的索引大于Link类的编号size.此时查询不到
public Object get(int index){ //如果当前要查询的索引大于链表的额size.那么直接返回null if(index>this.size){ return null; } // 每一的查询都要将foot从0开始 this.foot=0; // 此后交给Node结点来判断 return this.root.getNode(index); }
getNode(index)的实现
public Object getNode(int index) { //外部类Link调用内部对象this.foot,外部类直接对内部类的访问 // 如果当前结点的编号自增==当前索引 if(Link.this.foot++==index){ // 返回数据 return this.obj; }else { return this.next.getNode(index); } }
修改链表元素,和上述的查询实现基本一致 public void set(int index,Object obj);
public void set(int index,Object obj){ if(index>this.size){ return; } this.foot=0; this.root.setNode(index,obj); } public void setNode(int index, Object obj) { if(Link.this.foot++==index){ this.obj=obj;//数据的设置 }else { this.next.setNode(index,obj); } }
总结
NO | 方法名称 | 类型 | 备注 |
---|---|---|---|
1 | public void add(Object obj) | 普通方法 | 向链表之中添加数 |
2 | public int size() | 普通方法 | 取得链表的长度 |
3 | public boolean isEmpty() | 普通方法 | 判断链表是否为空 |
4 | public boolean contains(Object obj) | 普通方法 | 判断链表是否存在某个元素 |
3 | public Object get(int index) | 普通方法 | 根据链表索引查询某个元素 |
3 | public void set(int index,Object obj) | 普通方法 | 修改某个元素 |