关于LinkedList的梳理
请先参看: 关于LinkedList的梳理。
LinkedList是链式存储的双链表,而LinkedList是基于节点(Node类)组成的链表实现的。
说明:内部类,即指非static的内部类,或称非静态内部类。嵌套类,即指static的内部类,或称静态内部类。
不喜欢静态的说法,因为static不是一般意义上的静态动态的意思,static是属于类的意思(而不属于类的任何一个对象)。比如,每个对象都没有static变量、函数,而是每个对象可以访问类的static变量、函数。此外可以通过类的名字去访问static的变量、方法,但是不能访问非static的变量。因此static的变量、方法也被称为类变量、类函数,非static的函数在main中被调用时,前面必须要引用的对象,而类方法可以直接写。
显然,加了static修饰后,同为static的main函数可以直接调用。
最后,建议直接称为static的类、方法、内部类。
在考虑设计方面,LinkedList包含三个类:
- 1 LinkedList类本身,它应该包含到到两端的链、表的大小和一些方法;
- 2 Node类,表示节点,就像数组 a[]中的一个元素 a[i],节点包含数据、以及到前一个节点和到下一个节点的链,还有一些适当的构造方法。
- 3 LinkedListIterator类,和ArrayListIterator一样,该类是Iterator接口的具体实现,抽象了位置的概念。
关于链的个人理解:
A到B的链就是任意可以由A联系到B的方式,如数组的每个位置和前后位置间自然存在着基于序号的链,又如Node节点,两节点间通过相互记录彼此的“关系”构建链,A说B在“前”,B说A在“后”,那么这种建立的关系就是实质上的链,这种链具体是通过Node类里表示“前”、“后”关系的成员变量来实现。A、B可以是对等的关系,如各Node节点间就是双向的链(也可单向);也可以不是对等的关系,如集合指向某具体元素的单链,这里的LinkedList到两端节点的链。
下面说一下前面ArrayList中未包含或未提及的部分:
- 1 使用了头尾节点,通过这些标记节点(sentinel node),排除了许多特殊情形,极大的简化了编码。
- 2 应该注意到,在ArrayList中,所有成员变量都是私有的,此外还有一些私有成员方法,实际上一些公开方法对私有成员变量的改动,也是通过调用可以实质改动私有变量的方法来完成的。这样虽然繁琐,但是保证了数据的安全,1是其他类不能自己写方法改变这个类的成员,2是这个类一些最基础的改变变量的方法可以抽象出来,并做成私有的,其他公开方法对该类私有变量的改变基于这些私有方法,更安全,过程也更清晰。这里的LinkedList也一样,而且私有方法更多,因为跟私有嵌套类Node有关,所以公开的add、get、remove方法,都有更基础的基于Node节点的私有方法:addBefore(Node p, T x),remove(Node p)等。
- 3 Node是私有嵌套类,是private的嵌套类,但是对所在的外部类是可见的,这很好,因为Node只需要也只允许外部类LinkedList来改变其数据。若嵌套类的成员也是private的,仍有外部类对嵌套类的成员是可见的。原因:private的嵌套类,就是外部类的一个private的成员,private是针对外部类以外而言的;同样的,嵌套类里成员的private,也是针对外部类以外而言的;所以Node类的所有成员变量属性在Node为private时,是无关紧要的,一般用public。我们需要外部类可以对Node进行修改、外部类之外的其他类不可改动,已经通过Node的private属性实现了,LinkedList以外的类根本见不到Node类。
- 4 这里增加了一个新的成员叫做modCount,他是为了检测那种可能使得迭代器无效的结构上的修改,和那种非法的迭代器remove方法,即该数据用于检测迭代是否正常进行的。具体来说,在建立一个新的迭代器后,迭代器将用expectedModCount储存集合的modCount,expectedModCount++和modCount++分别放在迭代器内、外,如果迭代正常,此后每次迭代二者相同,如果迭代异常,则二者不等。每次对迭代法方法(next()和remove())的调用都将比较集合内的modCount和迭代器里的相应expectedModCount,不匹配就抛出异常。可以看到,这个逻辑可以运用在实现Iterable的类中,比较迭代器内外迭代次数是否匹配看迭代是否正常。
- 5 最后,remove方法其实要求一定要先next(),才能remove,而且只remove一次,remove本次迭代的数据、节点等。因此这里增加错误检测,在LinkedListIterator类中新增一个默认为false的boolean变量okToRemove,next()后为true,remove()先检测okToRemove,若为false直接抛出异常,若为true就remove,然后将okToRemove改为false。PS:Java.util.LinkedList没采用这个,系统库那个还没仔细理解怎么进行错误检测的。
下面复现上面的流程。同样接口定义远没有官方的完善,只择取了部分方法;同时为和系统库的接口、类相区别,命名前加My。
特别强调的是,为了简洁地实现反向的迭代,和在一次迭代上 实现新增的 add()和set(),将Iterator接口进行扩展为ListIterator接口。这很重要,我文字举一个例子:
编程实现逆序的,删除所有的奇数序号的项,且所有偶数序号的项的值减1。
这是很简单的要求,实际上就是一次迭代就可以完成,走到某一项,进行
O
(
1
)
O(1)
O(1)的操作即可。但是如果没有迭代器新增的这几个
O
(
1
)
O(1)
O(1)的方法,那实际上就很可能运算复杂度大大提高,因为很可能出现,走到某节点是一次迭代,add方法不是从该节点运行,而是从头绕到该节点,就又是一轮迭代。后面的invertedToString() 方法就是逆向输出双链表,就用到了这里新的抽象方法。
此外,这里的indexOf(T)等方法可以看到for循环和while循环的改写,下面两段代码是一样的效果:
public int indexOf(T x) {
int index = 0;
Node<T> temp = beginMarker.next;
if (x == null) {
while(temp != null) {
if (temp.data == null) {
return index;
} // of if
temp = temp.next;
index++;
} // of while
} else {
while(temp != null) {
if (x.equals(temp.data)) {
return index;
} // of if
temp = temp.next;
index++;
} // of while
} // of if else
index = -1;
return index;
} // of indexOf
改写如下,加一个first只为了让逻辑显示更清晰:
public int indexOf(T x) {
int index = 0;
Node<T> first = beginMarker.next;
if (x == null) {
for (Node<T> temp = first; temp != null; temp = temp.next) {
if (temp.data == null) {
return index;
} // of if
index++;
} // of for
} else {
for (Node<T> temp = first; temp != null; temp = temp.next) {
if (x.equals(temp.data)) {
return index;
} // of if
index++;
} // of for
} // of if else
index = -1;
return index;
} // of indexOf
下面梳理整个过程。接口从小到大是:
public interface MyIterator<T> {
boolean hasNext();
T next();
void remove();
}
MyListIterator接口:
public interface MyListIterator<T> extends MyIterator<T> {
boolean hasNext();
T next();
void remove();
boolean hasPrevious();
T previous();
void add(T x);
void set(T newValue);
}
public interface MyIterable<T> {
MyIterator<T> myIterator();
}
public interface MyCollection<T> extends MyIterable<T> {
int size();
boolean isEmpty();
void clear();
boolean contain();
boolean add(T x);
boolean remove(T x);
}
public interface MyList<T> extends MyCollection<T> {
MyIterator<T> myIterator();
int size();
boolean isEmpty();
void clear();
boolean contain();
boolean add(T x);
boolean remove(T x);
T get(int idx);
T set(int idx, T newVal);
void add(int idx, T x);
void remove(int idx);
}
然后给出MyLinkedList类的框架:
public class MyLinkedList<T> implements MyList<T> {
private static class Node<T> {} // of class Node
private int theSize;
private int modCount = 0;
private Node<T> beginMarker;
private Node<T> endMarker;
public MyLinkedList() {} // of the first constructor
private void doClear() {} // of doClear
public MyLinkedList(T[] paraArray) {} // of the second constructor
public int size() {} // of size
public boolean isEmpty() {} // of isEmpty
public void clear() {} // of clear
public boolean contain(T x) {} // of contain
public boolean add(T x) {} // of add(T)
public void add(int idx, T x) {} // of add(int)
private void addBefore(Node<T> p, T x) {} // of addBefore
private Node<T> getNode(int idx, int lower, int upper) {} // of getNode(int, int, int)
private Node<T> getNode(int idx) {} // of getNode(int)
public boolean remove(T x) {} // of remove(T)
public T remove(int idx) {} // of remove(int)
private T remove(Node<T> p) {} // of remove(Node<T>)
public T get(int idx) {} // of get
public int indexOf(T x) {} // of indexOf
public T set(int idx, T newValue) {} // of set
public MyIterator<T> myIterator() {} // of myIterator
private class LinkedListIterator implements MyListIterator<T> {
private Node<T> nextNode = beginMarker.next;
private int expectedModCount = modCount;
private boolean okToRemove = false;
public boolean hasNext() {} // of hasNext
public T next() {} // of next
public void remove() {} // of remove
private Node<T> prevNode = endMarker.prev;
public boolean hasPrevious() {} // of hasPrevious
public T previous() {} // of previous
public void add(T x) {} // of add
public void set(T newValue) {} // of set
} // of classLinkedListIterator
public String toString() {} // of toString
public String invertedToString() {} // of toString
public static void main(String[] args) {} // of main
} // of class MyLinkedList<T>
最后对上述框架进行完善,得到最后的 LinkedList<T> 为:
2021/7/21进行修订,没再使用自己的接口,而是采用Java的接口,主要是为了用Java编译器自动实现for-each循环,每个for-each都自己改写成迭代器太麻烦了。
修订前:
package myLinkedList;
import myAPI.MyIterator;
import myAPI.MyList;
import myAPI.MyListIterator;
public MyIterator<T> myIterator() {
return new LinkedListIterator();
}// of myIterator()
// The time complexities of the methods in the Iterator are all O(1).
private class LinkedListIterator implements MyListIterator<T> {
}// of class LinkedListIterator
}// of class MyLinkedList<T>
修订后:
package myList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* @author CatInCoffee.
* @version 1.2 Revised on 2021/7/21.
*/
public class MyLinkedList<T> implements Iterable<T> {
private static class Node<T> {
public Node<T> prev;
public T data;
public Node<T> next;
public Node(T paraData) {
prev = null;
data = paraData;
next = null;
}// of Node's first constructor
public Node(Node<T> paraPrev, T paraData, Node<T> paraNext) {
prev = paraPrev;
data = paraData;
next = paraNext;
}// of Node's second constructor
}// of class Node<T>
private Node<T> beginMarker;
private Node<T> endMarker;
private int theSize;
private int modCount = 0;
// *****************************************************************************************
public MyLinkedList() {
clear();
}// of the first constructor
public void clear() {
beginMarker = new Node<T>(null);
endMarker = new Node<T>(beginMarker, null, null);
beginMarker.next = endMarker;
theSize = 0;
modCount++;
}// of clear()
// O(N)
public MyLinkedList(T[] paraArray) {
clear();
for (int i = 0; i < paraArray.length; i++) {
addBefore(endMarker, paraArray[i]);
} // of for i
}// of the second constructor
// *****************************************************************************************
public int size() {
return theSize;
}// of size()
public boolean isEmpty() {
return size() == 0;
}// of isEmpty()
// O(N)
public boolean contain(T x) {
return indexOf(x) >= 0;
}// of contain(T)
// *****************************************************************************************
// Append the specified element to the end of this list.
public boolean add(T x) {
add(size(), x);
return true;
}// of add(T)
// O(N)
public void add(int idx, T x) {
addBefore(getNode(idx, 0, size()), x);
}// of add(int)
/**
* Add an item to this collection at specified position p. Items at or after
* that position are slid one position higher. O(1)
*
* @param p The node at position p, and it's the Node to add before.
* @param x the item to add.
*/
private void addBefore(Node<T> p, T x) {
Node<T> newNode = new Node<>(p.prev, x, p);
newNode.prev.next = newNode;
p.prev = newNode;
theSize++;
modCount++;
}// of addBefore(Node<T>, T)
/**
* O(N) Here we cannot use remove(indexOf(T)) = remove(getNode(indexOf(T)))
*/
public boolean remove(T x) {
try {
remove(getNode(x));
return true;
} catch (NoSuchElementException e) {
// System.out.println("No such element.");
return false;
} // of try-catch
}// of remove(T)
public T remove(int idx) {
return remove(getNode(idx));
}// of remove(int)
private T remove(Node<T> p) {
p.next.prev = p.prev;
p.prev.next = p.next;
theSize--;
modCount++;
return p.data;
}// of remove(Node<T>)
// *****************************************************************************************
public T get(int idx) {
return getNode(idx).data;
}// of get(int)
/**
* Get the Node at position idx, which must range from 0 to size() - 1. O(N)
*/
private Node<T> getNode(int idx) {
return getNode(idx, 0, size() - 1);
}// of getNode(int)
/**
* Get the Node at position idx, which must range from lower to upper. O(N)
*
* @param idx index to search at.
* @param lower lowest valid index.
* @param upper highest valid index.
* @return internal node corresponding to idx.
*/
private Node<T> getNode(int idx, int lower, int upper) {
Node<T> p;
if (idx < lower || idx > upper) {
throw new IndexOutOfBoundsException();
} // of if
if (idx < size() / 2) {
p = beginMarker.next; // idx 0 corresponding to beginMarker.next
for (int i = 0; i < idx; i++) {
p = p.next;
} // of for i
} else {
p = endMarker; // idx theSize corresponding to endMarker
for (int i = size(); i > idx; i--) {
p = p.prev;
} // of for i
} // of if-else
return p;
}// of getNode(int, int, int)
// O(N)
private Node<T> getNode(T x) {
Node<T> first = beginMarker.next;
if (x == null) {
for (Node<T> temp = first; temp != endMarker; temp = temp.next) {
if (temp.data == null) {
return temp;
}
} // of for temp
} else {
for (Node<T> temp = first; temp != endMarker; temp = temp.next) {
if (x.equals(temp.data)) {
return temp;
} // of if
} // of for temp
} // of if-else
throw new NoSuchElementException();
}// of getNode(T)
/**
* Return the index of the first occurrence of the specified element in this
* list, or -1 if this list doesn't contain the element. More formally, returns
* the lowest index, or -1 if there is no such index. O(N)
*
* @param paraValue element to search for
* @return the index of the first occurrence of the specified element in this
* list, or -1 if this list doesn't contain the element.
*/
public int indexOf(T x) {
int index = 0;
// Node<T> first = beginMarker.next;
// if (x == null) {
// for (Node<T> temp = first; temp != endMarker; temp = temp.next) { // Here temp as a node is not null, but
// // temp.data could be null.
// if (temp.data == null) {
// return index;
// } // of if
// index++;
// } // of for
// } else {
// for (Node<T> temp = first; temp != endMarker; temp = temp.next) {
// if (x.equals(temp.data)) {
// return index;
// } // of if
// index++;
// } // of for
// } // of if else
Node<T> temp = beginMarker.next;
if (x == null) {
while(temp != endMarker) {
if (temp.data == null) {
return index;
} // of if
temp = temp.next;
index++;
} // of while
} else {
while(temp != endMarker) {
if (x.equals(temp.data)) {
return index;
} // of if
temp = temp.next;
index++;
} // of while
} // of if else
index = -1;
return index;
}// of indexOf
// O(1)
public T set(int idx, T newValue) {
Node<T> p = getNode(idx);
T oldValue = p.data;
p.data = newValue;
return oldValue;
}// of set(int, T)
// *****************************************************************************************
public Iterator<T> iterator() {
return new LinkedListIterator();
}// of myIterator()
// The time complexities of the methods in the Iterator are all O(1).
private class LinkedListIterator implements Iterator<T> {
private Node<T> nextNode = beginMarker.next;
private int expectedModCount = modCount;
private boolean okToRemove = false;
public boolean hasNext() {
return nextNode != endMarker;
}// of hasNext()
public T next() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
} // of if
if (!hasNext()) {
throw new NoSuchElementException();
} // of if
T nextItem = nextNode.data;
nextNode = nextNode.next;
okToRemove = true;
return nextItem;
}// of next()
public void remove() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
} // of if
if (!okToRemove) {
throw new IllegalStateException();
} // of if
MyLinkedList.this.remove(nextNode.prev);
expectedModCount++;
okToRemove = false;
}// of remove()
private Node<T> prevNode = endMarker.prev;
public boolean hasPrevious() {
return prevNode != beginMarker;
}// of hasPrevious()
public T previous() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
} // of if
if (!hasPrevious()) {
throw new NoSuchElementException();
} // of if
T prevItem = prevNode.data;
prevNode = prevNode.prev;
okToRemove = true;
return prevItem;
}// of previous()
public void backwardRemove() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
} // of if
if (!okToRemove) {
throw new IllegalStateException();
} // of if
MyLinkedList.this.remove(prevNode.next);
expectedModCount++;
okToRemove = false;
}// of backwardRemove()
public void add(T x) {
addBefore(nextNode.prev, x);
}// of add(T)
public void set(T newValue) {
nextNode.prev.data = newValue;
}// of set(T)
}// of class LinkedListIterator
// *****************************************************************************************
// O(N)
public String toString() {
LinkedListIterator itr = (LinkedListIterator) this.iterator();
String s = "empty.";
if (!isEmpty()) {
s = "{ ";
while (itr.hasNext()) {
s += itr.next() + " ";
} // of while
s += "}";
} // of if
return s;
}// of toString()
// O(N)
public String backwardToString() {
LinkedListIterator itr = (LinkedListIterator) this.iterator();
String s = "empty.";
if (!isEmpty()) {
s = "{ ";
while (itr.hasPrevious()) {
s += itr.previous() + " ";
} // of while
s += "}";
} // of if
return s;
}// of backwardtoString()
// *****************************************************************************************
public static void main(String[] args) {
Character[] tempArray = { 'd', 'e', 'f', 'a' };
MyLinkedList<Character> testLinkedList = new MyLinkedList<>(tempArray);
System.out.println("After being initialized, the list is: " + testLinkedList.toString());
Character tempValue = 'i';
System.out.println(
"The judgement that the list contains " + tempValue + " is :" + testLinkedList.contain(tempValue));
int tempPosition = testLinkedList.indexOf(tempValue);
System.out.println("The position of " + tempValue + " is " + tempPosition);
tempValue = 'f';
System.out.println(
"The judgement that the list contains " + tempValue + " is :" + testLinkedList.contain(tempValue));
tempPosition = testLinkedList.indexOf(tempValue);
System.out.println("The position of " + tempValue + " is " + tempPosition + "\n");
tempPosition = 1;
tempValue = 'c';
testLinkedList.add(tempPosition, tempValue);
System.out.println(
"After adding " + tempValue + " to position " + tempPosition + ", the list is: " + testLinkedList);
testLinkedList.add(Character.valueOf('z'));
System.out.println("After adding z to the end, the list is: " + testLinkedList);
tempPosition = 3;
tempValue = 'b';
testLinkedList.set(tempPosition, tempValue);
System.out.println(
"After seting " + tempValue + " at position " + tempPosition + ", the list is: " + testLinkedList);
tempPosition = 5;
testLinkedList.remove(tempPosition);
System.out
.println("After removing data at position " + tempPosition + ", the list is: " + testLinkedList + "\n");
tempValue = 'e';
System.out.println("removing value " + tempValue + " could be : " + testLinkedList.remove(tempValue)
+ ", and the list is: " + testLinkedList);
tempValue = 'f';
System.out.println("removing value " + tempValue + " could be : " + testLinkedList.remove(tempValue)
+ ", and the list is: " + testLinkedList);
System.out.println("After inverting the sequence, the list is: " + testLinkedList.backwardToString());
testLinkedList.clear();
System.out.println("After the clear() method, the list is: " + testLinkedList);
}// of main
}// of class MyLinkedList<T>
结果和修订前一致:
After being initialized, the list is: { d e f a }
The judgement that the list contains i is :false
The position of i is -1
The judgement that the list contains f is :true
The position of f is 2
After adding c to position 1, the list is: { d c e f a }
After adding z to the end, the list is: { d c e f a z }
After seting b at position 3, the list is: { d c e b a z }
After removing data at position 5, the list is: { d c e b a }
removing value e could be : true, and the list is: { d c b a }
removing value f could be : false, and the list is: { d c b a }
After inverting the sequence, the list is: { a b c d }
After the clear() method, the list is: empty.
PS:注意到链表的 getNode(int) 方法,即通过下标找到节点,有了该核心方法,对链表的get、add、set、delete就只需要进行
O
(
1
)
O(1)
O(1)的操作。而getNode(int)是通过头节点或尾节点一个一个.next找到需要的节点。那么,如果我们存储中间的关键节点,是否就有可能实现大大降低找到该节点的复杂度呢?
比如,存有917个数据的链表,对第477个数据进行查找、增加、删除等操作。如果在存链表时,每隔256个节点就额外记录该节点,那么序号477的节点就不需要从初始节点开始查找,而是从额外存储的512这个节点查找。怎么确定分多少组每组多少元素(即储存哪些节点),怎么存储和覆盖之前的某些节点,都是繁琐的细节问题。我想的是这样的设计是不是背离了链表这种基础结构本身的意义,而是应该放在具体问题上作为具体的一种优化方法?
啊,后面来看,上面的想法最终的实现就会趋向二叉树……