03链表

1、链表

1.1、概念

数据存储在内存中一个个节点中,这些节点的内存地址不连续,是随机分散在内存中的,有头节点和尾节点,可以认为头节点的索引为0,头节点存储着下一个节点,也就是索引为1的节点的内存地址,索引为1的节点又存储下一个节点的内存地址,以此类推,尾节点中存储的内存地址为null。

1.2、常见链表
  • 单向链表

在这里插入图片描述

  • 双向链表

在这里插入图片描述

  • 单向循环链表

    在单向链表的基础上,尾节点的next指向头节点

  • 双向循环链表

    在双向链表的基础上,头节点的prev指向尾节点,尾节点的next指向头节点

2、单向链表

2.1、获取元素

在链表中,要获取元素,首先要得到元素所在的节点对象,因此必须要有相对应的获取节点对象的方法

    private Node<E> node(int index){
        rangeCheck(index);
        Node<E> node = first;
        for (int i = 0; i < index; i++){
            node = node.next;
        }
        return node;
    }

first中存储的是第一个节点的内存地址,要想获得第一个节点,就需要进行一次.next操作,要想获得第二个节点,就需要进行两次,编写for循环来获得想要得到的节点位置。

     public E get(int index){
        return node(index).element;
     }

获取了该节点后,可以很轻松的获取到其中的元素。

2.2、改变元素
     public E set(int index, E element){
         Node<E> node = node(index);
         E old = node.element;
         node.element = element;
         return old;
     }

与获取元素的方式一样,同样是得到该节点,然后改变这个节点的元素。

2.3、添加元素
    public void add(int index, E element){
        rangeCheckForAdd(index);
        if ( index == 0){
            //如果为0,则说明添加到第一个节点
            first = new Node<>(element, first);
        }else {
            Node<E> preNode = node(index - 1);
            preNode.next = new Node<>(element, preNode.next);
        }
        size++;
    }

添加元素分为两种情况:

  • 往第一个位置添加元素

在这里插入图片描述

只需要让first的箭头指向要添加的节点,再让要添加的节点的next指向之前的第一个节点即可

在这里插入图片描述

  • 往其他位置添加元素

在这里插入图片描述

需要获取到前一个节点,将前一个节点的next赋值给要添加的节点的next,再将前一个节点的next指向要添加的节点即可。

在这里插入图片描述

2.4、删除元素
    public E remove(int index){
        rangeCheck(index);
        Node<E> node = first;
        if (index == 0){
            //如果index==0,就说明该节点为第一个节点
            first = first.next;
        }else {
            Node<E> preNode = node(index - 1);
            node = preNode.next;
            preNode.next = node.next;
        }
        size--;
        return node.element;
    }

删除元素还是分为两种情况

  • 删除首元素

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z3YwUONT-1593258815852)(/1593161795876.png)]

    直接让first指向第二个节点即可

在这里插入图片描述

  • 删除其他位置的元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dzCQ4oxI-1593258815854)(/1593161994390.png)]

先获取前一个节点,通过前一个节点获取要删除的节点,再将要删除节点的next赋值给前一个节点。

3、双向链表

3.1、与单向链表的区别

双向链表与单向链表的区别就是多了一个指向前一个节点的属性prev,并且在first的基础上有了last指向最后一个节点,这样做可以提高效率,如果要检索前半部分的元素,从first开始遍历即可,如果要检索后半部分的元素,则从last开始,效率提升了一倍。

在JDK中自带的LinkedList也是双向链表

3.2、获取元素

正如上面所说,可以根据索引的位置判断从first找起还是从last找起。

    private Node<E> node(int index){
        rangeCheck(index);
        //先判断这个节点的位置,如果在后半段则从last找起,前半段从first找起
        Node<E> node = null;
        if (index > (size >> 2)){
            node = last;
            for (int i = size - 1; i > index; i--){
                node = node.pre;
            }
        }else {
            node = first;
            for (int i = 0; i < index; i++){
                node = node.next;
            }
        }
        return node;
    }
3.3、添加元素
    public void add(int index, E element){
        rangeCheckForAdd(index);
        //获得要插入位置的原节点,也就是插入节点的下一个节点
        if (index == size){//当链表中没有元素或者往最后添加元素时
            Node<E> oldLast = last;
            last = new Node<E>(element, null, last);
            if (oldLast == null){
                first = last;
            }else {
                oldLast.next = last;
            }

        }else {
            Node<E> next = node(index);
            Node<E> pre = next.pre;
            Node<E> node = new Node<>(element,next,pre);
            if (pre == null){
                first = node;
            }else {
                pre.next = node;
            }
            next.pre = node;

        }
        size++;
    }

分为两种情况

  • 往链表的最后一位添加元素

    首先获取之前的last,然后让last指向要添加的节点,最后让之前的last的next指向要添加的节点。

    不过要注意的是如果是往第一个位置添加元素,也就意味着这个元素是链表中的第一个元素,此时需要让first=last**(此时last已经指向了新添加的节点)**

  • 往其他位置添加元素

    首先获取原来位置的节点,通过该节点获取前一个节点,再将这两个节点赋值给要添加的节点的pre和next,最后再让前一个节点的next指向要添加的节点,让原来位置节点的pre指向要添加的节点即可。

    需要注意的是如果前一个节点为null,也就意味着往第一个位置添加元素,因此这时候需要first指向要添加的节点。

3.4、删除元素
    public E remove(int index){
        rangeCheck(index);
        Node<E> node = node(index);
        Node<E> pre = node.pre;
        Node<E> next = node.next;
        if (pre == null){
            first = next;
        }else {
            pre.next = next;
        }
        if (next == null){
            last = pre;
        }else {
            next.pre = pre;
        }
        size--;
        return node.element;
    }
  1. 首先还是先获得要删除的节点以及前后两个节点
  2. 先不考虑删除头节点和尾节点,如果要删除一个节点,只需要让前一个节点的next指向要删除节点的下一个节点,让后一个节点的pre指向要删除节点的前一个节点。
  3. 再考虑特殊情况,删除头节点,头节点没有next,因此让first指向头节点的下一个节点,后面的操作与之前共用。
  4. 考虑删除尾节点,让last指向尾节点的前一个节点,后面的操作与之前共用。

5、单向循环链表

单向循环链表主要在添加和删除元素上面跟单向链表有一些区别

5.1、添加
    public void add(int index, E element){
        rangeCheckForAdd(index);
        Node<E> newNode = new Node<>(element, first);
        if ( index == 0){
            //如果为0,则说明添加到第一个节点
            if (size == 0){
                newNode.next = newNode;
                first = newNode;
            }else {
                Node<E> last = node(size - 1);
                first = newNode;
                last.next = first;
            }
        }else {
            Node<E> preNode = node(index - 1);
            newNode.next = preNode.next;
            preNode.next = newNode;
        }
        size++;
    }

只有往第一个位置添加元素时才与单向链表有区别,往其他位置添加元素并没有区别。

当往第一个位置添加元素时需要判断是否是第一个节点,如果是,则让该节点的next指向自身,如果不是,则获取最后一个节点,让最后一个节点的next指向要添加的节点。

5.2、删除元素
    public E remove(int index){
        rangeCheck(index);
        Node<E> node = first;
        if (index == 0){
            //如果index==0,就说明该节点为第一个节点
            if (size == 1){
                first = null;
            }else {
                Node<E> last = node(size - 1);
                first = first.next;
                last.next = first;
            }
        }else {
            Node<E> preNode = node(index - 1);
            node = preNode.next;
            preNode.next = node.next;
        }
        size--;
        return node.element;

只有删除第一个节点时才与单向链表有区别。

需要让first指向要删除节点的下一个节点,让最后一个节点的next指向新的first即可。需要注意的是,如果链表中只剩下一个元素,需要另外处理,只需要让first指向null即可。这是因为最后一个节点也是第一个节点,如果让first指向下一个节点也就相当于指向自身,该节点依旧不会被删除。

6、双向循环链表

6.1、添加元素
    public void add(int index, E element){
        rangeCheckForAdd(index);
        //获得要插入位置的原节点,也就是插入节点的下一个节点
        if (index == size){//当链表中没有元素或者往最后添加元素时
            Node<E> oldLast = last;
            Node<E> newLast = new Node<E>(element, first, oldLast);
            if (oldLast == null){
                newLast.pre = newLast;
                newLast.next = newLast;
                first = newLast;
                last = first;
            }else {
                oldLast.next = newLast;
                first.pre = newLast;
                last = newLast;
            }

        }else {
            Node<E> next = node(index);
            Node<E> pre = next.pre;
            Node<E> node = new Node<>(element,next,pre);
            next.pre = node;
            pre.next = node;
            if (pre == last){
                first = node;
                last.next = first;
            }
        }
        size++;
    }

添加元素时主要分为两种情况

  • 往末尾添加

    让之前尾节点的next指向要添加的节点,让头节点的prev指向要添加的节点,再让last指向要添加的节点。

    **注意:**当要添加的节点是链表中的第一个节点时,该节点的pre以及next都指向其自身,并且first和last也都指向该节点。

  • 往其他位置添加

    大部分与双向链表一样,唯一的区别就是当往第一个位置添加元素时需要让最后一个节点的next指向该节点。

6.2、删除元素
    public E remove(int index){
        rangeCheck(index);
        Node<E> node = node(index);
        if (size == 1){
            first = null;
            last = null;
        } else {
            Node<E> pre = node.pre;
            Node<E> next = node.next;
            pre.next = next;
            next.pre = pre;
            if (pre == last){
                first = next;
            }
            if (next == first){
                last = pre;
            }
        }
        size--;
        return node.element;
    }

  • 先考虑最常见的情况,获得要删除节点的前后节点,改变前后节点的next和prev指向,即可成功删除元素。
  • 当要删除的元素在头节点时,在之前的基础上,让first指向要删除节点的后一个节点即可。
  • 当要删除节点在尾节点时,在之前的基础上,让last指向要删除节点的前一个节点即可。
  • 需要注意的是,如果size为零,则需要让first和last都指向null才能删除最后一个节点,原因与上面单向循环链表所说的一样。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值