目录
在 小肥柴慢慢手写数据结构(C篇)(2-1 单链表 SingleLinkedList self版实现(1))中我们实现了C版的单链表,后又学习了反转、快慢指针等知识点,还对比了严版教材的实现,现在用Java再写一遍,练习OOP。
注:代码参考刘大佬的github
2-1 上代码
- MyLinkedList.java
public class MyLinkedList<E> {
private Node DummyHead;
private int size;
private class Node {
private E e;
private Node next;
public Node() {
this(null, null);
}
public Node(E e) {
this(e, null);
}
public Node(E e, MyLinkedList<E>.Node next) {
this.e = e;
this.next = next;
}
@Override
public String toString() {
return e == null ? null : e.toString();
}
}
public MyLinkedList() {
DummyHead = new Node();
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void add(int index, E e) {
if (index < 0 || index > size)
throw new IllegalArgumentException("Add failed. Illegal index.");
Node prev = DummyHead;
for (int i = 0; i < index; i++)
prev = prev.next;
/*
* 等价于 Node node = new Node(e); node.next = prev.next; prev.next = node;
*/
prev.next = new Node(e, prev.next);
size++;
}
public void addFirst(E e) {
add(0, e);
}
public void addLast(E e) {
add(size, e);
}
public E get(int index) {
if (index < 0 || index > size)
throw new IllegalArgumentException("Get failed. Illegal index.");
Node cur = DummyHead;
for (int i = 0; i < size; i++)
cur = cur.next;
return cur.e;
}
public E getFirst() {
return get(0);
}
public E getLast() {
return get(size - 1);
}
public void set(int index, E e) {
if (index < 0 || index > size)
throw new IllegalArgumentException("Set failed. Illegal index.");
Node cur = DummyHead;
for (int i = 0; i < index; i++)
cur = cur.next;
cur.e = e;
}
public boolean contains(E e) {
Node cur = DummyHead.next;
while (cur != null) {
if (cur.equals(e))
return true;
cur = cur.next;
}
return false;
}
public E remove(int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("Remove failed. Index is illegal.");
Node prev = DummyHead;
for (int i = 0; i < index; i++)
prev = prev.next;
Node ret = prev.next;
prev.next = prev.next.next;
ret.next = null;
size--;
return ret.e;
}
public E removeFirst() {
return remove(0);
}
public E removeLast() {
return remove(size - 1);
}
public void removeElem(E e) {
Node prev = DummyHead;
while (prev.next != null) {
if (prev.next.equals(e))
break;
prev = prev.next;
}
if (prev.next != null) {
Node del = prev.next;
prev.next = prev.next.next;
del.next = null;
del = null;
size--;
}
}
@Override
public String toString() {
StringBuilder strBld = new StringBuilder();
Node cur = DummyHead.next;
while (cur != null) {
strBld.append(cur).append("->");
cur = cur.next;
}
strBld.append("null");
return strBld.toString();
}
}
2-2 代码细节
- 将节点类型Node声明为私有内部类,隐藏实现,仅暴露给外部操作API。
- 丰富Node的构造函数,可以使用缺省值。
- 用Node的构造函数巧妙解决了节点插入问题中保留“下一个指向”的问题。
Node node = new Node(e);
node.next = prev.next;
prev.next = node;
等价于
prev.next = new Node(e, prev.next); - 时间复杂度上并没有太多讨论的点,只是需要注意链表是牺牲了随机访问的特性,换取了动态的特性,也就是插入和删除很容易,但是修改和查询没有动态数组ArrayList快。
- 还是OOP写起来顺手,哈哈哈。
2-3 源码阅读
网上源码解析的帖子很多,直接学习即可,注意JDK源码中使用双链表实现,且包含了E对象的null处理,即链表节点存在,但节点元素内容为空!
(参考) 《JAVA源码分析》:LinkedList.
2-4 总结
Java的LinkedList实现比较简单,思路非常清晰,在代码复用和接口设计上值得学习。