數據結構整理

  • 數組封裝

接口html

public interface Array<T> {
    T[] getData();
    int getSize();
    int getCapacity();
    boolean isEmpty();
    void addLast(T t);
    void addFirst(T t);
    void add(int index, T t);
    T get(int index);
    T getLast();
    T getFirst();
    void set(int index, T t);
    boolean contains(T t);
    int find(T t);
    void removeElement(T t);
    T remove(int index);
    T removeFirst();
    T removeLast();

    /**
     * 交換兩個索引的元素的位置
     * @param i
     * @param j
     */
    void swap(int i,int j);
}

實現類java

public class DefaultArray<T> implements Array<T> {
    private static final int factor = 2;
    private T[] data;
    private int size;

    @SuppressWarnings("unchecked")
    public DefaultArray(int capacity) {
        data = (T[]) new Object[capacity];
        size = 0;
    }

    public DefaultArray() {
        this(20);
    }

    @SuppressWarnings("unchecked")
    public DefaultArray(T[] data) {
        this.data = (T[]) new Object[data.length + 1];
        for (int i = 0;i < data.length;i++) {
            this.data[i] = data[i];
        }
        this.size = data.length;
    }

    @Override
    public T[] getData() {
        return data;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public int getCapacity() {
        return data.length;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void addLast(T value) {
        add(size,value);
    }

    @Override
    public void addFirst(T value) {
        add(0,value);
    }

    @Override
    public void add(int index,T value) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("添加失敗,要求index >=0且index<=size");
        }
        for (int i = size;i > index;i--) {
            data[i] = data[i - 1];
        }
        data[index] = value;
        size++;
        if (size == data.length) {
            resize(getCapacity() * factor);
        }
    }

    @Override
    public T get(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("獲取失敗,index參數錯誤");
        }
        return data[index];
    }

    @Override
    public T getLast() {
        return get(size - 1);
    }

    @Override
    public T getFirst() {
        return get(0);
    }

    @Override
    public void set(int index,T value) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("設置失敗,index參數錯誤");
        }
        data[index] = value;
    }

    @Override
    public boolean contains(T value) {
        for (int i = 0;i < size;i++) {
            if (data[i].equals(value)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int find(T value) {
        for (int i = 0;i < size;i++) {
            if (data[i].equals(value)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public void removeElement(T value) {
        int index = find(value);
        if (index != -1) {
            remove(index);
        }
    }

    @Override
    public T remove(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("移除失敗,index參數錯誤");
        }
        T ret = data[index];
        for (int i = index;i < size;i++) {
            data[i] = data[i + 1];
        }
        size--;
        if (size <= getCapacity() / 4 && getCapacity() / 2 != 0) {
            resize(getCapacity() / factor);
        }
        return ret;
    }

    @Override
    public T removeFirst() {
        return remove(0);
    }

    @Override
    public T removeLast() {
        return remove(size - 1);
    }

    @Override
    public void swap(int i, int j) {
        if (i < 0 || i >= size || j < 0 || j >= size) {
            throw new IllegalArgumentException("索引非法");
        }
        T temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

    @SuppressWarnings("unchecked")
    private void resize(int capacity) {
        if (capacity == size) {
            capacity++;
        }
        T[] newData = (T[]) new Object[capacity];
        for (int i = 0;i < size;i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("DefaultArray(data=[");
        for (int i = 0;i < size - 1;i++) {
            builder.append(data[i] + ",");
        }
        builder.append(data[size -1] + "], size=" + size);
        return builder.toString();
    }
}

調用node

public class ArrayMain {
    public static void main(String[] args) {
        Array<Integer> array = new DefaultArray<>(3);
        array.addLast(66);
        array.addLast(99);
        array.addLast(88);
        System.out.println(array);
        array.add(1,77);
        array.addFirst(100);
        array.set(4,11);
        array.remove(2);
        System.out.println(array);
        System.out.println(array.get(3));
        array.remove(1);
        array.removeFirst();
        array.removeFirst();
        System.out.println(array);
    }
}

運行結果mysql

DefaultArray(data=[66,99,88], size=3
DefaultArray(data=[100,66,99,11], size=4
11
DefaultArray(data=[11], size=1算法

  • 時間複雜度的簡單示例

O(n) 線性關係計算sql

計算整形數組的和數據庫

@FunctionalInterface
public interface On {
    int compute(Integer[] nums);
}
public class SumOn {
    public int sum(Array<Integer> array,On on) {
        return on.compute(array.getData());
    }
}
public class Main {
    public static void main(String[] args) {
        Array<Integer> array = new DefaultArray<>(new Integer[]{1,2,3,4,5,6,7});
        SumOn sumOn = new SumOn();
        int sum = sumOn.sum(array, nums ->
                Stream.of(nums).reduce((x, y) -> x + y).get());
        System.out.println(sum);
    }
}

運行結果api

28數組

線性方程: T = c1*n + c2,而O(n)指的是忽略常數c1,c2。如如下關係,不管計算中有具體多少步執行安全

T = 2*n + 2       O(n)

T = 2000*n + 10000   O(n)     漸進時間複雜度

T = 1*n*n +0        O(n^2)    雖然這裏的常數很小,描述n趨近於無窮的狀況

T = 2*n*n + 300*n + 10     O(n^2)     低階項300*n會被忽略

對於Array各操做的時間複雜度

添加操做 

addLast(value)               O(1)

addFirst(value)               O(n)

add(index,value)            嚴格計算須要機率論,平均來講 O(n/2) = O(n)

從總體上看,添加操做是一個O(n)級別的時間複雜度

擴容

resize                             O(n)

刪除操做

removeLast()                 O(1)

removeFirst()                 O(n)

remove(index)                O(n/2) = O(n)

從總體上看,刪除操做是一個O(n)級別的時間複雜度

縮容

resize                            O(n)

修改操做

set(index,value)           O(1)

swap(i,j)                        O(1)

查找操做

get(index)                     O(1)

contains(value)            O(n)

find(value)                    O(n)

  • 數組棧封裝

棧是數組的子集,是一種後進先出的數據結構

接口

public interface Stack<T> {
    /**
     * 入棧
     * @param t
     */
    void push(T t);

    /**
     * 出棧
     * @return
     */
    T pop();

    /**
     * 獲取棧頂元素
     * @return
     */
    T peek();
    int getSize();
    boolean isEmpty();
}

實現類

@ToString
public class ArrayStack<T> implements Stack<T> {
    private Array<T> array;

    public ArrayStack(int capacity) {
        array = new DefaultArray<>(capacity);
    }

    public ArrayStack() {
        array = new DefaultArray<>();
    }

    @Override
    public void push(T value) {
        array.addLast(value);
    }

    @Override
    public T pop() {
        return array.removeLast();
    }

    @Override
    public T peek() {
        return array.getLast();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    public int getCapacity() {
        return array.getCapacity();
    }
}

調用

public class StackMain {
    public static void main(String[] args) {
        Stack<Integer> stack = new ArrayStack<>(5);
        for (int i = 0;i < 5;i++) {
            stack.push(i);
            System.out.println(stack);
        }
        stack.pop();
        System.out.println(stack);
    }
}

運行結果

ArrayStack(array=DefaultArray(data=[0], size=1)
ArrayStack(array=DefaultArray(data=[0,1], size=2)
ArrayStack(array=DefaultArray(data=[0,1,2], size=3)
ArrayStack(array=DefaultArray(data=[0,1,2,3], size=4)
ArrayStack(array=DefaultArray(data=[0,1,2,3,4], size=5)
ArrayStack(array=DefaultArray(data=[0,1,2,3], size=4)

對於Stack各操做的時間複雜度

  • push(value)              O(1) 均攤
  • Pop()                         O(1) 均攤
  • peek()                       O(1)
  • getSize()                   O(1)
  • isEmpty()                  O(1)

LeetCode算法題 https://leetcode-cn.com/problems/valid-parentheses/

這是LeetCode的第20題——有效的括號

咱們先用JDK的棧來完成,這裏的Stack爲java.util.Stack,該類繼承於Vector

public class JavaSolution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for (int i = 0;i < s.length();i++) {
            char c = s.charAt(i);
            if (c == '(' || c == '[' || c == '{') {
                stack.push(c);
            }else {
                if (stack.isEmpty()) {
                    return false;
                }
                char topChar = stack.pop();
                if (c == ')' && topChar != '(') {
                    return false;
                }
                if (c == ']' && topChar != '[') {
                    return false;
                }
                if (c == '}' && topChar != '{') {
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        JavaSolution solution = new JavaSolution();
        String patten = "{}[()]";
        System.out.println(solution.isValid(patten));
    }
}

運行結果

true

提交給LeetCode

固然咱們也可使用咱們本身封裝的棧對象來處理

public class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new ArrayStack<>();
        for (int i = 0;i < s.length();i++) {
            char c = s.charAt(i);
            if (c == '(' || c == '[' || c == '{') {
                stack.push(c);
            }else {
                if (stack.isEmpty()) {
                    return false;
                }
                char topChar = stack.pop();
                if (c == ')' && topChar != '(') {
                    return false;
                }
                if (c == ']' && topChar != '[') {
                    return false;
                }
                if (c == '}' && topChar != '{') {
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        String patten = "{[()]}";
        boolean valid = solution.isValid(patten);
        System.out.println(valid);
    }
}

運行結果

true

  • 數組隊列封裝

隊列的操做是數組的子集,只能從一端添加元素(隊尾),從另外一端取出元素(隊首),是一種先進先出的數據結構

接口

public interface Queue<T> {
    /**
     * 入隊
     * @param t
     */
    void enqueue(T t);

    /**
     * 出隊
     * @return
     */
    T dequeue();

    /**
     * 獲取隊首元素
     * @return
     */
    T getFront();
    int getSize();
    boolean isEmpty();
}

實現類

@ToString
public class ArrayQueue<T> implements Queue<T> {
    private Array<T> array;

    public ArrayQueue(int capacity) {
        array = new DefaultArray<>(capacity);
    }

    public ArrayQueue() {
        array = new DefaultArray<>();
    }

    @Override
    public void enqueue(T value) {
        array.addLast(value);
    }

    @Override
    public T dequeue() {
        return array.removeFirst();
    }

    @Override
    public T getFront() {
        return array.getFirst();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    public int getCapaticy() {
        return array.getCapacity();
    }
}

調用

public class QueueMain {
    public static void main(String[] args) {
        Queue<Integer> queue = new ArrayQueue<>(10);
        for (int i = 0;i < 10;i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

運行結果

ArrayQueue(array=DefaultArray(data=[0], size=1)
ArrayQueue(array=DefaultArray(data=[0,1], size=2)
ArrayQueue(array=DefaultArray(data=[0,1,2], size=3)
ArrayQueue(array=DefaultArray(data=[1,2], size=2)
ArrayQueue(array=DefaultArray(data=[1,2,3], size=3)
ArrayQueue(array=DefaultArray(data=[1,2,3,4], size=4)
ArrayQueue(array=DefaultArray(data=[1,2,3,4,5], size=5)
ArrayQueue(array=DefaultArray(data=[2,3,4,5], size=4)
ArrayQueue(array=DefaultArray(data=[2,3,4,5,6], size=5)
ArrayQueue(array=DefaultArray(data=[2,3,4,5,6,7], size=6)
ArrayQueue(array=DefaultArray(data=[2,3,4,5,6,7,8], size=7)
ArrayQueue(array=DefaultArray(data=[3,4,5,6,7,8], size=6)
ArrayQueue(array=DefaultArray(data=[3,4,5,6,7,8,9], size=7)
ArrayQueue(array=DefaultArray(data=[3,4,5,6,7,8,9,10], size=8)

對於ArrayQueue操做的時間複雜度

enqueue(value)                      O(1) 均攤

dequeue()                               O(n)

getFront()                               O(1)

getSize()                                 O(1)

isEmpty()                                O(1)

鑑於數組隊列每一次出隊都會將隊列中的全部元素前移,時間複雜度爲O(n),因此爲了剋制這種狀況,咱們須要構建一個循環隊列,把數組當作首尾相接的一個環,出隊時無需移動隊列中的元素。此時將再也不復用Array的實現。

  • 循環隊列封裝

實現類

public class LoopQueue<T> implements Queue<T> {
    private T[] data;
    /**
     * 循環隊列隊首
     */
    private int front;
    /**
     * 循環隊列隊尾
     */
    private int tail;
    /**
     * 循環隊列成員數量
     */
    private int size;
    /**
     * 擴容因子
     */
    private static final int factor = 2;

    @SuppressWarnings("unchecked")
    public LoopQueue(int capacity) {
        data = (T[]) new Object[capacity + 1];
        front = tail = 0;
        size = 0;
    }

    public LoopQueue() {
        this(20);
    }

    public int getCapacity() {
        return data.length - 1;
    }

    @Override
    public void enqueue(T value) {
        //判斷隊列是否已滿
        if ((tail + 1) % data.length == front) {
            resize(getCapacity() * factor);
        }
        data[tail] = value;
        tail = (tail + 1) % data.length;
        size++;
    }

    @Override
    public T dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("沒法從空隊列出隊");
        }
        T ret = data[front];
        data[front] = null;
        front = (front + 1) % data.length;
        size--;
        if (size <= getCapacity() / 4 && getCapacity() / 2 != 0) {
            resize(getCapacity() / factor);
        }
        return ret;
    }

    @Override
    public T getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("隊列爲空");
        }
        return data[front];
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return front == tail;
    }

    @SuppressWarnings("unchecked")
    private void resize(int capacity) {
        T[] newData = (T[]) new Object[capacity + 1];
        for (int i = 0;i < size;i++) {
            //將老數組的元素放入新數組時,將從新將0定位新數組的front
            newData[i] = data[(front + i) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("LoopQueue{data=[");
        for (int i = 0;i < size - 1;i++) {
            builder.append(data[(front + i) % data.length] + ",");
        }
        builder.append(data[(tail - 1) % data.length] + "], size=" + size);
        return builder.toString();
    }
}

調用

public class QueueMain {
    public static void main(String[] args) {
        Queue<Integer> queue = new LoopQueue<>(7);
        for (int i = 0;i <= 10;i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

運行結果

LoopQueue{data=[0], size=1
LoopQueue{data=[0,1], size=2
LoopQueue{data=[0,1,2], size=3
LoopQueue{data=[1,2], size=2
LoopQueue{data=[1,2,3], size=3
LoopQueue{data=[1,2,3,4], size=4
LoopQueue{data=[1,2,3,4,5], size=5
LoopQueue{data=[2,3,4,5], size=4
LoopQueue{data=[2,3,4,5,6], size=5
LoopQueue{data=[2,3,4,5,6,7], size=6
LoopQueue{data=[2,3,4,5,6,7,8], size=7
LoopQueue{data=[3,4,5,6,7,8], size=6
LoopQueue{data=[3,4,5,6,7,8,9], size=7
LoopQueue{data=[3,4,5,6,7,8,9,10], size=8

對於LoopQueue操做的時間複雜度

enqueue(value)                        O(1) 均攤

dequeue()                                 O(1) 均攤

getFront()                                  O(1)

getSize()                                    O(1)

isEmpty()                                   O(1)

兩種隊列的性能測試

public class TestQueueMain {
    private static double testQueue(Queue<Integer> q,int opCount) {
        long startTime = System.nanoTime();
        Random random = new Random();
        for (int i = 0;i < opCount;i++) {
            q.enqueue(random.nextInt(Integer.MAX_VALUE));
        }
        for (int i = 0;i < opCount;i++) {
            q.dequeue();
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int opCount = 100000;
        Queue<Integer> arrayQueue = new ArrayQueue<>();
        double time1 = testQueue(arrayQueue,opCount);
        System.out.println("ArrayQueue,time: " + time1 + " s");

        Queue<Integer> loopQueue = new LoopQueue<>();
        double time2 = testQueue(loopQueue,opCount);
        System.out.println("LoopQueue,time: " + time2 + " s");
    }
}

測試結果

ArrayQueue,time: 2.737708686 s
LoopQueue,time: 0.011393786 s

  • 單向鏈表封裝

以前的動態數組,棧,隊列都是底層依託靜態數組,靠resize()解決固定容量問題,而鏈表是真正的動態數據結構,不須要處理固定容量的問題。但缺點也很明顯,喪失了隨機訪問的能力。不適合用於索引有語義的狀況。

接口

public interface List<T> {
    int getSize();
    boolean isEmpty();
    void addFirst(T t);
    void add(int index,T t);
    void addLast(T t);
    T get(int index);
    T getFirst();
    T getLast();
    void set(int index,T t);
    boolean contains(T t);
    T remove(int index);
    T removeFirst();
    T removeLast();
    void removeElement(T t);
}

實現類

public class LinkedList<T> implements List<T> {
    @AllArgsConstructor
    @Data
    private class Node {
        private T element;
        private Node next;

        public Node(T element) {
            this(element,null);
        }

        public Node() {
            this(null,null);
        }

        @Override
        public String toString() {
            return element.toString();
        }
    }

    /**
     * 鏈表虛擬頭節點
     */
    private Node dummyHead;
    private int size;

    public LinkedList() {
        dummyHead = new Node();
        size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void add(int index,T value) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("添加錯誤,非法索引");
        }

        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.getNext();
        }
        prev.setNext(new Node(value, prev.getNext()));
        size++;

    }

    @Override
    public void addFirst(T value) {
        add(0,value);
    }

    @Override
    public void addLast(T value) {
        add(size,value);
    }

    @Override
    public T get(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("獲取錯誤,非法索引");
        }
        Node cur = dummyHead.getNext();
        for (int i = 0;i < index;i++) {
            cur = cur.getNext();
        }
        return cur.getElement();
    }

    @Override
    public T getFirst() {
        return get(0);
    }

    @Override
    public T getLast() {
        return get(size - 1);
    }

    @Override
    public void set(int index, T value) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("設置錯誤,非法索引");
        }
        Node cur = dummyHead.getNext();
        for (int i = 0;i < index;i++) {
            cur = cur.getNext();
        }
        cur.setElement(value);
    }

    @Override
    public boolean contains(T value) {
        Node cur = dummyHead.getNext();
        while (cur != null) {
            if (cur.getElement().equals(value)) {
                return true;
            }
            cur = cur.getNext();
        }
        return false;
    }

    @Override
    public T remove(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("刪除錯誤,非法索引");
        }
        Node prev = dummyHead;
        for (int i = 0;i < index;i++) {
            prev = prev.getNext();
        }
        Node retNode = prev.getNext();
        prev.setNext(retNode.getNext());
        retNode.setNext(null);
        size--;
        return retNode.getElement();
    }

    @Override
    public T removeFirst() {
        return remove(0);
    }

    @Override
    public T removeLast() {
        return remove(size - 1);
    }

    @Override
    public void removeElement(T value) {
        Node pre = dummyHead;
        while (pre.getNext() != null) {
            if (value.equals(pre.getNext().getElement())) {
                Node delNode = pre.getNext();
                pre.setNext(delNode.getNext());
                delNode.setNext(null);
                size--;
                break;
            }
            pre = pre.getNext();
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        Node cur = dummyHead.getNext();
        while (cur != null) {
            builder.append(cur + "->");
            cur = cur.getNext();
        }
        builder.append("NULL");
        return builder.toString();
    }
}

調用

public class ListMain {
    public static void main(String[] args) {
        List<Integer> linkedList = new LinkedList<>();
        for (int i = 0;i < 5;i++) {
            linkedList.add(i,i);
            System.out.println(linkedList);
        }
        linkedList.add(3,666);
        System.out.println(linkedList);

        linkedList.addLast(777);
        System.out.println(linkedList);

        linkedList.set(4,888);
        System.out.println(linkedList);

        System.out.println(linkedList.get(5));
        System.out.println(linkedList.getFirst());
        System.out.println(linkedList.getLast());
        System.out.println(linkedList.contains(3));

        linkedList.removeLast();
        System.out.println(linkedList);

        linkedList.remove(4);
        System.out.println(linkedList);

        linkedList.removeFirst();
        System.out.println(linkedList);

        linkedList.removeLast();
        System.out.println(linkedList);
        linkedList.removeElement(2);
        System.out.println(linkedList);
        System.out.println(linkedList.getLast());
    }
}

運行結果

0->NULL
0->1->NULL
0->1->2->NULL
0->1->2->3->NULL
0->1->2->3->4->NULL
0->1->2->666->3->4->NULL
0->1->2->666->3->4->777->NULL
0->1->2->666->888->4->777->NULL
4
0
777
false
0->1->2->666->888->4->NULL
0->1->2->666->4->NULL
1->2->666->4->NULL
1->2->666->NULL
1->666->NULL
666

LinkedList各操做的時間複雜度

添加操做                                O(n)

addLast(value)                      O(n)

addFirst(value)                      O(1)

add(index,value)                    O(n/2) = O(n)

刪除操做                                O(n)

removeLast()                         O(n)

removeFirst()                         O(1)

remove(index)                       O(n/2) = O(n)

removeElement(value)         O(n)

修改操做

set(index,value)                    O(n)

查找操做                                O(n)

get(index)                              O(n)

contains(value)                     O(n)

因爲鏈表中對鏈表頭的各操做都是O(1)的,對應於棧這種數據結構,是很是方便的,並且也無需擴容這一律念。

  • 鏈表棧封裝

實現類

@ToString
public class LinkedListStack<T> implements Stack<T> {
    private List<T> list;

    public LinkedListStack() {
        list = new LinkedList<>();
    }

    @Override
    public void push(T value) {
        list.addFirst(value);
    }

    @Override
    public T pop() {
        return list.removeFirst();
    }

    @Override
    public T peek() {
        return list.getFirst();
    }

    @Override
    public int getSize() {
        return list.getSize();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }
}

調用

public class StackMain {
    public static void main(String[] args) {
        Stack<Integer> stack = new LinkedListStack<>();
        for (int i = 0;i < 5;i++) {
            stack.push(i);
            System.out.println(stack);
        }
        stack.pop();
        System.out.println(stack);
    }
}

運行結果

LinkedListStack(list=0->NULL)
LinkedListStack(list=1->0->NULL)
LinkedListStack(list=2->1->0->NULL)
LinkedListStack(list=3->2->1->0->NULL)
LinkedListStack(list=4->3->2->1->0->NULL)
LinkedListStack(list=3->2->1->0->NULL)

兩種棧的性能測試

public class TestStackMain {
    private static double testStack(Stack<Integer> stack,int opCount) {
        long startTime = System.nanoTime();
        Random random = new Random();
        for (int i = 0;i < opCount;i++) {
            stack.push(random.nextInt(Integer.MAX_VALUE));
        }
        for (int i = 0;i < opCount;i++) {
            stack.pop();
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int opCount = 10000000;
        Stack<Integer> arrayStack = new ArrayStack<>();
        double time1 = testStack(arrayStack,opCount);
        System.out.println("ArrayStack,time: " + time1 + " s");

        Stack<Integer> linkedListStack = new LinkedListStack<>();
        double time2 = testStack(linkedListStack,opCount);
        System.out.println("LinkedListStack,time: " + time2 + " s");
    }
}

測試結果

ArrayStack,time: 0.891863504 s
LinkedListStack,time: 3.731664658 s

對於這個測試結果來講,LinkedListStack要明顯高於ArrayStack,這主要是Java的分配內存的特性決定,由於LinkedListStack有更多的new操做,要不斷的分配內存空間給新的對象node。但這裏要說明的是,其實它們兩個在算法層面的時間複雜度是一致的

  • 鏈表隊列封裝

因爲隊列的特性,一端進,一端出,因此咱們要添加一個尾節點,因此LinkedList沒法複用。

實現類

public class LinkedListQueue<T> implements Queue<T> {
    @AllArgsConstructor
    @Data
    private class Node {
        private T element;
        private Node next;

        public Node(T element) {
            this(element,null);
        }

        public Node() {
            this(null,null);
        }

        @Override
        public String toString() {
            return element.toString();
        }
    }

    /**
     * 隊列頭節點
     */
    private Node head;
    /**
     * 隊列尾節點
     */
    private Node tail;
    /**
     * 隊列元素個數
     */
    private int size;

    public LinkedListQueue() {
        head = null;
        tail = null;
        size = 0;
    }

    @Override
    public void enqueue(T value) {
        //當整個隊列爲空的時候
        if (tail == null) {
            tail = new Node(value);
            head = tail;
        }else { //從尾部入隊
            tail.setNext(new Node(value));
            tail = tail.getNext();
        }
        size++;
    }

    @Override
    public T dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("沒法從空隊列出隊");
        }
        //從頭部出隊
        Node retNode = head;
        head = head.getNext();
        retNode.setNext(null);
        if (head == null) {
            tail = null;
        }
        size--;
        return retNode.getElement();
    }

    @Override
    public T getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("隊列爲空");
        }
        return head.getElement();
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Queue: front ");
        Node cur = head;
        while (cur != null) {
            builder.append(cur + "->");
            cur = cur.getNext();
        }
        builder.append("NULL tail");
        return builder.toString();
    }
}

調用

public class QueueMain {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedListQueue<>();
        for (int i = 0;i <= 10;i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

運行結果

Queue: front 0->NULL tail
Queue: front 0->1->NULL tail
Queue: front 0->1->2->NULL tail
Queue: front 1->2->NULL tail
Queue: front 1->2->3->NULL tail
Queue: front 1->2->3->4->NULL tail
Queue: front 1->2->3->4->5->NULL tail
Queue: front 2->3->4->5->NULL tail
Queue: front 2->3->4->5->6->NULL tail
Queue: front 2->3->4->5->6->7->NULL tail
Queue: front 2->3->4->5->6->7->8->NULL tail
Queue: front 3->4->5->6->7->8->NULL tail
Queue: front 3->4->5->6->7->8->9->NULL tail
Queue: front 3->4->5->6->7->8->9->10->NULL tail

對三種隊列進行性能測試

public class TestQueueMain {
    private static double testQueue(Queue<Integer> q,int opCount) {
        long startTime = System.nanoTime();
        Random random = new Random();
        for (int i = 0;i < opCount;i++) {
            q.enqueue(random.nextInt(Integer.MAX_VALUE));
        }
        for (int i = 0;i < opCount;i++) {
            q.dequeue();
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int opCount = 100000;
        Queue<Integer> arrayQueue = new ArrayQueue<>();
        double time1 = testQueue(arrayQueue,opCount);
        System.out.println("ArrayQueue,time: " + time1 + " s");

        Queue<Integer> loopQueue = new LoopQueue<>();
        double time2 = testQueue(loopQueue,opCount);
        System.out.println("LoopQueue,time: " + time2 + " s");

        Queue<Integer> linkedListQueue = new LinkedListQueue<>();
        double time3 = testQueue(linkedListQueue,opCount);
        System.out.println("LinkedListQueue,time: " + time3 + " s");
    }
}

測試結果

ArrayQueue,time: 2.727521755 s
LoopQueue,time: 0.011994964 s
LinkedListQueue,time: 0.008463007 s

可是若是咱們將opCount設爲1千萬,arrayQueue不參與,由於arrayQueue是O(n)級別的,時間過長,沒法完成運算

public class TestQueueMain {
    private static double testQueue(Queue<Integer> q,int opCount) {
        long startTime = System.nanoTime();
        Random random = new Random();
        for (int i = 0;i < opCount;i++) {
            q.enqueue(random.nextInt(Integer.MAX_VALUE));
        }
        for (int i = 0;i < opCount;i++) {
            q.dequeue();
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int opCount = 10000000;
//        Queue<Integer> arrayQueue = new ArrayQueue<>();
//        double time1 = testQueue(arrayQueue,opCount);
//        System.out.println("ArrayQueue,time: " + time1 + " s");

        Queue<Integer> loopQueue = new LoopQueue<>();
        double time2 = testQueue(loopQueue,opCount);
        System.out.println("LoopQueue,time: " + time2 + " s");

        Queue<Integer> linkedListQueue = new LinkedListQueue<>();
        double time3 = testQueue(linkedListQueue,opCount);
        System.out.println("LinkedListQueue,time: " + time3 + " s");
    }
}

測試結果

LoopQueue,time: 1.198824936 s
LinkedListQueue,time: 3.531655449 s

則此處LinkedListQueue比LoopQueue高的緣由在以前的棧測試中已經說過了。經過查看GC日誌-XX:+PrintGCDetails,咱們能夠看到

[GC (Allocation Failure) [PSYoungGen: 65536K->10720K(76288K)] 65536K->47444K(251392K), 0.0399149 secs] [Times: user=0.47 sys=0.03, real=0.04 secs]
[GC (Allocation Failure) [PSYoungGen: 76256K->10736K(141824K)] 112980K->102884K(316928K), 0.0615931 secs] [Times: user=0.74 sys=0.03, real=0.06 secs]
[GC (Allocation Failure) [PSYoungGen: 128805K->10720K(141824K)] 220954K->185957K(317440K), 0.1094546 secs] [Times: user=1.09 sys=0.07, real=0.11 secs]
[Full GC (Ergonomics) [PSYoungGen: 10720K->0K(141824K)] [ParOldGen: 175237K->83146K(279552K)] 185957K->83146K(421376K), [Metaspace: 3396K->3396K(1056768K)], 0.6877482 secs] [Times: user=4.42 sys=0.04, real=0.68 secs]
LoopQueue,time: 1.213829799 s
[GC (Allocation Failure) [PSYoungGen: 131072K->10752K(196608K)] 214218K->168218K(476160K), 0.4136930 secs] [Times: user=5.33 sys=0.01, real=0.42 secs]
[GC (Allocation Failure) [PSYoungGen: 196608K->10752K(272896K)] 354074K->354714K(616960K), 0.9071643 secs] [Times: user=11.66 sys=0.08, real=0.91 secs]
[Full GC (Ergonomics) [PSYoungGen: 10752K->0K(272896K)] [ParOldGen: 343962K->271533K(605696K)] 354714K->271533K(878592K), [Metaspace: 3479K->3479K(1056768K)], 1.8924275 secs] [Times: user=24.48 sys=0.03, real=1.89 secs]
LinkedListQueue,time: 3.412228277 s
Heap
 PSYoungGen      total 272896K, used 131467K [0x000000076ab00000, 0x0000000792600000, 0x00000007c0000000)
  eden space 262144K, 50% used [0x000000076ab00000,0x0000000772b62e80,0x000000077ab00000)
  from space 10752K, 0% used [0x000000077ab00000,0x000000077ab00000,0x000000077b580000)
  to   space 185344K, 0% used [0x0000000787100000,0x0000000787100000,0x0000000792600000)
 ParOldGen       total 605696K, used 271533K [0x00000006c0000000, 0x00000006e4f80000, 0x000000076ab00000)
  object space 605696K, 44% used [0x00000006c0000000,0x00000006d092b670,0x00000006e4f80000)
 Metaspace       used 3486K, capacity 4566K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 390K, committed 512K, reserved 1048576K

經過GC日誌,咱們能夠看到有大量的對象進入了老年代,並無在年輕代被回收,以致於發生了全GC。咱們調整JVM內存來看一下-Xmx1G -Xms1G

[GC (Allocation Failure) [PSYoungGen: 260301K->43511K(305664K)] 260301K->83257K(1005056K), 0.0813630 secs] [Times: user=0.99 sys=0.04, real=0.08 secs]
LoopQueue,time: 0.443129398 s
[GC (Allocation Failure) [PSYoungGen: 305655K->43495K(305664K)] 345401K->254481K(1005056K), 1.0359483 secs] [Times: user=12.20 sys=0.14, real=1.04 secs]
LinkedListQueue,time: 1.261171544 s
Heap
 PSYoungGen      total 305664K, used 223003K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 262144K, 68% used [0x00000007aab00000,0x00000007b5a4d0e8,0x00000007bab00000)
  from space 43520K, 99% used [0x00000007bd580000,0x00000007bfff9dc8,0x00000007c0000000)
  to   space 43520K, 0% used [0x00000007bab00000,0x00000007bab00000,0x00000007bd580000)
 ParOldGen       total 699392K, used 210985K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000)
  object space 699392K, 30% used [0x0000000780000000,0x000000078ce0a708,0x00000007aab00000)
 Metaspace       used 3488K, capacity 4566K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 390K, committed 512K, reserved 1048576K

經過調整JVM內存,咱們能夠看到全GC消失了,所使用的時間大幅度減小。可是咱們能夠看到eden區的使用空間太小,只有68%,因此咱們要調整新生代的內存分配比例

-XX:+PrintGCDetails -Xmx1G -Xms1G -XX:+UseParallelGC -XX:+UseCompressedOops -XX:SurvivorRatio=4 -XX:ParallelGCThreads=8

[GC (Allocation Failure) [PSYoungGen: 233472K->57831K(291328K)] 233472K->174728K(990720K), 0.1696696 secs] [Times: user=1.25 sys=0.09, real=0.17 secs]
LoopQueue,time: 0.521021115 s
[GC (Allocation Failure) [PSYoungGen: 291303K->57831K(291328K)] 408200K->279688K(990720K), 0.6708571 secs] [Times: user=5.25 sys=0.10, real=0.67 secs]
LinkedListQueue,time: 0.866029237 s
Heap
 PSYoungGen      total 291328K, used 290384K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 233472K, 99% used [0x00000007aab00000,0x00000007b8e1a330,0x00000007b8f00000)
  from space 57856K, 99% used [0x00000007bc780000,0x00000007bfff9dc8,0x00000007c0000000)
  to   space 57856K, 0% used [0x00000007b8f00000,0x00000007b8f00000,0x00000007bc780000)
 ParOldGen       total 699392K, used 221857K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000)
  object space 699392K, 31% used [0x0000000780000000,0x000000078d8a84f8,0x00000007aab00000)
 Metaspace       used 3488K, capacity 4566K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 390K, committed 512K, reserved 1048576K

經過調整eden區和survivor區(包括from和to)的比例,咱們能夠看到eden區的使用效率提高到了99%,而LinkedListQueue的運行時間也由1.261171544 s降到了0.866029237 s。

LeetCode算法題https://leetcode-cn.com/problems/remove-linked-list-elements/submissions/

這是LeetCode的第203題,移除鏈表元素

刪除鏈表中等於給定值 val 的全部節點。

示例:

輸入: 1->2->6->3->4->5->6, val = 6
輸出: 1->2->3->4->5

咱們先定義一個ListNode

public class ListNode {
    public int val;
    public ListNode next;
    public ListNode(int x) {
        val = x;
    }
}
public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //移除全部可能值爲val的頭部節點
        while (head != null && head.val == val) {
            ListNode delNode = head;
            head = head.next;
            delNode.next = null;
        }
        if (head == null) {
            return null;
        }
        //移除全部可能值爲val的中間節點
        ListNode prev = head;
        while (prev.next != null) {
            if (prev.next.val == val) {
                ListNode delNode = prev.next;
                prev.next = delNode.next;
                delNode.next = null;
            }else {
                prev = prev.next;
            }
        }
        return head;
    }
}

提交後,結果以下

固然除了特殊處理頭節點之外,咱們還能夠創建虛擬頭節點

public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //創建虛擬頭節點
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        //移除全部可能值爲val的中間節點
        ListNode prev = dummyHead;
        while (prev.next != null) {
            if (prev.next.val == val) {
                ListNode delNode = prev.next;
                prev.next = delNode.next;
                delNode.next = null;
            }else {
                prev = prev.next;
            }
        }
        return dummyHead.next;
    }
}

提交給LeetCode

本身實現一個測試用例

改寫ListNode

public class ListNode {
    public int val;
    public ListNode next;
    public ListNode(int x) {
        val = x;
    }

    /**
     * 經過一個數組轉化成一個鏈表
     * @param arr
     */
    public ListNode(int[] arr) {
        if (arr == null || arr.length == 0) {
            throw new IllegalArgumentException("數組不能爲空");
        }
        this.val = arr[0];
        ListNode cur = this;
        for (int i = 1;i < arr.length;i++) {
            cur.next = new ListNode(arr[i]);
            cur = cur.next;
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        ListNode cur = this;
        while (cur != null) {
            builder.append(cur.val + "->");
            cur = cur.next;
        }
        builder.append("NULL");
        return builder.toString();
    }
}
public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //創建虛擬頭節點
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        //移除全部可能值爲val的中間節點
        ListNode prev = dummyHead;
        while (prev.next != null) {
            if (prev.next.val == val) {
                ListNode delNode = prev.next;
                prev.next = delNode.next;
                delNode.next = null;
            }else {
                prev = prev.next;
            }
        }
        return dummyHead.next;
    }

    public static void main(String[] args) {
        int[] values = {1,2,6,3,4,5,6};
        ListNode node = new ListNode(values);
        System.out.println(node);
        Solution solution = new Solution();
        ListNode listNode = solution.removeElements(node, 6);
        System.out.println(listNode);
    }
}

測試結果

1->2->6->3->4->5->6->NULL
1->2->3->4->5->NULL

  • 遞歸

咱們先來看一個求和的遞歸

public class Sum {
    private static int sum(int[] arr) {
        return sum(arr,0);
    }

    private static int sum(int[] arr,int l) {
        //求解最基本的問題
        if (l == arr.length) {
            return 0;
        }
        //把原問題轉化成更小的問題
        return arr[l] + sum(arr,l + 1);
    }

    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7,8,9};
        System.out.println(sum(arr));
    }
}

運行結果

45

  • 鏈表的遞歸性

鏈表具備自然的遞歸性,它能夠表示爲頭節點+少了頭節點的鏈表。

使用遞歸來求解LeetCode的第203題

public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //求解最基本的問題
        if (head == null) {
            return null;
        }
        //把原問題轉化成更小的問題
        //此時把鏈表拆分紅頭節點+一個不包含頭節點的鏈表
        ListNode node = removeElements(head.next, val);
        //拼接頭節點
        if (head.val == val) {
            return node;
        }else {
            head.next = node;
            return head;
        }
    }

    public static void main(String[] args) {
        int[] values = {1,2,6,3,4,5,6};
        ListNode node = new ListNode(values);
        System.out.println(node);
        Solution solution = new Solution();
        ListNode listNode = solution.removeElements(node, 6);
        System.out.println(listNode);
    }
}

運行結果

1->2->6->3->4->5->6->NULL
1->2->3->4->5->NULL

在LeetCode上提交

最後改寫成一個簡潔的表述

public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //求解最基本的問題
        if (head == null) {
            return null;
        }
        //把原問題轉化成更小的問題
        //此時把鏈表拆分紅頭節點+一個不包含頭節點的鏈表
        head.next = removeElements(head.next, val);
        //是否拼接頭節點
        return head.val == val?head.next : head;
    }

    public static void main(String[] args) {
        int[] values = {1,2,6,3,4,5,6};
        ListNode node = new ListNode(values);
        System.out.println(node);
        Solution solution = new Solution();
        ListNode listNode = solution.removeElements(node, 6);
        System.out.println(listNode);
    }
}

運行結果

1->2->6->3->4->5->6->NULL
1->2->3->4->5->NULL

調試鏈表的遞歸性

public class Solution {
    public ListNode removeElements(ListNode head, int val,int depth) {
        String depthString = generateDepthString(depth);
        System.out.print(depthString);
        System.out.println("調用: 刪除 " + val + "在 " + head + "中");
        //求解最基本的問題
        if (head == null) {
            System.out.print(depthString);
            System.out.println("返回:" + head);
            return null;
        }
        //把原問題轉化成更小的問題
        //此時把鏈表拆分紅頭節點+一個不包含頭節點的鏈表
        ListNode node = removeElements(head.next, val,depth + 1);
        System.out.print(depthString);
        System.out.println("刪除 " + val + "以後: " + node);
        //是否拼接頭節點
        ListNode ret;
        if (head.val == val) {
            ret = node;
        }else {
            head.next = node;
            ret = head;
        }
        System.out.print(depthString);
        System.out.println("返回: " + ret);
        return ret;
    }

    private String generateDepthString(int depth) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0;i < depth;i++) {
            builder.append("--");
        }
        return builder.toString();
    }

    public static void main(String[] args) {
        int[] values = {1,2,6,3,4,5,6};
        ListNode node = new ListNode(values);
        System.out.println(node);
        Solution solution = new Solution();
        ListNode listNode = solution.removeElements(node, 6,0);
        System.out.println(listNode);
    }
}

運行結果

1->2->6->3->4->5->6->NULL
調用: 刪除 6在 1->2->6->3->4->5->6->NULL中
--調用: 刪除 6在 2->6->3->4->5->6->NULL中
----調用: 刪除 6在 6->3->4->5->6->NULL中
------調用: 刪除 6在 3->4->5->6->NULL中
--------調用: 刪除 6在 4->5->6->NULL中
----------調用: 刪除 6在 5->6->NULL中
------------調用: 刪除 6在 6->NULL中
--------------調用: 刪除 6在 null中
--------------返回:null
------------刪除 6以後: null
------------返回: null
----------刪除 6以後: null
----------返回: 5->NULL
--------刪除 6以後: 5->NULL
--------返回: 4->5->NULL
------刪除 6以後: 4->5->NULL
------返回: 3->4->5->NULL
----刪除 6以後: 3->4->5->NULL
----返回: 3->4->5->NULL
--刪除 6以後: 3->4->5->NULL
--返回: 2->3->4->5->NULL
刪除 6以後: 2->3->4->5->NULL
返回: 1->2->3->4->5->NULL
1->2->3->4->5->NULL

  • 單向鏈表的遞歸改寫

實現類

public class RecursiveLinkedList<T> implements List<T> {
    @AllArgsConstructor
    @Data
    private class Node {
        private T element;
        private Node next;

        public Node(T element) {
            this(element,null);
        }

        public Node() {
            this(null,null);
        }

        @Override
        public String toString() {
            return element.toString();
        }
    }

    /**
     * 鏈表虛擬頭節點
     */
    private Node dummyHead;
    private int size;

    public RecursiveLinkedList() {
        dummyHead = new Node();
        size = 0;
    }
    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void addFirst(T value) {
        add(0,value);
    }

    @Override
    public void add(int index, T value) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("添加錯誤,非法索引");
        }
        dummyHead = add(-1,index,dummyHead,value);
    }

    private Node add(int i,int index,Node node,T value) {
        if (i == index - 1) {
            size++;
            node.setNext(new Node(value,node.getNext()));
            return node;
        }
        node.setNext(add(i + 1,index,node.getNext(),value));
        return node;
    }

    @Override
    public void addLast(T value) {
        add(size,value);
    }

    @Override
    public T get(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("查找錯誤,非法索引");
        }
        return get(0,index,dummyHead.getNext());
    }

    private T get(int i,int index,Node node) {
        if (i == index) {
            return node.getElement();
        }
        return get(i + 1,index,node.getNext());
    }

    @Override
    public T getFirst() {
        return get(0);
    }

    @Override
    public T getLast() {
        return get(size - 1);
    }

    @Override
    public void set(int index, T value) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("更新錯誤,非法索引");
        }
        set(0,index,dummyHead.getNext(),value);
    }

    private void set(int i,int index,Node node,T value) {
        if (i == index) {
            node.setElement(value);
            return;
        }
        set(i + 1,index,node.getNext(),value);
    }

    @Override
    public boolean contains(T value) {
        return contains(dummyHead.getNext(),value);
    }

    private boolean contains(Node node,T value) {
        if (node == null) {
            return false;
        }
        if (node.getElement().equals(value)) {
            return true;
        }
        return contains(node.getNext(),value);

    }

    @Override
    public T remove(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("刪除錯誤,非法索引");
        }
        return remove(-1,index,dummyHead).getElement();
    }

    private Node remove(int i,int index,Node node) {
        if (i == index - 1) {
            Node ret = node.getNext();
            node.setNext(ret.getNext());
            ret.setNext(null);
            size--;
            return ret;
        }
        return remove(i + 1,index,node.getNext());
    }

    @Override
    public T removeFirst() {
        return remove(0);
    }

    @Override
    public T removeLast() {
        return remove(size - 1);
    }

    @Override
    public void removeElement(T value) {
        dummyHead = removeElement(dummyHead,value);
    }

    private Node removeElement(Node node,T value) {
        if (node.getNext() == null) {
            return null;
        }
        else {
            if (value.equals(node.getNext().getElement())) {
                Node delNode = node.getNext();
                node.setNext(delNode.getNext());
                delNode.setNext(null);
                size--;
                return node;
            }
        }
        node.setNext(removeElement(node.getNext(),value));
        return node;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        Node cur = dummyHead.getNext();
        while (cur != null) {
            builder.append(cur + "->");
            cur = cur.getNext();
        }
        builder.append("NULL");
        return builder.toString();
    }
}

調用

public class ListMain {
    public static void main(String[] args) {
        List<Integer> linkedList = new RecursiveLinkedList<>();
        for (int i = 0;i < 5;i++) {
            linkedList.add(i,i);
            System.out.println(linkedList);
        }
        linkedList.add(3,666);
        System.out.println(linkedList);

        linkedList.addLast(777);
        System.out.println(linkedList);

        linkedList.set(4,888);
        System.out.println(linkedList);

        System.out.println(linkedList.get(5));
        System.out.println(linkedList.getFirst());
        System.out.println(linkedList.getLast());
        System.out.println(linkedList.contains(3));

        linkedList.removeLast();
        System.out.println(linkedList);

        linkedList.remove(4);
        System.out.println(linkedList);

        linkedList.removeFirst();
        System.out.println(linkedList);

        linkedList.removeLast();
        System.out.println(linkedList);
        linkedList.removeElement(2);
        System.out.println(linkedList);
        System.out.println(linkedList.getLast());
    }
}

運行結果

0->NULL
0->1->NULL
0->1->2->NULL
0->1->2->3->NULL
0->1->2->3->4->NULL
0->1->2->666->3->4->NULL
0->1->2->666->3->4->777->NULL
0->1->2->666->888->4->777->NULL
4
0
777
false
0->1->2->666->888->4->NULL
0->1->2->666->4->NULL
1->2->666->4->NULL
1->2->666->NULL
1->666->NULL
666

  • 雙向鏈表的封裝

實現類

public class MutualLinkedList<T> implements List<T> {
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    private class Node {
        private T element;
        private Node prev;
        private Node next;

        public Node(T element) {
            this(element,null,null);
        }

        @Override
        public String toString() {
            return element.toString();
        }
    }

    private Node dummyHead;
    private Node tail;
    private int size;

    public MutualLinkedList() {
        dummyHead = new Node();
        tail = null;
        size = 0;
    }
    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void addFirst(T value) {
        add(0,value);
    }

    @Override
    public void add(int index, T value) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("添加錯誤,非法索引");
        }
        if (index <= size / 2 + 1) {
            Node prev = dummyHead;
            for (int i = 0; i < index; i++) {
                prev = prev.getNext();
            }
            Node node = new Node(value,prev,prev.getNext());
            if (prev.getNext() != null) {
                prev.getNext().setPrev(node);
            }else {
                tail = node;
            }
            prev.setNext(node);
        }else {
            if (index == size) {
                addLast(value);
                return;
            } else {
                Node cur = tail;
                for (int i = size; i > index + 1; i--) {
                    cur = cur.getPrev();
                }
                Node node = new Node(value, cur.getPrev(), cur);
                cur.getPrev().setNext(node);
                cur.setPrev(node);
            }
        }
        size++;
    }

    @Override
    public void addLast(T value) {
        Node node = new Node(value,tail,tail.getNext());
        tail.setNext(node);
        tail = node;
        size++;
    }

    @Override
    public T get(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("獲取錯誤,非法索引");
        }
        if (index <= size / 2) {
            Node cur = dummyHead.getNext();
            for (int i = 0;i < index;i++) {
                cur = cur.getNext();
            }
            return cur.getElement();
        }else {
            Node cur = tail;
            for (int i = size;i > index + 1;i--) {
                cur = cur.getPrev();
            }
            return cur.getElement();
        }
    }

    @Override
    public T getFirst() {
        return get(0);
    }

    @Override
    public T getLast() {
        return tail.getElement();
    }

    @Override
    public void set(int index, T value) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("設置錯誤,非法索引");
        }
        if (index <= size / 2) {
            Node cur = dummyHead.getNext();
            for (int i = 0;i < index;i++) {
                cur = cur.getNext();
            }
            cur.setElement(value);
        }else {
            Node cur = tail;
            for (int i = size;i > index + 1;i--) {
                cur = cur.getPrev();
            }
            cur.setElement(value);
        }
    }

    @Override
    public boolean contains(T value) {
        Node cur = dummyHead.getNext();
        while (cur != null) {
            if (cur.getElement().equals(value)) {
                return true;
            }
            cur = cur.getNext();
        }
        return false;
    }

    @Override
    public T remove(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("刪除錯誤,非法索引");
        }
        if (index <= size / 2) {
            Node prev = dummyHead;
            for (int i = 0;i < index;i++) {
                prev = prev.getNext();
            }
            Node ret = prev.getNext();
            prev.setNext(ret.getNext());
            ret.getNext().setPrev(prev);
            ret.setNext(null);
            ret.setPrev(null);
            size--;
            return ret.getElement();
        }else {
            Node cur = tail;
            for (int i = size;i > index + 1;i--) {
                cur = cur.getPrev();
            }
            cur.getPrev().setNext(cur.getNext());
            if (cur.getNext() != null) {
                cur.getNext().setPrev(cur.getPrev());
            }else {
                tail = cur.getPrev();
            }
            cur.setPrev(null);
            cur.setNext(null);
            size--;
            return cur.getElement();
        }
    }

    @Override
    public T removeFirst() {
        return remove(0);
    }

    @Override
    public T removeLast() {
        Node node = tail;
        tail = tail.getPrev();
        tail.setNext(null);
        node.setPrev(null);
        size--;
        return node.getElement();
    }

    @Override
    public void removeElement(T value) {
        Node pre = dummyHead;
        while (pre.getNext() != null) {
            if (value.equals(pre.getNext().getElement())) {
                Node delNode = pre.getNext();
                pre.setNext(delNode.getNext());
                delNode.setNext(null);
                size--;
                break;
            }
            pre = pre.getNext();
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        Node cur = dummyHead.getNext();
        while (cur != null) {
            builder.append(cur + "->");
            cur = cur.getNext();
        }
        builder.append("NULL");
        return builder.toString();
    }
}

調用

public class ListMain {
    public static void main(String[] args) {
        List<Integer> linkedList = new MutualLinkedList<>();
        for (int i = 0;i < 5;i++) {
            linkedList.add(i,i);
            System.out.println(linkedList);
        }
        linkedList.add(3,666);
        System.out.println(linkedList);

        linkedList.addLast(777);
        System.out.println(linkedList);

        linkedList.set(4,888);
        System.out.println(linkedList);

        System.out.println(linkedList.get(5));
        System.out.println(linkedList.getFirst());
        System.out.println(linkedList.getLast());
        System.out.println(linkedList.contains(3));

        linkedList.removeLast();
        System.out.println(linkedList);

        linkedList.remove(4);
        System.out.println(linkedList);

        linkedList.removeFirst();
        System.out.println(linkedList);

        linkedList.removeLast();
        System.out.println(linkedList);
        linkedList.removeElement(2);
        System.out.println(linkedList);
        System.out.println(linkedList.getLast());
    }
}

運行結果

0->NULL
0->1->NULL
0->1->2->NULL
0->1->2->3->NULL
0->1->2->3->4->NULL
0->1->2->666->3->4->NULL
0->1->2->666->3->4->777->NULL
0->1->2->666->888->4->777->NULL
4
0
777
false
0->1->2->666->888->4->NULL
0->1->2->666->4->NULL
1->2->666->4->NULL
1->2->666->NULL
1->666->NULL
666

MutualLinkedList各操做的時間複雜度

添加操做                                O(n/2) = O(n)

addLast(value)                      O(1)

addFirst(value)                      O(1)

add(index,value)                    O(n/2) = O(n)

刪除操做                                O(n)

removeLast()                         O(1)

removeFirst()                         O(1)

remove(index)                       O(n/2) = O(n)

修改操做

set(index,value)                    O(n/2) = O(n)

查找操做                                O(n)

get(index)                              O(n/2) = O(n)

contains(value)                     O(n)

根據addLast(value),addFirst(value) ,removeLast() ,removeFirst() 都是O(1)的複雜度,因此用雙向鏈表來實現隊列也是很簡單的。

  • 二分搜索樹的封裝

二分搜索樹是一種可比較大小的二叉樹,它能夠飽和,也能夠非飽和。

接口

public interface Tree<T> {
    int getSize();
    boolean isEmpty();
    void add(T t);
    boolean contains(T t);

    /**
     * 前序遍歷
     */
    void preOrder();

    /**
     * 中序遍歷
     */
    void inOrder();

    /**
     * 後序遍歷
     */
    void postOrder();

    /**
     * 層序遍歷,又稱廣度遍歷
     * 而前、中、後序遍歷又稱深度遍歷
     */
    void levelOrder();

    /**
     * 查找最小元素
     * @return
     */
    T minimum();

    /**
     * 查找最大元素
     * @return
     */
    T maximum();

    /**
     * 刪除最小元素
     * @return
     */
    T removeMin();

    /**
     * 刪除最大元素
     * @return
     */
    T removeMax();

    /**
     * 刪除任意節點
     * @return
     */
    void remove(T t);

    /**
     * 找出比t小的最大值
     * @param t
     * @return
     */
    T floor(T t);

    /**
     * 找出比t大的最小值
     * @param t
     * @return
     */
    T ceil(T t);
}

實現類

public class BST<T extends Comparable<T>> implements Tree<T> {
    @AllArgsConstructor
    @Data
    private class Node{
        private T element;
        private Node left;
        private Node right;

        public Node(T element) {
            this(element,null,null);
        }
    }

    private Node root;
    private int size;

    public BST() {
        root = null;
        size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void add(T value) {
        root = add(root,value);
    }

    private Node add(Node node,T value) {
        //遞歸終止條件
        if (node == null) {
            size++;
            return new Node(value);
        }
        //遞歸
        if (value.compareTo(node.getElement()) < 0) {
            node.setLeft(add(node.getLeft(),value));
        }else if (value.compareTo(node.getElement()) > 0) {
            node.setRight(add(node.getRight(),value));
        }
        return node;
    }

    @Override
    public boolean contains(T value) {
        return contains(root,value);
    }

    private boolean contains(Node node,T value) {
        if (node == null) {
            return false;
        }
        if (value.compareTo(node.getElement()) == 0) {
            return true;
        }else if (value.compareTo(node.getElement()) < 0) {
            return contains(node.getLeft(),value);
        }else {
            return contains(node.getRight(),value);
        }
    }

    @Override
    public void preOrder() {
        preOrder(root);
    }

    private void preOrder(Node node) {
        if (node == null) {
            return;
        }

        System.out.println(node.getElement());
        preOrder(node.getLeft());
        preOrder(node.getRight());
    }

    /**
     * 非遞歸前序遍歷
     */
    public void preOrderNR() {
        Stack<Node> stack = new ArrayStack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            Node cur = stack.pop();
            System.out.println(cur.getElement());
            if (cur.getRight() != null) {
                stack.push(cur.getRight());
            }
            if (cur.getLeft() != null) {
                stack.push(cur.getLeft());
            }
        }
    }

    @Override
    public void inOrder() {
        inOrder(root);
    }

    private void inOrder(Node node) {
        if (node == null) {
            return;
        }
        inOrder(node.getLeft());
        System.out.println(node.getElement());
        inOrder(node.getRight());
    }

    @Override
    public void postOrder() {
        postOrder(root);
    }

    private void postOrder(Node node) {
        if (node == null) {
            return;
        }
        postOrder(node.getLeft());
        postOrder(node.getRight());
        System.out.println(node.getElement());
    }

    @Override
    public void levelOrder() {
        Queue<Node> queue = new LoopQueue<>();
        queue.enqueue(root);
        while (!queue.isEmpty()) {
            Node cur = queue.dequeue();
            System.out.println(cur.getElement());

            if (cur.getLeft() != null) {
                queue.enqueue(cur.getLeft());
            }
            if (cur.getRight() != null) {
                queue.enqueue(cur.getRight());
            }
        }
    }

    @Override
    public T minimum() {
        if (size == 0) {
            throw new IllegalArgumentException("二分搜索樹爲空");
        }
        return minimum(root).getElement();
    }

    private Node minimum(Node node) {
        if (node.getLeft() == null) {
            return node;
        }
        return minimum(node.getLeft());
    }

    @Override
    public T maximum() {
        if (size == 0) {
            throw new IllegalArgumentException("二分搜索樹爲空");
        }
        return maximum(root).getElement();
    }

    private Node maximum(Node node) {
        if (node.getRight() == null) {
            return node;
        }
        return maximum(node.getRight());
    }

    @Override
    public T removeMin() {
        T ret = minimum();
        root = removeMin(root);
        return ret;
    }

    private Node removeMin(Node node) {
        //遞歸的終止條件,當節點的左子樹爲空時,遞歸達到最大深度
        if (node.getLeft() == null) {
            //保存最終節點的右子樹
            Node rightNode = node.getRight();
            //將最終節點分離
            node.setRight(null);
            size--;
            //將保存的右子樹拼接回最終節點的父節點
            return rightNode;
        }
        //從根節點開始不斷向左子樹遞歸,並逐層返回刪除後的子節點從新
        //設爲上層節點的左節點
        node.setLeft(removeMin(node.getLeft()));
        return node;
    }

    @Override
    public T removeMax() {
        T ret = maximum();
        root = removeMax(root);
        return ret;
    }

    private Node removeMax(Node node) {
        if (node.getRight() == null) {
            Node leftNode = node.getLeft();
            node.setLeft(null);
            size--;
            return leftNode;
        }

        node.setRight(removeMax(node.getRight()));
        return node;
    }

    @Override
    public void remove(T value) {
        root = remove(root,value);
    }

    private Node remove(Node node,T value) {
        if (node == null) {
            return null;
        }
        if (value.compareTo(node.getElement()) < 0) {
            node.setLeft(remove(node.getLeft(),value));
            return node;
        }else if (value.compareTo(node.getElement()) > 0) {
            node.setRight(remove(node.getRight(),value));
            return node;
        }else {
            //待刪除節點左子樹爲空的狀況
            if (node.getLeft() == null) {
                Node rightNode = node.getRight();
                node.setRight(null);
                size--;
                return rightNode;
            }
            //待刪除節點右子樹爲空的狀況
            if (node.getRight() == null) {
                Node leftNode = node.getLeft();
                node.setLeft(null);
                size--;
                return leftNode;
            }
            //待刪除節點左右子樹均不爲空的狀況
            //找到比待刪除節點大的最小節點,即待刪除節點右子樹的最小節點
            //用這個節點頂替待刪除節點的位置
            Node successor = minimum(node.getRight());
            successor.setRight(removeMin(node.getRight()));
            successor.setLeft(node.getLeft());
            node.setLeft(null);
            node.setRight(null);
            return successor;
            //找到比待刪除節點小的最大節點,即待刪除節點左子樹的最大節點
            //用這個節點頂替待刪除節點的位置,此二種方法取其一便可
//            Node predecessor = maximum(node.getLeft());
//            predecessor.setLeft(removeMax(node.getLeft()));
//            predecessor.setRight(node.getRight());
//            node.setLeft(null);
//            node.setRight(null);
//            return predecessor;
        }
    }

    @Override
    public T floor(T value) {
        Node node = floor(root,value);
        if (node != null) {
            return node.getElement();
        }
        return null;
    }

    private Node floor(Node node,T value) {
        if (node == null) {
            return null;
        }
        if (value.compareTo(node.getElement()) <= 0) {
            return floor(node.getLeft(),value);
        }else {
            if (node.getRight() == null) {
                return node;
            }else if (node.getRight().getLeft() == null &&
                    value.compareTo(node.getRight().getElement()) <= 0) {
                return node;
            }else if (node.getRight().getRight() == null &&
                    value.compareTo(node.getRight().getElement()) > 0) {
                return node.getRight();
            }
            return floor(node.getRight(),value);
        }
    }

    @Override
    public T ceil(T value) {
        Node node = ceil(root,value);
        if (node != null) {
            return node.getElement();
        }
        return null;
    }

    private Node ceil(Node node,T value) {
        if (node == null) {
            return null;
        }
        if (value.compareTo(node.getElement()) >= 0) {
            return ceil(node.getRight(),value);
        }else {
            if (node.getLeft() == null) {
                return node;
            }else if (value.compareTo(maximum(node.getLeft()).getElement()) >= 0) {
                return node;
            }else if (node.getLeft().getRight() == null &&
                    value.compareTo(node.getLeft().getElement()) >= 0) {
                return node;
            }else if (node.getLeft().getLeft() == null &&
                    value.compareTo(node.getLeft().getElement()) < 0) {
                return node.getLeft();
            }
            return ceil(node.getLeft(),value);
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        generateBSTString(root,0,builder);
        return builder.toString();
    }

    private void generateBSTString(Node node,int depth,StringBuilder builder) {
        if (node == null) {
            builder.append(generateDepthString(depth) + "null\n");
            return;
        }
        builder.append(generateDepthString(depth) + node.getElement() + "\n");
        generateBSTString(node.getLeft(),depth + 1,builder);
        generateBSTString(node.getRight(),depth + 1,builder);
    }

    private String generateDepthString(int depth) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0;i < depth;i++) {
            builder.append("--");
        }
        return builder.toString();
    }
}

調用

public class TreeMain {
    public static void main(String[] args) {
        Tree<Integer> bst = new BST<>();
        Integer[] nums = {50,30,60,80,40,20};
        Stream.of(nums).forEach(bst::add);
        System.out.println("遞歸前序遍歷");
        bst.preOrder();
        System.out.println("非遞歸前序遍歷");
        ((BST)bst).preOrderNR();
        ///////////////////////////////
        //            50              //
        //           / \             //
        //          30  60            //
        //         / \   \           //
        //        20  40  80          //
        ///////////////////////////////
        System.out.println("中序遍歷");
        bst.inOrder();
        System.out.println("後續遍歷");
        bst.postOrder();
        System.out.println("層序遍歷");
        bst.levelOrder();
        System.out.println();
        System.out.println(bst);
        System.out.println(bst.floor(35));
        System.out.println(bst.ceil(45));
        System.out.println();
        bst.remove(30);
        System.out.println(bst);
    }
}

運行結果

遞歸前序遍歷
50
30
20
40
60
80
非遞歸前序遍歷
50
30
20
40
60
80
中序遍歷
20
30
40
50
60
80
後續遍歷
20
40
30
80
60
50
層序遍歷
50
30
60
20
40
80

50
--30
----20
------null
------null
----40
------null
------null
--60
----null
----80
------null
------null

30
50

50
--40
----20
------null
------null
----null
--60
----null
----80
------null
------null

對刪除最小,最大元素進行測試

public class TreeMain1 {
    public static void main(String[] args) {
        Tree<Integer> bst = new BST<>();
        Random random = new Random();
        for (int i = 0;i < 1000;i++) {
            bst.add(random.nextInt(10000));
        }
        Array<Integer> numsMin = new DefaultArray<>();
        while (!bst.isEmpty()) {
            numsMin.addLast(bst.removeMin());
        }
        System.out.println(numsMin);
        for (int i = 1;i < numsMin.getSize();i++) {
            if (numsMin.get(i - 1) > numsMin.get(i)) {
                throw new IllegalArgumentException("錯誤");
            }
        }
        System.out.println("removeMin測試成功");

        for (int i = 0;i < 1000;i++) {
            bst.add(random.nextInt(10000));
        }
        Array<Integer> numsMax = new DefaultArray<>();
        while (!bst.isEmpty()) {
            numsMax.addLast(bst.removeMax());
        }
        System.out.println(numsMax);
        for (int i = 1;i < numsMax.getSize();i++) {
            if (numsMax.get(i - 1) < numsMax.get(i)) {
                throw new IllegalArgumentException("錯誤");
            }
        }
        System.out.println("removeMax測試成功");
    }
}

運行結果

DefaultArray(data=[1,8,9,21,25,36,69,77,88,89,109,116,121,132,135,146,168,211,220,238,242,249,254,296,304,314,316,320,353,364,380,391,395,399,402,409,416,421,425,426,450,457,463,473,478,491,519,534,543,545,563,573,579,581,590,591,600,644,648,655,661,670,697,710,712,713,717,724,734,739,744,750,754,787,803,812,813,824,835,838,841,843,844,867,869,882,907,912,933,937,940,943,952,959,970,972,974,988,991,996,1000,1004,1019,1031,1032,1040,1064,1069,1085,1117,1120,1122,1131,1148,1151,1154,1174,1226,1232,1233,1234,1235,1243,1246,1283,1291,1301,1327,1338,1340,1389,1395,1403,1411,1416,1420,1424,1434,1440,1447,1450,1459,1461,1471,1474,1493,1495,1497,1499,1510,1521,1528,1533,1537,1549,1564,1569,1585,1586,1608,1613,1621,1644,1670,1672,1674,1677,1699,1713,1717,1739,1754,1756,1763,1783,1786,1795,1797,1814,1819,1828,1843,1850,1855,1866,1875,1894,1897,1902,1913,1918,1925,1952,1995,1998,2014,2025,2027,2041,2045,2046,2059,2071,2074,2079,2102,2118,2120,2132,2139,2155,2156,2163,2165,2170,2193,2194,2205,2209,2234,2238,2266,2267,2272,2273,2277,2282,2302,2313,2331,2353,2360,2370,2380,2390,2416,2435,2439,2449,2454,2475,2478,2502,2514,2521,2522,2543,2568,2578,2590,2612,2617,2646,2662,2663,2665,2667,2677,2683,2687,2688,2691,2708,2711,2716,2751,2783,2794,2813,2817,2826,2853,2866,2879,2883,2885,2899,2914,2921,2926,2933,3003,3007,3020,3048,3059,3080,3083,3105,3108,3142,3150,3162,3218,3226,3230,3249,3258,3268,3285,3287,3293,3305,3322,3323,3340,3356,3372,3379,3386,3394,3412,3417,3433,3441,3451,3460,3483,3509,3511,3518,3534,3542,3561,3567,3584,3593,3599,3609,3618,3627,3632,3647,3659,3668,3670,3675,3676,3695,3697,3706,3712,3738,3754,3756,3765,3771,3801,3812,3820,3823,3834,3841,3846,3848,3853,3857,3866,3896,3906,3910,3915,3943,3949,3960,3971,3984,3990,4010,4024,4027,4030,4033,4059,4079,4080,4090,4104,4111,4122,4135,4138,4146,4150,4151,4169,4176,4193,4194,4202,4206,4207,4211,4216,4226,4243,4244,4251,4264,4269,4272,4292,4301,4305,4315,4322,4342,4352,4362,4383,4397,4398,4412,4414,4417,4430,4458,4474,4487,4490,4506,4564,4583,4587,4608,4622,4632,4642,4643,4652,4654,4665,4673,4674,4676,4699,4706,4707,4714,4722,4730,4732,4758,4759,4765,4788,4842,4850,4851,4856,4880,4887,4891,4892,4893,4897,4903,4905,4926,4949,4969,4972,4978,4979,4984,5037,5050,5059,5060,5079,5087,5122,5141,5145,5154,5183,5184,5193,5201,5215,5227,5229,5231,5241,5252,5259,5267,5315,5323,5331,5339,5343,5344,5357,5359,5397,5403,5405,5410,5412,5416,5417,5419,5444,5446,5465,5489,5490,5493,5504,5506,5510,5524,5536,5545,5552,5557,5563,5577,5586,5587,5590,5607,5615,5632,5674,5684,5698,5699,5719,5721,5726,5727,5739,5747,5749,5753,5762,5774,5776,5796,5815,5822,5836,5840,5848,5856,5861,5882,5896,5899,5911,5913,5916,5932,5945,5965,5968,5972,5982,5994,6014,6023,6032,6050,6076,6084,6116,6119,6120,6130,6131,6140,6145,6150,6154,6162,6169,6173,6187,6191,6195,6253,6260,6277,6295,6300,6304,6306,6318,6325,6336,6337,6343,6356,6362,6378,6383,6392,6404,6416,6428,6460,6492,6534,6555,6560,6571,6574,6580,6582,6590,6593,6597,6605,6608,6611,6615,6626,6636,6641,6649,6652,6659,6662,6672,6676,6686,6691,6712,6753,6773,6779,6781,6782,6789,6790,6807,6808,6817,6842,6849,6852,6863,6869,6885,6886,6892,6896,6899,6906,6925,6937,6950,6957,6960,6970,6994,7019,7027,7043,7045,7054,7064,7083,7096,7098,7100,7125,7136,7160,7163,7190,7193,7228,7229,7236,7239,7248,7249,7253,7254,7265,7280,7281,7291,7330,7335,7344,7346,7348,7386,7391,7394,7395,7407,7418,7423,7428,7443,7448,7450,7454,7457,7465,7475,7479,7484,7488,7491,7492,7496,7501,7515,7520,7538,7579,7581,7588,7592,7595,7611,7619,7634,7641,7682,7703,7704,7720,7733,7737,7765,7773,7778,7789,7794,7800,7814,7827,7835,7898,7909,7916,7934,7940,7952,7957,7963,7966,7970,7978,7981,7989,8008,8025,8030,8033,8034,8039,8049,8059,8070,8077,8078,8080,8081,8090,8092,8127,8130,8132,8156,8162,8165,8177,8183,8190,8193,8212,8216,8219,8229,8246,8250,8258,8261,8279,8303,8321,8337,8339,8347,8378,8388,8419,8427,8428,8433,8456,8458,8462,8463,8481,8483,8485,8493,8494,8503,8511,8513,8533,8557,8560,8571,8585,8588,8626,8627,8628,8633,8661,8671,8675,8679,8686,8692,8716,8721,8722,8723,8730,8745,8777,8815,8833,8834,8837,8855,8863,8881,8890,8914,8915,8925,8935,8962,8988,9005,9031,9040,9060,9066,9077,9078,9113,9118,9130,9131,9133,9135,9154,9160,9165,9166,9167,9170,9175,9185,9190,9203,9218,9219,9227,9231,9232,9233,9253,9261,9265,9273,9279,9286,9292,9299,9303,9306,9335,9345,9352,9362,9392,9397,9410,9411,9413,9436,9461,9470,9477,9486,9515,9520,9526,9533,9554,9560,9574,9581,9584,9596,9606,9608,9609,9614,9619,9631,9640,9652,9653,9663,9664,9717,9742,9756,9760,9771,9776,9790,9793,9835,9852,9853,9861,9880,9883,9889,9909,9918,9919,9937,9941,9945,9947,9960,9975,9978,9982,9996], size=948
removeMin測試成功
DefaultArray(data=[9985,9960,9946,9943,9940,9933,9931,9919,9911,9906,9905,9903,9896,9882,9867,9866,9849,9840,9824,9823,9820,9817,9814,9813,9774,9770,9764,9763,9734,9698,9692,9688,9677,9675,9670,9662,9653,9645,9612,9591,9582,9581,9554,9548,9542,9520,9515,9492,9468,9439,9420,9419,9415,9400,9385,9383,9372,9345,9330,9318,9295,9278,9268,9267,9263,9256,9210,9208,9206,9197,9192,9191,9164,9154,9151,9147,9145,9124,9117,9098,9084,9074,9068,9064,9034,9028,9024,9021,8967,8950,8946,8907,8903,8884,8880,8876,8872,8869,8865,8852,8850,8839,8838,8827,8825,8821,8807,8806,8804,8803,8796,8791,8782,8776,8756,8753,8744,8728,8727,8726,8709,8704,8702,8693,8685,8684,8673,8668,8653,8604,8600,8580,8579,8562,8552,8548,8547,8532,8531,8523,8522,8518,8509,8488,8453,8427,8424,8409,8400,8384,8369,8356,8350,8341,8322,8317,8290,8284,8280,8266,8260,8255,8250,8231,8202,8196,8191,8190,8173,8143,8129,8117,8096,8094,8092,8066,8052,8046,8037,8036,8024,8021,8012,8006,7994,7991,7982,7981,7967,7956,7951,7946,7921,7916,7876,7869,7864,7862,7852,7838,7837,7835,7833,7825,7824,7821,7807,7790,7736,7717,7715,7706,7691,7686,7676,7675,7663,7641,7637,7635,7615,7606,7596,7591,7587,7569,7540,7529,7513,7509,7508,7507,7491,7485,7480,7475,7470,7460,7429,7426,7425,7401,7400,7379,7372,7363,7344,7343,7339,7307,7306,7295,7290,7287,7283,7276,7256,7224,7223,7220,7203,7199,7192,7172,7168,7167,7164,7155,7144,7143,7137,7124,7086,7082,7072,7058,7044,7025,7023,7022,7018,7003,6998,6996,6989,6985,6971,6965,6958,6937,6932,6926,6913,6910,6909,6906,6894,6892,6862,6848,6839,6837,6836,6832,6824,6823,6821,6812,6787,6776,6771,6763,6749,6748,6745,6736,6725,6723,6717,6714,6711,6688,6672,6653,6649,6640,6635,6625,6623,6617,6612,6611,6600,6594,6546,6544,6537,6522,6520,6500,6490,6473,6451,6444,6441,6437,6436,6417,6405,6370,6366,6361,6359,6358,6355,6333,6326,6309,6302,6285,6265,6248,6235,6234,6224,6219,6216,6209,6196,6182,6174,6169,6168,6165,6153,6147,6141,6139,6131,6121,6095,6071,6047,6038,6027,6025,6018,6012,5975,5970,5965,5964,5959,5957,5930,5926,5920,5914,5911,5907,5902,5900,5891,5868,5848,5807,5774,5768,5758,5748,5736,5684,5682,5669,5665,5660,5644,5640,5639,5637,5636,5634,5616,5606,5586,5585,5566,5553,5535,5527,5524,5514,5496,5488,5480,5455,5450,5446,5442,5420,5369,5367,5363,5356,5320,5317,5293,5279,5247,5237,5231,5216,5208,5206,5184,5180,5168,5159,5144,5137,5123,5091,5088,5085,5084,5081,5067,5061,5026,5015,5007,5002,4992,4986,4980,4976,4975,4946,4945,4935,4934,4932,4926,4922,4912,4895,4890,4886,4872,4870,4844,4831,4795,4790,4786,4766,4756,4753,4738,4737,4721,4717,4711,4709,4693,4682,4674,4669,4668,4619,4615,4591,4590,4587,4586,4583,4581,4553,4547,4544,4541,4540,4526,4515,4501,4475,4468,4453,4411,4402,4391,4384,4361,4355,4354,4338,4331,4323,4321,4318,4315,4311,4307,4274,4270,4268,4264,4263,4253,4248,4232,4226,4225,4223,4203,4192,4166,4158,4152,4148,4127,4091,4080,4079,4076,4038,4037,4026,4013,3996,3985,3969,3956,3950,3939,3938,3936,3930,3906,3897,3876,3868,3851,3844,3843,3816,3802,3801,3799,3782,3781,3780,3774,3767,3765,3764,3753,3750,3749,3741,3731,3729,3722,3715,3706,3697,3665,3664,3657,3647,3625,3624,3623,3609,3599,3594,3574,3571,3554,3552,3548,3542,3531,3519,3508,3479,3472,3464,3451,3449,3444,3436,3427,3420,3417,3416,3366,3358,3329,3321,3318,3316,3289,3257,3246,3243,3221,3210,3206,3201,3192,3161,3149,3132,3121,3115,3106,3093,3090,3077,3075,3064,3063,3050,3041,3021,3019,3017,3014,3008,2999,2964,2953,2942,2904,2903,2894,2871,2862,2854,2853,2836,2826,2795,2788,2773,2765,2763,2748,2730,2714,2703,2695,2652,2650,2637,2636,2622,2621,2603,2591,2581,2579,2576,2575,2571,2563,2551,2543,2535,2524,2513,2500,2491,2490,2482,2467,2464,2462,2455,2454,2448,2431,2427,2424,2383,2370,2356,2352,2329,2311,2303,2283,2273,2271,2269,2251,2249,2203,2201,2195,2177,2175,2161,2151,2142,2135,2120,2110,2100,2094,2087,2082,2078,2077,2070,2063,2055,2050,2032,2027,2026,2009,2001,1955,1944,1941,1938,1923,1912,1890,1878,1877,1871,1865,1859,1844,1830,1827,1824,1799,1798,1795,1779,1769,1762,1728,1709,1706,1703,1700,1687,1660,1622,1616,1610,1593,1559,1540,1533,1532,1524,1513,1506,1492,1486,1467,1457,1433,1423,1413,1387,1384,1375,1366,1360,1358,1357,1342,1331,1320,1309,1307,1304,1296,1295,1259,1227,1222,1205,1193,1182,1165,1141,1130,1125,1123,1118,1107,1103,1098,1088,1080,1065,1063,1059,1027,1019,998,978,959,958,946,941,938,931,927,908,907,901,898,895,890,883,869,859,858,848,843,836,831,810,802,775,758,751,737,735,729,728,721,706,702,683,670,640,629,620,584,554,548,542,538,537,521,503,501,487,484,478,477,476,449,441,427,424,416,400,393,377,367,358,346,323,310,293,292,280,274,263,251,244,235,224,219,205,190,176,175,154,149,138,134,125,118,109,105,102,99,96,93,85,77,74,64,41,37,10,2], size=949
removeMax測試成功

  • 二分搜索樹集合的封裝

集合是一種不包含重複元素的數據結構

接口

public interface Set<T> {
    void add(T t);
    void remove(T t);
    boolean contains(T t);
    int getSize();
    boolean isEmpty();
}

實現類

public class BSTSet<T extends Comparable<T>> implements Set<T> {
    private Tree<T> bst;

    public BSTSet() {
        bst = new BST<>();
    }
    @Override
    public void add(T value) {
        bst.add(value);
    }

    @Override
    public void remove(T value) {
        bst.remove(value);
    }

    @Override
    public boolean contains(T value) {
        return bst.contains(value);
    }

    @Override
    public int getSize() {
        return bst.getSize();
    }

    @Override
    public boolean isEmpty() {
        return bst.isEmpty();
    }
}

調用

咱們會先從一本《傲慢與偏見》的英文電子書中讀出多少個單詞,而後放入集合中,查看有多少不重複的單詞。

文件讀取

public class FileOperation {
    /**
     * 讀取文件名稱爲filename中的內容,並將其中包含的全部詞語放進words中
     * @param filename
     * @param words
     * @return
     */
    public static boolean readFile(String filename, Array<String> words) {
        if (filename == null || words == null) {
            System.out.println("filename爲空或words爲空");
            return false;
        }
        Scanner scanner;

        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis),"UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }else {
                return false;
            }
        } catch (FileNotFoundException e) {
            System.out.println("沒法打開" + filename);
            return false;
        }
        //簡單分詞
        if (scanner.hasNextLine()) {
            String contents = scanner.useDelimiter("\\A").next();
            int start = firstCharacterIndex(contents,0);
            for (int i = start + 1;i <= contents.length();) {
                if (i == contents.length() || !Character.isLetter(contents.charAt(i))) {
                    String word = contents.substring(start,i).toLowerCase();
                    words.addLast(word);
                    start = firstCharacterIndex(contents,i);
                    i = start + 1;
                }else {
                    i++;
                }
            }
        }
        return true;

    }

    private static int firstCharacterIndex(String s,int start) {
        for (int i = start;i < s.length();i++) {
            if (Character.isLetter(s.charAt(i))) {
                return i;
            }
        }
        return s.length();
    }
}

測試

public class SetMain {
    public static void main(String[] args) {
        System.out.println("傲慢與偏見");
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile("/Users/admin/Downloads/pride-and-prejudice.txt",words);
        System.out.println("共有單詞: " + words.getSize());
        Set<String> set = new BSTSet<>();
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(set::add);
        System.out.println("共有不一樣的單詞: " + set.getSize());
    }
}

運行結果

傲慢與偏見
共有單詞: 125901
共有不一樣的單詞: 6530

  • 鏈表集合的封裝

實現類(如下的List和LinkedList皆爲以前自主實現的鏈表封裝而非JDK的List接口和LinkedList的實現類)

public class LinkedListSet<T> implements Set<T> {
    private List<T> list;

    public LinkedListSet() {
        list = new LinkedList<>();
    }
    @Override
    public void add(T value) {
        if (!list.contains(value)) {
            list.addFirst(value);
        }
    }

    @Override
    public void remove(T value) {
        list.removeElement(value);
    }

    @Override
    public boolean contains(T value) {
        return list.contains(value);
    }

    @Override
    public int getSize() {
        return list.getSize();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }
}

調用

public class SetMain {
    public static void main(String[] args) {
        System.out.println("傲慢與偏見");
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile("/Users/admin/Downloads/pride-and-prejudice.txt",words);
        System.out.println("共有單詞: " + words.getSize());
        Set<String> set = new LinkedListSet<>();
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(set::add);
        System.out.println("共有不一樣的單詞: " + set.getSize());
    }
}

運行結果

傲慢與偏見
共有單詞: 125901
共有不一樣的單詞: 6530

雖然運行結果相同,但咱們能明顯感受到在獲取不一樣的單詞的時候,運行時間明顯偏長。

兩種集合的性能測試

public class TestSetMain {
    private static double testSet(Set<String> set,String filename) {
        long startTime = System.nanoTime();
        System.out.println(filename);
        Array<String> words = new DefaultArray<>();
        if (FileOperation.readFile(filename,words)) {
            System.out.println("共有單詞: " + words.getSize());
            Stream.of(words.getData()).filter(data -> data != null)
                    .forEach(set::add);
            System.out.println("共有不一樣單詞: " + set.getSize());
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        String filename = "/Users/admin/Downloads/pride-and-prejudice.txt";
        Set<String> bstSet = new BSTSet<>();
        double time1 = testSet(bstSet,filename);
        System.out.println("BST Set: " + time1 + " s");
        System.out.println();
        Set<String> linkedListSet = new LinkedListSet<>();
        double time2 = testSet(linkedListSet,filename);
        System.out.println("LinkedList Set: " + time2 + " s");
    }
}

測試結果

/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣單詞: 6530
BST Set: 0.140089974 s

/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣單詞: 6530
LinkedList Set: 2.184659522 s

對於LinkedListSet操做的時間複雜度

add(value)                       O(n)

contains(value)               O(n)

remove(value)                 O(n)

對於BSTSet操做的時間複雜度

add(value)                       O(h)

contains(value)               O(h)

remove(value)                 O(h)

以上h爲二分搜索樹的高度。如今咱們來看一下h與n的關係是什麼。假設一顆二分搜索樹是飽和的,以下圖所示

假設該二分搜索樹共h層

則0層的節點數爲                     1

1層的節點數爲                         2

2層的節點數爲                        4

3層的節點數爲                        8

4層的節點數爲                        16

h-1層的節點數爲                    2^(h-1)

則總節點數 = 2^0 + 2^1 + 2^2 + 2^3 + 2^4+ …… + 2^(h-1),咱們能夠看到這是一個等比數列,它的公比爲2。根據等比數列的求和公式可得(q爲底數,此時q爲2;a1爲常數,此時a1爲1;n爲層數,此時爲h),關於該公式的推導能夠參考https://www.iqiyi.com/w_19rr8796p1.html  ,最後可得

最後可得總節點數n = 2^h - 1,則h = log2(n + 1),其中紅色2爲底數。

省略掉常數和底數,最後獲得二分搜索樹的時間複雜度爲O(log n)

對於BSTSet操做的時間複雜度能夠改成

add(value)                       O(log n)

contains(value)               O(log n)

remove(value)                 O(log n)

咱們來直觀的看一下log n和線性複雜度n的增加狀況

                              log n                           n

n = 16                     4                               16               相差4倍

n = 1024                10                              1024           相差100倍

n = 100萬              20                              100萬          相差5萬倍

以上咱們是根據滿二分搜索樹來推斷的,但二分搜索樹也有一種最壞的狀況,就是它的高度等於它的節點數(h == n),這樣它就退化成了一個鏈表,這樣它的時間複雜度就是O(n)而不是O(log n)了。

LeetCode算法題 https://leetcode-cn.com/problems/unique-morse-code-words/

這是LeetCode的804題——惟一摩爾斯密碼詞

咱們先用JDK的TreeSet來完成。該類是實現了紅黑樹爲底層的集合。(此處的Set爲java.util.Set,TreeSet爲java.util.TreeSet)

public class Solution {
    public int uniqueMorseRepresentations(String[] words) {
        String[] codes = {".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."};
        Set<String> set = new TreeSet<>();
        Stream.of(words).forEach(word -> {
            StringBuilder builder = new StringBuilder();
            for (int i = 0;i < word.length();i++) {
                builder.append(codes[word.charAt(i) - 'a']);
            }
            set.add(builder.toString());
        });
        return set.size();
    }
}

提交給LeetCode

  • 鏈表映射封裝

映射是一種表示key,value鍵值對的數據結構

接口

public interface Map<K,V> {
    void add(K key,V value);
    V remove(K key);
    boolean contains(K key);
    V get(K key);
    void set(K key,V value);
    int getSize();
    boolean isEmpty();
}

實現類

因爲映射有兩個泛型,因此以前實現的LinkedList將再也不復用。

public class LinkedListMap<K,V> implements Map<K,V> {
    @AllArgsConstructor
    @Data
    @NoArgsConstructor
    private class Node {
        private K key;
        private V value;
        private Node next;

        public Node(K key) {
            this(key,null,null);
        }

        public Node(K key,V value) {
            this(key,value,null);
        }

        @Override
        public String toString() {
            return key.toString() + " : " + value.toString();
        }
    }

    private Node dummyHead;
    private int size;

    public LinkedListMap() {
        dummyHead = new Node();
        size = 0;
    }

    @Override
    public void add(K key, V value) {
        Node node = getNode(key);
        if (node == null) {
            dummyHead.setNext(new Node(key,value,dummyHead.getNext()));
            size++;
        }else {
            node.setValue(value);
        }
    }

    @Override
    public V remove(K key) {
        Node pre = dummyHead;
        while (pre.getNext() != null) {
            if (key.equals(pre.getNext().getKey())) {
                Node delNode = pre.getNext();
                pre.setNext(delNode.getNext());
                delNode.setNext(null);
                size--;
                return delNode.getValue();
            }
            pre = pre.getNext();
        }
        return null;
    }

    @Override
    public boolean contains(K key) {
        return getNode(key) != null;
    }

    @Override
    public V get(K key) {
        Node node = getNode(key);
        return node == null ? null : node.getValue();
    }

    @Override
    public void set(K key, V value) {
        Node node = getNode(key);
        if (node == null) {
            throw new IllegalArgumentException(key + "不存在");
        }
        node.setValue(value);
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    private Node getNode(K key) {
        Node cur = dummyHead.getNext();
        while (cur != null) {
            if (key.equals(cur.getKey())) {
                return cur;
            }
            cur = cur.getNext();
        }
        return null;
    }
}

調用

public class MapMain {
    public static void main(String[] args) {
        System.out.println("傲慢與偏見");
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile("/Users/admin/Downloads/pride-and-prejudice.txt",words);
        System.out.println("共有單詞: " + words.getSize());
        Map<String,Integer> map = new LinkedListMap<>();
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(word -> {
                    if (map.contains(word)) {
                        map.set(word,map.get(word) + 1);
                    }else {
                        map.add(word,1);
                    }
                });
        System.out.println("共有不一樣的單詞: " + map.getSize());
        System.out.println("pride出現的次數: " + map.get("pride"));
        System.out.println("prejudice出現的次數: " + map.get("prejudice"));
    }
}

運行結果(時間較慢)

傲慢與偏見
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11

  • 二分搜索樹映射封裝

因爲映射有兩個泛型,因此以前實現的BST將再也不復用

實現類

public class BSTMap<K extends Comparable<K>,V> implements Map<K,V> {
    @Data
    @AllArgsConstructor
    private class Node{
        private K key;
        private V value;
        private Node left;
        private Node right;

        public Node(K key) {
            this(key,null,null,null);
        }
        public Node(K key,V value) {
            this(key,value,null,null);
        }
    }

    private Node root;
    private int size;

    public BSTMap() {
        root = null;
        size = 0;
    }
    @Override
    public void add(K key, V value) {
        root = add(root,key,value);
    }

    private Node add(Node node, K key,V value) {
        //遞歸終止條件
        if (node == null) {
            size++;
            return new Node(key,value);
        }
        //遞歸
        if (key.compareTo(node.getKey()) < 0) {
            node.setLeft(add(node.getLeft(),key,value));
        }else if (key.compareTo(node.getKey()) > 0) {
            node.setRight(add(node.getRight(),key,value));
        }else {
            node.setValue(value);
        }
        return node;
    }

    @Override
    public V remove(K key) {
        Node node = getNode(root,key);
        if (node != null) {
            root = remove(root,key);
            return node.getValue();
        }
        return null;
    }

    private Node remove(Node node,K key) {
        if (node == null) {
            return null;
        }
        if (key.compareTo(node.getKey()) < 0) {
            node.setLeft(remove(node.getLeft(),key));
            return node;
        }else if (key.compareTo(node.getKey()) > 0) {
            node.setRight(remove(node.getRight(),key));
            return node;
        }else {
            //待刪除節點左子樹爲空的狀況
            if (node.getLeft() == null) {
                Node rightNode = node.getRight();
                node.setRight(null);
                size--;
                return rightNode;
            }
            //待刪除節點右子樹爲空的狀況
            if (node.getRight() == null) {
                Node leftNode = node.getLeft();
                node.setLeft(null);
                size--;
                return leftNode;
            }
            //待刪除節點左右子樹均不爲空的狀況
            //找到比待刪除節點大的最小節點,即待刪除節點右子樹的最小節點
            //用這個節點頂替待刪除節點的位置
            Node successor = minimum(node.getRight());
            successor.setRight(removeMin(node.getRight()));
            successor.setLeft(node.getLeft());
            node.setLeft(null);
            node.setRight(null);
            return successor;
        }
    }

    private Node minimum(Node node) {
        if (node.getLeft() == null) {
            return node;
        }
        return minimum(node.getLeft());
    }

    private Node removeMin(Node node) {
        if (node.getLeft() == null) {
            Node rightNode = node.getRight();
            node.setRight(null);
            size--;
            return rightNode;
        }
        node.setLeft(removeMin(node.getLeft()));
        return node;
    }

    @Override
    public boolean contains(K key) {
        return getNode(root,key) != null;
    }

    @Override
    public V get(K key) {
        Node node = getNode(root,key);
        return node == null ? null : node.getValue();
    }

    @Override
    public void set(K key, V value) {
        Node node = getNode(root,key);
        if (node == null) {
            throw new IllegalArgumentException(key + "不存在");
        }
        node.setValue(value);
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    private Node getNode(Node node,K key) {
        if (node == null) {
            return null;
        }
        if (key.compareTo(node.getKey()) == 0) {
            return node;
        }else if (key.compareTo(node.getKey()) < 0) {
            return getNode(node.getLeft(),key);
        }else {
            return getNode(node.getRight(),key);
        }
    }
}

調用

public class MapMain {
    public static void main(String[] args) {
        System.out.println("傲慢與偏見");
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile("/Users/admin/Downloads/pride-and-prejudice.txt",words);
        System.out.println("共有單詞: " + words.getSize());
        Map<String,Integer> map = new BSTMap<>();
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(word -> {
                    if (map.contains(word)) {
                        map.set(word,map.get(word) + 1);
                    }else {
                        map.add(word,1);
                    }
                });
        System.out.println("共有不一樣的單詞: " + map.getSize());
        System.out.println("pride出現的次數: " + map.get("pride"));
        System.out.println("prejudice出現的次數: " + map.get("prejudice"));
    }
}

運行結果(時間較快)

傲慢與偏見
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11

兩種映射的性能測試

public class TestMapMain {
    private static double testMap(Map<String,Integer> map,String filename) {
        long startTime = System.nanoTime();
        System.out.println(filename);
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile(filename,words);
        System.out.println("共有單詞: " + words.getSize());
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(word -> {
                    if (map.contains(word)) {
                        map.set(word,map.get(word) + 1);
                    }else {
                        map.add(word,1);
                    }
                });
        System.out.println("共有不一樣的單詞: " + map.getSize());
        System.out.println("pride出現的次數: " + map.get("pride"));
        System.out.println("prejudice出現的次數: " + map.get("prejudice"));
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        String filename = "/Users/admin/Downloads/pride-and-prejudice.txt";
        Map<String,Integer> bstMap = new BSTMap<>();
        double time1 = testMap(bstMap,filename);
        System.out.println("BST Map: " + time1 + " s");

        Map<String,Integer> linkedListMap = new LinkedListMap<>();
        double time2 = testMap(linkedListMap,filename);
        System.out.println("LinkedList Map: " + time2 + " s");
    }
}

測試結果

/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11
BST Map: 0.178329962 s
/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11
LinkedList Map: 9.18811603 s

對於LinkedListMap各操做的時間複雜度

add(key,value)                       O(n)

remove(key)                          O(n)

contains(key)                        O(n)

get(key)                                 O(n)

set(key,value)                        O(n)

對於BSTMap各操做的時間複雜度

add(key,value)                       O(log n) 平均          O(n) 最差

remove(key)                          O(log n) 平均          O(n) 最差

contains(key)                        O(log n) 平均          O(n) 最差

get(key)                                 O(log n) 平均          O(n) 最差

set(key,value)                        O(log n) 平均          O(n) 最差

LeetCode算法題 https://leetcode-cn.com/problems/intersection-of-two-arrays/

這是LeetCode的第349題——兩個數組的交集

咱們先用JDK的TreeSet來完成。該類是實現了紅黑樹爲底層的集合。(此處的Set爲java.util.Set,TreeSet爲java.util.TreeSet,List爲java.util.List,ArrayList爲java.util.ArrayList)

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> set = new TreeSet<>();
        for (int num : nums1) {
            set.add(num);
        }
        List<Integer> list = new ArrayList<>();
        for (int num : nums2) {
            if (set.contains(num)) {
                list.add(num);
                set.remove(num);
            }
        }
        int[] res = new int[list.size()];
        for (int i = 0;i < list.size();i++) {
            res[i] = list.get(i);
        }
        return res;
    }
}

提交給LeetCode

LeetCode算法題 https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/

這是LeetCode的350題——兩個數組的交集II

咱們先用JDK的TreeMap來完成,該類是實現了紅黑樹爲底層的映射。(此處的Map爲java.util.Map,TreeMap爲java.util.TreeMap,List爲java.util.List,ArrayList爲java.util.ArrayList)

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Map<Integer,Integer> map = new TreeMap<>();
        for (int num : nums1) {
            if (map.putIfAbsent(num,1) != null) {
                map.put(num,map.get(num) + 1);
            }
        }
        List<Integer> list = new ArrayList<>();
        for (int num : nums2) {
            if (map.containsKey(num)) {
                list.add(num);
                map.put(num,map.get(num) - 1);
                if (map.get(num) == 0) {
                    map.remove(num);
                }
            }
        }
        int[] ret = new int[list.size()];
        for (int i = 0;i < list.size();i++) {
            ret[i] = list.get(i);
        }
        return ret;
    }
}

提交到LeetCode

  • 最大堆封裝

堆是一種徹底二叉樹,徹底二叉樹就是從根節點依次向下層,每一層從左到右依次填充元素的二叉樹。徹底二叉樹能夠不是滿二叉樹。而最大堆的特性是某個節點的值老是不大於其父節點的值。

相似於上圖這種就是一個最大堆,它同時知足了徹底二叉樹的特性。在最大堆中,並非說上層的節點值必定比下層的節點值要大,而是每個節點值不比其父節點大。因爲其依次填充性,咱們也能夠用數組來看待這個最大堆。因此咱們能夠用數組來作最大堆的底層實現,二叉樹中父節點,左右子節點的索引關係能夠表示爲:已知一個節點的索引i(不管它是左節點仍是右節點),它的父節點的索引爲                         (根節點的索引爲0)

parent(i) = (i - 1) / 2

已知一個父節點的索引i,其左右子節點的索引爲

left child = 2 * i + 1

right child = 2 * i + 2

接口

public interface Heap<T> {
    int getSize();
    boolean isEmpty();
    void add(T t);

    /**
     * 取出最大元素(最大堆)或最小元素(最小堆)
     * @return
     */
    T extract();

    /**
     * 查看最大元素(最大堆)或最小元素(最小堆)
     * @return
     */
    T findHeap();

    /**
     * 取出堆中最大元素(最大堆)或最小元素(最小堆)
     * 而且替換成元素t
     * @param t
     * @return
     */
    T replace(T t);
}

實現類

public class MaxHeap<T extends Comparable<T>> implements Heap<T> {
    private Array<T> data;

    public MaxHeap(int capacity) {
        data = new DefaultArray<>(capacity);
    }

    public MaxHeap() {
        data = new DefaultArray<>();
    }

    /**
     * 將任意一個數組轉化成最大堆,此種方式稱爲Heapify
     * 過程爲先找到非葉節點的最後一個節點,依次向根節點遍歷,
     * 將每個節點浮動下沉,便可完成任意數組向最大堆的轉化
     * 找最後一個非葉節點的方法即爲找到最後一個葉節點(數組的最後一個索引size-1)
     * 的父節點
     * 將n個元素逐一插入到一個空堆中,時間複雜度爲O(nlog n)
     * heapify的過程,時間複雜度爲O(n)
     * @param arr
     */
    public MaxHeap(T[] arr) {
        data = new DefaultArray<>(arr);
        for (int i = parent(getSize() - 1);i >= 0;i--) {
            siftDown(i);
        }
    }

    @Override
    public int getSize() {
        return data.getSize();
    }

    @Override
    public boolean isEmpty() {
        return data.isEmpty();
    }

    @Override
    public void add(T value) {
        data.addLast(value);
        siftUp(data.getSize() - 1);
    }

    /**
     * 堆節點浮動上移
     * @param index
     */
    private void siftUp(int index) {
        while (index > 0 &&
                data.get(parent(index)).compareTo(data.get(index)) < 0) {
            data.swap(index,parent(index));
            index = parent(index);
        }
    }

    @Override
    public T extract() {
        T ret = findHeap();
        data.swap(0,data.getSize() - 1);
        data.removeLast();
        siftDown(0);
        return ret;
    }

    /**
     * 堆節點浮動下沉
     * @param index
     */
    private void siftDown(int index) {
        while (leftChild(index) < data.getSize()) {
            int j = leftChild(index);
            if (j + 1 < data.getSize() &&
                    data.get(j + 1).compareTo(data.get(j)) > 0) {
                j = rightChile(index);
            }
            //data[j]是左右孩子的最大值
            if (data.get(index).compareTo(data.get(j)) >= 0) {
                break;
            }
            data.swap(index,j);
            index = j;
        }
    }

    @Override
    public T findHeap() {
        if (isEmpty()) {
            throw new IllegalArgumentException("堆爲空,不能作此操做");
        }
        return data.get(0);
    }

    @Override
    public T replace(T value) {
        T ret = findHeap();
        data.set(0,value);
        siftDown(0);
        return ret;
    }

    /**
     * 返回徹底二叉樹的數組表示中,一個索引所表示的元素的父親節點的索引
     * @param index
     * @return
     */
    private int parent(int index) {
        if (index == 0) {
            throw new IllegalArgumentException("索引0沒有父節點");
        }
        return (index - 1) / 2;
    }

    /**
     * 返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引
     * @param index
     * @return
     */
    private int leftChild(int index) {
        return index * 2 + 1;
    }

    /**
     * 返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引
     * @param index
     * @return
     */
    private int rightChile(int index) {
        return index * 2 + 2;
    }
}

調用

public class HeapMain {
    public static void main(String[] args) {
        int n = 1000000;
        Heap<Integer> maxHeap = new MaxHeap<>();
        Random random = new Random();
        for (int i = 0;i < n;i++) {
            maxHeap.add(random.nextInt(Integer.MAX_VALUE));
        }
        int[] arr = new int[n];
        for (int i = 0;i < n;i++) {
            arr[i] = maxHeap.extract();
        }
        for (int i = 1;i < n;i++) {
            if (arr[i - 1] < arr[i]) {
                throw new IllegalArgumentException("出錯");
            }
        }
        System.out.println("測試最大堆完成");
    }
}

運行結果

測試最大堆完成

對Heapify和非Heapify兩種數組轉化最大堆的測試

public class TestHeapMain {
    private static double testHeap(Integer[] testData,boolean isHeapify) {
        long startTime = System.nanoTime();
        Heap<Integer> maxHeap;
        if (isHeapify) {
            maxHeap = new MaxHeap<>(testData);
        }else {
            maxHeap = new MaxHeap<>();
            Stream.of(testData).forEach(maxHeap::add);
        }
        int[] arr = new int[testData.length];
        for (int i = 0;i < testData.length;i++) {
            arr[i] = maxHeap.extract();
        }
        for (int i = 1;i < testData.length;i++) {
            if (arr[i - 1] < arr[i]) {
                throw new IllegalArgumentException("錯誤");
            }
        }
        System.out.println("測試最大堆成功");
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int n = 1000000;
        Random random = new Random();
        Integer[] testData = new Integer[n];
        for (int i = 0;i < n;i++) {
            testData[i] = random.nextInt(Integer.MAX_VALUE);
        }
        double time1 = testHeap(testData,false);
        System.out.println("不使用heapify: " + time1 + " s");
        System.out.println();
        double time2 = testHeap(testData,true);
        System.out.println("使用heapify: " + time2 + " s");
    }
}

測試結果

測試最大堆成功
不使用heapify: 0.876574063 s

測試最大堆成功
使用heapify: 0.496209799 s

  • 優先隊列封裝

實現類

public class PriorityQueue<T extends Comparable<T>> implements Queue<T> {
    private Heap<T> heap;

    public PriorityQueue() {
        heap = new MaxHeap<>();
    }

    @Override
    public void enqueue(T value) {
        heap.add(value);
    }

    @Override
    public T dequeue() {
        return heap.extract();
    }

    @Override
    public T getFront() {
        return heap.findHeap();
    }

    @Override
    public int getSize() {
        return heap.getSize();
    }

    @Override
    public boolean isEmpty() {
        return heap.isEmpty();
    }
}

LeetCode算法題 https://leetcode-cn.com/problems/top-k-frequent-elements/

這是LeetCode的第347題——前K個高頻元素

咱們先用JDK的優先隊列來完成,該優先隊列使用的是平衡二叉最小堆來完成的。這裏的全部類型皆爲JDK自帶類型,均爲java.util包中。

class Solution {
    /**
     * 頻次
     */
    private class Freq implements Comparable<Freq> {
        private int e;
        private int freq;

        public Freq(int e, int freq) {
            this.e = e;
            this.freq = freq;
        }

        public int getE() {
            return e;
        }

        public void setE(int e) {
            this.e = e;
        }

        public int getFreq() {
            return freq;
        }

        public void setFreq(int freq) {
            this.freq = freq;
        }

        @Override
        public int compareTo(Freq o) {
            if (this.freq < o.freq) {
                return -1;
            }else if (this.freq > o.freq) {
                return 1;
            }else {
                return 0;
            }
        }
    }

    public List<Integer> topKFrequent(int[] nums, int k) {
        Map<Integer,Integer> map = new TreeMap<>();
        for (int num : nums) {
            if (map.putIfAbsent(num,1) != null) {
                map.put(num,map.get(num) + 1);
            }
        }
        Queue<Freq> pq = new PriorityQueue<>();
        map.keySet().stream().forEach(key -> {
            if (pq.size() < k) {
                pq.add(new Freq(key,map.get(key)));
            }else if (map.get(key) > pq.peek().getFreq()) {
                pq.poll();
                pq.add(new Freq(key,map.get(key)));
            }
        });
        List<Integer> res = new LinkedList<>();
        while (!pq.isEmpty()) {
            res.add(pq.poll().getE());
        }
        return res;
    }
}

提交給LeetCode

  • 線段樹封裝

當咱們在一個區間中進行統計和查詢的時候,好比查詢一個區間[i,j]的最大值,最小值,或者區間數字和,若是使用數組來遍歷的話,它是一個O(n)的時間複雜度,但若是使用線段樹,則時間複雜度就會減小到O(log n)級別。在線段樹的應用中,區間自己是固定的,不考慮向區間中增長,刪除元素的操做。

若是上圖是一個求和的線段樹,則根節點是整個區間A[0-7]全部元素的和,而下面的非葉節點分支則是不一樣中間分區間的元素的和。但並不是全部的線段樹都是一顆滿二叉樹。

由上圖能夠看到,線段樹也不是一顆徹底二叉樹,但線段樹是平衡二叉樹。所謂平衡二叉樹是指不一樣葉節點的深度最大不超過1。因此以前所說的堆也是平衡二叉樹,但二分搜索樹可能不是一顆平衡二叉樹。雖然線段樹不是徹底二叉樹,但依然能夠用數組的形式來表示,咱們能夠將其看做是一顆滿二叉樹,只不過缺失的節點用空來表示。

若是區間有n個元素,用數組來組成線段樹,根據滿二叉樹的原理,若是元素個數n恰好爲2^k次冪個(如8個,16個),則組成線段樹的數組空間須要2n,由於上層節點數之和約等於葉節點數(如葉節點是8的時候,上層節點數之和爲7)。而元素個數不爲2^k次冪時,就須要增長一排葉節點來組成滿二叉樹,而增長的這一排就會約等於上層滿二叉樹的節點數之和,上層滿二叉樹之和爲2n,則組成線段樹的數組空間須要4n。

接口

public interface SegmentTree<T> {
    T get(int index);
    int getSize();

    /**
     * 返回區間[queryL,queryR]的值
     * @param queryL
     * @param queryR
     * @return
     */
    T query(int queryL,int queryR);
    void set(int index,T t);
}

實現類

public class DefaultSegmentTree<T> implements SegmentTree<T> {
    /**
     * 區間元素
     */
    private T[] data;
    /**
     * 線段樹
     */
    private T[] tree;
    /**
     * 函數式接口,繼承於BiFunction,用作線段樹規則(求和,求最大,最小等等)
     */
    private BinaryOperator<T> merger;

    @SuppressWarnings("unchecked")
    public DefaultSegmentTree(T[] data,BinaryOperator<T> merger) {
        this.merger = merger;
        this.data = (T[]) new Object[data.length];
        for (int i = 0;i < data.length;i++) {
            this.data[i] = data[i];
        }
        tree = (T[]) new Object[data.length * 4];
        buildSegmentTree(0,0,data.length - 1);
    }

    /**
     * 在treeIndex的位置建立表示區間[l...r]的線段樹
     * @param treeIndex 根節點的索引
     * @param l 區間左邊界
     * @param r 區間右邊界
     */
    private void buildSegmentTree(int treeIndex,int l,int r) {
        if (l == r) {
            tree[treeIndex] = data[l];
            return;
        }
        int leftTreeIndex = leftChild(treeIndex);
        int rightTreeIndex = rightChile(treeIndex);
        int mid = l + (r - l) / 2;
        buildSegmentTree(leftTreeIndex,l,mid);
        buildSegmentTree(rightTreeIndex,mid + 1,r);
        tree[treeIndex] = merger.apply(tree[leftTreeIndex],tree[rightTreeIndex]);
    }

    @Override
    public T get(int index) {
        if (index < 0 || index >= data.length) {
            throw new IllegalArgumentException("索引非法");
        }
        return data[index];
    }

    @Override
    public int getSize() {
        return data.length;
    }

    @Override
    public T query(int queryL, int queryR) {
        if (queryL < 0 || queryL >= data.length
                || queryR < 0 || queryR >= data.length || queryL > queryR) {
            throw new IllegalArgumentException("索引非法");
        }
        return query(0,0,data.length - 1,queryL,queryR);
    }

    /**
     * 在以treeID爲根的線段樹中[l...r]的範圍裏,搜索區間[queryL...queryR]的值
     * @param treeIndex 根節點的索引
     * @param l 區間的左邊界
     * @param r 區間的右邊界
     * @param queryL 查詢的左邊界
     * @param queryR 查詢的右邊界
     * @return
     */
    private T query(int treeIndex,int l,int r,int queryL,int queryR) {
        if (l == queryL && r == queryR) {
            return tree[treeIndex];
        }
        int mid = l + (r - l) / 2;
        int leftTreeIndex = leftChild(treeIndex);
        int rightTreeIndex = rightChile(treeIndex);
        if (queryL >= mid + 1) {
            return query(rightTreeIndex,mid + 1,r,queryL,queryR);
        }else if (queryR <= mid) {
            return query(leftTreeIndex,l,mid,queryL,queryR);
        }
        T leftResult = query(leftTreeIndex, l, mid, queryL, mid);
        T rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);
        return merger.apply(leftResult,rightResult);
    }

    @Override
    public void set(int index, T value) {
        if (index < 0 || index >= data.length) {
            throw new IllegalArgumentException("索引非法");
        }
        data[index] = value;
        set(0,0,data.length - 1,index,value);
    }

    /**
     * 在以treeIndex爲根的線段樹中更新index的值爲value
     * @param treeIndex
     * @param l
     * @param r
     * @param index
     * @param value
     */
    private void set(int treeIndex,int l,int r,int index,T value) {
        if (l == r) {
            tree[treeIndex] = value;
            return;
        }
        int mid = l + (r - l) / 2;
        int leftTreeIndex = leftChild(treeIndex);
        int rightTreeIndex = rightChile(treeIndex);
        if (index >= mid + 1) {
            set(rightTreeIndex,mid + 1,r,index,value);
        }else {
            set(leftTreeIndex,l,mid,index,value);
        }
        tree[treeIndex] = merger.apply(tree[leftTreeIndex],tree[rightTreeIndex]);
    }

    /**
     * 返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引
     * @param index
     * @return
     */
    private int leftChild(int index) {
        return index * 2 + 1;
    }

    /**
     * 返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引
     * @param index
     * @return
     */
    private int rightChile(int index) {
        return index * 2 + 2;
    }

    @Override
    public String toString() {
        return "DefaultSegmentTree{" +
                "tree=" + Arrays.toString(tree) +
                '}';
    }
}

調用

public class SegmentTreeMain {
    public static void main(String[] args) {
        Integer[] nums = {-2,0,3,-5,2,-1};
        SegmentTree<Integer> segmentTree = new DefaultSegmentTree<>(nums,(x,y) -> x + y);
        System.out.println(segmentTree);
        System.out.println(segmentTree.query(0,2));
        segmentTree.set(3,5);
        System.out.println(segmentTree);
        System.out.println(segmentTree.query(1,4));
    }
}

運行結果

DefaultSegmentTree{tree=[-3, 1, -4, -2, 3, -3, -1, -2, 0, null, null, -5, 2, null, null, null, null, null, null, null, null, null, null, null]}
1
DefaultSegmentTree{tree=[7, 1, 6, -2, 3, 7, -1, -2, 0, null, null, 5, 2, null, null, null, null, null, null, null, null, null, null, null]}
10

從結果能夠看到-3爲全部元素的和。1爲前3個元素的和,-4爲後3個元素的和。而咱們要查詢的0到2區間的和爲1。-5變動爲5後,整個線段樹從新變動結果。

LeetCode算法題 https://leetcode-cn.com/problems/range-sum-query-immutable/

這是LeetCode算法題的303題——區域和檢索-數組不可變

這裏咱們就使用線段樹來完成。

class NumArray {
    private SegmentTree<Integer> segmentTree;

    public NumArray(int[] nums) {
        if (nums.length > 0) {
            Integer[] data = new Integer[nums.length];
            for (int i = 0;i < nums.length;i++) {
                data[i] = nums[i];
            }
            segmentTree = new DefaultSegmentTree<>(data,(x, y) -> x + y);
        }
    }

    public int sumRange(int i, int j) {
        if (segmentTree == null) {
            throw new IllegalArgumentException("線段樹爲空");
        }
        return segmentTree.query(i,j);
    }
}

提交給LeetCode(提交的時候須要把線段樹的接口以及實現類以私有方式放入NumArray類中)

不使用線段樹,而採用預處理來完成

class NumArray {
    //預處理,sum[i]存儲前i個元素和,sum[0] = 0
    //sum[i]存儲nums[0...i-1]的和
    private int[] sum;
    public NumArray(int[] nums) {
        sum = new int[nums.length + 1];
        sum[0] = 0;
        for (int i = 1;i < sum.length;i++) {
            sum[i] = sum[i - 1] + nums[i - 1];
        }
    }

    public int sumRange(int i, int j) {
        return sum[j + 1] - sum[i];
    }
}

提交給LeetCode

LeetCode算法題 https://leetcode-cn.com/problems/range-sum-query-mutable/

這是LeetCode的第307題——區域和檢索-數組可修改

咱們依然使用線段樹來處理

class NumArray {
    private SegmentTree<Integer> segmentTree;

    public NumArray(int[] nums) {
        if (nums.length > 0) {
            Integer[] data = new Integer[nums.length];
            for (int i = 0;i < nums.length;i++) {
                data[i] = nums[i];
            }
            segmentTree = new DefaultSegmentTree<>(data,(x,y) -> x + y);
        }
    }

    public void update(int i, int val) {
        if (segmentTree == null) {
            throw new IllegalArgumentException("線段樹爲空");
        }
        segmentTree.set(i,val);
    }

    public int sumRange(int i, int j) {
        if (segmentTree == null) {
            throw new IllegalArgumentException("線段樹爲空");
        }
        return segmentTree.query(i,j);
    }
}

提交給LeetCode(提交的時候須要把線段樹的接口以及實現類以私有方式放入NumArray類中)

  • 字典樹封裝

字典樹是一種查詢每個條目的時間複雜度,和字典中一共有多少條目無關的數據結構。而其時間複雜度爲O(w),w爲查詢單詞的長度。字典樹是一個多叉樹,對於小寫英文字母來講,能夠有26個指向下個節點的指針。

接口

public interface Trie {
    int getSize();
    void add(String word);
    boolean contains(String word);
    boolean isEmpty();
    boolean isPrefix(String prefix);
    void remove(String word);
}

實現類(此處的Map和TreeMap皆爲JDK內置)

public class DefaultTrie implements Trie {
    @Data
    private class Node {
        //單詞節點的末尾
        private Boolean word;
        private Map<Character,Node> next;

        public Node(boolean word) {
            this.word = word;
            next = new TreeMap<>();
        }

        public Node() {
            this(false);
        }
    }

    private Node root;
    private int size;

    public DefaultTrie() {
        root = new Node();
        size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public void add(String word) {
        Node cur = root;
        for (int i = 0;i < word.length();i++) {
            char c = word.charAt(i);
            cur.getNext().putIfAbsent(c,new Node());
            cur = cur.getNext().get(c);
        }
        if (!cur.getWord()) {
            cur.setWord(true);
            size++;
        }
    }

    @Override
    public boolean contains(String word) {
        Node cur = root;
        for (int i = 0;i < word.length();i++) {
            char c = word.charAt(i);
            if (cur.getNext().get(c) == null) {
                return false;
            }
            cur = cur.getNext().get(c);
        }
        return cur.getWord();
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public boolean isPrefix(String prefix) {
        Node cur = root;
        for (int i = 0;i < prefix.length();i++) {
            char c = prefix.charAt(i);
            if (cur.getNext().get(c) == null) {
                return false;
            }
            cur = cur.getNext().get(c);
        }
        return true;
    }

    @Override
    public void remove(String word) {
        if (contains(word)) {
            remove(word,word.length() - 1);
         }
    }

    private void remove(String word,int index) {
        if (index + 1 == word.length()) {
            Node node = get(word,index);
            char c = word.charAt(index);
            if (node.getNext().size() == 1 && mapIsEmpty(node.getNext().get(c).getNext())) {
                node.setNext(null);
            }else if (node.getNext().size() == 1 && node.getNext().get(c).getWord()) {
                node.getNext().get(c).setWord(false);
                size--;
                return;
            }else {
                node.getNext().remove(c);
                size--;
                return;
            }
            word = word.substring(0,word.length() - 1);
        }
        remove(word,index - 1);
    }

    private Node get(String word,int index) {
        Node cur = root;
        for (int i = 0;i < index;i++) {
            char c = word.charAt(i);
            cur = cur.getNext().get(c);
        }
        return cur;
    }

    private boolean mapIsEmpty(Map<?, ?> map) {
        return map == null || map.isEmpty();
    }

    @Override
    public String toString() {
        return "DefaultTrie{" +
                "root=" + root +
                ", size=" + size +
                '}';
    }
}

調用

public class TrieMain {
    public static void main(String[] args) {
        System.out.println("傲慢與偏見");
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile("/Users/admin/Downloads/pride-and-prejudice.txt",words);
        System.out.println("共有單詞: " + words.getSize());
        Trie trie = new DefaultTrie();
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(trie::add);
        System.out.println("共有不一樣的單詞: " + trie.getSize());
    }
}

運行結果

傲慢與偏見
共有單詞: 125901
共有不一樣的單詞: 6530

刪除測試

public class TrieRemoveMain {
    public static void main(String[] args) {
        Trie trie = new DefaultTrie();
        trie.add("adfatr");
        trie.add("adfadhy");;
        trie.add("adfasd");
        trie.remove("adfadhy");
        System.out.println(trie);
    }
}

運行結果

DefaultTrie{root=DefaultTrie.Node(word=false, next={a=DefaultTrie.Node(word=false, next={d=DefaultTrie.Node(word=false, next={f=DefaultTrie.Node(word=false, next={a=DefaultTrie.Node(word=false, next={s=DefaultTrie.Node(word=false, next={d=DefaultTrie.Node(word=true, next={})}), t=DefaultTrie.Node(word=false, next={r=DefaultTrie.Node(word=true, next={})})})})})})}), size=2}

因爲字典樹的設計有可能佔用空間過大,因此有一種設計爲壓縮字典樹

關於壓縮字典樹的應用能夠參考字典樹收集(初步讀寫鎖實現線程安全,待續)

LeetCode算法題 https://leetcode-cn.com/problems/implement-trie-prefix-tree/

這是LeetCode的208題——實現Trie(前綴樹)

該題跟咱們實現的字典樹徹底同樣,只是方法名稱不一樣。

class Trie {
    private class Node {
        //單詞節點的末尾
        private Boolean word;
        private Map<Character,Node> next;

        public Node(boolean word) {
            this.word = word;
            next = new TreeMap<>();
        }

        public Node() {
            this(false);
        }

        public Boolean getWord() {
            return word;
        }

        public void setWord(Boolean word) {
            this.word = word;
        }

        public Map<Character, Node> getNext() {
            return next;
        }

        public void setNext(Map<Character, Node> next) {
            this.next = next;
        }
    }

    private Node root;

    /** Initialize your data structure here. */
    public Trie() {
        root = new Node();
    }

    /** Inserts a word into the trie. */
    public void insert(String word) {
        Node cur = root;
        for (int i = 0;i < word.length();i++) {
            char c = word.charAt(i);
            cur.getNext().putIfAbsent(c,new Node());
            cur = cur.getNext().get(c);
        }
        if (!cur.getWord()) {
            cur.setWord(true);
        }
    }

    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        Node cur = root;
        for (int i = 0;i < word.length();i++) {
            char c = word.charAt(i);
            if (cur.getNext().get(c) == null) {
                return false;
            }
            cur = cur.getNext().get(c);
        }
        return cur.getWord();
    }

    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        Node cur = root;
        for (int i = 0;i < prefix.length();i++) {
            char c = prefix.charAt(i);
            if (cur.getNext().get(c) == null) {
                return false;
            }
            cur = cur.getNext().get(c);
        }
        return true;
    }
}

提交給LeetCode

LeetCode算法題 https://leetcode-cn.com/problems/add-and-search-word-data-structure-design/

這是LeetCode的第211題——添加與搜索單詞-數據結構設計

這道題的重點依然是遞歸的使用

class WordDictionary {
    private class Node {
        //單詞節點的末尾
        private Boolean word;
        private Map<Character,Node> next;

        public Node(boolean word) {
            this.word = word;
            next = new TreeMap<>();
        }

        public Node() {
            this(false);
        }

        public Boolean getWord() {
            return word;
        }

        public void setWord(Boolean word) {
            this.word = word;
        }

        public Map<Character, Node> getNext() {
            return next;
        }

        public void setNext(Map<Character, Node> next) {
            this.next = next;
        }
    }

    private Node root;

    /** Initialize your data structure here. */
    public WordDictionary() {
        root = new Node();
    }

    /** Adds a word into the data structure. */
    public void addWord(String word) {
        Node cur = root;
        for (int i = 0;i < word.length();i++) {
            char c = word.charAt(i);
            cur.getNext().putIfAbsent(c,new Node());
            cur = cur.getNext().get(c);
        }
        if (!cur.getWord()) {
            cur.setWord(true);
        }
    }

    /** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
    public boolean search(String word) {
        return match(root,word,0);
    }

    private boolean match(Node node,String word,int index) {
        if (index == word.length()) {
            return node.getWord();
        }
        char c = word.charAt(index);
        if (c != '.') {
            if (node.getNext().get(c) == null) {
                return false;
            }
            return match(node.getNext().get(c),word,index + 1);
        }else {
            //遍歷全部的字符節點,進行遞歸
            for (char nextChar : node.getNext().keySet()) {
                if (match(node.getNext().get(nextChar),word,index + 1)) {
                    return true;
                }
            }
            return false;
        }
    }
}

提交給LeetCode

LeetCode算法題 https://leetcode-cn.com/problems/map-sum-pairs/

這是LeetCode的第677題——鍵值映射

一樣也是使用遞歸來獲取

class MapSum {
    private class Node {
        private int value;
        private Map<Character,Node> next;

        public Node(int value) {
            this.value = value;
            next = new TreeMap<>();
        }

        public Node() {
            this(0);
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }

        public Map<Character, Node> getNext() {
            return next;
        }

        public void setNext(Map<Character, Node> next) {
            this.next = next;
        }
    }

    private Node root;

    /** Initialize your data structure here. */
    public MapSum() {
        root = new Node();
    }

    public void insert(String key, int val) {
        Node cur = root;
        for (int i = 0;i < key.length();i++) {
            char c = key.charAt(i);
            cur.getNext().putIfAbsent(c,new Node());
            cur = cur.getNext().get(c);
        }
        cur.setValue(val);
    }

    public int sum(String prefix) {
        Node cur = root;
        for (int i = 0;i < prefix.length();i++) {
            char c = prefix.charAt(i);
            if (cur.getNext().get(c) == null) {
                return 0;
            }
            cur = cur.getNext().get(c);
        }
        return sum(cur);
    }

    private int sum(Node node) {
        int ret = node.getValue();
        for (char c : node.getNext().keySet()) {
            ret += sum(node.getNext().get(c));
        }
        return ret;
    }
}

提交給LeetCode

  • 並查集封裝

並查集是解決鏈接問題的一種數據結構。咱們認爲在同一個集合中,兩個元素是必然鏈接的,而不一樣的集合是不鏈接的。在並查集中,相似於線段樹,一樣不考慮並查集的增長、刪除元素的操做。

接口

public interface UnionFind {
    int getSize();

    /**
     * 查看元素p和元素q是否所屬同一個集合
     * @param p
     * @param q
     * @return
     */
    boolean isConnected(int p,int q);

    /**
     * 合併元素p和元素q所屬的集合
     * @param p
     * @param q
     */
    void unionElements(int p,int q);
}

數組實現類

public class ArrayUnionFind implements UnionFind {
    //該數組的索引,咱們稱爲元素的編號
    //該數組的值,咱們稱之爲元素所屬不一樣集合的編號
    //若是不一樣索引的值相同,則表示不一樣的元素屬於同一個集合
    private int[] id;

    public ArrayUnionFind(int size) {
        id = new int[size];
        for (int i = 0;i < id.length;i++) {
            id[i] = i;
        }
    }

    /**
     * 查找元素p所對應的集合編號
     * @param p
     * @return
     */
    private int find(int p) {
        if (p < 0 || p >= id.length) {
            throw new IllegalArgumentException("p越界");
        }
        return id[p];
    }

    @Override
    public int getSize() {
        return id.length;
    }

    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    @Override
    public void unionElements(int p, int q) {
        int pID = find(p);
        int qID = find(q);
        if (pID == qID) {
            return;
        }
        for (int i = 0;i < id.length;i++) {
            if (id[i] == pID) {
                id[i] = qID;
            }
        }
    }
}

對於ArrayUnionFind各操做的時間複雜度

isConnected(p,q)                      O(1)

unionElements(p,q)                  O(n)

因爲ArrayUnionFind的unionElements()方法是O(n)級別的,因此在數據量比較大的狀況下,它是沒法支撐的。但由於它的isConnected()方法是O(1)級別的,因此咱們稱該實現類爲Quick Find,而咱們要改進的是Quick Union。

樹實現類

咱們將數組中的每一個元素都當成一顆獨立的樹,初始化時,它們都是本身指向本身的指針。意思就是說每一個元素的集合編號就是它們本身。

若是此時咱們要改變元素4的集合編號變動爲3的時候

此時咱們又要將3的元素的集合編號變動爲8

元素6的集合編號變動爲5

元素9的集合編號變動與4相同,因爲4的屬於集合編號3的,而元素3是屬於集合編號8的,因此要讓9與4有相同的集合編號,因此9須要指向4的根節點的集合編號8.

元素2的集合編號變動爲1

元素5的集合編號變動爲0

元素7的集合編號變動與2相同,因此7的集合編號變動爲2的根節點1

元素6的集合編號變動與2相同,只須要變動6的根結點0的集合編號爲2的根節點的集合編號1

 

public class TreeUnionFind implements UnionFind {
    //該數組表示每一個元素爲一個向上指向本身指針的樹
    //當數組的值與索引相等時,表示本身指向本身
    //當數組元素的值發生變更爲其餘元素的索引時,表示向上指針指向其餘元素
    private int[] parent;

    public TreeUnionFind(int size) {
        parent = new int[size];
        for (int i = 0;i < parent.length;i++) {
            parent[i] = i;
        }
    }

    /**
     * 不斷向上查找元素的根節點,直到找到根節點
     * O(h)複雜度,h爲樹的高度
     * @param p
     * @return
     */
    private int find(int p) {
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p越界");
        }
        while (p != parent[p]) {
            p = parent[p];
        }
        return p;
    }

    @Override
    public int getSize() {
        return parent.length;
    }

    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        parent[pRoot] = qRoot;
    }
}

對於TreeUnionFind各操做的時間複雜度

isConnected(p,q)                      O(h) h爲樹的高度

unionElements(p,q)                  O(h) h爲樹的高度

兩種並查集的性能測試

public class TestUnionFindMain {
    private static double testUF(UnionFind uf,int m) {
        int size = uf.getSize();
        Random random = new Random();
        long startTime = System.nanoTime();
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.unionElements(a,b);
        }
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.isConnected(a,b);
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int size = 1000000;
        int m = 10000;
        UnionFind arrayUnionFind = new ArrayUnionFind(size);
        System.out.println("arrayUnionFind : " + testUF(arrayUnionFind,m) + " s");

        UnionFind treeUnionFind = new TreeUnionFind(size);
        System.out.println("treeUnionFind : " + testUF(treeUnionFind,m) + " s");
    }
}

測試結果

arrayUnionFind : 2.232692422 s
treeUnionFind : 0.002538113 s

可是當咱們調整m的合併集合次數的時候

public class TestUnionFindMain {
    private static double testUF(UnionFind uf,int m) {
        int size = uf.getSize();
        Random random = new Random();
        long startTime = System.nanoTime();
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.unionElements(a,b);
        }
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.isConnected(a,b);
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int size = 100000;
        int m = 100000;
        UnionFind arrayUnionFind = new ArrayUnionFind(size);
        System.out.println("arrayUnionFind : " + testUF(arrayUnionFind,m) + " s");

        UnionFind treeUnionFind = new TreeUnionFind(size);
        System.out.println("treeUnionFind : " + testUF(treeUnionFind,m) + " s");
    }
}

測試結果

arrayUnionFind : 4.139503265 s
treeUnionFind : 8.657331935 s

出現這種狀況,是由於在數組並查集,雖然它的合併集合unionElements()方法是O(n)級別的,但因爲數組的內存地址是連續的,而樹並查級的內存地址是非連續的,這裏須要有必定的時間延遲,連續地址比非連續地址要快。而更重要的是增大了合併的次數m,有可能產生了相似於鏈表的樹結構,也就是說樹的高度接近於鏈表的長度,因此O(h)接近於O(n)。針對這個問題,咱們須要改進合併集合的方式,讓樹向多分叉方向變化,而不是向鏈表方向變化。

改進的樹並查集實現類,咱們稱之爲基於樹的節點元素個數的優化——size優化

public class SizeTreeUnionFind implements UnionFind {
    //該數組表示每一個元素爲一個向上指向本身指針的樹
    //當數組的值與索引相等時,表示本身指向本身
    //當數組元素的值發生變更爲其餘元素的索引時,表示向上指針指向其餘元素
    private int[] parent;
    //sz[i]表示以i爲根的集合中元素個數
    private int[] sz;

    public SizeTreeUnionFind(int size) {
        parent = new int[size];
        sz = new int[size];
        for (int i = 0;i < parent.length;i++) {
            parent[i] = i;
            sz[i] = 1;
        }
    }

    /**
     * 不斷向上查找元素的根節點,直到找到根節點
     * O(h)複雜度,h爲樹的高度
     * @param p
     * @return
     */
    private int find(int p) {
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p越界");
        }
        while (p != parent[p]) {
            p = parent[p];
        }
        return p;
    }

    @Override
    public int getSize() {
        return parent.length;
    }

    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        //根據兩個元素所在樹的元素個數不一樣判斷合併方向
        //將元素個數少的集合合併到元素個數多的集合上,下降樹的高度
        if (sz[pRoot] < sz[qRoot]) {
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }else {
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
}

從新測試

public class TestUnionFindMain {
    private static double testUF(UnionFind uf,int m) {
        int size = uf.getSize();
        Random random = new Random();
        long startTime = System.nanoTime();
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.unionElements(a,b);
        }
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.isConnected(a,b);
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int size = 100000;
        int m = 100000;
        UnionFind arrayUnionFind = new ArrayUnionFind(size);
        System.out.println("arrayUnionFind : " + testUF(arrayUnionFind,m) + " s");

        UnionFind sizeTreeUnionFind = new SizeTreeUnionFind(size);
        System.out.println("sizeTreeUnionFind : " + testUF(sizeTreeUnionFind,m) + " s");
    }
}

測試結果

arrayUnionFind : 4.08657561 s
sizeTreeUnionFind : 0.019326149 s

基於下降樹的高度的思想,咱們又能夠從新修改樹並查集的代碼,專門基於樹的高度來進行優化

假設有如今的這種狀況

此時咱們要將4的元素的集合編號變動爲與2相同,根據size的優化思路,因爲2所在集合的元素要多於4所在集合的元素,因此咱們此時合併集合後的結果以下,4的根節點8指向2的根節點7.

可是這樣合併集合,無疑增大了樹的高度,因此咱們要讓原樹的高度低的合併到原樹的高度高的中去,合併後結果以下

層級並查樹實現類

public class RankTreeUnionFind implements UnionFind {
    //該數組表示每一個元素爲一個向上指向本身指針的樹
    //當數組的值與索引相等時,表示本身指向本身
    //當數組元素的值發生變更爲其餘元素的索引時,表示向上指針指向其餘元素
    private int[] parent;
    //rank[i]表示以i爲根的集合所表示的樹的層數
    private int[] rank;

    public RankTreeUnionFind(int size) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0;i < parent.length;i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    /**
     * 不斷向上查找元素的根節點,直到找到根節點
     * O(h)複雜度,h爲樹的高度
     * @param p
     * @return
     */
    private int find(int p) {
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p越界");
        }
        while (p != parent[p]) {
            p = parent[p];
        }
        return p;
    }

    @Override
    public int getSize() {
        return parent.length;
    }

    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        //根據兩個元素所在樹的rank不一樣判斷合併方向
        //將rank低的集合合併到rank高的集合上
        if (rank[pRoot] < rank[qRoot]) {
            parent[pRoot] = qRoot;
        }else if (rank[qRoot] < rank[pRoot]) {
            parent[qRoot] = pRoot;
        }else {
            parent[qRoot] = pRoot;
            rank[pRoot]++;
        }
    }
}

如今咱們調大合併集合的次數,從新測試,因爲數組並查集合並集合方法爲O(n)級別,因此不參與,沒法完成運算

public class TestUnionFindMain {
    private static double testUF(UnionFind uf,int m) {
        int size = uf.getSize();
        Random random = new Random();
        long startTime = System.nanoTime();
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.unionElements(a,b);
        }
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.isConnected(a,b);
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int size = 10000000;
        int m = 10000000;
//        UnionFind arrayUnionFind = new ArrayUnionFind(size);
//        System.out.println("arrayUnionFind : " + testUF(arrayUnionFind,m) + " s");

        UnionFind sizeTreeUnionFind = new SizeTreeUnionFind(size);
        System.out.println("sizeTreeUnionFind : " + testUF(sizeTreeUnionFind,m) + " s");

        UnionFind rankTreeUnionFind = new RankTreeUnionFind(size);
        System.out.println("rankTreeUnionFind : " + testUF(rankTreeUnionFind,m) + " s");
    }
}

測試結果

sizeTreeUnionFind : 5.12385286 s
rankTreeUnionFind : 4.823445856 s

路徑壓縮
如今咱們都知道了,在並查集中,相似於下面這種樹的集合實際上是一個意思,它們都表示0、一、二、三、4這個5個元素屬於同一個集合,集合編號爲0,它們的根節點都是0。但因爲這三種樹的深度不一樣,因此查詢根節點的效率是不一樣的。

而路徑壓縮要解決的問題就是將一顆比較高的樹壓縮成一顆比較矮的樹。當咱們在查找一個節點的根節點find()的過程當中,順便進行一下路徑壓縮。好比咱們在最左邊的圖(相似鏈表)中查找4的根結點的時候,將4的父節點變動爲其父節點的父節點,即parent[4] = parent[parent[4]],這樣圖形就發生了以下的變化

因爲2並不是根節點,繼續向上遍歷,同時將2的父節點變動爲2的父節點的父節點parent[2] = parent[parent[2]],這樣圖形就發生了以下變化。

因爲找到了根節點0,此時查找根節點find()結束。而整顆樹的高度也變得更矮。因此SizeTreeUnionFind和RankTreeUnionFind也相應變動爲

public class SizeTreeUnionFind implements UnionFind {
    //該數組表示每一個元素爲一個向上指向本身指針的樹
    //當數組的值與索引相等時,表示本身指向本身
    //當數組元素的值發生變更爲其餘元素的索引時,表示向上指針指向其餘元素
    private int[] parent;
    //sz[i]表示以i爲根的集合中元素個數
    private int[] sz;

    public SizeTreeUnionFind(int size) {
        parent = new int[size];
        sz = new int[size];
        for (int i = 0;i < parent.length;i++) {
            parent[i] = i;
            sz[i] = 1;
        }
    }

    /**
     * 不斷向上查找元素的根節點,直到找到根節點
     * O(h)複雜度,h爲樹的高度
     * @param p
     * @return
     */
    private int find(int p) {
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p越界");
        }
        while (p != parent[p]) {
            parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;
    }

    @Override
    public int getSize() {
        return parent.length;
    }

    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        //根據兩個元素所在樹的元素個數不一樣判斷合併方向
        //將元素個數少的集合合併到元素個數多的集合上,下降樹的高度
        if (sz[pRoot] < sz[qRoot]) {
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }else {
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
}
public class RankTreeUnionFind implements UnionFind {
    //該數組表示每一個元素爲一個向上指向本身指針的樹
    //當數組的值與索引相等時,表示本身指向本身
    //當數組元素的值發生變更爲其餘元素的索引時,表示向上指針指向其餘元素
    private int[] parent;
    //rank[i]表示以i爲根的集合所表示的樹的層數
    private int[] rank;

    public RankTreeUnionFind(int size) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0;i < parent.length;i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    /**
     * 不斷向上查找元素的根節點,直到找到根節點
     * O(h)複雜度,h爲樹的高度
     * @param p
     * @return
     */
    private int find(int p) {
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p越界");
        }
        while (p != parent[p]) {
            parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;
    }

    @Override
    public int getSize() {
        return parent.length;
    }

    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        //根據兩個元素所在樹的rank不一樣判斷合併方向
        //將rank低的集合合併到rank高的集合上
        if (rank[pRoot] < rank[qRoot]) {
            parent[pRoot] = qRoot;
        }else if (rank[qRoot] < rank[pRoot]) {
            parent[qRoot] = pRoot;
        }else {
            parent[qRoot] = pRoot;
            rank[pRoot]++;
        }
    }
}

從新測試TestUnionFindMain,結果以下

sizeTreeUnionFind : 3.952670823 s
rankTreeUnionFind : 3.968889342 s

固然咱們也能夠直接藉助遞歸將一顆樹的高度變成2

遞歸層級並查集實現類

public class RecursiveRankTreeUnionFind implements UnionFind {
    //該數組表示每一個元素爲一個向上指向本身指針的樹
    //當數組的值與索引相等時,表示本身指向本身
    //當數組元素的值發生變更爲其餘元素的索引時,表示向上指針指向其餘元素
    private int[] parent;
    //rank[i]表示以i爲根的集合所表示的樹的層數
    private int[] rank;

    public RecursiveRankTreeUnionFind(int size) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0;i < parent.length;i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    /**
     * 不斷向上查找元素的根節點,直到找到根節點
     * O(h)複雜度,h爲樹的高度
     * @param p
     * @return
     */
    private int find(int p) {
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p越界");
        }
        if (p != parent[p]) {
            parent[p] = find(parent[p]);
        }
        return parent[p];
    }

    @Override
    public int getSize() {
        return parent.length;
    }

    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        //根據兩個元素所在樹的rank不一樣判斷合併方向
        //將rank低的集合合併到rank高的集合上
        if (rank[pRoot] < rank[qRoot]) {
            parent[pRoot] = qRoot;
        }else if (rank[qRoot] < rank[pRoot]) {
            parent[qRoot] = pRoot;
        }else {
            parent[qRoot] = pRoot;
            rank[pRoot]++;
        }
    }
}

測試

public class TestUnionFindMain {
    private static double testUF(UnionFind uf,int m) {
        int size = uf.getSize();
        Random random = new Random();
        long startTime = System.nanoTime();
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.unionElements(a,b);
        }
        for (int i = 0;i < m;i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.isConnected(a,b);
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        int size = 10000000;
        int m = 10000000;
//        UnionFind arrayUnionFind = new ArrayUnionFind(size);
//        System.out.println("arrayUnionFind : " + testUF(arrayUnionFind,m) + " s");

        UnionFind sizeTreeUnionFind = new SizeTreeUnionFind(size);
        System.out.println("sizeTreeUnionFind : " + testUF(sizeTreeUnionFind,m) + " s");

        UnionFind rankTreeUnionFind = new RankTreeUnionFind(size);
        System.out.println("rankTreeUnionFind : " + testUF(rankTreeUnionFind,m) + " s");

        UnionFind recursiveRankTreeUnionFind = new RecursiveRankTreeUnionFind(size);
        System.out.println("recursiveRankTreeUnionFind : " + testUF(recursiveRankTreeUnionFind,m) + " s");
    }
}

測試結果

sizeTreeUnionFind : 3.891047143 s
rankTreeUnionFind : 3.949480955 s
recursiveRankTreeUnionFind : 4.489701369 s

由結果可知,遞歸層級並查集的速度要慢過非遞歸,緣由在於非遞歸的方式也能夠最終將一棵樹的高度下降爲2,只不過不是一次調用,而是屢次調用。而遞歸的方式自己會增長複雜度,增大開銷。以下圖所示,若是咱們再次調用find(4)的時候,4的父節點就指向了父節點2的父節點0.

若是再調用一次find(3),則將最終演變成一顆高度爲2的樹

  • AVL樹封裝

AVL樹是一種保持平衡的二分搜索樹,使得該二分搜索樹不會退化爲一個鏈表。

平衡二叉樹——對於任意一個節點,左子樹和右子樹的高度差不能超過1。平衡二叉樹的高度和節點數量之間的關係也是O(log n)的。

由此能夠定義任意一個節點的高度爲左右子樹較高的那顆子樹的高度+1,加的這個1就是節點自己。

以上圖數字5爲例,他的左右子樹的高度,分別爲2和1,則5節點的高度爲左右子樹中高的子樹的高度加1,則其高度爲3.

咱們定義一個節點的平衡因子爲該節點左右子樹的高度差,則很明顯,當平衡因子爲大於0的時候,說明左子樹高於右子樹;當平衡因子小於0的時候,說明右子樹高於左子樹。而當平衡因子大於1或者小於-1的時候,咱們知道,該節點失去了平衡性。

又根據平衡二叉樹的定義,咱們知道上面這顆二分搜索樹並非一個平衡二分搜索樹,由於到8這個節點的時候,它的平衡因子爲2,超過了1.因此咱們在AVL樹的實現的時候,須要保持8這個節點的平衡性。

要維護一個節點的平衡性須要考慮4種狀況

一、一直向一顆樹的左側添加元素,咱們能夠用LL來標記

此時能夠確定的是該節點的平衡因子是大於1的(左子樹高過右子樹2,通常也就高過2,不會超過2),且該節點的左節點的平衡因子大於等於0纔多是一直向左側添加元素.

右旋轉

假設咱們如今在樹的最左側添加了一個節點,並打破了平衡性,y的平衡因子爲2.根據二分搜索樹的性質(節點左子樹的元素一概小於該節點的元素,節點右子樹的元素,一概大於該節點元素),此時圖中各個元素的大小排序爲

T1 < z < T2 < x < T3 < y < T4

當咱們轉換圖形的位置以下時

各個元素的大小排序爲

T1 < z < T2 < x < T3 < y < T4,可見元素的順序並無發生改變,說明這樣的一顆二分搜索樹跟以前的是等價的。

而所要作出的調整就爲將x的右節點變成y,x的原右節點T3變爲y的左節點,咱們將此過程稱爲右旋轉。旋轉完以後,咱們須要維護x跟y的高度。

二、一直向一棵樹的右側一直添加元素,咱們能夠用RR來標記

此時能夠確定的是該節點的平衡因子是小於-1的(右子樹高過左子樹2),且該節點的右節點的平衡因子小於等於0纔多是一直向右側添加元素.

左旋轉

假設咱們如今在樹的最右側添加了一個節點,並打破了平衡性,y的平衡因子爲-2.根據二分搜索樹的性質(節點左子樹的元素一概小於該節點的元素,節點右子樹的元素,一概大於該節點元素),此時圖中各個元素的大小排序爲

T4 < y < T3 < x < T1 < z < T2

當咱們轉換圖形的位置以下時

各個元素的大小排序爲

T4 < y < T3 < x < T1 < z < T2,可見元素的順序並無發生改變,說明這樣的一顆二分搜索樹跟以前的是等價的。

而所要作出的調整就爲將x的左節點變成y,x的原左節點T3變爲y的右節點,咱們將此過程稱爲左旋轉。旋轉完以後,咱們須要維護x跟y的高度。

三、向節點的左節點添加右節點,咱們能夠用LR標識

此時能夠確定的是該節點的平衡因子是大於1的(左子樹高過右子樹2),且該節點的左節點的平衡因子小於0纔多是這種狀況。

如今咱們知道不管對一顆二分搜索樹作左旋轉仍是右旋轉都不會改變一個二分搜索樹的排序的性質,那麼咱們對x作一次左旋轉,將x變成z的左節點,T2變成x的右節點。固然z要變成y的左節點。

那麼此時,就變成了LL的狀況,按照LL來處理就行了。

四、向節點的右節點添加左節點,咱們能夠用RL標識

此時能夠確定的是該節點的平衡因子是小於-1的(右子樹高過左子樹2),且該節點的右節點的平衡因子大於0纔多是這種狀況。

此時咱們只須要將x作一次右旋轉,將x變成z的右節點,將T3變成x的左節點,固然z變成y的右節點。

那麼此時,就變成了RR的狀況,按照RR來處理就行了。

實現類

public class AVLTree<T extends Comparable<T>> implements Tree<T> {
    @Data
    @AllArgsConstructor
    private class Node{
        private T element;
        private Node left;
        private Node right;
        //節點的高度,在節點的左右子樹增長、刪除節點時須要維護該節點的高度
        private int height;

        public Node(T element) {
            this(element,null,null,1);
        }
    }

    private Node root;
    private int size;

    public AVLTree() {
        root = null;
        size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    private int getHeight(Node node) {
        if (node == null) {
            return 0;
        }
        return node.getHeight();
    }

    /**
     * 得到節點node的平衡因子(左右子樹的高度差)
     * @param node
     * @return
     */
    private int getBalanceFactor(Node node) {
        if (node == null) {
            return 0;
        }
        return getHeight(node.getLeft()) - getHeight(node.getRight());
    }

    /**
     * 判斷該二叉樹是不是一顆二分搜索樹
     * @return
     */
    public boolean isBST() {
        Array<T> elements = new DefaultArray<>();
        inOrder(root,elements);
        for (int i = 1;i < elements.getSize();i++) {
            if (elements.get(i - 1).compareTo(elements.get(i)) > 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判斷該二叉樹是不是一顆平衡二叉樹
     * @return
     */
    public boolean isBalanced() {
        return isBalanced(root);
    }

    private boolean isBalanced(Node node) {
        if (node == null) {
            return true;
        }
        int balanceFactor = getBalanceFactor(node);
        if (Math.abs(balanceFactor) > 1) {
            return false;
        }
        return isBalanced(node.getLeft()) && isBalanced(node.getRight());
    }

    /**
     * 對節點node進行右旋轉操做,返回旋轉後新的根節點(原node的左節點)
     * @param node
     * @return
     */
    private Node rightRotate(Node node) {
        Node ret = node.getLeft();
        Node rightRet = ret.getRight();
        //向右旋轉的過程
        ret.setRight(node);
        node.setLeft(rightRet);
        //更新height
        node.setHeight(Math.max(getHeight(node.getLeft()),getHeight(node.getRight())) + 1);
        ret.setHeight(Math.max(getHeight(ret.getLeft()),getHeight(ret.getRight())) + 1);

        return ret;
    }

    /**
     * 對節點node進行左旋轉操做,返回旋轉後新的根節點(原node的右節點)
     * @param node
     * @return
     */
    private Node leftRotate(Node node) {
        Node ret = node.getRight();
        Node leftRet = ret.getLeft();
        //向左旋轉的過程
        ret.setLeft(node);
        node.setRight(leftRet);
        //更新height
        node.setHeight(Math.max(getHeight(node.getLeft()),getHeight(node.getRight())) + 1);
        ret.setHeight(Math.max(getHeight(ret.getLeft()),getHeight(ret.getRight())) + 1);

        return ret;
    }

    @Override
    public void add(T value) {
        root = add(root,value);
    }

    private Node add(Node node,T value) {
        //遞歸終止條件
        if (node == null) {
            size++;
            return new Node(value);
        }
        //遞歸
        if (value.compareTo(node.getElement()) < 0) {
            node.setLeft(add(node.getLeft(),value));
        }else if (value.compareTo(node.getElement()) > 0) {
            node.setRight(add(node.getRight(),value));
        }
        return service(node);
    }

    @Override
    public boolean contains(T value) {
        return contains(root,value);
    }

    private boolean contains(Node node,T value) {
        if (node == null) {
            return false;
        }
        if (value.compareTo(node.getElement()) == 0) {
            return true;
        }else if (value.compareTo(node.getElement()) < 0) {
            return contains(node.getLeft(),value);
        }else {
            return contains(node.getRight(),value);
        }
    }

    @Override
    public void preOrder() {
        preOrder(root);
    }

    private void preOrder(Node node) {
        if (node == null) {
            return;
        }

        System.out.println(node.getElement());
        preOrder(node.getLeft());
        preOrder(node.getRight());
    }

    /**
     * 非遞歸前序遍歷
     */
    public void preOrderNR() {
        Stack<Node> stack = new ArrayStack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            Node cur = stack.pop();
            System.out.println(cur.getElement());
            if (cur.getRight() != null) {
                stack.push(cur.getRight());
            }
            if (cur.getLeft() != null) {
                stack.push(cur.getLeft());
            }
        }
    }

    @Override
    public void inOrder() {
        inOrder(root);
    }

    private void inOrder(Node node) {
        if (node == null) {
            return;
        }
        inOrder(node.getLeft());
        System.out.println(node.getElement());
        inOrder(node.getRight());
    }

    private void inOrder(Node node,Array<T> elements) {
        if (node == null) {
            return;
        }
        inOrder(node.getLeft(),elements);
        elements.addLast(node.getElement());
        inOrder(node.getRight(),elements);
    }

    @Override
    public void postOrder() {
        postOrder(root);
    }

    private void postOrder(Node node) {
        if (node == null) {
            return;
        }
        postOrder(node.getLeft());
        postOrder(node.getRight());
        System.out.println(node.getElement());
    }

    @Override
    public void levelOrder() {
        Queue<Node> queue = new LoopQueue<>();
        queue.enqueue(root);
        while (!queue.isEmpty()) {
            Node cur = queue.dequeue();
            System.out.println(cur.getElement());

            if (cur.getLeft() != null) {
                queue.enqueue(cur.getLeft());
            }
            if (cur.getRight() != null) {
                queue.enqueue(cur.getRight());
            }
        }
    }

    @Override
    public T minimum() {
        if (size == 0) {
            throw new IllegalArgumentException("二分搜索樹爲空");
        }
        return minimum(root).getElement();
    }

    private Node minimum(Node node) {
        if (node.getLeft() == null) {
            return node;
        }
        return minimum(node.getLeft());
    }

    @Override
    public T maximum() {
        if (size == 0) {
            throw new IllegalArgumentException("二分搜索樹爲空");
        }
        return maximum(root).getElement();
    }

    private Node maximum(Node node) {
        if (node.getRight() == null) {
            return node;
        }
        return maximum(node.getRight());
    }

    @Override
    public T removeMin() {
        T ret = minimum();
        root = removeMin(root);
        return ret;
    }

    private Node removeMin(Node node) {
        //遞歸的終止條件,當節點的左子樹爲空時,遞歸達到最大深度
        if (node.getLeft() == null) {
            //保存最終節點的右子樹
            Node rightNode = node.getRight();
            //將最終節點分離
            node.setRight(null);
            size--;
            //將保存的右子樹拼接回最終節點的父節點
            return rightNode;
        }
        //從根節點開始不斷向左子樹遞歸,並逐層返回刪除後的子節點從新
        //設爲上層節點的左節點
        node.setLeft(removeMin(node.getLeft()));
        return service(node);
    }

    @Override
    public T removeMax() {
        T ret = maximum();
        root = removeMax(root);
        return ret;
    }

    private Node removeMax(Node node) {
        if (node.getRight() == null) {
            Node leftNode = node.getLeft();
            node.setLeft(null);
            size--;
            return leftNode;
        }

        node.setRight(removeMax(node.getRight()));
        return service(node);
    }

    @Override
    public void remove(T value) {
        root = remove(root,value);
    }

    private Node remove(Node node,T value) {
        if (node == null) {
            return null;
        }
        Node retNode;
        if (value.compareTo(node.getElement()) < 0) {
            node.setLeft(remove(node.getLeft(),value));
            retNode = node;
        }else if (value.compareTo(node.getElement()) > 0) {
            node.setRight(remove(node.getRight(),value));
            retNode = node;
        }else {
            //待刪除節點左子樹爲空的狀況
            if (node.getLeft() == null) {
                Node rightNode = node.getRight();
                node.setRight(null);
                size--;
                retNode = rightNode;
            }
            //待刪除節點右子樹爲空的狀況
            else if (node.getRight() == null) {
                Node leftNode = node.getLeft();
                node.setLeft(null);
                size--;
                retNode = leftNode;
            }else {
                //待刪除節點左右子樹均不爲空的狀況
                //找到比待刪除節點大的最小節點,即待刪除節點右子樹的最小節點
                //用這個節點頂替待刪除節點的位置
                Node successor = minimum(node.getRight());
                successor.setRight(removeMin(node.getRight()));
                successor.setLeft(node.getLeft());
                node.setLeft(null);
                node.setRight(null);
                retNode = successor;
                //找到比待刪除節點小的最大節點,即待刪除節點左子樹的最大節點
                //用這個節點頂替待刪除節點的位置,此二種方法取其一便可
//            Node predecessor = maximum(node.getLeft());
//            predecessor.setLeft(removeMax(node.getLeft()));
//            predecessor.setRight(node.getRight());
//            node.setLeft(null);
//            node.setRight(null);
//            return predecessor;
            }
        }
        if (retNode == null) {
            return null;
        }
        return service(retNode);
    }

    /**
     * 維護節點平衡
     * @param node
     * @return
     */
    private Node service(Node node) {
        //更新height
        node.setHeight(1 + Math.max(getHeight(node.getLeft()),getHeight(node.getRight())));
        //計算平衡因子
        int balanceFactor = getBalanceFactor(node);

        //平衡維護
        //LL
        if (balanceFactor > 1 && getBalanceFactor(node.getLeft()) >= 0) {
            return rightRotate(node);
        }
        //RR
        if (balanceFactor < -1 && getBalanceFactor(node.getRight()) <= 0) {
            return leftRotate(node);
        }
        //LR
        if (balanceFactor > 1 && getBalanceFactor(node.getLeft()) < 0) {
            node.setLeft(leftRotate(node.getLeft()));
            return rightRotate(node);
        }
        //RL
        if (balanceFactor < -1 && getBalanceFactor(node.getRight()) > 0) {
            node.setRight(rightRotate(node.getRight()));
            return leftRotate(node);
        }
        return node;
    }

    @Override
    public T floor(T value) {
        Node node = floor(root,value);
        if (node != null) {
            return node.getElement();
        }
        return null;
    }

    private Node floor(Node node,T value) {
        if (node == null) {
            return null;
        }
        if (value.compareTo(node.getElement()) <= 0) {
            return floor(node.getLeft(),value);
        }else {
            if (node.getRight() == null) {
                return node;
            }else if (node.getRight().getLeft() == null &&
                    value.compareTo(node.getRight().getElement()) <= 0) {
                return node;
            }else if (node.getRight().getRight() == null &&
                    value.compareTo(node.getRight().getElement()) > 0) {
                return node.getRight();
            }
            return floor(node.getRight(),value);
        }
    }

    @Override
    public T ceil(T value) {
        Node node = ceil(root,value);
        if (node != null) {
            return node.getElement();
        }
        return null;
    }

    private Node ceil(Node node,T value) {
        if (node == null) {
            return null;
        }
        if (value.compareTo(node.getElement()) >= 0) {
            return ceil(node.getRight(),value);
        }else {
            if (node.getLeft() == null) {
                return node;
            }else if (value.compareTo(maximum(node.getLeft()).getElement()) >= 0) {
                return node;
            }else if (node.getLeft().getRight() == null &&
                    value.compareTo(node.getLeft().getElement()) >= 0) {
                return node;
            }else if (node.getLeft().getLeft() == null &&
                    value.compareTo(node.getLeft().getElement()) < 0) {
                return node.getLeft();
            }
            return ceil(node.getLeft(),value);
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        generateBSTString(root,0,builder);
        return builder.toString();
    }

    private void generateBSTString(Node node,int depth,StringBuilder builder) {
        if (node == null) {
            builder.append(generateDepthString(depth) + "null\n");
            return;
        }
        builder.append(generateDepthString(depth) + node.getElement() + "\n");
        generateBSTString(node.getLeft(),depth + 1,builder);
        generateBSTString(node.getRight(),depth + 1,builder);
    }

    private String generateDepthString(int depth) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0;i < depth;i++) {
            builder.append("--");
        }
        return builder.toString();
    }
}
  • AVL樹映射封裝

實現類

public class AVLTreeMap<K extends Comparable<K>,V> implements Map<K,V> {
    @Data
    @AllArgsConstructor
    private class Node{
        private K key;
        private V value;
        private Node left;
        private Node right;
        private int height;

        public Node(K key) {
            this(key,null,null,null,1);
        }
        public Node(K key,V value) {
            this(key,value,null,null,1);
        }
    }

    private Node root;
    private int size;

    public AVLTreeMap() {
        root = null;
        size = 0;
    }

    private int getHeight(Node node) {
        if (node == null) {
            return 0;
        }
        return node.getHeight();
    }

    private int getBalanceFactor(Node node) {
        if (node == null) {
            return 0;
        }
        return getHeight(node.getLeft()) - getHeight(node.getRight());
    }

    private Node rightRotate(Node node) {
        Node ret = node.getLeft();
        Node retRight = ret.getRight();
        ret.setRight(node);
        node.setLeft(retRight);
        node.setHeight(Math.max(getHeight(node.getLeft()),getHeight(node.getRight())) + 1);
        ret.setHeight(Math.max(getHeight(ret.getLeft()),getHeight(ret.getRight())) + 1);
        return ret;
    }

    private Node leftRotate(Node node) {
        Node ret = node.getRight();
        Node retLeft = ret.getLeft();
        ret.setLeft(node);
        node.setRight(retLeft);
        node.setHeight(Math.max(getHeight(node.getLeft()),getHeight(node.getRight())) + 1);
        ret.setHeight(Math.max(getHeight(ret.getLeft()),getHeight(ret.getRight())) + 1);
        return ret;
    }

    @Override
    public void add(K key, V value) {
        root = add(root,key,value);
    }

    private Node add(Node node, K key, V value) {
        //遞歸終止條件
        if (node == null) {
            size++;
            return new Node(key,value);
        }
        //遞歸
        if (key.compareTo(node.getKey()) < 0) {
            node.setLeft(add(node.getLeft(),key,value));
        }else if (key.compareTo(node.getKey()) > 0) {
            node.setRight(add(node.getRight(),key,value));
        }else {
            node.setValue(value);
        }
        return service(node);
    }

    @Override
    public V remove(K key) {
        Node node = getNode(root,key);
        if (node != null) {
            root = remove(root,key);
            return node.getValue();
        }
        return null;
    }

    private Node remove(Node node, K key) {
        if (node == null) {
            return null;
        }
        Node retNode;
        if (key.compareTo(node.getKey()) < 0) {
            node.setLeft(remove(node.getLeft(),key));
            retNode = node;
        }else if (key.compareTo(node.getKey()) > 0) {
            node.setRight(remove(node.getRight(),key));
            retNode = node;
        }else {
            //待刪除節點左子樹爲空的狀況
            if (node.getLeft() == null) {
                Node rightNode = node.getRight();
                node.setRight(null);
                size--;
                retNode = rightNode;
            }
            //待刪除節點右子樹爲空的狀況
            else if (node.getRight() == null) {
                Node leftNode = node.getLeft();
                node.setLeft(null);
                size--;
                retNode = leftNode;
            }else {
                //待刪除節點左右子樹均不爲空的狀況
                //找到比待刪除節點大的最小節點,即待刪除節點右子樹的最小節點
                //用這個節點頂替待刪除節點的位置
                Node successor = minimum(node.getRight());
                successor.setRight(removeMin(node.getRight()));
                successor.setLeft(node.getLeft());
                node.setLeft(null);
                node.setRight(null);
                retNode = successor;
            }
        }
        if (retNode == null) {
            return null;
        }
        return service(retNode);
    }

    private Node service(Node node) {
        node.setHeight(1 + Math.max(getHeight(node.getLeft()),getHeight(node.getRight())));
        int balanceFactor = getBalanceFactor(node);
        if (balanceFactor > 1 && getBalanceFactor(node.getLeft()) >= 0) {
            return rightRotate(node);
        }
        if (balanceFactor < -1 && getBalanceFactor(node.getRight()) <= 0) {
            return leftRotate(node);
        }
        if (balanceFactor > 1 && getBalanceFactor(node.getLeft()) < 0) {
            node.setLeft(leftRotate(node.getLeft()));
            return rightRotate(node);
        }
        if (balanceFactor < -1 && getBalanceFactor(node.getRight()) > 0) {
            node.setRight(rightRotate(node.getRight()));
            return leftRotate(node);
        }
        return node;
    }

    private Node minimum(Node node) {
        if (node.getLeft() == null) {
            return node;
        }
        return minimum(node.getLeft());
    }

    private Node removeMin(Node node) {
        if (node.getLeft() == null) {
            Node rightNode = node.getRight();
            node.setRight(null);
            size--;
            return rightNode;
        }
        node.setLeft(removeMin(node.getLeft()));
        return service(node);
    }

    @Override
    public boolean contains(K key) {
        return getNode(root,key) != null;
    }

    @Override
    public V get(K key) {
        Node node = getNode(root,key);
        return node == null ? null : node.getValue();
    }

    @Override
    public void set(K key, V value) {
        Node node = getNode(root,key);
        if (node == null) {
            throw new IllegalArgumentException(key + "不存在");
        }
        node.setValue(value);
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    private Node getNode(Node node, K key) {
        if (node == null) {
            return null;
        }
        if (key.compareTo(node.getKey()) == 0) {
            return node;
        }else if (key.compareTo(node.getKey()) < 0) {
            return getNode(node.getLeft(),key);
        }else {
            return getNode(node.getRight(),key);
        }
    }
}

調用

public class MapMain {
    public static void main(String[] args) {
        System.out.println("傲慢與偏見");
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile("/Users/admin/Downloads/pride-and-prejudice.txt",words);
        System.out.println("共有單詞: " + words.getSize());
        Map<String,Integer> map = new AVLTreeMap<>();
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(word -> {
                    if (map.contains(word)) {
                        map.set(word,map.get(word) + 1);
                    }else {
                        map.add(word,1);
                    }
                });
        System.out.println("共有不一樣的單詞: " + map.getSize());
        System.out.println("pride出現的次數: " + map.get("pride"));
        System.out.println("prejudice出現的次數: " + map.get("prejudice"));
    }
}

運行結果

傲慢與偏見
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11

三種映射的性能測試

public class TestMapMain {
    private static double testMap(Map<String,Integer> map,String filename) {
        long startTime = System.nanoTime();
        System.out.println(filename);
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile(filename,words);
        System.out.println("共有單詞: " + words.getSize());
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(word -> {
                    if (map.contains(word)) {
                        map.set(word,map.get(word) + 1);
                    }else {
                        map.add(word,1);
                    }
                });
        System.out.println("共有不一樣的單詞: " + map.getSize());
        System.out.println("pride出現的次數: " + map.get("pride"));
        System.out.println("prejudice出現的次數: " + map.get("prejudice"));
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        String filename = "/Users/admin/Downloads/pride-and-prejudice.txt";
        Map<String,Integer> bstMap = new BSTMap<>();
        double time1 = testMap(bstMap,filename);
        System.out.println("BST Map: " + time1 + " s");

        Map<String,Integer> linkedListMap = new LinkedListMap<>();
        double time2 = testMap(linkedListMap,filename);
        System.out.println("LinkedList Map: " + time2 + " s");

        Map<String,Integer> avlTreeMap = new AVLTreeMap<>();
        double time3 = testMap(avlTreeMap,filename);
        System.out.println("AVLTree Map: " + time3 + " s");
    }
}

測試結果

/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11
BST Map: 0.166421303 s
/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11
LinkedList Map: 9.416305469 s
/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11
AVLTree Map: 0.065798621 s

  • AVL樹集合封裝

實現類

public class AVLTreeSet<T extends Comparable<T>> implements Set<T> {
    private Tree<T> avlTree;

    public AVLTreeSet() {
        avlTree = new AVLTree<>();
    }

    @Override
    public void add(T value) {
        avlTree.add(value);
    }

    @Override
    public void remove(T value) {
        avlTree.remove(value);
    }

    @Override
    public boolean contains(T value) {
        return avlTree.contains(value);
    }

    @Override
    public int getSize() {
        return avlTree.getSize();
    }

    @Override
    public boolean isEmpty() {
        return avlTree.isEmpty();
    }
}

三種集合的性能測試

public class TestSetMain {
    private static double testSet(Set<String> set,String filename) {
        long startTime = System.nanoTime();
        System.out.println(filename);
        Array<String> words = new DefaultArray<>();
        if (FileOperation.readFile(filename,words)) {
            System.out.println("共有單詞: " + words.getSize());
            Stream.of(words.getData()).filter(data -> data != null)
                    .forEach(set::add);
            System.out.println("共有不一樣單詞: " + set.getSize());
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        String filename = "/Users/admin/Downloads/pride-and-prejudice.txt";
        Set<String> bstSet = new BSTSet<>();
        double time1 = testSet(bstSet,filename);
        System.out.println("BST Set: " + time1 + " s");
        System.out.println();
        Set<String> linkedListSet = new LinkedListSet<>();
        double time2 = testSet(linkedListSet,filename);
        System.out.println("LinkedList Set: " + time2 + " s");
        System.out.println();
        Set<String> avlTreeSet = new AVLTreeSet<>();
        double time3 = testSet(avlTreeSet,filename);
        System.out.println("AVLTree Set: " + time3 + " s");
    }
}

測試結果

/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣單詞: 6530
BST Set: 0.153552536 s

/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣單詞: 6530
LinkedList Set: 2.292920811 s

/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣單詞: 6530
AVLTree Set: 0.057089254 s

  • 紅黑樹封裝

紅黑樹是一種等價於2-3樹的數據結構,它知足如下幾個特性

  1. 每一個節點或者是紅色,或者是黑色的
  2. 根節點是黑色的
  3. 每個葉子結點(最後的空節點)是黑色的
  4. 若是一個節點是紅色的,那麼他的孩子節點都是黑色的
  5. 從任意一個節點到葉子節點,通過的黑色節點是同樣的

2-3樹

知足二分搜索樹的基本性質,節點能夠存放一個元素或者兩個元素。是一顆絕對平衡的樹(各個子樹的高度差爲0)

2-3樹維護絕對平衡2-3樹在添加新節點的時候永遠不會添加到一個空節點,咱們依次添加42-37-12-18-6-11-5的過程依次以下

42爲起始二節點的根節點,添加37會放入到42的同節點的左邊產生節點的融合,產生一個三節點。

添加12會暫時融合爲一個四節點,

而後轉化爲一個帶左右子節點的二叉樹。其中每個節點都是二節點。

添加18,因爲根節點37的左節點不爲空,因此18直接添加到左節點,變成一個三節點

添加6的時候,會暫時把6添加到根節點的左節點,變成一個四節點

而後拆解成3個二節點的二叉樹。

因爲12的父節點是一個二節點,因此能夠將12於父節點37融合爲一個三節點。

添加11的時候,只須要將11與6融合成一個三節點。

增長5的時候,先暫時跟六、11的三節點融合成一個四節點

再拆分紅3個二節點

再將6暫時融合到它的父節點,變成一個四節點。

再將六、十二、37的四節點拆成三個二節點。

由次保持了2-3樹的絕對平衡。

按照各類狀況,2-3樹新增節點有以下的各類

一、插入二節點

二、插入三節點(根節點)

三、插入三節點(葉節點,且父節點爲二節點)

從左子節點進入

從中子節點進入

四、插入三節點(葉節點,且父節點爲三節點)

從左子節點進入

從中子節點進入

從右子節點進入

2-3樹刪除最小元素

刪除最小元素,不管如何若是最左的葉節點是一個三節點,這是很是容易的,只須要刪除三節點的左元素。

因此咱們認定刪除必須發生在三節點或者臨時的四節點中(由於四節點刪除最小元素依然是一個三節點,依然符合2-3樹的性質)

但若是最左的葉節點是一個二節點,此時咱們應該先把二節點給變換成一個三節點或者一個臨時四節點再進行刪除。

待刪除節點的父節點爲二節點,且父節點的中子節點是一個二節點

待刪除節點的父節點爲二節點,父節點的中子節點是一個三節點

待刪除節點的父節點爲三節點,父節點的中子節點是一個二節點

待刪除節點的父節點爲三節點,父節點的中子節點是一個三節點

 

2-3接口

public interface BTree<T> {
    void add(T t);
    boolean contains(T t);
}

2-3樹實現類

public class TwoThreeTree<T extends Comparable<T>> implements BTree<T> {
    private static final int N = 3;

    private class Node {
        @Getter
        @Setter
        private Node parent;
        //子節點,3個,左、中、右三個子節點
        private Node[] childNodes;
        //節點保存的元素,1-2個
        private T[] elements;
        //節點保存的元素個數
        @Getter
        private int size;

        @SuppressWarnings("unchecked")
        public Node(Class<T> type) {
            parent = null;
            childNodes = (Node[]) Array.newInstance(Node.class,N);
            elements = (T[]) Array.newInstance(type,N - 1);
            size = 0;
        }

        /**
         * 判斷是不是葉子節點
         * @return
         */
        private boolean isLeaf() {
            return childNodes[0] == null;
        }

        /**
         * 判斷節點存儲數據是否滿了
         * @return
         */
        private boolean isFull() {
            return size == N - 1;
        }

        /**
         * 將子節點鏈接
         * @param index 鏈接的位置(左子樹、中子樹、仍是右子樹)
         * @param child
         */
        private void connectChild(int index,Node child) {
            childNodes[index] = child;
            if (child != null) {
                child.setParent(this);
            }
        }

        /**
         * 解除該節點和某個節點之間的鏈接
         * @param index 解除鏈接的位置
         * @return
         */
        private Node disconnectChild(int index) {
            Node ret = childNodes[index];
            childNodes[index] = null;
            return ret;
        }

        /**
         * 獲取節點左或右的值
         * @param index 0爲左,1爲右
         * @return
         */
        private T getData(int index) {
            return elements[index];
        }

        /**
         * 獲取某個位置的子樹
         * @param index 0爲左子樹,1爲中子樹,2爲右子樹
         * @return
         */
        private Node getChild(int index) {
            return childNodes[index];
        }

        /**
         * 尋找value在節點的位置
         * @param value
         * @return 未找到返回-1
         */
        private int findItem(T value) {
            for (int i = 0;i < size;i++) {
                if (elements[i] == null) {
                    break;
                }else if (elements[i].compareTo(value) == 0) {
                    return i;
                }
            }
            return -1;
        }

        /**
         * 向節點添加元素
         * @param value
         * @return 返回添加的位置,0或1
         */
        private int add(T value) {
            if (size == 2) {
                throw new IllegalArgumentException("節點已滿");
            }
            size++;
            for (int i = N - 2;i >= 0;i--) {
                if (elements[i] == null) {
                    continue;
                }else {
                    if (value.compareTo(elements[i]) < 0) {
                        elements[i + 1] = elements[i];
                    }else {
                        elements[i + 1] = value;
                        return i + 1;
                    }
                }
            }
            elements[0] = value;
            return 0;
        }

        /**
         * 移除最後一個元素(先右後左)
         * @return
         */
        private T remove() {
            T ret = elements[size - 1];
            elements[size - 1] = null;
            size--;
            return ret;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "elements=" + Arrays.toString(elements) +
                    '}';
        }
    }
    private Node root;
    private Class<T> type;

    public TwoThreeTree(Class<T> type) {
        this.type = type;
        root = new Node(type);
    }

    @Override
    public void add(T value) {
        add(root,value);
    }

    private void add(Node node,T value) {
        //尋找葉節點
        while (true) {
            if (node.isLeaf()) {
                break;
            }else {
                node = getNextChild(node,value);
            }
        }
        //若是是一個三節點
        if (node.isFull()) {
            split(node,value);
        }else {
            //若是是一個二節點則直接在節點中添加元素
            node.add(value);
        }
    }

    /**
     * 根據value的值獲取節點的子節點(可能爲左、中、右子節點)
     * @param node
     * @param value
     * @return
     */
    private Node getNextChild(Node node,T value) {
        for (int i = 0;i < node.getSize();i++) {
            if (node.getData(i).compareTo(value) > 0) {
                return node.getChild(i);
            }
        }
        return node.getChild(node.getSize());
    }

    /**
     * 裂變函數,裂變節點,該節點爲一個三節點
     * @param node 被裂變的節點
     * @param value 要保存的元素
     */
    private void split(Node node,T value) {
        Node parent = node.getParent();
        Node newNode = new Node(this.type);
        //裂變後的中間節點
        Node midNode = new Node(this.type);
        T mid;
        //若是元素比節點左值要小
        if (value.compareTo(node.getData(0)) < 0) {
            //建立一個節點右值的新節點
            newNode.add(node.remove());
            //取出節點的左值作爲中間元素,此時節點沒有任何值
            mid = node.remove();
            //節點放入新元素value
            node.add(value);
        //若是元素比節點左值大,比右值小
        }else if (value.compareTo(node.getData(1)) < 0) {
            //建立一個節點右值的新節點,此時節點還剩下左值
            newNode.add(node.remove());
            //將新增元素作爲中間元素
            mid = value;
        //若是元素比節點右值大
        }else {
            //取出節點右值作爲中間元素,此時節點還剩下左值
            mid = node.remove();
            //建立一個新增元素的新節點
            newNode.add(value);
        }
        if (node == root) {
            root = midNode;
        }
        //中間節點添加中間元素
        midNode.add(mid);
        //中間節點鏈接左子節點
        midNode.connectChild(0,node);
        //中間節點鏈接中子節點(二節點的中子節點能夠理解爲二叉樹的右節點)
        midNode.connectChild(1,newNode);
        connectNode(parent,midNode);
    }

    /**
     * 鏈接node和parent
     * @param parent
     * @param node node爲二節點
     */
    private void connectNode(Node parent,Node node) {
        //獲取中間節點的惟一元素
        T data = node.getData(0);
        if (node == root) {
            return;
        }
        //若是父節點是一個三節點
        if (parent.isFull()) {
            //獲取父節點的父節點
            Node gParent = parent.getParent();
            Node newNode = new Node(this.type);
            Node temp1,temp2;
            T itemData;
            //若是中間節點元素比父節點的左值小
            //這種狀況應該是從父節點的左子節點進來的中間節點
            if (data.compareTo(parent.getData(0)) < 0) {
                //父節點分裂中子節點
                temp1 = parent.disconnectChild(1);
                //父節點分裂右子節點
                temp2 = parent.disconnectChild(2);
                //將分裂出的中子節點和右子節點鏈接到父節點的右值的新節點上
                //成爲該新節點的左子節點和中子節點(二節點的中子節點能夠理解爲二叉樹的右節點)
                newNode.connectChild(0,temp1);
                newNode.connectChild(1,temp2);
                newNode.add(parent.remove());

                itemData = parent.remove();
                parent.add(itemData);
                //父節點鏈接中間節點爲左子節點
                parent.connectChild(0,node);
                //父節點鏈接新節點爲中子節點,此時父節點是一個二節點
                parent.connectChild(1,newNode);
             //若是中間節點元素比父節點的左值大,比右值小
             //這種狀況應該是從父節點的中子節點進來的中間節點
            }else if (data.compareTo(parent.getData(1)) < 0) {
                //父節點分裂左子節點
                temp1 = parent.disconnectChild(0);
                //父節點分裂右子節點
                temp2 = parent.disconnectChild(2);
                Node tempNode = new Node(this.type);
                //建立一個父節點右值的新節點
                newNode.add(parent.remove());
                //將中間節點分裂出的中子節點鏈接到新節點的左子節點
                newNode.connectChild(0,node.disconnectChild(1));
                //將父節點分裂出的右子節點作爲新節點的中子節點
                newNode.connectChild(1,temp2);
                //建立一個父節點左值的臨時節點(此時父節點中無值)
                tempNode.add(parent.remove());
                //將父節點分裂出的左子節點作爲臨時節點的左子節點
                tempNode.connectChild(0,temp1);
                //將中間節點分裂出的左子節點作爲臨時節點的中子節點
                tempNode.connectChild(1,node.disconnectChild(0));
                //將中間節點的惟一值移除並添加到父節點
                parent.add(node.remove());
                //將臨時節點作爲父節點的左子節點
                parent.connectChild(0,tempNode);
                //將新節點作爲父節點的中子節點
                parent.connectChild(1,newNode);
             //若是中間節點元素比父節點的右值大
             //這種狀況應該是從父節點的右子節點進來的中間節點
            }else {
                //分裂父節點的左子節點
                temp1 = parent.disconnectChild(0);
                //分裂父節點的中子節點
                temp2 = parent.disconnectChild(1);
                //取出父節點的右值
                itemData = parent.remove();
                //建立一個父節點左值的新節點(此時父節點無值)
                newNode.add(parent.remove());
                //將分裂的左子節點作爲新節點的左子節點
                newNode.connectChild(0,temp1);
                //將分裂的中子節點作爲新節點的中子節點
                newNode.connectChild(1,temp2);
                //去除父節點的右子節點(此時右子節點會被垃圾回收)
                parent.disconnectChild(2);
                //將父節點的原右值添加回父節點作爲惟一值
                //此時父節點爲一個二節點
                parent.add(itemData);
                //將新節點作爲父節點的左子節點
                parent.connectChild(0,newNode);
                //將中間節點作爲父節點的中子節點
                parent.connectChild(1,node);
            }
            //將父節點作爲中間節點向上遞歸到父節點的父節點(即祖節點)
            connectNode(gParent,parent);
         //若是父節點是一個二節點
        }else {
            //若是中間節點元素比父節點的惟一元素小
            //這種狀況應該是從父節點的左子節點進來的中間節點
            if (data.compareTo(parent.getData(0)) < 0) {
                //分裂父節點的中子節點
                Node tempNode = parent.disconnectChild(1);
                //分裂中間節點的左子節點作爲父節點的左子節點
                parent.connectChild(0,node.disconnectChild(0));
                //分裂中間節點的中子節點作爲父節點的中子節點
                parent.connectChild(1,node.disconnectChild(1));
                //將原父節點的中子節點作爲父節點的右子節點
                parent.connectChild(2,tempNode);
             //若是中間節點元素比父節點的惟一元素大
             //這種狀況應該是從父節點的中子節點進來的中間節點
            }else {
                //分裂中間節點的左子節點作爲父節點的中子節點
                parent.connectChild(1,node.disconnectChild(0));
                //分裂中間節點的中子節點作爲父節點的右子節點
                parent.connectChild(2,node.disconnectChild(1));
            }
            //父節點添加中間節點元素(此時父節點爲一個三節點)
            parent.add(data);
        }
    }

    @Override
    public boolean contains(T value) {
        Node node = contains(root,value);
        if (node != null) {
            return true;
        }
        return false;
    }

    private Node contains(Node node,T value) {
        if (node == null) {
            return null;
        }
        for (int i = 0; i < node.getSize(); i++) {
            if (node.getData(i).compareTo(value) > 0) {
                return contains(node.getChild(i), value);
            } else if (node.getData(i).compareTo(value) == 0) {
                return node;
            }
        }
        return contains(node.getChild(node.getSize()), value);
    }
}

調用(此處List爲JDK中的java.util.List)

public class BTreeMain {
    public static void main(String[] args) {
        BTree<Integer> bTree = new TwoThreeTree<>(Integer.class);
        List<Integer> list = Arrays.asList(42,37,12,18,6,11,5);
        list.stream().forEach(bTree::add);
        System.out.println(bTree.contains(18));
        System.out.println(bTree.contains(11));
        System.out.println(bTree.contains(32));
    }
}

運行結果

true
true
false

題外話

其實2-3樹就是一個3階B樹,還有2-3-4樹屬於4階B樹。而m階的B樹特性

        1.若是根節點不是葉子節點那麼至少有兩個子樹。

  2.全部葉子節點都位於同一層。

  3.節點包含:關鍵字數組,指向孩子節點的指針數組,關鍵字數量。

以上也說明B樹是一顆絕對平衡樹。

而對於咱們數據庫mysql索引使用的B+樹在B樹之上作了修改

B+樹:因爲B樹進行遍歷的時候效率過低,而對於數據庫和文件系統來講會常常進行範圍查詢,因此產生了B+樹。

B+樹能夠看做是信息都是在葉子節點上,其餘非葉子節點都是索引,目的是找到葉子節點,每一個非葉子節點都保存葉子節點最小值及最小值所在葉子節點的索引,而且葉子節點之間有指針指向。

(圖片來源於網絡)

區別:

1.功能上說,B+遍歷,範圍查詢效率高。

2.結構上說:B+信息都保存在葉子節點上,其餘節點保存最小值索引,而且關鍵字對應的地址都在葉子節點上,而B樹中非葉子節點也保存關鍵字對應的地址。

節點結構:B樹的性質B+樹通常都知足。

  B樹:

    關鍵字數組,關鍵字對應的地址數組  子節點的指針數組,關鍵字的數量(子節點的最小數量是階數的二分之一)

  B+樹:

    關鍵字數組,關鍵字數量,子節點的指針數組。(每一個節點關鍵字數量和子節點數量相同,而且每一個關鍵字都是對應一個子節點關鍵字的最小值)

2-3樹與紅黑樹的等價關係

二節點

三節點(全部的紅色節點都是左傾斜的)

在三節點中,咱們能夠看到紅黑樹中的紅色節點表示了2-3樹中的三節點的左值,黑色節點表示了2-3樹中三節點的右值。

則如下這棵2-3樹

就等價於

固然爲了更加形象,咱們能夠將這顆紅黑樹變成這個樣子

如今咱們來看一下紅黑樹的幾個特性

一、每一個節點或者是紅色,或者是黑色的 這是廢話

二、根節點是黑色的 若是在2-3樹中,根節點要麼是二節點,要麼是三節點,那麼不管是哪種,對應紅黑樹中,紅色節點必定是左傾斜的,因此根節點必定是黑色的。

三、每個葉子結點(最後的空節點)是黑色的 對於一個空的紅黑樹,它的根節點是空的,而且根節點必定是黑色的,因此空節點也必定是黑色的。

四、若是一個節點是紅色的,那麼他的孩子節點都是黑色的 由於在2-3樹中,三節點的左值鏈接的要麼是一個二節點,要麼是一個三節點。二節點確定是黑色的,而三節點就類比於看根節點同樣,紅色節點必定是左傾斜的,因此鏈接到父三節點的必定是子三節點的右值,因此是黑色的。

五、從任意一個節點到葉子節點,通過的黑色節點是同樣的 因爲2-3樹是一顆絕對平衡的樹,因此紅黑樹中黑色節點對應2-3樹要麼是一個二節點,要麼是一個三節點的右值,紅黑樹中保證了黑色節點的絕對平衡型。但紅黑樹總體並不必定是一顆平衡二叉樹。如今咱們假設一個2-3樹的全部節點都是一個三節點,那麼對應紅黑樹,它的高度就是2log n的,但因爲2是一個常數,因此紅黑樹的時間複雜度依然是O(log n)的。雖然紅黑樹和AVL樹都是O(log n)級別的,可是若是隻是查詢元素來講,AVL樹的性能會更好一點,但對於頻繁的增長刪除來講,紅黑樹的性能就比AVL樹要好。

紅黑樹添加新元素

在2-3樹中添加元素的時候,咱們都知道,2-3樹是不會將元素添加到空節點的,它必定是將元素添加到一個已經存在的節點上。那麼對應紅黑樹中,咱們能夠理解成,咱們初始添加的元素必定是一個紅色的節點,由於只有紅色的節點纔會是對已存在節點的融合。

可是在添加根節點的時候,由於咱們新添加節點是一個紅色的節點,而紅黑樹的根節點必定是一個黑色的節點,因此在添加根節點的時候,咱們須要主動把根節點的顏色變成黑色。但還有一種狀況,就是在2-3樹中,當某個節點的子節點(多是左、中、右子節點)發生向上融合的時候

該圖中向上融合的是節點元素爲4的節點(此處稱爲中間節點),向上融合的中間節點必定是紅色的,因此4這個節點必定是紅色的,在向上融合中發生裂變,6這個節點變成中間節點,繼續向上融合,因此6這個節點也是一個紅色的節點。

但若是6這個節點沒有父節點,那麼它就是一個根節點,因此咱們也須要主動把它的顏色變成黑色。此過程能夠參考代碼中的add(T value)的第二行代碼。

如今咱們在紅黑樹中依次增長42-37-66,來看一看這個過程。

42爲其實紅黑樹的根節點,因此是一個黑色的節點,添加37會根據二分搜索樹的性質,添加到42的左節點,再根據紅黑樹的性質,變成紅色的節點。

但若是第一個添加的元素是37,第二個添加的元素是42該怎麼辦呢?

很明顯它不知足紅黑樹的基本性質(紅色的節點必定是左傾斜的),因此咱們要將其進行一個左旋轉,就變成了上面的以42爲根節點,37爲左節點的狀況。因此不管是哪一個先進來,其結果都同樣。(左旋轉的定義請參考AVL樹中的內容,惟一不一樣的是咱們要記得維護兩個節點的顏色),此過程對應於代碼中的leftRotate()方法

在添加66的過程當中,根據二分搜索樹的性質,66應該添加到42的右節點

這在2-3樹中,至關於一個這樣的過程,它最終會分裂成3個二節點。因此在對應的紅黑樹中,最終分裂成的二節點都應該是黑色的。

但若是中間節點42有父節點,就表示中間節點須要跟它的父節點作融合,那麼此時中間節點是紅色的節點。(這裏須要注意的是,咱們只判斷中間節點是不是根節點的時候才把它變成黑色,若是是向上融合的節點,就是紅色的),此過程對應於代碼中的flipColors()方法

如今咱們來依次向紅黑樹中添加42-37-12,來看看會是什麼狀況。根據二分搜索樹的性質,會將12添加到37的左節點

在2-3樹中,這個過程,就是這樣的一個狀況,它最終會裂變成3個二節點。

對應於紅黑樹中,咱們須要對根節點42作一個右旋轉(右旋轉的定義請參考AVL樹中的內容,惟一不一樣的是咱們要記得維護兩個節點的顏色),此過程請參考代碼中的rightRotate()方法。

在右旋轉完成後,咱們須要將新的中間節點變成原來中間節點的顏色,將向右旋轉的42元素的原中間節點變成紅色。

可是咱們知道在2-3樹中,二節點都是黑色的,因此上圖的狀況就跟咱們以前處理42-37-66的過程同樣了,只須要進行一個顏色翻轉,因此最終的結果以下圖所示(這裏一樣考慮了中間節點向上融合的狀況,若是最終斷定37是根結點,會將37轉變爲黑色)

如今咱們來依次向紅黑樹中添加42-37-40,來看看會是什麼狀況。根據二分搜索樹的性質,40將會被添加爲37的右節點。

其實這種狀況並不須要藉助於2-3樹來作分析,咱們只須要將37這個節點進行一次左旋轉就能夠了

這樣就變成了相似依次添加42-37-12的過程,按照這個過程進行處理就行了。

根據以上的種種狀況,咱們能夠給紅黑樹添加新元素總結爲如下的一整個流程。

咱們能夠看到這是一個在2-3樹的三節點中添加一個元素的過程,若是咱們添加的是一個比紅色節點大,比黑色節點小的狀況,就如上圖同樣。那若是咱們添加的元素比紅色節點小的話,就等於從第一步直接跳到了第三步。

那若是咱們添加的元素比黑色節點大的話,就至關於從第一步直接跳到了第四步

在紅黑樹整個添加元素的過程當中,首先是按照二分搜索樹來進行添加的,這一點沒有變,只不過在二分搜索樹中添加完成了節點,咱們須要對添加的節點進行紅黑樹性質的維護。維護的過程正是上面的整個過程,要根據不一樣的狀況進行判斷。

而判斷的依據就是要看這個新添加的元素的節點到底添加到了它的父節點的哪一個位置。

若是這個新節點在其父節點的右節點且其父節點的左節點是黑色節點的時候(意思是父節點至關於2-3樹的二節點或者三節點的左值),對其父節點進行左旋轉。

若是這個新節點在其父節點的左節點當其父節點自己是紅色節點的時候(意思是父節點至關於2-3樹的三節點的左值),對其祖節點(至關於2-3樹的三節點的右值)進行右旋轉。

若是這個新節點在其父節點的右節點,且其父節點的左節點是紅色節點的時候(意思是父節點至關於2-3樹的三節點的右值),對其父節點以及父節點的左右節點進行顏色翻轉。

紅黑樹實現類

public class RedBlackTree<T extends Comparable<T>> implements Tree<T> {
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    @Data
    @AllArgsConstructor
    private class Node{
        private T element;
        private Node left;
        private Node right;
        private Boolean color;

        public Node(T element) {
            this(element,null,null,RED);
        }
    }

    private Node root;
    private int size;

    public RedBlackTree() {
        root = null;
        size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 判斷節點node的顏色
     * @param node
     * @return
     */
    private boolean isRed(Node node) {
        if (node == null) {
            return BLACK;
        }
        return node.getColor();
    }

    /**
     * 左旋轉
     * @param node
     * @return
     */
    private Node leftRotate(Node node) {
        Node ret = node.getRight();
        Node retLeft = ret.getLeft();
        node.setRight(retLeft);
        ret.setLeft(node);
        ret.setColor(node.getColor());
        node.setColor(RED);
        return ret;
    }

    /**
     * 右旋轉
     * @param node
     * @return
     */
    private Node rightRotate(Node node) {
        Node ret = node.getLeft();
        Node retRight = ret.getRight();
        node.setLeft(retRight);
        ret.setRight(node);
        ret.setColor(node.getColor());
        node.setColor(RED);
        return ret;
    }

    /**
     * 顏色翻轉
     * @param node
     */
    private void flipColors(Node node) {
        node.setColor(RED);
        node.getLeft().setColor(BLACK);
        node.getRight().setColor(BLACK);
    }

    private Node balance(Node node) {
        if (isRed(node.getRight())) {
            node = leftRotate(node);
        }
        //維護紅黑樹
        if (isRed(node.getRight()) && !isRed(node.getLeft())) {
            node = leftRotate(node);
        }
        if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft())) {
            node = rightRotate(node);
        }
        if (isRed(node.getLeft()) && isRed(node.getRight())) {
            flipColors(node);
        }
        return node;
    }

    @Override
    public void add(T value) {
        root = add(root,value);
        root.setColor(BLACK);
    }

    private Node add(Node node, T value) {
        //遞歸終止條件
        if (node == null) {
            size++;
            return new Node(value);
        }
        //遞歸
        if (value.compareTo(node.getElement()) < 0) {
            node.setLeft(add(node.getLeft(),value));
        }else if (value.compareTo(node.getElement()) > 0) {
            node.setRight(add(node.getRight(),value));
        }
        //維護紅黑樹
        if (isRed(node.getRight()) && !isRed(node.getLeft())) {
            node = leftRotate(node);
        }
        if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft())) {
            node = rightRotate(node);
        }
        if (isRed(node.getLeft()) && isRed(node.getRight())) {
            flipColors(node);
        }
        return node;
    }

    @Override
    public boolean contains(T value) {
        return contains(root,value);
    }

    private boolean contains(Node node, T value) {
        if (node == null) {
            return false;
        }
        if (value.compareTo(node.getElement()) == 0) {
            return true;
        }else if (value.compareTo(node.getElement()) < 0) {
            return contains(node.getLeft(),value);
        }else {
            return contains(node.getRight(),value);
        }
    }

    @Override
    public void preOrder() {
        preOrder(root);
    }

    private void preOrder(Node node) {
        if (node == null) {
            return;
        }

        System.out.println(node.getElement());
        preOrder(node.getLeft());
        preOrder(node.getRight());
    }

    /**
     * 非遞歸前序遍歷
     */
    public void preOrderNR() {
        Stack<Node> stack = new ArrayStack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            Node cur = stack.pop();
            System.out.println(cur.getElement());
            if (cur.getRight() != null) {
                stack.push(cur.getRight());
            }
            if (cur.getLeft() != null) {
                stack.push(cur.getLeft());
            }
        }
    }

    @Override
    public void inOrder() {
        inOrder(root);
    }

    private void inOrder(Node node) {
        if (node == null) {
            return;
        }
        inOrder(node.getLeft());
        System.out.println(node.getElement());
        inOrder(node.getRight());
    }

    @Override
    public void postOrder() {
        postOrder(root);
    }

    private void postOrder(Node node) {
        if (node == null) {
            return;
        }
        postOrder(node.getLeft());
        postOrder(node.getRight());
        System.out.println(node.getElement());
    }

    @Override
    public void levelOrder() {
        Queue<Node> queue = new LoopQueue<>();
        queue.enqueue(root);
        while (!queue.isEmpty()) {
            Node cur = queue.dequeue();
            System.out.println(cur.getElement());

            if (cur.getLeft() != null) {
                queue.enqueue(cur.getLeft());
            }
            if (cur.getRight() != null) {
                queue.enqueue(cur.getRight());
            }
        }
    }

    @Override
    public T minimum() {
        if (size == 0) {
            throw new IllegalArgumentException("二分搜索樹爲空");
        }
        return minimum(root).getElement();
    }

    private Node minimum(Node node) {
        if (node.getLeft() == null) {
            return node;
        }
        return minimum(node.getLeft());
    }

    @Override
    public T maximum() {
        if (size == 0) {
            throw new IllegalArgumentException("二分搜索樹爲空");
        }
        return maximum(root).getElement();
    }

    private Node maximum(Node node) {
        if (node.getRight() == null) {
            return node;
        }
        return maximum(node.getRight());
    }

    @Override
    public T removeMin() {
        T ret = minimum();
        if (!isRed(root.getLeft()) && !isRed(root.getRight())) {
            root.setColor(RED);
        }
        root = removeMin(root);
        if (!isEmpty()) {
            root.setColor(BLACK);
        }
        return ret;
    }

    private Node removeRedLeft(Node node) {
        if (isRed(node) && !isRed(node.getLeft()) && !isRed(node.getLeft().getLeft())) {
            node.getLeft().setColor(RED);
        }
        if (isRed(node.getRight().getLeft())) {
            node.setRight(rightRotate(node.getRight()));
            node = leftRotate(node);
        }
        return node;
    }

    private Node removeMin(Node node) {
        if (node.getLeft() == null) {
            size--;
            return null;
        }
        if (!isRed(node.getLeft()) && !isRed(node.getLeft().getLeft())) {
            node = removeRedLeft(node);
        }
        node.setLeft(removeMin(node.getLeft()));
        return balance(node);
    }

    @Override
    public T removeMax() {
        T ret = maximum();
        if (!isRed(root.getLeft()) && !isRed(root.getRight())) {
            root.setColor(RED);
        }
        root = removeMax(root);
        if (!isEmpty()) {
            root.setColor(BLACK);
        }
        return ret;
    }

    private Node removeRedRight(Node node) {
        if (isRed(node) && !isRed(node.getRight()) && !isRed(node.getRight().getLeft())) {
            node.getRight().setColor(RED);
        }
        if (!isRed(node.getLeft().getLeft())) {
            node = rightRotate(node);
        }
        return node;
    }

    private Node removeMax(Node node) {
        if (isRed(node.getLeft())) {
            node = rightRotate(node);
        }
        if (node.getRight() == null) {
            size--;
            return null;
        }
        if (!isRed(node.getRight()) && !isRed(node.getRight().getLeft())) {
            node = removeRedRight(node);
        }
        node.setRight(removeMax(node.getRight()));
        return balance(node);
    }

    @Override
    public void remove(T value) {
        if (!isRed(root.getLeft()) && !isRed(root.getRight())) {
            root.setColor(RED);
        }
        root = remove(root,value);
        if (!isEmpty()) {
            root.setColor(BLACK);
        }
    }

    private Node remove(Node node,T value) {
        if (value.compareTo(node.getElement()) < 0) {
            if (!isRed(node.getLeft()) && !isRed(node.getLeft().getLeft())) {
                size--;
                node = removeRedLeft(node);
            }
            node.setLeft(remove(node.getLeft(),value));
        }else {
            if (isRed(node.getLeft())) {
                node = rightRotate(node);
            }
            if (value.compareTo(node.getElement()) == 0 && node.getRight() == null) {
                return null;
            }
            if (!isRed(node.getRight()) && !isRed(node.getRight().getLeft())) {
                node = removeRedRight(node);
            }
            if (value.compareTo(node.getElement()) == 0) {
                node.setElement(minimum(node.getRight()).getElement());
                node.setRight(removeMin(node.getRight()));
            }else {
                node.setRight(remove(node.getRight(),value));
            }
        }
        return balance(node);
    }

    @Override
    public T floor(T value) {
        Node node = floor(root,value);
        if (node != null) {
            return node.getElement();
        }
        return null;
    }

    private Node floor(Node node, T value) {
        if (node == null) {
            return null;
        }
        if (value.compareTo(node.getElement()) <= 0) {
            return floor(node.getLeft(),value);
        }else {
            if (node.getRight() == null) {
                return node;
            }else if (node.getRight().getLeft() == null &&
                    value.compareTo(node.getRight().getElement()) <= 0) {
                return node;
            }else if (node.getRight().getRight() == null &&
                    value.compareTo(node.getRight().getElement()) > 0) {
                return node.getRight();
            }
            return floor(node.getRight(),value);
        }
    }

    @Override
    public T ceil(T value) {
        Node node = ceil(root,value);
        if (node != null) {
            return node.getElement();
        }
        return null;
    }

    private Node ceil(Node node, T value) {
        if (node == null) {
            return null;
        }
        if (value.compareTo(node.getElement()) >= 0) {
            return ceil(node.getRight(),value);
        }else {
            if (node.getLeft() == null) {
                return node;
            }else if (value.compareTo(maximum(node.getLeft()).getElement()) >= 0) {
                return node;
            }else if (node.getLeft().getRight() == null &&
                    value.compareTo(node.getLeft().getElement()) >= 0) {
                return node;
            }else if (node.getLeft().getLeft() == null &&
                    value.compareTo(node.getLeft().getElement()) < 0) {
                return node.getLeft();
            }
            return ceil(node.getLeft(),value);
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        generateBSTString(root,0,builder);
        return builder.toString();
    }

    private void generateBSTString(Node node, int depth, StringBuilder builder) {
        if (node == null) {
            builder.append(generateDepthString(depth) + "null\n");
            return;
        }
        builder.append(generateDepthString(depth) + node.getElement() + "\n");
        generateBSTString(node.getLeft(),depth + 1,builder);
        generateBSTString(node.getRight(),depth + 1,builder);
    }

    private String generateDepthString(int depth) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0;i < depth;i++) {
            builder.append("--");
        }
        return builder.toString();
    }
}
  • 紅黑樹映射封裝

實現類

public class RedBlackTreeMap<K extends Comparable<K>,V> implements Map<K,V> {
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    @Data
    @AllArgsConstructor
    private class Node{
        private K key;
        private V value;
        private Node left;
        private Node right;
        private Boolean color;

        public Node(K key) {
            this(key,null,null,null,RED);
        }
        public Node(K key,V value) {
            this(key,value,null,null,RED);
        }
    }

    private Node root;
    private int size;

    public RedBlackTreeMap() {
        root = null;
        size = 0;
    }

    /**
     * 判斷節點node的顏色
     * @param node
     * @return
     */
    private boolean isRed(Node node) {
        if (node == null) {
            return BLACK;
        }
        return node.getColor();
    }

    /**
     * 左旋轉
     * @param node
     * @return
     */
    private Node leftRotate(Node node) {
        Node ret = node.getRight();
        Node retLeft = ret.getLeft();
        node.setRight(retLeft);
        ret.setLeft(node);
        ret.setColor(node.getColor());
        node.setColor(RED);
        return ret;
    }

    /**
     * 右旋轉
     * @param node
     * @return
     */
    private Node rightRotate(Node node) {
        Node ret = node.getLeft();
        Node retRight = ret.getRight();
        node.setLeft(retRight);
        ret.setRight(node);
        ret.setColor(node.getColor());
        node.setColor(RED);
        return ret;
    }

    /**
     * 顏色翻轉
     * @param node
     */
    private void flipColors(Node node) {
        node.setColor(RED);
        node.getLeft().setColor(BLACK);
        node.getRight().setColor(BLACK);
    }

    @Override
    public void add(K key, V value) {
        root = add(root,key,value);
    }

    private Node add(Node node, K key,V value) {
        //遞歸終止條件
        if (node == null) {
            size++;
            return new Node(key,value);
        }
        //遞歸
        if (key.compareTo(node.getKey()) < 0) {
            node.setLeft(add(node.getLeft(),key,value));
        }else if (key.compareTo(node.getKey()) > 0) {
            node.setRight(add(node.getRight(),key,value));
        }else {
            node.setValue(value);
        }
        //維護紅黑樹
        if (isRed(node.getRight()) && !isRed(node.getLeft())) {
            node = leftRotate(node);
        }
        if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft())) {
            node = rightRotate(node);
        }
        if (isRed(node.getLeft()) && isRed(node.getRight())) {
            flipColors(node);
        }
        return node;
    }

    @Override
    public V remove(K key) {
        throw new IllegalArgumentException("未支持的方法");
    }

    @Override
    public boolean contains(K key) {
        return getNode(root,key) != null;
    }

    @Override
    public V get(K key) {
        Node node = getNode(root,key);
        return node == null ? null : node.getValue();
    }

    @Override
    public void set(K key, V value) {
        Node node = getNode(root,key);
        if (node == null) {
            throw new IllegalArgumentException(key + "不存在");
        }
        node.setValue(value);
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    private Node getNode(Node node,K key) {
        if (node == null) {
            return null;
        }
        if (key.compareTo(node.getKey()) == 0) {
            return node;
        }else if (key.compareTo(node.getKey()) < 0) {
            return getNode(node.getLeft(),key);
        }else {
            return getNode(node.getRight(),key);
        }
    }
}

四種映射的性能測試

public class TestMapMain {
    private static double testMap(Map<String,Integer> map,String filename) {
        long startTime = System.nanoTime();
        System.out.println(filename);
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile(filename,words);
        System.out.println("共有單詞: " + words.getSize());
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(word -> {
                    if (map.contains(word)) {
                        map.set(word,map.get(word) + 1);
                    }else {
                        map.add(word,1);
                    }
                });
        System.out.println("共有不一樣的單詞: " + map.getSize());
        System.out.println("pride出現的次數: " + map.get("pride"));
        System.out.println("prejudice出現的次數: " + map.get("prejudice"));
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        String filename = "/Users/admin/Downloads/pride-and-prejudice.txt";
        Map<String,Integer> bstMap = new BSTMap<>();
        double time1 = testMap(bstMap,filename);
        System.out.println("BST Map: " + time1 + " s");

        Map<String,Integer> linkedListMap = new LinkedListMap<>();
        double time2 = testMap(linkedListMap,filename);
        System.out.println("LinkedList Map: " + time2 + " s");

        Map<String,Integer> avlTreeMap = new AVLTreeMap<>();
        double time3 = testMap(avlTreeMap,filename);
        System.out.println("AVLTree Map: " + time3 + " s");

        Map<String,Integer> redBlackTreeMap = new RedBlackTreeMap<>();
        double time4 = testMap(redBlackTreeMap,filename);
        System.out.println("RedBlackTree Map: " + time4 + " s");
    }
}

測試結果

/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11
BST Map: 0.171121365 s
/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11
LinkedList Map: 9.213896232 s
/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11
AVLTree Map: 0.070547497 s
/Users/admin/Downloads/pride-and-prejudice.txt
共有單詞: 125901
共有不一樣的單詞: 6530
pride出現的次數: 53
prejudice出現的次數: 11
RedBlackTree Map: 0.061147762 s

LeetCode算法題 https://leetcode-cn.com/problems/first-unique-character-in-a-string/

這是LeetCode的第387題——字符串中的第一個惟一字符

由於立刻要進入散列哈希表的話題,咱們就先使用HashMap來解題

class Solution {
    public int firstUniqChar(String s) {
        Map<Character,Integer> map = new HashMap<>();
        for (int i = 0;i < s.length();i++) {
            if (map.putIfAbsent(s.charAt(i),1) != null) {
                map.put(s.charAt(i),map.get(s.charAt(i)) + 1);
            }
        }
        for (int i = 0;i < s.length();i++) {
            if (map.get(s.charAt(i)) == 1) {
                return i;
            }
        }
        return -1;
    }
}

提交給LeetCode

固然爲了更好的說明哈希表的內部原理,咱們將方法改寫一下,只使用數組來實現。

public class Solution {
    public int firstUniqChar(String s) {
        int[] freq = new int[26];
        for (int i = 0;i < s.length();i++) {
            freq[s.charAt(i) - 'a']++;
        }
        for (int i = 0;i < s.length();i++) {
            if (freq[s.charAt(i) - 'a'] == 1) {
                return i;
            }
        }
        return -1;
    }
}

提交給LeetCode

  • 哈希表封裝

哈希是經過犧牲可比較性可排序性,來獲取比樹更快的時間複雜度的一種數據結構,而要實現哈希,最重要的在於兩點:哈希函數的設計,哈希衝突的解決。

哈希函數的設計

哈希函數的設計是爲了把"元素"經過哈希函數獲得的"索引"在數組中分佈越均勻越好。

整型

小範圍的正整數直接使用,小範圍的負整數進行偏移(-100~100->0~200)。

大整數,如身份證號,一般作法:取模,好比,取後四位,等同於mod 10000,可是取模有陷阱,若是取模的數選擇不當,會形成分佈不均勻。好比身份證中有的數字有日期的語義,若是取後六位mod 1000000,那麼這個數就永遠不可能大於320000.

若是不考慮具體數字有語義的話,一個簡單的解決辦法:模一個素數

10 % 4 = 2                     10 % 7 = 3

20 % 4 = 0                    20 % 7 = 6

30 % 4 = 2                    30 % 7 = 2

40 % 4 = 0                    40 % 7 = 4

50 % 4 = 2                    50 % 7 = 1

經過上面兩組數據的比較,咱們能夠看到4是一個合數,7是一個素數,則很明顯,對7取模所獲得的數分佈更加均勻。

對於在什麼範圍內的數取模的素數是多少分佈更加均勻,能夠參考https://planetmath.org/goodhashtableprimes,其實這些也是數學家計算出來的結果。

浮點型

在計算機中都是32位或64位的二進制表示,只不過計算機解析成了浮點數。

咱們只須要將其轉成整型處理便可,而後再進行取模。

字符串

一樣轉成整型處理

code = c * B^3 + o * B^2 + d * B^1 + e * B^0

固然這個B是一個基數,由咱們根據字符串的範圍(如是否包含大小寫,是否包含符號,是否包含漢字)來決定。

則咱們的哈希函數能夠設計爲:hash(code) = (c * B^3 + o * B^2 + d * B^1 + e * B^0) % M

                                                                         = ((((c * B) + o) * B + d) * B + e) % M

                                                                         = ((((c % M) * B + o) % B * B + d) % M * B + e) % M

對於程序來講能夠當作是以下所示

int hash = 0;
for (int i = 0;i < s.length();i++) {
    hash = (hash * B + s.charAt(i)) % M;
}

複合類型

一樣是轉成整型來處理。

哈希函數的設計有不少種,但原則爲

  1. 一致性: 若是a == b,則hash(a) == hash(b)
  2. 高效性: 計算高效簡便
  3. 均勻性: 哈希值均勻分佈

固然在Java中,咱們通常都是以對象的hashCode值來進行轉化成整型的。

public class HashCodeMain {
    public static void main(String[] args) {
        int a = 42;
        System.out.println(((Integer)a).hashCode());
        int b = -42;
        System.out.println(((Integer)b).hashCode());
        double c = 3.1415926;
        System.out.println(((Double)c).hashCode());
        String d = "code";
        System.out.println(d.hashCode());
    }
}

運行結果

42
-42
219937201
3059181

但若是對於一個普通的類,咱們就須要重寫它的hashCode()方法(此處名字不區分大小寫),若是要斷定兩個該類的對象是否相等,就須要重寫equals()方法

@AllArgsConstructor
public class Student {
    private int grade;
    private int cls;
    String firstName;
    String lastName;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (grade != student.grade) return false;
        if (cls != student.cls) return false;
        if (!firstName.toLowerCase().equals(student.firstName.toLowerCase())) return false;
        return lastName.toLowerCase().equals(student.lastName.toLowerCase());
    }

    @Override
    public int hashCode() {
        int B = 31;
        int result = 0;
        result = B * result + grade;
        result = B * result + cls;
        result = B * result + firstName.toLowerCase().hashCode();
        result = B * result + lastName.toLowerCase().hashCode();
        return result;
    }
}
public class HashCodeMain {
    public static void main(String[] args) {
        int a = 42;
        System.out.println(((Integer)a).hashCode());
        int b = -42;
        System.out.println(((Integer)b).hashCode());
        double c = 3.1415926;
        System.out.println(((Double)c).hashCode());
        String d = "code";
        System.out.println(d.hashCode());
        Student student = new Student(3,2,"bobo","liu");
        System.out.println(student.hashCode());
    }
}

運行結果

42
-42
219937201
3059181
94107933

但若是咱們沒有重寫Object父類的hashCode()方法,咱們依然也可使用hashCode()方法,只不過此時打印出來的hashCode()值是JVM爲咱們分配的一個內存地址,而此時每個對象的內存地址都必定是不相同的。

public class HashCodeMain {
    public static void main(String[] args) {
        int a = 42;
        System.out.println(((Integer)a).hashCode());
        int b = -42;
        System.out.println(((Integer)b).hashCode());
        double c = 3.1415926;
        System.out.println(((Double)c).hashCode());
        String d = "code";
        System.out.println(d.hashCode());
        Student student = new Student(3,2,"bobo","liu");
        System.out.println(student.hashCode());
        Student student1 = new Student(3,2,"bobo","liu");
        System.out.println(student1.hashCode());
        System.out.println(student.equals(student1));
    }
}

運行結果

42
-42
219937201
3059181
1496724653
553264065
false

可是若是咱們重寫了hashCode()和equals()方法後。

運行結果

42
-42
219937201
3059181
94107933
94107933
true

哈希衝突的處理——鏈地址法

這是一個有M個空間大小的數組,當咱們有一個元素要放入這個數組中的時候

通常咱們會對這個元素進行一次hashCode的獲取,再對M取模——hashCode(k1) % M

但有可能hashCode(k1)是一個負的哈希值,此時咱們徹底可使用取絕對值的形式將負號給抹去

Math.abs(hashCode(k1)) % M

咱們已經知道了一個 int 型數值是 4 個字節。每一個字節有 8 位。但對於一個 int 或者其它整數類型如 (long)的數值而言還要注意的是,它的最高位是符號位。

最高位爲0表示正數。
最高位爲1表示負數
原碼 將一個數字轉換成二進制就是這個數值的原碼。
int a = 5; //原碼 0000 0000 0000 0000 0000 0000 0000 0101

int b = -3; //原碼 1000 0000 0000 0000 0000 0000 0000 0011

在進行位運算的時候,咱們要將負號抹去,須要跟二進制0111 1111 1111 1111 1111 1111 1111 1111(十六進制爲0x7fffffff)進行一次與運算,這樣最高位的符號位1就被消除了,而其餘位都保持不變。

因此消除負號在位運算中也能夠寫成

(hashCode(k1) & 0x7fffffff) % M

更多關於位運算的內容能夠參考Java的二進制位操做整理

如今咱們假設k1通過hash計算出來的索引值爲4,而再來一個k2通過hash計算出來的索引值爲1

此時再來一個k3,它的索引值也爲1,這個時候就跟k2產生了哈希衝突,此時咱們須要將k3放到1的位置,而且跟k2組成一個鏈表,放在k2的next節點。

但對於這種結構的鏈接能夠屬於查找表的範疇,咱們也能夠徹底不使用鏈表,而使用樹結構(好比紅黑樹)來構建查找表。因此咱們在數組中存的就能夠是一個TreeSet或者TreeMap

在JDK8以前,每個位置對應一個鏈表,從JDK8開始,當哈希衝突達到必定程度,每個位置從鏈表轉成紅黑樹。可是這裏有一個問題,那就是紅黑樹是一個二分搜索樹,因此要求每個存入紅黑樹的元素必須是可比較,可查找的,即實現了Comparable接口的元素,纔會轉成紅黑樹,不然是不會轉成紅黑樹的,依然以鏈表的形式存在,由於鏈表並無這個要求,不須要元素必須是可比較的。

接口

public interface Hash<T> {
    int getSize();
    void add(T t);
    void remove(T t);
    boolean contains(T t);
}

實現類(Set接口和TreeSet爲JDK中的java.util.Set和java.util.TreeSet)

public class HashTable<T> implements Hash<T> {
    private final int[] capacity = {53,97,193,389,769,1543,3079,6151,12289,24593,
                                    49157,98317,196613,393241,786433,1572869,3145739,
                                    6291469,12582917,25165843,50331653,100663319,
                                    201326611,402653189,805306457,1610612741};
    //容忍度上界
    private static final int upperTol = 10;
    //容忍度下屆
    private static final int lowerTol = 2;
    private int capacityIndex = 0;

    private Set<T>[] hashtable;
    private int M;
    private int size;

    @SuppressWarnings("unchecked")
    public HashTable() {
        this.M = capacity[capacityIndex];
        size = 0;
        hashtable = new TreeSet[M];
        for (int i = 0;i < M;i++) {
            hashtable[i] = new TreeSet<>();
        }
    }

    private int hash(T value) {
        //消除value的哈希值的正負號再對M取模
        return (value.hashCode() & 0x7fffffff) % M;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public void add(T value) {
        Set<T> set = hashtable[hash(value)];
        if (!set.contains(value)) {
            set.add(value);
            size++;
            if (size >= upperTol * M && capacityIndex + 1 < capacity.length) {
                capacityIndex++;
                resize(capacity[capacityIndex]);
            }
        }
    }

    @Override
    public void remove(T value) {
        Set<T> set = hashtable[hash(value)];
        if (set.contains(value)) {
            set.remove(value);
            size--;
            if (size < lowerTol * M && capacityIndex - 1 >= 0) {
                capacityIndex--;
                resize(capacity[capacityIndex]);
            }
        }
    }

    @Override
    public boolean contains(T value) {
        return hashtable[hash(value)].contains(value);
    }

    @SuppressWarnings("unchecked")
    private void resize(int newM) {
        Set<T>[] newHashTable = new TreeSet[newM];
        for (int i = 0;i < newM;i++) {
            newHashTable[i] = new TreeSet<>();
        }
        int oldM = this.M;
        this.M = newM;
        for (int i = 0;i < oldM;i++) {
            Set<T> set = hashtable[i];
            set.stream().forEach(value -> newHashTable[hash(value)].add(value));
        }
        this.hashtable = newHashTable;
    }
}
  • Hash映射封裝

實現類(此處SortedMap接口和TreeMap爲JDK的java.util.SortedMap,java.util.TreeMap)

public class HashMap<K,V> implements Map<K,V> {
    private final int[] capacity = {53,97,193,389,769,1543,3079,6151,12289,24593,
            49157,98317,196613,393241,786433,1572869,3145739,
            6291469,12582917,25165843,50331653,100663319,
            201326611,402653189,805306457,1610612741};

    //容忍度上界
    private static final int upperTol = 10;
    //容忍度下屆
    private static final int lowerTol = 2;
    private int capacityIndex = 0;

    private SortedMap<K,V>[] hashtable;
    private int M;
    private int size;

    @SuppressWarnings("unchecked")
    public HashMap() {
        this.M = capacity[capacityIndex];
        size = 0;
        hashtable = new TreeMap[M];
        for (int i = 0;i < M;i++) {
            hashtable[i] = new TreeMap<>();
        }
    }

    private int hash(K key) {
        //消除key的哈希值的正負號再對M取模
        return (key.hashCode() & 0x7fffffff) % M;
    }

    @Override
    public void add(K key, V value) {
        SortedMap<K,V> map = hashtable[hash(key)];
        if (map.containsKey(key)) {
            map.put(key, value);
        }else {
            map.put(key, value);
            size++;
            if (size >= upperTol * M && capacityIndex + 1 < capacity.length) {
                capacityIndex++;
                resize(capacity[capacityIndex]);
            }
        }
    }

    @Override
    public V remove(K key) {
        SortedMap<K,V> map = hashtable[hash(key)];
        if (map.containsKey(key)) {
            V ret = map.remove(key);
            size--;
            if (size < lowerTol * M && capacityIndex - 1 >= 0) {
                capacityIndex--;
                resize(capacity[capacityIndex]);
            }
            return ret;
        }
        return null;
    }

    @Override
    public boolean contains(K key) {
        return hashtable[hash(key)].containsKey(key);
    }

    @Override
    public V get(K key) {
        return hashtable[hash(key)].get(key);
    }

    @Override
    public void set(K key, V value) {
        SortedMap<K,V> map = hashtable[hash(key)];
        if (!map.containsKey(key)) {
            throw new IllegalArgumentException("key不存在");
        }
        map.put(key,value);
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @SuppressWarnings("unchecked")
    private void resize(int newM) {
        SortedMap[] newHashTable = new TreeMap[newM];
        for (int i = 0;i < newM;i++) {
            newHashTable[i] = new TreeMap();
        }
        int oldM = this.M;
        this.M = newM;
        for (int i = 0;i < oldM;i++) {
            SortedMap<K,V> map = hashtable[i];
            map.keySet().stream().forEach(key -> newHashTable[hash(key)].put(key,map.get(key)));
        }
        this.hashtable = newHashTable;
    }
}

對五種映射的性能測試

public class TestMapMain {
    private static double testMap(Map<String,Integer> map,String filename) {
        long startTime = System.nanoTime();
        System.out.println(filename);
        Array<String> words = new DefaultArray<>();
        FileOperation.readFile(filename,words);
        System.out.println("共有單詞: " + words.getSize());
        Stream.of(words.getData()).filter(word -> word != null)
                .forEach(word -> {
                    if (map.contains(word)) {
                        map.set(word,map.get(word) + 1);
                    }else {
                        map.add(word,1);
                    }
                });
        System.out.println("共有不一樣的單詞: " + map.getSize());
        System.out.println("pride出現的次數: " + map.get("pride"));
        System.out.println("prejudice出現的次數: " + map.get("prejudice"));
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
    public static void main(String[] args) {
        String filename = "/Users/admin/Downloads/pride-and-prejudice.txt";
        Map<String,Integer> bstMap = new BSTMap<>();
        double time1 = testMap(bstMap,filename);
        System.out.println("BST Map: " + time1 + " s");

        Map<String,Integer> linkedListMap = new LinkedListMap<>();
        double time2 = testMap(linkedListMap,filename);
        System.out.println("LinkedList Map: " + time2 + " s");

        Map<String,Integer> avlTreeMap = new AVLTreeMap<>();
        double time3 = testMap(avlTreeMap,filename);
        System.out.println("AVLTree Map: " + time3 + " s");

        Map<String,Integer> redBlackTreeMap = new RedBlackTreeMap<>();
        double time4 = testMap(redBlackTreeMap,filename);
        System.out.println("RedBlackTree Map: " + time4 + " s");
        
        Map<String,Integer> hashMap = new HashMap<>();
        double time5 = testMap(hashMap,filename);
        System.out.println("Hash Map: " + time5 + " s");
    }
}

測試結果

/Users/admin/Downloads/pride-and-prejudice.txt 共有單詞: 125901 共有不一樣的單詞: 6530 pride出現的次數: 53 prejudice出現的次數: 11 BST Map: 0.168990167 s /Users/admin/Downloads/pride-and-prejudice.txt 共有單詞: 125901 共有不一樣的單詞: 6530 pride出現的次數: 53 prejudice出現的次數: 11 LinkedList Map: 9.299100543 s /Users/admin/Downloads/pride-and-prejudice.txt 共有單詞: 125901 共有不一樣的單詞: 6530 pride出現的次數: 53 prejudice出現的次數: 11 AVLTree Map: 0.067501294 s /Users/admin/Downloads/pride-and-prejudice.txt 共有單詞: 125901 共有不一樣的單詞: 6530 pride出現的次數: 53 prejudice出現的次數: 11 RedBlackTree Map: 0.058438515 s /Users/admin/Downloads/pride-and-prejudice.txt 共有單詞: 125901 共有不一樣的單詞: 6530 pride出現的次數: 53 prejudice出現的次數: 11 Hash Map: 0.039921075 s

相關文章
相關標籤/搜索