加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 电商 > 正文

自己动手实现java数据结构(二) 链表

发布时间:2021-04-04 12:12:50 所属栏目:电商 来源:网络整理
导读:1.链表介绍 前面我们已经介绍了向量,向量是基于数组进行数据存储的 线性表 。今天,要介绍的是线性表的另一种实现方式--- 链表 。 链表和向量都是线性表,从使用者的角度上依然被视为一个线性的列表结构。但是,链表内部存储数据的方式却和向量大不相同:链

  链表内部维护着首、尾哨兵两个内部节点(双端),作为链表的第一个和最后一个节点,新插入的节点总是处于这两个哨兵节点之间,用户也无法感知哨兵节点的存在。哨兵节点并不用于保存数据,其存在的主要目的是为了简化边界条件的逻辑。

  举个例子:在节点插入时,必须判断当前是否第一个节点,来决定是否需要建立和之前节点的联系;在节点删除时,也必须判断自己的左右节点是否存在,避免空指针异常。首尾哨兵节点的引入使得任何情况下,节点插入,删除时都可以确保其左右节点一定存在,从而避免大量的异常判断。

  可以看到,哨兵节点的引入简化了代码在边界条件时的各种判断逻辑。这提高了执行效率,更重要的是降低了复杂性,使代码变得更容易理解,也更可靠。

{
    
     * 链表 头部哨兵节点
     * private Node<E> first;
    
     * 链表 尾部哨兵节点
     *  last;
    
     * 链表 逻辑大小
     *  size;

    public LinkedList() {
        this.first = new Node<>();
        this.last = ();

        :::将首尾哨兵节点 进行连接
        this.first.right = .last;
        this.last.left = .first;

        :::初始化size
        this.size = 0;
    }
}

3.3?较为简单的?size(),isEmpty(),indexOf(),contains()方法实现:

    @Override
     size() {
        return .size;
    }

    @Override
     isEmpty() {
        return (this.size == 0);
    }

    @Override
     indexOf(E e) {
        :::当前节点 = 列表头部哨兵
        Node<E> currentNode = if(e != null){
            :::如果不是查询null元素

            :::遍历列表
            for(int i=0; i<this.size; i++){
                :::不断切换当前节点 ==> "当前节点 = 当前节点的右节点"
                currentNode = currentNode.right;
                :::如果满足要求(注意: equals顺序不能反,否则可能会有currentNode.data为空,出现空指针异常)
                if(e.equals(currentNode.data)){
                    :::返回下标
                    return i;
                }
            }
        }else{
            :::如果是查询null元素

            :::如果是null元素
                if(currentNode.data == ){
                     i;
                }
            }
        }
        :::遍历列表未找到相等的元素,返回特殊值"-1"代表未找到
        return -1;
    }

    @Override
     contains(E e) {
        :::复用indexOf方法,如果返回-1代表不存在;反之,则代表存在
        return (indexOf(e) != -1);
    }

链表 indexOf、contains方法的时间复杂度:  

  indexOf方法的实现和向量类似,是通过一次循环遍历来进行查询的,从头部节点不停地跳转到下一个节点,直到发现目标节点或者到达尾部哨兵节点。

  因此indexOf方法、contains方法的时间复杂度都是O(n)。

3.4 链表增删改查接口实现

3.4.1?下标越界检查

  链表基于下标的增删改查操作都需要进行下标的越界检查。这里优化了前面"向量篇"博客中提到的缺陷,针对不同的错误,会抛出不同类型的自定义异常,使客户端可以进行更细致的异常处理。

   
     * 插入时,下标越界检查
     *  index 下标值
     void rangeCheckForAdd( index){
        :::如果大于size的值,抛出异常
        :::注意:插入时,允许插入线性表的末尾,因此(index == size)是合法的
        if(index > .size){
            throw new OutOfIndexBoundException("index error  index=" + index + " size=" + .size) ;
        }
        if(index < 0new NegativeOfIndexException("index error  index=" + index + " size=" + .size) ;
        }
    }

    
     * 下标越界检查
     * void rangeCheck(:::如果大于等于size的值,抛出异常
        if(index >= .size) ;
        }
    }

  同时,链表基于下标的操作都需要先查询出下标对应的内部节点,通过操纵对应的内部节点来进行相关操作。因此需要抽象出一个通用的查询方法。

   
     * 返回下标对应的 内部节点
     * private Node<E> find(:::要求调用该方法前,已经进行了下标越界校验
        :::出于效率的考虑,不进行重复校验

        :::判断下标在当前列表的大概位置(前半段 or 后半段)尽量减少遍历查询的次数
        if(index < this.size/2:::下标位于前半段

            :::从头部结点出发,进行遍历(从左到右)
            Node<E> currentNode = .first.right;
            :::迭代(index)次
            int i=0; i<index; i++){
                currentNode = currentNode.right;
            }

             currentNode;
        }:::下标位于后半段

            :::从尾部结点出发,进行遍历(从右到左)
            Node<E> currentNode = .last.left;
            :::迭代(size-index-1)次
            int i=index; i<size-1; i++ currentNode.left;
            }
             currentNode;
        }
    }

find方法的时间复杂度?:

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读