设为首页 - 加入收藏 ASP站长网(Aspzz.Cn)- 科技、建站、经验、云计算、5G、大数据,站长网!
热搜: 手机 数据 公司
当前位置: 首页 > 服务器 > 安全 > 正文

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

发布时间:2021-04-01 11:52 所属栏目:53 来源:网络整理
导读:链表内部维护着首、尾哨兵两个内部节点(双端),作为链表的第一个和最后一个节点,新插入的节点总是处于这两个哨兵节点之间,用户也无法感知哨兵节点的存在。哨兵节点并不用于保存数据,其存在的主要目的是为了简化

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

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

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

{
    
     * 链表 头部哨兵节点
     * 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方法的时间复杂度?:

  可以看到,find方法会根据下标所处的大概位置决定开始遍历的方向(尽量减少迭代查询的次数),下标越接近头部或者尾部,查询次数越少。因此下标在靠近头部、尾部时效率较高,而在居中位置效率较低。

(编辑:ASP站长网)

网友评论
推荐文章
    热点阅读