• 数据结构:链表(1)


    顺序表的优缺点

    缺点:

    1.插入数据必须移动其他数据,最坏情况下,就是插入到0位置。时间复杂度O(N)

    2.删除数据必须移动其他数据,最坏情况下,就是删除0位置。时间复杂度O(N)

    3.扩容之后,有可能会浪费空间。

    优点:

    在给定下标进行查找的时候

    总结:顺序表比较适合进行给定下路查找的场景

    🆗链表可以完美解决顺序表的缺点


    链表是什么?

    一个链表其实就是一辆火车

    火车的每个车厢相当于一个节点,链表节点长这样

    其中,数据域存储数据 

    而火车之间要有挂钩链接,这就需要next域出马了,next读取下一个节点的地址并把它记录在next域里面,下面这个图也是单向链表,一条路走到黑

    分类

    有六种分类

    常见的是红色圈起来的两个

    带头的长啥样?头部存的数据是无效的,跟这个链表没有关系,这个头节点的值永远不会改变

    循环的?

    双向的?两边都能走

    链表结构特点:物理上不一定是连续的,但逻辑上是连续的


    单向链表

    手搓一个

    初始化

    实现方法有下面这些

    1. //IList.java 接口
    2. public interface IList {
    3. //头插法
    4. void addFirst(int data);
    5. //尾插法
    6. void addLast(int data);
    7. //任意位置插入,第一个数据节点为0号下标
    8. void addIndex(int index,int data);
    9. //查找是否包含关键字key是否在单链表当中
    10. boolean contains(int key);
    11. //删除第一次出现关键字为key的节点
    12. void remove(int key);
    13. //删除所有值为key的节点
    14. void removeAllKey(int key);
    15. //得到单链表的长度
    16. int size();
    17. void clear();
    18. void display();
    19. }

    再新建一个模块重写这些方法

    1. public class MySingleList implements IList{
    2. @Override
    3. public void addFirst(int data) {
    4. }
    5. @Override
    6. public void addLast(int data) {
    7. }
    8. @Override
    9. public void addIndex(int index, int data) {
    10. }
    11. @Override
    12. public boolean contains(int key) {
    13. return false;
    14. }
    15. @Override
    16. public void remove(int key) {
    17. }
    18. @Override
    19. public void removeAllKey(int key) {
    20. }
    21. @Override
    22. public int size() {
    23. return 0;
    24. }
    25. @Override
    26. public void clear() {
    27. }
    28. @Override
    29. public void display() {
    30. }
    31. }

    ok!接下来我们要设置一个节点的内部类把要用到的属性加上

    1. static class ListNode{
    2. public int val;
    3. public ListNode next; //定义next域
    4. //节点类构造方法
    5. public ListNode(int val){
    6. this.val = val;
    7. }
    8. }

    为什么是static呢?因为每个节点都一样,都由数据域和next域组成的,直接static定义共同特点。

    那第三行的ListNode next 怎么理解呢?因为next指向的是下一个节点的地址,而下一个节点的类型也是ListNode,所以这里的next一定是ListNode类型


    好接下来定义一个链表的头

    public ListNode head;

    注意不要放到内部类里面,因为head是链表的成员而不是节点的成员

    给几个节点赋上值

    1.如何让node1的下一个节点是node2

    node1.next = node2 //表示node2地址0x46给到node1

    node1.next = node2;
    node2.next = node3;
    node3.next = node4;
    node4.next = node5;

    node1是局部变量,整个过程走完之后node1会被回收怎么办?

    两种方法:

    第一种,把刚刚那个函数类型改成ListNode,函数返回node1

    第二种,我们不是定义了head了吗,直接在函数末尾写上

    this.head = node1;    这样就行了

    检验结果


    遍历列表

    1.怎么从一个节点走到下一个节点

    2.怎么判断所有节点都遍历完了

    一个循环,循环终止条件是head为空,我们按照这个思想把display代码完成

    这个代码有一个问题,遍历完列表之后head为空,整个头节点地址和值全都无了

    解决办法是创一个中间变量cur把head锁死,遍历完之后cur为空了,但是head本质上不会变

    1. @Override
    2. public void display() {
    3. ListNode cur = this.head;
    4. while(cur != null){
    5. System.out.println(cur.val + " ");
    6. cur = cur.next;
    7. }
    8. }

    有这个代码的基础,链表长度和包含函数也不在话下

    1. @Override
    2. public int size() {
    3. int count = 0;
    4. ListNode cur = this.head;
    5. while(cur != null){
    6. cur = cur.next;
    7. count++;
    8. }
    9. return count;
    10. }
    11. @Override
    12. public boolean contains(int key) {
    13. ListNode cur = this.head;
    14. while(cur != null){
    15. if(cur.val == key){
    16. return true;
    17. }
    18. cur = cur.next;
    19. }
    20. return false;
    21. }

    头插法

    链表里面有节点

    三步走

    1. 实例化一个节点

    2.改变插入节点的next

    3.改变head

    无节点

    直接把head定义成这个节点

    1. @Override
    2. public void addFirst(int data) {
    3. //实例化节点对象
    4. ListNode node = new ListNode(data);
    5. //无节点
    6. if(this.head == null){
    7. this.head = node;
    8. //有节点
    9. }else{
    10. node.next = this.head;
    11. this.head = node;
    12. }
    13. }

    注意这个插入是倒叙插入 


    尾插法

    指的是将待插入的节点存放到链表的最后一个位置

    步骤:

    1.实例化一个节点

    2.把cur走到最后一个节点

    3.cur.next = node; //插入下一个节点

    1. @Override
    2. public void addLast(int data) {
    3. ListNode node = new ListNode(data);
    4. ListNode cur = this.head;
    5. if (this.head == null){
    6. this.head = node;
    7. }else {
    8. //找到尾巴
    9. while (cur.next != null) {
    10. cur = cur.next;
    11. }
    12. //cur现在指向最后一个节点
    13. cur.next = node;
    14. }
    15. }

    如果想要让cur停在最后一个节点的位置 cur.next != null;

    如果想把整个链表都遍历完,就是cur != null;

    注意:尾插法时间复杂度是O(N),因为要遍历完整个链表n个节点后才插入元素


    在任意位置插入节点

    1.让cur走index-1步

    2.node.next = cur.next;

    cur.next = node;

    在插入一个节点的时候,一定要先绑定后面这个节点

    1. @Override
    2. public void addIndex(int index, int data) {
    3. if(index < 0 || index > size()){
    4. return;
    5. }
    6. if(index == size()){
    7. addLast(data);
    8. return;
    9. }
    10. ListNode cur = searchPrev(index);
    11. ListNode node = new ListNode(data);
    12. node.next = cur.next;
    13. cur.next = node;
    14. }
    15. private ListNode searchPrev(int index){
    16. ListNode cur = this.head;
    17. int count = 0;
    18. while(count!=index-1){
    19. cur = cur.next;
    20. count++;
    21. }
    22. return cur;
    23. }

    删除元素

    现在我们想删除第三个节点,把第三个节点设成del

    在一个循环里面让cur遍历整个链表

    判断 cur.next.val == key,找到cur的位置

    后面cur = cur.next

    1. @Override
    2. public void remove(int key) {
    3. if(this.head == null){
    4. //一个节点都没有,删除不了
    5. return;
    6. }
    7. //删除头节点
    8. if(this.head.val == key){
    9. this.head = this.head.next;
    10. return;
    11. }
    12. //1、找到前驱
    13. ListNode cur = findPrev(key);
    14. //2、判断返回值是否为空?
    15. if(cur == null){
    16. System.out.println("没有你要删除的数字");
    17. return;
    18. }
    19. //3、删除
    20. ListNode del = cur.next;
    21. cur.next = del.next;
    22. }
    23. //找到关键字key的前一个节点的地址
    24. private ListNode findPrev(int key){
    25. ListNode cur = this.head;
    26. while(cur.next!=null){
    27. if(cur.next.val == key){
    28. return cur;
    29. }
    30. }
    31. return null;
    32. }

    删除所有值为key的节点 

    比如下面删除所有值为23的节点

    定义cur为当前要删除的节点

    prev为当前要删除的节点的前驱

    cur找到了就直接把prev的next地址改成cur下一个节点的地址,cur继续往下遍历

    while(cur!=null){

            if(cur.val == key){

                    prev.next = cur.next; //删除操作

                    cur = cur.next;

            }else{

                    prev = cur;

                    cur = cur.next

            }

    }

    1. @Override
    2. public void removeAllKey(int key) {
    3. if(this.head == null){
    4. return;
    5. }
    6. ListNode prev = head;
    7. ListNode cur = head.next;
    8. while(cur!=null){
    9. if(cur.val == key){
    10. prev.next = cur.next;
    11. cur = cur.next;
    12. //不等于就往下走
    13. }else{
    14. prev = cur;
    15. cur = cur.next;
    16. }
    17. }
    18. if(head.val == key){
    19. head = head.next;
    20. }
    21. }

    清空链表

    把一个节点的val = null, next = null

    再把链表的head = null

    注意:这里的val = null不是直接让你 cur.val = null,拿第一个节点来说,如果你把值置为空,那么0x46被替换成null,cur找不到下一个节点的地址0x46了

    我们可以拿一个变量curNext来记录cur下一个节点的位置,把cur.next 置为空之后,cur往后挪到curNext的位置,继续置空下一个节点

    注意别忘了头节点,整个遍历完之后还要把头节点也置为空

    1. @Override
    2. public void clear() {
    3. ListNode cur = this.head;
    4. while(cur!=null){
    5. ListNode curNext = cur.next;
    6. cur.next = null;
    7. cur = curNext;
    8. }
    9. head = null;
    10. }

  • 相关阅读:
    2022辽宁省赛(A,B,D,E,F,G,I,M)
    关于java.lang.NoSuchMethodException异常解决问题
    React 注意事项
    ArcGIS学习(十六)基于交通网络的城市情景分析
    CMake 安装升级更高版本
    模仿蜘蛛工作原理 苏黎世联邦理工学院研发牛油果机器人可在雨林树冠穿行
    Python3 VSCode 配置
    MySQL使用SHOW PROCESSLIST 详解
    2023东莞理工大学考研介绍
    【AI】PyTorch入门(五):构建神经模型
  • 原文地址:https://blog.csdn.net/hellg/article/details/133321460