持续学习合集--链表

链表

本篇文章也是我学习网课而总结的一篇博文,网课地址链接《玩转算法系列–玩转数据结构》

  • 链表是一种线性结构,真正的动态数据结构。
  • 数据存储是一种节点的形式。
  • 具有真正的动态机制不需要担心容量的问题。
  • 也丧失了随机访问的能力(并非绝对)

重要的组成部分–节点

一个Node中除了保存元素外还指向下一个Node;
在此处我们将它们作为私有内部类实现,使用者不用了解其内部结构只需要了解我们提供的方法。

private class Node{
    public E e;
    public 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();
    }
}

public Node next; 就是指向下一个节点的关键成员变量,如果是队尾则指向null。

成员变量

拥有一个头部节点指向目前链表结构的头部成员变量;
这里解释一个知识:

虚拟头节点
在链表中我们创造了多少个Node时,我们都拥有一个指向head的指向变量。
但是如果我们添加一个虚拟的头节点,当我们每次往头部添加Node时,头部节点没有前一个节点逻辑上会复杂些许(这个说法有点点争议),就可以使用这个虚拟头节点,使我们的逻辑较为轻便。
同时也因为存在虚拟头节点,所以类似于查询、修改、删除、赋值操作时所赋予的index,就是所对应的index。

/** 
* 虚拟头节点 
*/
private Node dummyHead;

/** 
* 长度 
*/
private int size;

增、删、改、查

由于链表是一组指向的结构,并且在成员变量中拥有头部的指向,因此操作头部时是很高效的(这与数组是刚好相反的);
链表中对元素的更改主要在于对指向的维护,避免破坏整个链表的结构。

  • 增加的方法:
    /**
     * 在链表的index(0-based)位置添加新的元素e
     * @param index
     * @param e
     */
    public void add(int index, E e){

        if(index < 0 || index > size){
            throw new IllegalArgumentException("Add failed. Illegal index.");
        }
       
        Node prev = dummyHead;

        for(int i = 0 ; i < index ; i ++){
            //循环获取index位置的
            prev = prev.next;
        }
        
        //Node node = new Node(e);
        //node.next = prev.next;
        //prev.next = node.next;
        //下面一行代码就可以代表上述三行代码
        prev.next = new Node(e, prev.next);
        size ++;
    }

    /**
     * 在链表头添加新的元素e
     * @param e
     */
    public void addFirst(E e){
        add(0, e);
    }

    /**
     * 在链表末尾添加新的元素e
     * @param e
     */
    public void addLast(E e){
        add(size, e);
    }
  • 查询的方法
    /**
     * 获得链表的第index(0-based)个位置的元素
     * @param index
     * @return
     */
    public E get(int index){

        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Get failed. Illegal index.");
        }

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++) {
            cur = cur.next;
        }
        return cur.e;
    }

    /**
     * 获得链表的第一个元素
     * @return
     */
    public E getFirst(){
        return get(0);
    }

    /**
     * 获得链表的最后一个元素
     * @return
     */
    public E getLast(){
        return get(size - 1);
    }
    
    /**
     * 查找链表中是否有元素e
     * @param e
     * @return
     */
    public boolean contains(E e){
        Node cur = dummyHead.next;
        while(cur != null){
            if(cur.e.equals(e)){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
  • 赋值的方法
    /**
     * 修改链表的第index(0-based)个位置的元素为e
     * @param index
     * @param e
     */
    public void set(int index, E e){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Set failed. Illegal index.");
        }

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++){
            cur = cur.next;
        }
        cur.e = e;
    }
  • 删除的方法:
    /**
     * 从链表中删除index(0-based)位置的元素, 返回删除的元素
     * @param index
     * @return
     */
    public E remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Remove failed. Index is illegal.");
        }

        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 --;

        return retNode.e;
    }

    /**
     * 从链表中删除第一个元素, 返回删除的元素
     * @return
     */
    public E removeFirst(){
        return remove(0);
    }

    /**
     * 从链表中删除最后一个元素, 返回删除的元素
     * @return
     */
    public E removeLast(){
        return remove(size - 1);
    }

    /**
     * 从链表中删除元素e
     * @param e
     */
    public void removeElement(E e){

        Node prev = dummyHead;
        while(prev.next != null){
            if(prev.next.e.equals(e)){
                break;
            }
            prev = prev.next;
        }

        if(prev.next != null){
            Node delNode = prev.next;
            prev.next = delNode.next;
            delNode.next = null;
        }
    }

时间复杂度分析

方法时间复杂度
addO(n)
addFirstO(1)
addLastO(n)
getO(n)
getFirstO(1)
getLastO(n)
setO(n)
containsO(n)
removeO(n)
removeFirstO(1)
removeLastO(n)
removeElementO(n)

总体而言:此链表的许多操作都是O(n)的时间复杂度,因此并不适合进行增删改查,但是链表中有一个特点,就是对头部操作的时间复杂度均为O(1),因此特别适合进行头部操作,对比数组链表不会造成大量的开销(数组扩/缩容时)。

小结:

链表的特点决定了其操作的特点,天然支持动态,是数组外一种很重要的数据结构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值