head: 指向表头
tail: 指向表尾
size: 记录链表结点(insert/delete是要注意修改)
单链表中直接删除最后一个元素必须要遍历链表,因为要找到链尾前一个结点,设置该结点的next为null。
创建单链表class的时候,把结点类作为内置类。 This design allows Java to differentiate this node type from forms of nodes we may define for use in other structure.
public class SinglyLinkedList<E>{
private static class Node<E>{
private E element;
private Node next;
public Node(E e, Node n){
element = e;
next = n;
}
public E getElement() { return element; }
public Node getNext() { return next; }
public void setNext(Node n) { next = n; }
}
private Node head = null;
private Node tail = null;
private int size = 0;
public SinglyLinkedList() {}
//access methods
public int size() { return size; }
public boolean isEmpty() { return size == 0; }
public E first(){
if(isEmpty()) return null;
return head.getElement();
}
public E last(){
if(isEmpty()) return null;
return tail.getElement();
}
//update methods
public void addFirst(E e){
head = new Node<>(e, head);
if(size == 0) tail = head;
size++;
}
public void addLast(E e){
Node newest = new Node<>(e, null);
if(isEmpty()) head = newest;
else
tail.setNext(newest);
tail = newest;
size++;
}
public E removeFirst(){
if(isEmpty()) return null;
E answer = head.getElement();
head=head.getNext();
size--;
if(size==0) tail = null;
return answer;
}
}
单链表的不足:从上面的代码可以看出,如果要实现对中间的结点实现插入、删除的操作会很麻烦。
与单链表不同之处在于,tail.next不是null,而是head。
可以不需要head,用tail.next表示head。
循环链表的一个特点就是rotate(),可以第一个结点变成最后一个结点。
public class CircularlyLinkedList<E>{
//(nested node class identical to that of the SinglyLinkedList class)
private Node tail = null;
private int size = 0;
public CircularlyLinkedList() {}
//access methods
public int size() { return size; }
public boolean isEmpty() { return size == 0; }
public E first(){
if(isEmpty()) return null;
return tail.getNext().getElement();
}
public E last(){
if(isEmpty()) return null;
return tail.getElement();
}
//update methods
public void rotate(){
if(tail != null)
tail = tail.getNext();
}
public void addFirst(E e){
if(size == 0){
tail = new Node<>(e, null);
tail.setNext(tail);
}else{
Node newest = new Node<>(e, tail.getNext());
tail.setNext(newest);
}
size++;
}
public void addLast(E e){
addFirst(e);
tail = tail.getNext();
}
public E removeFirst(){
if(isEmpty()) return null;
Node head = tail.getNext();
if(head == tail) tail = null;
else tail.setNext(head.getNext());
size--;
return head.getElement();
}
}
sentinel的引入
在头跟尾引入两个sentinel(header、 trailer)。这样可以使得链表更加uniform。
The header and trailer nodes never change - only the nodes between them change.
We can treat all insertions in a unified manner, because a new node will always be placed between a pair of existing nodes. Every element that is to be deleted is guaranteed to be stored in a node that has neighbors on each side.
The use of a sentinel node in that implementation would eliminate the special case, as there would always be an existing node (possibly the header) before a new node.
public class DoublyLinkedList<E>{
//nested Node class
private static class Node<E>{
private E element;
private Node prev;
private Node next;
public Node(E e, Node p, Node n){
element = e;
prev = p;
next = n;
}
public E getElement() { return element; }
public Node getPrev() { return prev; }
public Node getNext() { return next; }
public void setPrev(Node p) { prev = p; }
public void setNext(Node p) { next = n; }
}
private Node header;
private Node trailer;
private int size = 0;
public DoublyLinkedList(){
header = new Node<>(null, null, null);
trailer = new Node<>(null, header, null);
header.setNext(trailer);
}
public int size(){
return size;
}
public boolean isEmpty() { return size == 0; }
public E first(){
if(isEmpty()) return null;
return header.getNext().getElement();
}
public E last(){
if(isEmpty()) return null;
return trailer.getPrev().getElement();
}
public void addFirst(E e){
addBetween(e, header, header.getNext());
}
public void addLast(E e){
addBetween(e, trailer.getPrev(), trailer);
}
public E removeFirst(){
if(isEmpty()) return null;
return remove(header.getNext());
}
public E removeLast(){
if(isEmpty()) return null;
return remove(trailer.getPrev());
}
private void addBetween(E e, Node predecessor, Node successor){
Node newest = new Node<>(e, predecessor, successor);
predecessor.setNext(newest);
successor.setPrev(newest);
size++;
}
private E remove(Node node){
Node predecessor = node.getPrev();
Node successor = node.getNext();
predecessor.setNext(successor);
successor.setPrev(predecessor);
size--;
return node.getElement();
}
}
a & b are both arrays
a == b: Tests if a and b refer to the same underlying array instance;
a.equals(b): This is identical to a == b. Arrays are not a true class type and do not override the Object.equals method.
Arrays.equals(a,b): This provides a more intuitive notion of equivalence, returning true if the arrays have the same length and all pairs of corresponding elements are “equal” to each other. More specifically, if the array elements are primitives, then it uses the standard == to compare values. If elements of the arrays are a reference type, then it makes pairwise comparisons a[k].equals(b[k]) in evaluating the equivalence.
Arrays.equals(a,b)在很大程度上已经够用了,但是如果a、b是二维数组的话,会出现问题。此时应该用Arrays.deepEquals(a,b)来比较。
Arrays.deepEquals(a,b)的signature:
static boolean deepEquals(Object[] a1, Object[] a2)
For SinglyLinkedList,
public boolean equals(Object o){
if(o == null) return false;
if(getClass() != o.getClass()) return false;
//Object的method, Returns the runtime class of this Object.
SinglyLinkedList other = (SinglelinkedList) o;
if(size() != other.size()) return false;
Node walkA = head;
Node walkB = other.head; //这里得把类里面的head变量变成public的才行
while(walkA!=null){
if(!walkA.getElement().equals(walkB.getElement())) return false;
walkA = walkA.getNext();
walkB = walkB.getNext();
}
return true;
}
Although our SinglyLinkedList class has a declared formal type parameter, we cannot detect at runtime whether the other list has a matching type parameter, we cannot detect at runtime whether the other list has a matching type.
So we revert to using a more classic approach with non parameterized type SinglyLinkedList and nonparameterized Node declarations. If the two lists have incompatible types, this will be detected when calling the equals method on corresponding elements.
shallow copy: for primitive variables, make a new copy; for reference variables, set them to the same Object.
deep copy: for reference variables, make another copy for them.
Object class provides a method called clone(), which is protected. It can be used to produce a shallow copy of an object.
A shallow copy is not always appropriate for all classes, and therefore, Java intentionally disables use of the clone() method by declaring it as protected, and by having it throw a CloneNotSupportedException when called. The author of a class must explicitly declare support for cloning by formally declaring that the class implements the Cloneable interface, and by declaring a public version of the clone() method. That public method can simply call the protected one to do the field-by-field assignment that results in a shallow copy. However, for many classes, the class may choose to implement a deeper version of cloning, in which some of the referenced objects are themselves cloned;
Object数组的deepClone
//when the Person class is declared as Cloneable;
Person[] guests = new Person[contacts.length];
for(int k = 0; k < contacts.length; k++){
guests[k] = (Person)contacts[k].clone();
}
二维数组的deepClone
public static int[][] deepClone(int[][] original){
int[][] backup = new int[original.length][];
for(int k = 0; k < original.length; k++)
backup[k] = original[k].clone();
return backup;
}
(以SinglyLinkedList为例)
Signature:
public class SinglyLinkedList<E> implements Cloneable
public SinglyLinkedList clone() throws CloneNotSupportedException{
//always use inherited Object.clone() to create the initial copy
SinglyLinkedList other = (SinglyLinkedList)super.clone();
if(size > 0){
other.head = new Node<>(head.getElement(), null);
Node walk = head.getNext();
Node otherTail = other.head;
while(walk != null){
Node newest = new Node<>(walk.getElement(), null);
otherTail.setNext(newest);
otherTail = newest;
walk = walk.getNext();
}
}
return other;
}