数据结构学习笔记 —— 链表

单链表Singly Linked List

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<E> next;

        public Node(E e, Node<E> n){
            element = e;
            next = n;
        }

        public E getElement() { return element; }

        public Node<E> getNext() { return next; }

        public void setNext(Node<E> n) { next = n; }
    }

    private Node<E> head = null;
    private Node<E> 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<E> 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;
    }
}

单链表的不足:从上面的代码可以看出,如果要实现对中间的结点实现插入、删除的操作会很麻烦。

Circularly Linked List

与单链表不同之处在于,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<E> 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<E> 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<E> head = tail.getNext();
        if(head == tail) tail = null;
        else tail.setNext(head.getNext());
        size--;
        return head.getElement();
    }
}

Doubly Linked Lists

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<E> prev;
        private Node<E> next;
        public Node(E e, Node<E> p, Node<E> n){
            element = e;
            prev = p;
            next = n;
        }

        public E getElement() { return element; }
        public Node<E> getPrev() { return prev; }
        public Node<E> getNext() { return next; }
        public void setPrev(Node<E> p) { prev = p; }
        public void setNext(Node<E> p) { next = n; }
    }

    private Node<E> header;
    private Node<E> 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<E> predecessor, Node<E> successor){
        Node<E> newest = new Node<>(e, predecessor, successor);
        predecessor.setNext(newest);
        successor.setPrev(newest);
        size++;
    }

    private E remove(Node<E> node){
        Node<E> predecessor = node.getPrev();
        Node<E> successor = node.getNext();
        predecessor.setNext(successor);
        successor.setPrev(predecessor);
        size--;
        return node.getElement();
    }
}

Equivalence Testing with Arrays

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)

Equivalence Testing with LinkedList

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.

Cloning Data Structures

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;

数组的clone

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;
}
LinkedList的clone

(以SinglyLinkedList为例)
Signature:

public class SinglyLinkedList<E> implements Cloneable
public SinglyLinkedList<E> clone() throws CloneNotSupportedException{
    //always use inherited Object.clone() to create the initial copy
    SinglyLinkedList<E> other = (SinglyLinkedList<E>)super.clone(); 
    if(size > 0){
        other.head = new Node<>(head.getElement(), null);
        Node<E> walk = head.getNext();
        Node<E> otherTail = other.head;
        while(walk != null){
            Node<E> newest = new Node<>(walk.getElement(), null);
            otherTail.setNext(newest);
            otherTail = newest;
            walk = walk.getNext();
        }
    }
    return other;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值