五种List集合的简单实现
- 一、数组形式
- 二、单向链表形式
- 三、含哨兵节点的单向链表形式
- 四、含哨兵节点的双向链表形式
- 五、含哨兵节点的环形链表形式
本文是对不同形式List集合的增删改查实现,仅是对学习过程进行记录
一、数组形式
关键点:
- 有三个成员变量:集合对象array、元素个数size、数组长度capacity
- 在扩容时,将旧array中的数据拷贝到新array对象中
- 新增先处理add(int index, int data)方法,考虑扩容、后移元素、赋值三个步骤
public class SelfArrayList {
/**
* 真实的数据
*/
private int[] array = {};
/**
* 集合真实大小
*/
private int size = 0;
/**
* 集合容量
*/
private int capacity = 8;
public void add(int data) {
initAndGroup();
add(size, data);
}
public void add(int index, int data) {
initAndGroup();
// 仅支持将元素添加到当前集合最后位置
if (index <= size) {
for (int i = size; i > index; i--) {
array[i] = array[i - 1];
}
array[index] = data;
size++;
}
}
public int delete() {
return array[--size];
}
public void update(int index, int data) {
if (index > 0 && index < size) {
array[index] = data;
}
}
public int get(int index) {
return array[index];
}
public void forEach(Consumer<Integer> consumer) {
for (int i = 0; i < size; i++) {
consumer.accept(array[i]);
}
}
private void initAndGroup() {
if (size == 0) {
array = new int[capacity];
} else if (size == capacity) {
capacity += capacity >> 1;
int[] newArray = new int[capacity];
System.arraycopy(array, 0, newArray, 0, size);
array = newArray;
}
}
}
二、单向链表形式
关键点:
- 成员变量就一个Node head节点
- 主要是三个关键方法add(int index, int value)、delete(int index)、findNode(int index),三个方法里面findNode(int index)又是关键
public class SelfLinkList {
private Node head = null;
public void addHead(int value) {
head = new Node(value, head);
}
public void addTail(int value) {
Node tail = findTail();
// 加入的节点为第一个节点
if (tail == null) {
addHead(value);
} else {
tail.next = new Node(value, null);
}
}
/**
* 核心方法 1 of 3
*/
public void add(int index, int value) {
if (index <= 0) {
addHead(value);
return;
}
// 找到对应索引的上一个节点
Node pre = findNode(index - 1);
if (pre == null) {
return;
}
pre.next = new Node(value, pre.next);
}
public Node deleteFirst() {
Node deleteNode = head;
head = head.next;
return deleteNode;
}
/**
* 核心方法 2 of 3
*/
public Node delete(int index) {
Node pre = findNode(index - 1);
// 删除头节点数据
if (pre == null) {
return deleteFirst();
}
Node cur = pre.next;
// 删除末尾不存在数据
if (cur == null) {
return null;
}
pre.next = cur.next;
return cur;
}
/**
* 核心方法 3 of 3
* index 小于0或者大于链表长度 返回null
*/
public Node findNode(int index) {
Node temp = head;
int nodeSize = 0;
// 循环遍历 找到对应的节点
while (temp != null) {
if (index == nodeSize) {
return temp;
}
temp = temp.next;
nodeSize++;
}
return null;
}
public void forEach(Consumer<Integer> consumer) {
Node temp = head;
while (temp != null) {
consumer.accept(temp.value);
temp = temp.next;
}
}
private Node findTail() {
if (head == null) {
return head;
}
Node tail = head;
while (tail.next != null) {
tail = tail.next;
}
return tail;
}
@AllArgsConstructor
public static class Node {
public int value;
public Node next;
}
}
三、含哨兵节点的单向链表形式
关键点:
- 自带一个head节点,其目的是为了简化后续的操作流程,避免出现null的场景
- 主要是三个关键方法add(int index, int value)、delete(int index)、findNode(int index),三个方法里面findNode(int index)又是关键,特别注意哨兵节点和普通参数节点返回下标的差异
public class SelfSentinelLinkList {
private final SelfSentinelLinkList.Node head = new SelfSentinelLinkList.Node(0, null);
public void addHead(int value) {
add(0, value);
}
public void addTail(int value) {
SelfSentinelLinkList.Node tail = findTail();
tail.next = new SelfSentinelLinkList.Node(value, null);
}
/**
* 核心方法 1 of 3
*/
public void add(int index, int value) {
SelfSentinelLinkList.Node pre = findNode(index - 1);
// 添加元素超过链表长度
if (pre == null) {
return;
}
pre.next = new SelfSentinelLinkList.Node(value, pre.next);
}
public SelfSentinelLinkList.Node deleteFirst() {
return delete(0);
}
/**
* 核心方法 2 of 3
*/
public SelfSentinelLinkList.Node delete(int index) {
SelfSentinelLinkList.Node pre = findNode(Math.max(-1, index - 1));
// 删除索引下标超过链表长度
if (pre == null) {
return null;
}
SelfSentinelLinkList.Node deleted = pre.next;
// 删除索引下标超过链表长度
if (deleted == null) {
return null;
}
pre.next = deleted.next;
return deleted;
}
/**
* 核心方法 3 of 3
* 哨兵节点 index=-1
* 数据节点 index>=0
*/
public SelfSentinelLinkList.Node findNode(int index) {
SelfSentinelLinkList.Node temp = head;
int nodeSize = -1;
while (temp != null) {
if (index == nodeSize) {
return temp;
}
temp = temp.next;
nodeSize++;
}
return null;
}
public void forEach(Consumer<Integer> consumer) {
SelfSentinelLinkList.Node temp = head.next;
while (temp != null) {
consumer.accept(temp.value);
temp = temp.next;
}
}
private SelfSentinelLinkList.Node findTail() {
SelfSentinelLinkList.Node tail = head;
while (tail.next != null) {
tail = tail.next;
}
return tail;
}
@AllArgsConstructor
public static class Node {
public int value;
public SelfSentinelLinkList.Node next;
}
}
四、含哨兵节点的双向链表形式
关键点:
- 默认初始化两个节点head、tail,每增加一个数据就new一个Node,并且同时维护前后4条关系链(new Node的时候已经维护了两条),删除同理
- 主要是三个关键方法add(int index, int value)、delete(int index)、findNode(int index),三个方法里面findNode(int index)又是关键
public class SelfSentinelDoublyLinkedList {
/**
* 头哨兵
*/
private final Node head = new Node(null, 0, null);
/**
* 尾哨兵
*/
private final Node tail = new Node(null, 0, null);
public SelfSentinelDoublyLinkedList() {
head.next = tail;
tail.prev = head;
}
public void addFirst(int value) {
add(0, value);
}
/**
* 双向链表的特色地方 就是在操作last的时候
*/
public void addLast(int value) {
Node last = tail.prev;
Node added = new Node(last, value, tail);
last.next = added;
tail.prev = added;
}
/**
* 新增数据 核心方法 1 of 3
*/
public void add(int index, int value) {
// 找到要插入数据的上一个节点
Node prev = findNode(index - 1);
if (prev == null) {
return;
}
Node next = prev.next;
Node inserted = new Node(prev, value, next);
prev.next = inserted;
next.prev = inserted;
}
public void deleteFirst() {
delete(0);
}
public void deleteLast() {
Node removed = tail.prev;
if (removed == head) {
return;
}
Node prev = removed.prev;
prev.next = tail;
tail.prev = prev;
}
/**
* 删除数据 核心方法 2 of 3
*/
public void delete(int index) {
Node prev = findNode(index - 1);
if (prev == null) {
return;
}
Node removed = prev.next;
if (removed == tail) {
return;
}
Node next = removed.next;
prev.next = next;
next.prev = prev;
}
/**
* 根据索引查找数据 核心方法 3 of 3
*/
private Node findNode(int index) {
int i = -1;
for (Node p = head; p != tail; p = p.next, i++) {
if (i == index) {
return p;
}
}
return null;
}
public void forEach(Consumer<Integer> consumer) {
Node p = head.next;
while (p != tail) {
consumer.accept(p.value);
p = p.next;
}
}
static class Node {
/**
* 上一个节点指针
*/
private Node prev;
/**
* 下一个节点指针
*/
private Node next;
/**
* 具体数据
*/
private final int value;
public Node(Node prev, int value, Node next) {
this.prev = prev;
this.value = value;
this.next = next;
}
}
}
五、含哨兵节点的环形链表形式
关键点:
- 默认只初始化一个节点sentinel,由于是一个环,所以该节点既可以是头哨兵,也可以是尾哨兵。增加一个数据就new一个Node,并且同时维护前后4条关系链(new Node的时候已经维护了两条)
- 在初始化SelfRingLinkedListSentinel这个链表的时候,构建好环的关联关系
- 新增、删除数据时,都需要维护好四条关系链
public class SelfRingLinkedListSentinel {
/**
* 哨兵节点
*/
private final Node sentinel = new Node(null, 0, null);
public SelfRingLinkedListSentinel() {
sentinel.next = sentinel;
sentinel.prev = sentinel;
}
/**
* 添加到第一个
*/
public void addFirst(int value) {
Node next = sentinel.next;
Node prev = sentinel;
Node added = new Node(prev, value, next);
prev.next = added;
next.prev = added;
}
/**
* 添加到最后一个
*/
public void addLast(int value) {
Node prev = sentinel.prev;
Node next = sentinel;
Node added = new Node(prev, value, next);
prev.next = added;
next.prev = added;
}
/**
* 删除第一个
*/
public void removeFirst() {
Node removed = sentinel.next;
if (removed == sentinel) {
return;
}
Node a = sentinel;
Node b = removed.next;
a.next = b;
b.prev = a;
}
/**
* 删除最后一个
*/
public void removeLast() {
Node removed = sentinel.prev;
if (removed == sentinel) {
return;
}
Node a = removed.prev;
Node b = sentinel;
a.next = b;
b.prev = a;
}
/**
* 根据值删除节点
*/
public void removeByValue(int value) {
Node removed = findNodeByValue(value);
if (removed != null) {
Node prev = removed.prev;
Node next = removed.next;
prev.next = next;
next.prev = prev;
}
}
/**
* 遍历找到对应的元素
*/
private Node findNodeByValue(int value) {
Node p = sentinel.next;
while (p != sentinel) {
if (p.value == value) {
return p;
}
p = p.next;
}
return null;
}
public void forEach(Consumer<Integer> consumer) {
Node p = sentinel.next;
while (p != sentinel) {
consumer.accept(p.value);
p = p.next;
}
}
static class Node {
int value;
Node prev;
Node next;
public Node(Node prev, int value, Node next) {
this.prev = prev;
this.value = value;
this.next = next;
}
}
}