java对单向单向链表的操作

本文详细介绍了单向链表的基本概念、数据结构定义及核心操作方法,包括添加、查询、修改元素等,并提供了完整的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方法名称类型备注
1public void add(Object obj)普通方法向链表之中添加数
2public int size()普通方法取得链表的长度
3public boolean isEmpty()普通方法判断链表是否为空
4public boolean contains(Object obj)普通方法判断链表是否存在某个元素
3public Object get(int index)普通方法根据链表索引查询某个元素
3public void set(int index,Object obj)普通方法修改某个元素
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值