java对单向单向链表的操作

来源:互联网 发布:linux socket udp监听 编辑:程序博客网 时间:2024/06/10 05:52

概述:众所周知,数据对于数据的存储时连续的,也就是说在计算机的内存中是一个整体的、连续的、不间断的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. 原理1:如果root为null,则链表的长度为null

  2. 原理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方法;

  1. 查询有多次,但是每一次的查询都要将foot属性设置为0;
  2. 如果当前查询的索引大于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) 普通方法 修改某个元素