美文网首页
LinkedList分析及实现

LinkedList分析及实现

作者: VictorBXv | 来源:发表于2017-11-17 01:22 被阅读0次

    一. 简单总结

    1. LinkedList底层实现方式是双向链表
    2. 链表的优缺点(当然是和数组比较而言)
      • 单链表的优缺点
        - 优点:增加,删除某一个元素快
        - 缺点:查询慢,自然修改某个元素的值也慢了(修改的前提是先要找到这个节点)
        • 单链表使用场景举例:MessageQueue(每次只能添加一个message,而且不能从尾节点访问,所以只能使用单链表,因此Message就是这里的Node了,因此Message类里面一定有一个next指针)
      • 双链表针对单链表查询慢的缺点,通过增加指针来通过折半查找提高查询效率,典型的通过空间换时间的做法。

    3.注意事项:

    • 在用java实现链表这种数据结构时,一定要严格区分对对象的引用对象之间的区别,即在Object object=new Object();中,另一个对象指向左边的object和指向右边的new Object()是完全不一样的。

    二. 单链表增删实现

    1. 添加一个节点
      构造节点
      • 特别注意:这二个成员变量是必须的,但在具体的使用场景中,还会有其他成员变量,而且不一定是 T data这种形式,也不一定叫Node,比如MessageQueue里面装的Message就是这里的Node,所以不要执迷于名字表象,而在于刻画出的数据模型本质,这才是数据结构要研究的东西
         class Node<T>{
           T data;//我们要存储的数据
           Node<T>  next;//指向下一个节点的引用(指针)     
         }
    
    增加一个节点
    //这二步的顺序一定不能错
    s.next=p.next;//第一步,将a[i+1]的地址赋值给s的指针域
    p.next=s;//第二步,将s的地址复制给a[i]的指针域
    
    1. 删除一个节点


      删除一个节点
    p.next=p.next.next;
    

    三. 双链表增删实现

    1. 添加一个节点
      情形一:


      增加一个节点
    s.prior=p;          //第一步,将p节点的地址赋值给s的前驱
    s.next=p.next;      //第二步,将p.next的节点的地址赋值给s的后继
    p.next.prior=s;     //第三步,将的s的地址赋值给p.next节点的前驱
    p.next=s;           //第四步,将s的地址赋值给p节点的后继
    

    情形二:


    增加一个节点
        s.prior=p.prior;   //第一步
        s.next=p;          //第二步
        p.prior.next=s;    //第三步
        p.prior=s;         //第四步
    

    注意到:情形一和情形二的第三步和第四步的顺序刚好的相反的,而且这两步的顺序一定不能错。

    这四步之间有严格的前后关系,不能轻易替换,当然还有另一种顺序,这里是实际开发中用的最多的写法,但思想都是一样的,这里的思路是
    (1) 先将新节点s的前驱和后继填满(即赋值),这就是第一第二步;
    (2) 再将s的地址分别赋值给p以及p.next节点,这就是第三第四步;

    • 特别注意:
      在添加一个节点的时候, 第一第二步一般是最先做的,这个前后顺序可以变,但是第三第四步的顺序一定不能变
    1. 删除一个节点


      删除一个节点
    p.prior.next=p.next; //第一步
    p.next.prior=p.prior; //第二步
    

    四. LinkedList源码手写实现(主要写增删改查操作)

    /**
     * linkedList底层实现方式:双链单循环表
     * 对linkedList实现增删改查操作
     */
    public class LinkedList<E> {
    /**
     * 对于LinkedList来说,三个重要的参数:这三个重要参数就是LinkedList的属性--->成员变量
     * 第一个:头节点
     * 第二个:尾节点
     *       其实,只要有一个头节点就可以拿到LinkedList的所有元素了,
     *       但是因为是双链表,可以通过尾节点利用二分查找提高效率,典型的用空间换时间
     * 
     * 第三个:长度
     * 这个属性是给调用者使用的,对LinkedList内部来说意义并不大
     */
    //头节点
    private Node<E> first;
    //尾节点
    private Node<E> last;
    //长度
    private int size;
    
    //空参构造方法
    public LinkedList() {
    }
    
    //如果传进来的是一个集合,就相当于把集合中的每个元素加入到LinkedList中
    public LinkedList(Collection<? extends E>/*E或者E的子类*/ c) {
        addAll(c);
    }
    
    /**
     * 添加集合
     *
     * @param c 要添加的集合
     */
    private boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
    
    /**
     * 在指定位置添加集合
     *
     * @param index 指定位置
     * @param c     要添加的集合
     */
    private boolean addAll(int index, Collection<? extends E> c) {
        if (index > size || index < 0) {
            throw new IndexOutOfBoundsException();
        }
        //先将其转换成数组,然后再一个个连起来
        Object[] a = c.toArray();
        int s = a.length;
        if (s == 0) {//表明集合的长度为0,没有什么要添加的
            return false;
        }
    
        Node<E> front, rear;
        //在最后的位置添加一个节点
         Node<E> pred, succ;
        //边界检测
        if (index == size) {
            //如果插入位置是size,则在头节点前面插入,否则在获取index处的节点插入
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.pre;
        }
    
    
        //将这些节点连起来
        for (Object o : a) {
            @SuppressWarnings("unchecked")
            E e = (E) o;
            Node<E> newNode = new Node<>(pred, null, e);
            if (pred == null) {
                first = newNode;
            } else {
                pred.next = newNode;
            }
            pred = newNode;
    
            //收尾处理
            if (succ == null) {
                last = pred;
            } else {
                pred.next = succ;
                succ.pre = pred;
            }
        }
        size += s;
        return true;
    }
    
    /**
     * 添加一个元素
     *
     * @param e
     * @return
     */
    public boolean add(E e) {
        //添加一个元素相当于在链表的尾部加入一个元素
        linkLast(e);
        return true;
    }
    
    /**
     * 在index的位置添加一个节点
     *
     * @param index 指定位置
     * @param e     要添加的值
     * @return 是否添加成功
     */
    public boolean add(int index, E e) {
        if (index < 0 || index > size) {
            return false;
        }
        //插入的位置刚好是尾节点,实际上size的位置是没有节点的,就相当于在尾部新添加一个元素
        if (index == size) {
            linkLast(e);
        } else {
            //在任意位置新添加一个节点,首先拿到index位置的节点,
            // 在这个节点之前插入一个元素,然后所有的节点“后移”一个位置
            //因此还需要得到index位置之前的节点
            //1.目标位置
            Node<E> target = node(index);
            //2.获取index前面的节点
            Node<E> prev = target.pre;
            //3.构造新的节点
            //4.将新的节点添加到index的位置
            //添加的第一第二步
            Node<E> newNode = new Node<>(prev, target, e);
    
             //第三步
            if (prev == null) {
                //表明target就是头节点,相当于在头节点前面在添加一个节点
                first = newNode;
            } else {
                prev.next = newNode;
            }
            //第四步
          // prev = newNode;//这种写法是错的,
          // 在java里面一定要弄清楚 `对对象的引用` 与 `对象` 之间的区别
         target.pre = newNode;
           
            //5.长度增加
            size++;
        }
        return true;
    }
    
    public E remove(int index) {
        if (index < 0 || index > size) {
            throw new NoSuchElementException();
        }
        Node<E> target = node(index);
        return unlinkNode(target);
    }
    
    private E unlinkNode(Node<E> p) {
      //        p.pre.next = p.next;
    //        p.next.pre = p.pre;
    
        Node<E> prev = p.pre;
        Node<E> next = p.next;
    
        if (prev == null) {
            //等价于删除头节点
            first = p.next;
        } else {
            prev.next = p.next;
        }
    
        if (next == null) {
            //等价于删除尾节点
            last = p.pre;
        } else {
            next.pre = p.pre;
        }
        return p.data;
    }
    
    /**
     * 获取指定位置的元素
     *
     * @param index
     * @return
     */
    public E get(int index) {
        if (index < 0 || index >= size) {
            return null;
        }
        return node(index).data;
    }
    
     private static final String TAG = "LinkedList";
    
    /**
     * 获取指定位置的节点
     * @param index
     * @return
     */
    private Node<E> node(int index) {
        //表明位于链表的前半部分
        if (index < (size >> 1)) {
            //如果位于链表的前半部分,则从头节点开始查找
            Node<E> node = first;
            //这里index代表要循环的次数,即从first节点开始往下找,需要找几次才能找到,
            // 因为从前往后数,first的index永远等于0
            //同理从后向前数,last的index永远为0
            //index表示指定位置的节点和头节点之间有几个节点,有几个节点就要通过指针向下找几次
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
            return node;
        } else {
            //从后往前查找
            Node<E> node = last;
            for (int i = size - 1; i > index; i--) {
                node = node.pre;
            }
            return node;
        }
    }
    
    private void linkLast(E e) {
        Node<E> l = last;
        Node<E> newNode = new Node<>(l, null, e);
        //如果最后一个节点为空,表明是一个空链,当前添加的元素是第一个元素
        if (l == null) {
            first = newNode;
        } else {
            l.next = newNode;
        }
        last = newNode;
        size++;
    }
    
    public int size() {
        return size;
    }
    
    
    //构造一个节点node,这个node是linkedList的数据单元
    private static class Node<E> {
        private Node<E> pre;//前驱
        private Node<E> next;//后继
        private E data;
    
        public Node(Node<E> pre, Node<E> next, E e) {
            this.pre = pre;
            this.next = next;
            this.data = e;
        }
    
        @Override
        public String toString() {
            return "Node{" +
                    "data=" + data +
                    '}';
           }
        }
     }
    

    相关文章

      网友评论

          本文标题:LinkedList分析及实现

          本文链接:https://www.haomeiwen.com/subject/kltamxtx.html