🔥博客主页: 小扳_-CSDN博客
❤感谢大家点赞👍收藏⭐评论✍
文章目录
1.0 双链表的说明
1.1 双链表 - 创建
1.2 双链表 - 根据索引查找节点
1.3 双链表 - 根据索引插入节点
1.4 双链表 - 头插节点
1.5 双链表 - 尾插
1.6 双链表 - 根据索引来删除节点
1.7 头删节点
1.8 尾删节点
1.9 实现迭代器循环
2.0 双链表完整的实现代码
3.0 环形双链表的说明
3.1 环形双链表 - 创建
3.2 环形双链表 - 头插节点
3.3 环形双链表 - 尾插节点
3.4 环形双链表 - 头删节点
3.5 环形双链表 - 尾删节点
3.6 环形链表 - 根据值来删除节点
4.0 环形双链表完整的实现代码
1.0 双链表的说明
双链表是一种数据结构,其中每个节点包含两个指针,一个指向前一个节点,一个指向后一个节点。双链表可以在任意位置插入或删除节点,而不需要像单链表那样遍历整个链表来找到需要操作的位置。双链表可以用于实现栈、队列、以及其他需要快速插入和删除操作的数据结构。由于每个节点包含两个指针,双链表的内存占用量通常比单链表更大。
1.1 双链表 - 创建
把双链表封装成一个类,类中还需要封装一个节点类 Node,该节点类的成员有指向前一个节点的变量 Node prev,值 value ,指向后一个节点的变量 Node next 。
代码如下:
public class MyDoubleLists{ private final Node hand; private final Node tail; private static class Node { public Node prev; public Node next; public int value; public Node(Node prev, int value, Node next) { this.prev = prev; this.next = next; this.value = value; } } public MyDoubleLists() { hand = new Node(null,0,null); tail = new Node(hand,-1,null); hand.next = tail; tail.prev = hand; } }
注意外部类中,还需要定义头节点,尾节点,再利用外部类的构造器中就可以完成对头尾节点的初始化了。内部类中的节点类建议用静态来修饰。
1.2 双链表 - 根据索引查找节点
由于这个方法可以为其他几个方法提供服务,因此,把这个方法独立出来。实现思路为:开始的节点应为 hand,循环终止条件为:p == tail 当指向尾节点的那一刻就该结束循环了。还要加一个变量 i = -1 ,每一次循环结束要进行 i++ ,当 i == index 时,找到了该索引的节点,返回该节点即可,若循环结束还是没找到,就得返回 null 。
代码如下:
//根据索引查找节点 private Node findNode(int index) { int i = -1; for (Node p = hand; p !=tail ; p = p.next,i++){ if (i == index) { return p; } } return null; }
这个方法一般不会对外开发,因此,加上 private 来修饰,当 i == -1 时,返回的时头节点,所以一开始的节点为 hand 。对外是索引从 0 开始才会存储值的,对于头节点存储什么值是不关心的。
1.3 双链表 - 根据索引插入节点
根据索引插入节点,提前需要准备好该节点的前一个节点 prev、该节点的后一个节点 next 。通过以上已经实现的方法来找到插入位置的前一个结点,prev = findNode(index - 1),后一个节点也就可以找到了,next = prev.next。
代码如下:
//根据索引插入节点 public void insert(int index, int value) { Node p = findNode(index - 1); if (p == null){ throw new RuntimeException("用索引访问失败"); } Node prev = p; Node next = prev.next; Node insertNode = new Node(prev, value,next); prev.next = insertNode; next.prev = insertNode; }
prev.next 指向新的节点,next.prev 指向上一个节点。新的节点的头节点的引用为 prev,尾节点为 next 。
1.4 双链表 - 头插节点
其实这个方法就比较简单了,这个就是根据索引 0 来插入节点的,就是一个索引等于0的特例,所以可以直接调用已经实现的 insert(0,int value) 方法。
代码如下:
//头插节点 public void addFirst(int value) { insert(0,value); }
1.5 双链表 - 尾插
对尾部的操作是双链表的一个很大的优势,对尾部的查询、增加节点、删除节点效率都很快,因为对尾节点是有记录的,就不用从头开始来查找尾部节点了,直接可以得到。
实现方法的思路为:需要找到插入的前一个节点,插入的后一个节点肯定是尾节点,所以,可以通过 prev = tail.prev,来找到插入的前一个节点。
代码如下:
//尾插节点 public void addLast(int value) { Node lats = tail; Node prev = lats.prev; Node addNode = new Node(prev,value,lats); prev.next = addNode; lats.prev = addNode; }
新的节点的前一个节点为 prev ,后节点为 tail,prev.next 与 tail.prev 就要更改为指向新的节点了。
1.6 双链表 - 根据索引来删除节点
先通过已经实现的 findNode() 方法来找到该索引的节点,还有找到删除该节点的前一个节点与后一个节点。
代码如下:
//根据索引来删除节点 public void remove(int index) { Node prev = findNode(index - 1); if (prev == null) { throw new RuntimeException("用索引来删除数据失败!"); } Node remove = prev.next; if (remove == tail) { throw new RuntimeException("用索引来删除数据失败!"); } Node next = remove.next; prev.next = next; next.prev = next; }
接着就可以将要删除的前一个节点指向要删除的节点后一个节点,但是考虑两种情况,第一种,当要删除的节点没找到,就要抛出异常了。第二种,当发现要删除的节点为 tail 时需要抛出异常,总不能自己把自己的尾巴 "切了吧" 然而不用考虑是否把头节点删除,因为要删除的节点总是拿不到头节点。
1.7 头删节点
当索引为0时,就是头删,是 remove(0) 方法的一个特例。可以直接来调用根据索引来删除节点的方法。
代码如下:
//头删节点 public void removeFirst() { remove(0); }
1.8 尾删节点
双链表尾删的效率是很高的,因为对尾节点是有记录的。
代码如下:
//尾删节点 public void removeLast() { Node remove = tail.prev; if (remove == hand){ throw new RuntimeException(); } Node prev = remove.prev; prev.next = tail; tail.prev = prev; }
需要找到三个节点,要删除的节点、删除节点的前一个节点、还有尾节点。需要注意的是,判断要删除的节点为头节点,需要抛出异常,总不能自己把自己的 " 头部 " 给切了吧。
1.9 实现迭代器循环
这个完成 Iterable 接口,创建新的该接口子类对象,重写两个方法即可。
代码如下:
//实现迭代器循环 @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { Node p = hand.next; @Override public boolean hasNext() { return p != tail; } @Override public Integer next() { int value = p.value; p = p.next; return value; } }; }
需要注意的是,头节点的值是不用在乎的,所以遍历开始为 hand.next ,循环结束条件为,p == tail,遍历到尾节点就要终止了。
2.0 双链表完整的实现代码
import java.util.Iterator; public class MyDoubleLists implements Iterable<Integer>{ private final Node hand; private final Node tail; private static class Node { public Node prev; public Node next; public int value; public Node(Node prev, int value, Node next) { this.prev = prev; this.next = next; this.value = value; } } public MyDoubleLists() { hand = new Node(null,0,null); tail = new Node(hand,-1,null); hand.next = tail; tail.prev = hand; } //根据索引查找节点 private Node findNode(int index) { int i = -1; for (Node p = hand; p !=tail ; p = p.next,i++){ if (i == index) { return p; } } return null; } //根据索引插入节点 public void insert(int index, int value) { Node p = findNode(index - 1); if (p == null){ throw new RuntimeException("用索引访问失败"); } Node prev = p; Node next = prev.next; Node insertNode = new Node(prev, value,next); prev.next = insertNode; next.prev = insertNode; } //头插节点 public void addFirst(int value) { insert(0,value); } //尾插节点 public void addLast(int value) { Node lats = tail; Node prev = lats.prev; Node addNode = new Node(prev,value,lats); prev.next = addNode; lats.prev = addNode; } //根据索引来删除节点 public void remove(int index) { Node prev = findNode(index - 1); if (prev == null) { throw new RuntimeException("用索引来删除数据失败!"); } Node remove = prev.next; if (remove == tail) { throw new RuntimeException("用索引来删除数据失败!"); } Node next = remove.next; prev.next = next; next.prev = next; } //头删节点 public void removeFirst() { remove(0); } //尾删节点 public void removeLast() { Node remove = tail.prev; if (remove == hand){ throw new RuntimeException(); } Node prev = remove.prev; prev.next = tail; tail.prev = prev; } //根据索引来查询数据 public int get(int index) { Node find = findNode(index); if (find == null){ throw new RuntimeException("根据该索引找不到数据"); } return find.value; } //实现迭代器循环 @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { Node p = hand.next; @Override public boolean hasNext() { return p != tail; } @Override public Integer next() { int value = p.value; p = p.next; return value; } }; } }
3.0 环形双链表的说明
环形双链表是一种数据结构,它类似于双向链表,但是链表的最后一个节点指向第一个节点,形成一个环形结构。简单来说,先对比与一般的双链表,环形双链表就是头尾节点都是同一个节点。
3.1 环形双链表 - 创建
把环形双链表看成一个对象,该类中需要封装一个节点的内部类,对外不开放的,需要用限制修饰符来修饰。外部类中的成员变为 sentinel ,即作为头节点,也作为尾节点。
代码如下:
public class MyAnnularDoubleList{ private final Node sentinel; private static class Node { public Node prev; public int value; public Node next; public Node(Node prev, int value, Node next) { this.prev = prev; this.value = value; this.next = next; } } public MyAnnularDoubleList() { sentinel = new Node(null,0,null); sentinel.prev = sentinel; sentinel.next = sentinel; } }
在创建环形双链表时,在用无参构造器就可以先把 sentinel 初始化了。
3.2 环形双链表 - 头插节点
根据 next = sentinel.next 就可以得到插入点的下一个节点,就可以把新节点的指向的前一个节点来指向 sntinel,新节点的指向后一个节点来指向 next 。
这里需要考虑一种情况,假设,该环形双链表中就只有一个 sentinel 节点,以上阐述的思路还能不能用呢?答案时可以的,如果实在理解不了的话,可以把一个 sentinel 节点,想象成两个 sentinel 节点,效果都是一样的,都是指向自己嘛。
代码如下:
//头插节点 public void addFirst(int value) { Node hand = sentinel; Node tail = sentinel.next; Node addNode = new Node(hand,value,tail); hand.next = addNode; tail.prev = addNode; }
3.3 环形双链表 - 尾插节点
根据 prev = sentinel.prev 来找到插入节点的前一个的节点,插入节点的后一个节点就是 sentinel ,新节点的指向前的节点为 prev,新节点的指向后的节点为 sentinel ,接着 prev.next 指向新节点,sentinel.prev 指向新节点。
代码如下:
//尾插节点 public void addLast(int value) { Node prev = sentinel.prev; Node next = sentinel; Node addNode = new Node(prev,value,next); prev.next = addNode; next.prev = addNode; }
同理,不需要额外考虑当节点只有一个时,以上代码依旧可以实现尾插节点。
3.4 环形双链表 - 头删节点
根据 remove = sentinel.next ,可以得到需要删除的节点,再由 next = remove.next,得到删除节点的后一个节点,这样三个节点后已知了,santinel.next 指向 next ,next.prev 指向sentinel ,剩下的没有被引用的节点(对象),会被 JVM 自动回收。
代码如下:
//头删节点 public void removeFirst() { Node remove = sentinel.next; if (remove == sentinel) { throw new RuntimeException("删除失败!"); } Node next = remove.next; sentinel.next = next; next.prev = sentinel; }
需要注意的是,当删除的节点为 sentinel 需要抛出异常,但是个人感觉不加判断也可以,sentinel 根本不会被删除,即使只有 sentinel 一直被删除。
3.5 环形双链表 - 尾删节点
根据 remove = sentinel.prev 得到需要删除的节点,通过 prev = remove.prev 得到需要删除的前一个节点,prev.next 指向 sentinel,sentinel.prev 指向 prev 即可。
代码如下:
//尾删节点 public void removeLast() { Node remove = sentinel.prev; Node prev = remove.prev; prev.next = sentinel; sentinel.prev = prev; }
同样的,当删除的节点为 sentinel 需要抛出异常,但是个人感觉不加判断也可以,sentinel 根本不会被删除,即使只有 sentinel 一直被删除。
3.6 环形链表 - 根据值来删除节点
先独立一个方法,根据值来查询节点,一开始的循环节点为 sentinel.next ,对于 sentinel 里面的值是什么根本不在乎的。循环终止条件为:p == sentinel 已经转完一圈了。找到就返回该节点,找不到就返回 null 。删除的原理是一样的,得先找到三个节点,然后把前后节点直接关联起来。
代码如下:
//根据值来删除节点 private Node findValue (int value) { for (Node p = sentinel.next; p != sentinel; p = p.next) { if (p.value == value) { return p; } } return null; } public void removeValue (int value) { Node remove = findValue(value); if (remove == null) { throw new RuntimeException("找不到该数据来删除相对应的节点"); } Node prev = remove.prev; Node next = remove.next; prev.next = next; next.prev = prev; }
需要注意的是,当接收到的节点为 null 时,需要抛出异常,找不到对应该值的节点。
4.0 环形双链表完整的实现代码
import java.util.Iterator; public class MyAnnularDoubleList implements Iterable<Integer>{ private final Node sentinel; private static class Node { public Node prev; public int value; public Node next; public Node(Node prev, int value, Node next) { this.prev = prev; this.value = value; this.next = next; } } public MyAnnularDoubleList() { sentinel = new Node(null,0,null); sentinel.prev = sentinel; sentinel.next = sentinel; } //头插节点 public void addFirst(int value) { Node hand = sentinel; Node tail = sentinel.next; Node addNode = new Node(hand,value,tail); hand.next = addNode; tail.prev = addNode; } //尾插节点 public void addLast(int value) { Node prev = sentinel.prev; Node next = sentinel; Node addNode = new Node(prev,value,next); prev.next = addNode; next.prev = addNode; } //头删节点 public void removeFirst() { Node remove = sentinel.next; if (remove == sentinel) { throw new RuntimeException("删除失败!"); } Node next = remove.next; sentinel.next = next; next.prev = sentinel; } //尾删节点 public void removeLast() { Node remove = sentinel.prev; Node prev = remove.prev; prev.next = sentinel; sentinel.prev = prev; } //根据值来删除节点 private Node findValue (int value) { for (Node p = sentinel.next; p != sentinel; p = p.next) { if (p.value == value) { return p; } } return null; } public void removeValue (int value) { Node remove = findValue(value); if (remove == null) { throw new RuntimeException("找不到该数据来删除相对应的节点"); } Node prev = remove.prev; Node next = remove.next; prev.next = next; next.prev = prev; } //实现迭代器 @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { Node p = sentinel.next; @Override public boolean hasNext() { return p != sentinel; } @Override public Integer next() { int value = p.value; p = p.next; return value; } }; } }