• java对单向单向链表的操作


    概述:众所周知,数据对于数据的存储时连续的,也就是说在计算机的内存中是一个整体的、连续的、不间断的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) 普通方法 修改某个元素
  • 相关阅读:
    velocity masterplate 第一个程序的运行
    java 关于集合框架
    java 关于>>>位运算
    QCA wifi驱动强制为HT40
    Linux内核调试方法的总结(转载)
    移动端利用webkitbox水平垂直居中
    Under Construction to Beta
    网站自动备份
    writely 邀请?
    Google Carlendar coming ?
  • 原文地址:https://www.cnblogs.com/gosaint/p/8242925.html
Copyright © 2020-2023  润新知