链表概念及模拟(单链表的模拟)

链表的概念及结构

链表是一种 物理存储结构上非连续 存储结构,数据元素之间的 逻辑顺序 是通过 地址 来链接的

  • 如图所示:链表就像一节火车,结点就像一个个车厢,由中间的横线链接起来
    请添加图片描述

  • 而链表的结构也非常多样
    1.单向与双向
    2.带头与不带头
    3.循环与非循环
    排列组合后,共有八种链表结构,但我们着重要掌握的是 无头单向非循环 与 无头双向非循环 两种结构

无头单向非循环链表实现

1) 构建结点

private static class LinkNode {  
     int val;  
     LinkNode next;  
  
     LinkNode(int val) {  
          this.val = val;  
     }  
}

// head 指向第一个结点
private LinkNode head; 
  • 这里解释一下为什么无头链表还要多加一个head结点

这是为了更方便一些去调用这个链表
带头链表里面的“头”,里面的数据是无效的
无头链表的“头”,里面的数据是有效的

这么说可能会有一些绕口,但只需记得无头链表里面的头结点只是概念上的头,只是为了方便我们去调用这个链表。


2) 头插法

  • 创建一个结点,将该结点与原头结点单向连接,然后头结点改为该结点。
  • 即将新结点node 的 next域指向原头结点head,然后头结点head更改为node
//头插法  
public void addFirst(int data) {  
     LinkNode node = new LinkNode(data);  
     node.next = head;  
     head = node;  
}
  • 图解:
    在这里插入图片描述

  • 若链表为空时,这个代码是否依旧可以运行?
    答:可以运行

  • 图解:空链表head为空,node 的 next域置为空,node变成头结点
    ![[Pasted image 20231009234850.png]]


3) 尾插法

  • 该单链表没有尾指针,所以需要定义一个 cur结点 去找尾结点
  • 找到之后,将尾结点与新结点node 单向连接
  • 但是这有一个问题,如果链表为空,head已经为null了,cur.next就会发生空指针异常。所以我们还需要加一个判断条件防止这种情况
//尾插法  
public void addLast(int data){  
     LinkNode node = new LinkNode(data);  
     // 当链表为空,自行添加一个头结点
     if (head == null) {  
	    head = node;  
		return;
     }
     LinkNode cur = head;  
     while (cur.next != null) {  
        cur = cur.next;  
     }  
     cur.next = node;  
}
  • 图解:
    ![[Pasted image 20231010113519.png]]

4) 插入指定位置(第一个数据结点为0号下标)

  • index范围:[0,size)

  • 思路:

  1. 插入之前判断所插入的位置 是否合法。链表不像顺序表,不必注意插入是否需要扩容
  2. 找到要插入的位置,先将后面的结点与新结点node 单向连接 起来,再将其于前面的结点 单向连接
  • 图解:假设插入2下标位置,我们就要 找到 2 的前驱结点 1,即 cur 要走 index-1 步。比如图中:index=2,我们就要找到下标1,所以是2-1=1步
    ![[Pasted image 20231010114637.png]] ^a896b1
// 找index的前驱结点
private LinkNode findPrev(int index) {  
     LinkNode cur = head;  
     while (index - 1 != 0) {  
          cur = cur.next;  
          index--;  
     }  
     return cur;  
}
  • 找到之后,将新结点node与后继结点单向连接,再与前驱结点单向连接
//任意位置插入,第一个数据节点为0号下标  
public boolean addIndex(int index,int data){  
     if (index < 0 || index > size()) {  
          return false;  
     }  
     // 如果index=0,直接头插法即可  
     if (index == 0) {  
          addFirst(data);  
     }  
     // 如果index要插入链表最后,尾插法即可  
     if (index == size()) {  
          addLast(data);  
     }  
     LinkNode cur = findPrev(index);  
     LinkNode node = new LinkNode(data);  
	 // 插入操作
     node.next = cur.next;  
     cur.next = node;  
     return true;
}
  • 图解:
    ![[Pasted image 20231010122243.png]]

5) 查找关键字key是否在单链表当中

  • 思路:
  • 定义一个临时结点cur 用于遍历链表,寻找key
// 查找是否包含关键字key是否在单链表当中  
// 定义一个临时结点cur 用于遍历链表,寻找key
public boolean contains(int key){  
     LinkNode cur = head;  
     while (cur != null) {  
          if (cur.val == key) {  
               return true;  
          }  
          cur = cur.next;  
     }  
     return false;  
}
  • 关于关于cur 与 cur.next != null 的解释
    • cur != null 是指 cur 遍历了整个链表,最后cur 指向 null
  • 而cur.next != null 是指 cur 只是遍历了链表size-1个结点,最后cur 指向的是最后一个结点

6) 删除第一次出现关键字为key的结点

  • 思路:
  • 删除操作,直接将删除结点的前驱连接到它的后继即可
  • 用临时结点 cur 去遍历链表寻找key
public void remove(int key){  
     LinkNode cur = head;  
     while (cur.next != null) {  
          // cur.next为删除结点  
          if (cur.next.val == key) {  
               // cur为删除结点的前驱  
               cur.next = cur.next.next;  
               return;          }  
          cur = cur.next;  
     }  
     System.out.println("没有你要删除的数据!");  
}
  • 但是上述代码是用 cur.next 去寻找关键字key,这就意味着头结点是没有找的,所以还需要再加一个判断条件,用于删除头结点。
//删除第一次出现关键字为key的节点  
public void remove(int key){  
     if (head == null) {  
          // 一个结点都没有,无法删除!  
          return;  
     }  
  
     if (head.val == key) {  
          // 将头结点更改为第二个结点  
          head = head.next;  
          return;     
     }  
  
     LinkNode cur = head;  
     while (cur.next != null) {  
          // cur.next为删除结点  
          if (cur.next.val == key) {  
               // cur为删除结点的前驱  
               cur.next = cur.next.next;  
               return;          }  
          cur = cur.next;  
     }  
     System.out.println("没有你要删除的数据!");  
}
  • 图解:
    在这里插入图片描述

7) 删除所有值为key的节点

  • 思路:双指针

  • 定义两个指针,cur 为要删除的结点,prev 为 cur 的前驱结点

  • cur 用于去寻找要删除的结点(称为危险结点),prev 用于确保 prev指向的结点是不需要删除的结点(称为安全结点)

  • 比如 prev 是线头,cur 是剪刀,cur 剪掉不需要的线,剪一个线 线头连一个。正常情况下不可能所有线都要剪掉,所以 cur 会遇到不需要剪掉的线,此时再将这个 安全线 定义为新的线头。重复循环,直到危险结点全部剪掉为止

  • 但是这种思路,我们是从第二个结点开始找的,所以头结点就漏掉了。我们需要最后再判断一下头结点是否为key

//删除所有值为key的节点  
public void removeAllKey(int key){  
     LinkNode prev = head;  
     LinkNode cur = head.next;  
     while (cur != null) {  
          if (cur.val == key) {  
               prev.next = cur.next;  
               cur = cur.next;  
          }else {  
               prev = cur;  
               cur = cur.next;  
          }  
          // cur = cur.next  
          // 因为if-else语句里面都有cur = cur.next,所以可以精简一下。这
          // 里为了好理解,就不精简了
     }  
  
     if (head.val == key) {  
          head = head.next;  
     }  
}
  • 图解:
    ![[Pasted image 20231010150611.png]]

8) 得到单链表的长度

  • 思路:
    1.用临时结点遍历整个链表,再用一个计数器进行计数,返回计数器即可
    2.定义一个usedSize用于记录结点个数,直接返回usedSize即可

现使用第一个思路:

//得到单链表的长度  
public int size(){  
     LinkNode cur = head;  
     int size = 0;  
     while (cur != null) {  
          size++;  
          cur = cur.next;  
     }  
     return size;  
}

9) 打印单链表

  • 遍历链表依次打印
//打印单链表  
public void display(){  
     LinkNode cur = head;  
     while (cur != null) {  
          System.out.print(cur.val + " ");  
          cur = cur.next;  
     }  
     System.out.println();  
}

10) 清空列表

  • 遍历链表,将每一个结点置空
  • 光有一个 cur 去遍历,cur结点置空后,就拿不到后继结点,所以需要加上一个curNext 结点用于定位后继结点
public void clear(){  
     LinkNode cur = head;  
     while (cur != null) {  
          LinkNode curNext = cur.next;  
          cur.next = null;  
          cur = curNext;  
     }  
     head = null;  
}
``
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Prectie.RTE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值