一、需要实现的几个API
public class MyLinkedList<E> {
final private Node<E> head, tail; //虚拟头、尾节点
private int size; //链表中元素个数
private static class Node<E> { //链表节点类
E val; //存放数据
Node<E> next; //前驱
Node<E> prev; //后继
Node(E val) {
this.val = val;
}
}
public MyLinkedList(); //构造函数
// *** 其他工具函数 ***
public int size(); //返回链表的元素个数
public boolean isEmpty(); //判断链表是否为空
private Node<E> getNode(int index); //返回索引为Index的节点
private void checkElementIndex(int index); //检查index对应的节点是否存在
private boolean isElementIndex(int index); //checkElementIndex的辅助函数
private void checkPositionIndex(int index); //检查index处是否可插入
private boolean isPositionIndex(int index); //checkPositionIndex的辅助函数
// *** 增 ***
public void addLast(E e); //在链表尾部添加一个元素e
public void addFirst(E e); //在链表头部添加一个元素e
public void add(int index, E element); //将元素element插入到索引为index的链表中
// *** 删 ***
public E removeFirst(); //删除第一个元素
public E removeLast(); //删除最后一个元素
public E remove(int index); //将链表中索引为index的元素删除
// *** 查 ***
public E get(int index) //返回链表中索引为index的元素的值
public E getFirst(); //返回第一个节点的值
public E getLast(); //返回最后一个节点的值
// *** 改 ***
public E set(int index, E val); //将索引为index的元素修改为val
}
二、构造函数
public MyLinkedList() {
this.head = new Node<>(null);
this.tail = new Node<>(null);
head.next = tail;
tail.prev = head;
this.size = 0;
}
链表为空时:head <-> tail
三、其他工具函数
1. size()和isEmpty()
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
2. getNode(index)
private Node<E> getNode(int index) {
checkElementIndex(index);
Node<E> p = head.next;
for (int i=0; i<index; i++) {
p = p.next;
}
return p;
}
3. 检查索引是否合法
//检查索引位置是否可存在元素
private void checkElementIndex(int index) {
if (!isElementIndex(index)) {
throw new IndexOutOfBoundsException("Index: "+ index + ", Size: " + size);
}
}
//checkElementIndex的辅助函数
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
//检查索引位置是否可以插入元素
private void checkPositionIndex(int index) {
if (!isPositionIndex(index)) {
throw new IndexOutOfBoundsException("Index: "+ index + ", Size: " + size);
}
}
//checkPositionIndex的辅助函数
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
四、增删查改
1. 增
链表头部插入节点x
public void addFirst(E e) {
//新节点x
Node<E> x = new Node<>(e);
Node<E> temp = head.next;
// head <-> temp
temp.prev = x;
x.next = temp;
head.next = x;
x.prev = head;
//head <-> x <-> temp
size++;
}
链表尾部插入节点x
public void addLast(E e) {
//新节点x
Node<E> x = new Node<>(e);
Node<E> temp = tail.prev;
// temp <-> tail
temp.next = x;
x.prev = temp;
x.next = tail;
tail.prev = x;
//temp <-> x <-> tail
size++;
}
在索引index处插入节点x
public void add(int index, E element) {
//检查所索引是否合法
checkPositionIndex(index);
//尾部插入时,调用addLast()
if (index == size) {
addLast(element);
return;
}
//获取索引index对应的节点
Node<E> p = getNode(index);
//temp为插入节点的前驱节点
Node<E> temp = p.prev;
// temp <-> p
// 要插入的新节点x
Node<E> x = new Node<>(element);
p.prev = x;
temp.next = x;
x.prev = temp;
x.next = p;
// temp <-> x <-> p
size++;
}
2. 删
删除第一个节点
public E removeFirst() {
//如果链表为空,删除失败
if (size < 1) {
throw new NoSuchElementException();
}
//x为要删除的节点,temp为x的后继节点
Node<E> x = head.next;
Node<E> temp = x.next;
// head <-> x <-> temp
head.next = temp;
temp.prev = head;
x.prev = null;
x.next = null;
// head <-> temp
size--;
return x.val;
}
删除最后一个节点
public E removeLast() {
//如果链表为空,删除失败
if (size < 1) {
throw new NoSuchElementException();
}
//x为要删除节点,temp为x的前驱节点
Node<E> x = tail.prev;
Node<E> temp = x.prev;
// temp <-> x <-> tail
tail.prev = temp;
temp.next = tail;
x.prev = null;
x.next = null;
// temp <-> tail
size--;
return x.val;
}
删除索引index对应的节点
public E remove(int index) {
//检查索引是否合法
checkElementIndex(index);
// 找到index对应的节点,即要删除节点
Node<E> x = getNode(index);
//prev为x的前驱节点,next为x的后继节点
Node<E> prev = x.prev;
Node<E> next = x.next;
// prev <-> x <-> next
prev.next = next;
next.prev = prev;
x.prev = x.next = null;
// prev <-> next
size--;
return x.val;
}
3. 查
查找第一个元素
public E getFirst() {
//如果链表为空,查找失败
if (size < 1) {
throw new NoSuchElementException();
}
return head.next.val;
}
查找最后一个元素
public E getLast() {
//如果链表为空,查找失败
if (size < 1) {
throw new NoSuchElementException();
}
return tail.prev.val;
}
查找索引为index的元素
public E get(int index) {
//检查是否为合法索引
checkElementIndex(index);
//获取索引index对应的节点
Node<E> p = getNode(index);
return p.val;
}
4. 改
public E set(int index, E val) {
checkElementIndex(index);
//找到要修改的节点
Node<E> p = getNode(index);
E oldVal = p.val;
p.val = val;
return oldVal;
}
总结
本文实现了链表的增删查改,要注意的是:
1. 正确检查Index是否合法,正常情况下,index的合法范围为[0, size-1],而插入时,index的合法范围为[0, size];
2. 实现getNode函数,以便于实现通过索引获取对应节点。
声明
本文为学习笔记,学习内容来自微信公众号labuladong的数据结构课程,侵删。