• 数据结构(2)-链表


    填补那些模棱两可的后知后觉

    什么是链表

    链表是一种用于存储数据集合的数据结构,他是最简单的动态数据结构。上一篇我们虽显然实现了一个简单的动态数组。单这仅仅是面向使用者而言,其实数组的底层还是维护的是一个静态的数组,我们只是简单的通过拷贝的当时实现容量的增减,但是!!!链表 则是真正意义上的动态数据结构。

    链表的优点

    • 真正的动态数据结构,不需要处理固定容量的问题
    • 能够在常数时间内扩展容量

    链表的缺点

    • 随机访问效率较低
    • 链表中需要维护一份指针引用,相对浪费内存

    链表与数组的简单对比

    • 数组最用用于索引有语意的情况,其最大特点就是支持快速的查询和修改
    • 链表不适用于索引有语意的情况,其最大的特点则是动态,能快读插入和删除数据

    实现一个自己的链表类

    同样,我们和上一篇数组的介绍中,我们来完成一个自已的链表,支持最简单的增删该查功能。

    public class LinkedList<E> {
    
        private class Node{
            private E e;
            private Node next;//指向下一个
    
            public Node(E e,Node next){
                this.e = e;
                this.next = next;
            }
    
            public Node(E e){
                this(e,null);
            }
    
            public Node(){
                this(null,null);
            }
    
            @Override
            public String toString() {
                return e.toString();
            }
    
        }
    
        private Node dummyHead;//虚拟头节点
    
        private int size;//元素个数
    
        public LinkedList(){
            dummyHead = new Node();
            size = 0;
        }
    
        //获取链表中的元素个数
        public int getSize(){
            return size;
        }
    
        //判断链表是否为空
        public boolean isEmpty(){
            return size == 0;
        }
    
        /**
         * 在指定索引处添加元素
         * @param index
         * @param e
         */
        public void add(int index,E e){
            if(index < 0 || index > size){
                throw new IllegalArgumentException("Add failed. Illegal index.");
            }
            //利用虚拟头节点,将添加索引处的前方元素全部前移一位
            Node prve = dummyHead;
    
            for (int i = 0; i < index; i++) {
                //将原元素的指针指向原来的位置
                prve = prve.next;
            }
            prve.next = new Node(e,prve.next);
            size++;
        }
    
        /**
         * 新增头节点
         */
        public void addFirst(E e){
    //        Node node = new Node(e);
    //        node.next = dummyHead;
    //        dummyHead = node;
    
            dummyHead = new Node(e,dummyHead);
        }
    
        /**
         * 在结尾处增加元素
         * @param e
         */
        public void addLast(E e){
            this.add(size,e);
        }
    
        public E get(int index){
            if(index < 0 || index > size){
                throw new IllegalArgumentException("Add failed. Illegal index.");
            }
    
            Node cur = dummyHead.next;
            for (int i = 0; i < size; i++) {
                cur = cur.next;
            }
    
            return cur.e;
        }
    
        //获取链表的第一个元素
        public E getFirst(){
            //可以直接返回虚拟头节点的指向的下一个元素
            return dummyHead.next.e;
        }
    
        //获取链表的最后一个元素
        public E getLast(){
            return this.get(size -1);
        }
    
        //修改链表中指定位置的元素
        public void set(int index ,E e){
            //思路与get方法类似,找到改元素,直接替换即可
            if(index < 0 || index > size){
                throw new IllegalArgumentException("Add failed. Illegal index.");
            }
    
            Node cur = dummyHead.next;
            for (int i = 0; i < size; i++) {
                cur = cur.next;
            }
            cur.e = e;
        }
    
        //查找链表中是否包含某个元素e
        public boolean contains(E e){
            //从第一个元素开始遍历,然后进行匹配
            Node cur = dummyHead.next;
            while (cur != null){
                if(cur.e.equals(e)){
                    return true;
                }
                cur = cur.next;
            }
    
            return false;
        }
    
        //删除指定位置的元素
        public void remove(int index){
            if(index < 0 || index > size){
                throw new IllegalArgumentException("Add failed. Illegal index.");
            }
            //首先定位此元素,与修改或查询一致,将改元素置空,然后将改元素原来的前后元素进行指向
            Node prev = dummyHead;
            for (int i = 0; i < index; i++) {
                prev = prev.next ;
            }
            //查询到了元素
            Node retNode = prev.next;
            //将前后元素进行关联
            prev.next = retNode.next;
            //直接指向空
            retNode.next = null;
            size --;
        }
    
        //删除链表中的第一个元素
        public void removeFirst(){
            //将虚拟头节点指向第二个元素即可
            dummyHead.next = dummyHead.next.next;
            size--;
        }
    
        // 从链表中删除最后一个元素, 返回删除的元素
        public void removeLast() {
             remove(size - 1);
        }
    
        @Override
        public String toString() {
            StringBuilder res = new StringBuilder();
    
            Node cur = dummyHead.next;
            while (cur != null) {
                res.append(cur + "->");
                cur = cur.next;
            }
            res.append("NULL");
    
            return res.toString();
        }
    }
    

    链表中虚拟头节点的作用

    因为第一个元素(真头节点)是没有前序节点的。当我们在做操作时都需要对头节点进行单独的判断,这样一来的话,真头节点的逻辑就有空。所以为了方便,我们设置一个虚拟的头节点,来屏蔽真头节点的特殊性。

    简单复杂度分析

    我们从链表的操作中可以很容易的看出,对于增删改查这几个操作的复杂度都是O(n)的,但是如果我们只是对链表头进行增/删/查的操作的话,那么它的复杂度就是O(1)的,这里也可以看出来我们的链表适合干的事情了..

  • 相关阅读:
    一个长串由一个字串循环构成
    区间边界 张贴海报的可见性
    summary
    分区本质 从磁盘物理层面优化查询 分区操作的锁表
    全局变量在反汇编中是怎么体现的
    MathType怎么打定积分竖线
    定积分换元法洛必达法则求极限
    静态链表的插入和删除
    Git恢复之前版本的两种方法reset、revert
    服务去耦合
  • 原文地址:https://www.cnblogs.com/wujiwen/p/9595258.html
Copyright © 2020-2023  润新知