1. 数据结构

数据结构就是把数据元素按照一定的关系组织起来的集合, 用来存储数据.

1.1. 逻辑结构分类

  • 集合: 集合结构中数据元素除了属于同一个集合外, 他们之间没有任何的关系.

  • 线性: 线性结构中的数据元素存在一对一的关系.

  • 树形: 树形结构中的数据元素存在一对多的层次关系.

  • 图形: 图形结构的数据元素是多对多的关系.

1.2. 物理结构分类

  • 顺序结构: 把数据存放到地址连续的存储单元里面, 其数据建的逻辑关系和物理关系是一致的.

  • 链式结构: 把数据元素存放在任意的存储单元里面, 这组存储单元可以是连续的也可以是不连续的.

2. 算法

算法指解题方案的准确而完整的描述, 是一系列解决问题的清晰指令.
算法代表着用系统的方法解决问题的策略机制.

2.1. 算法分析

  • 最坏情况分析: 记T(n)为输入规模为n时的最长运行时间.

  • 平均情况分析: 记T(n)为输入规模为n时的所有可能运行时间根据概率加权平均.

  • 最好情况分析: 记T(n)为输入规模为n时的最短运行时间.

  • 渐近分析: 忽略掉依赖于机器的常量, 检查程序运行的增量, 而不是具体的运行时间.

2.2. Big O 符号

表示规则
  • 用常数1取代运行时间中的所有加法常数.

  • 在修改后的运行次数中, 只保留高阶项.

  • 如果最高阶项存在, 且常数因子不为1, 则去除与这个项相乘的常数.

eg: \$3n^3+90n^2-5n+4096=O(n^3)\$

常见复杂度比较:

\$1 < lgn < sqrtn < n < nlgn < n^2 < n^3 < 2^n < n!\$

3. 表

3.1. 线性表

通过数组存储元素, 可以通过数组索引直接获取元素.

package me.jy.list;

/**
 * 基于数组实现线性表
 *
 * @author jy
 */
class ArrayList<T> implements List<T> {

    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] DEFAULT_EMPTY_DATA = {};

    private Object[] elements;

    private int size;

    public ArrayList() {
        this.elements = DEFAULT_EMPTY_DATA;
    }

    public ArrayList(int size) {
        this.elements = new Object[Math.max(0, size)];
    }

    @Override
    public void add(T e) {
        if (this.elements.length == size) {
            resize(size + (size >> 1));
        }
        elements[size++] = e;
    }

    private void resize(int i) {
        i = Math.max(DEFAULT_CAPACITY, i);
        Object[] newElements = new Object[i];
        System.arraycopy(elements, 0, newElements, 0, size);
        elements = newElements;
    }

    @Override
    public T get(int index) {
        checkRange(index);
        return (T) elements[index];
    }

    @Override
    public void remove(int index) {
        checkRange(index);
        elements[index] = null;
        if (index != --size) {
            System.arraycopy(elements, index + 1, elements, index, size - index + 1);
        }
    }

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

    private void checkRange(int index) {
        if (index >= size) {
            throw new RuntimeException("Index out of bounds!");
        }
    }

}

3.1.1. 时间复杂度

  • get: \$O(1)\$

  • add: \$O(n)\$

  • remove: \$O(n)\$

3.2. 链表

每个元素包含一个指针指向下一个元素.

package me.jy.list;

/**
 * 双向链表实现
 *
 * @author jy
 */
class LinkedList<T> implements List<T> {

    private ListNode<T> head;

    private ListNode<T> tail;

    private int size;

    // tag::reverse-list[]
    public static <T> ListNode<T> reverse(ListNode<T> head) {
        ListNode<T> prev = null;
        ListNode<T> current = head;
        while (current != null) {
            ListNode<T> next = current.getNext();
            current.setNext(prev);
            prev = current;
            current = next;
        }
        return prev;
    }
    // end::reverse-list[]

    @Override
    public void add(T e) {
        ListNode<T> node = new ListNode<>(e);
        if (this.tail == null) {
            head = this.tail = node;
        } else {
            node.setPrev(tail);
            this.tail.setNext(node);
            this.tail = node;
        }
        size++;
    }

    @Override
    public T get(int index) {
        ListNode<T> current = head;
        for (int i = 0; i < index && current != null; i++) {
            current = current.getNext();
        }
        if (current == null) {
            throw new RuntimeException("Index out of bound!");
        }
        return current.getData();
    }

    @Override
    public void remove(int index) {
        ListNode<T> current = head;
        for (int i = 0; i < index && current != null; i++) {
            current = current.getNext();
        }
        if (current == null) {
            throw new RuntimeException("Index out of bound!");
        }
        ListNode<T> prev = current.getPrev();
        if (prev == null) {
            head = current.getNext();
        } else {
            prev.setNext(current.getNext());
        }
        size--;
    }

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

}

3.2.1. 时间复杂度

  • get: \$O(n)\$

  • add: \$O(1)\$

  • remove: \$O(n)\$

3.2.2. ArrayList和LinkedList比较

  • ArrayList的实现基于数组, LinkedList的实现基于双向链表.

  • 对于随机访问, ArrayList优于LinkedList.
    对于新增元素, LinkedList优于ArrayList.

  • LinkedList比ArrayList更占内存.
    因为LinkedList要维护前驱和后继节点的指针.

3.2.3. 翻转单链表

    public static <T> ListNode<T> reverse(ListNode<T> head) {
        ListNode<T> prev = null;
        ListNode<T> current = head;
        while (current != null) {
            ListNode<T> next = current.getNext();
            current.setNext(prev);
            prev = current;
            current = next;
        }
        return prev;
    }

3.3. 队列

先进先出.

3.3.1. 数组实现队列

package me.jy.list;

/**
 * @author jy
 */
class ArrayQueue<T> {

    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] DEFAULT_EMPTY_DATA = {};

    private Object[] elements;

    private int size;

    public ArrayQueue() {
        this.elements = DEFAULT_EMPTY_DATA;
    }

    public ArrayQueue(int capacity) {
        this.elements = new Object[Math.max(capacity, DEFAULT_CAPACITY)];
    }

    public void push(T e) {
        if (this.elements.length == size) {
            resize(size + (size >> 1));
        }
        this.elements[size++] = e;
    }

    public T pop() {
        if (this.size == 0) {
            return null;
        }
        T data = (T) this.elements[0];
        System.arraycopy(elements, 1, elements, 0, --size);
        return data;
    }

    public int size() {
        return size;
    }

    private void resize(int i) {
        Object[] newElements = new Object[Math.max(i, DEFAULT_CAPACITY)];
        System.arraycopy(elements, 0, newElements, 0, size);
        this.elements = newElements;
    }
}

3.3.2. 链表实现队列

package me.jy.list;

/**
 * @author jy
 */
class LinkedQueue<T> {

    private ListNode<T> head;
    private ListNode<T> tail;

    private int size;

    public LinkedQueue() {
    }

    public void push(T e) {
        ListNode<T> node = new ListNode<>(e);
        if (head == null) {
            head = tail = node;
        } else {
            tail.setNext(node);
            tail = node;
        }
        size++;
    }

    public T pop() {
        if (size == 0) {
            return null;
        }

        ListNode<T> legacyHead = this.head;

        this.head = this.head.getNext();

        if (head == null) {
            this.tail = null;
        } else {
            this.head.setPrev(null);
        }

        legacyHead.setNext(null);
        size--;

        return legacyHead.getData();
    }

    public int size() {
        return size;
    }
}

3.4. 栈

先进后出.

3.4.1. 数组实现栈

package me.jy.list;

/**
 * 基于数组实现栈
 *
 * @author jy
 */
class ArrayStack<T> {

    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] DEFAULT_EMPTY_DATA = {};

    private Object[] elements;

    private int size;

    public ArrayStack() {
        this.elements = DEFAULT_EMPTY_DATA;
    }

    public ArrayStack(int capacity) {
        this.elements = new Object[Math.max(capacity, DEFAULT_CAPACITY)];
    }

    public void push(T e) {
        if (this.size == elements.length) {
            resize(size + (size >> 1));
        }
        this.elements[size++] = e;
    }

    private void resize(int i) {
        Object[] newElements = new Object[Math.max(i, DEFAULT_CAPACITY)];
        System.arraycopy(elements, 0, newElements, 0, size);
        this.elements = newElements;
    }

    public T pop() {
        if (this.size == 0) {
            return null;
        }
        T element = (T) this.elements[size - 1];
        this.elements[--size] = null;
        return element;
    }

    public int size() {
        return size;
    }
}

3.4.2. 双向链表实现栈

package me.jy.list;

/**
 * @author jy
 */
class LinkedStack<T> {

    private ListNode<T> head;
    private ListNode<T> tail;
    private int size;

    public void push(T e) {
        ListNode<T> node = new ListNode<>(e);
        if (tail == null) {
            head = this.tail = node;
        } else {
            tail.setNext(node);
            node.setPrev(tail);
            tail = node;
        }
        size++;
    }

    public T pop() {
        if (size == 0) {
            return null;
        }

        ListNode<T> legacyTail = this.tail;
        this.tail = this.tail.getPrev();

        if (tail == null) {
            head = null;
        } else {
            this.tail.setNext(null);
        }

        legacyTail.setPrev(null);
        size--;

        return legacyTail.getData();
    }

    public int size() {
        return size;
    }
}

3.5. 哈希表

对key计算hash值, 确定元素存放的位置.

3.5.1. 解决哈希冲突

  • 线性检测: 往散列表中插入数据, 如果某个数据经过hash后存放位置被占用了, 则从当前位置开始向后依次查找直到找到一个空闲的位置.

  • 二次检测: 每次检测的步长为平方数, 如 \$1^2,2^2,3^2...\$

  • 二次散列: 哈希冲突后第二次计算hash值.

  • 链地址法: 使用链表存储冲突的元素.

4. 树

4.1. 术语

  • 根节点

  • 叶子节点

  • 深度

  • 高度

  • 满二叉树: 除了叶子节点, 其他节点均有两个子节点.

  • 完全二叉树: 最后一层叶子节点从左到右排列, 最后一层以上的节点均有两个节点.

4.2. 二叉树

树中任意一个节点, 其左子节点的值都小于该节点的值, 其右子节点的值都大于该节点的值.

4.2.1. 实现

package me.jy.tree;

import java.util.Optional;

/**
 * @author jy
 */
public class BinarySearchTree<T extends Comparable<? super T>> {

    private Node root;

    public void insert(T value) {
        Node node = new Node(value);
        if (root == null) {
            root = node;
            return;
        }
        Node current = root;
        while (true) {
            int compare = current.value.compareTo(value);
            if (compare == 0) {
                return;
            }
            if (compare < 0) {
                if (current.right == null) {
                    current.right = node;
                    break;
                }
                current = current.right;
            } else {
                if (current.left == null) {
                    current.left = node;
                    break;
                }
                current = current.left;
            }
        }
    }

    public void remove(T value) {
        checkValue(value);
        remove(value, root);
    }

    public T findMin() {
        if (root == null) {
            return null;
        }
        return findMinNode(root).value;
    }

    public T findMax() {
        if (root == null) {
            return null;
        }
        return findMaxNode(root).value;
    }

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

    public int height() {
        return height(root);
    }

    private int height(Node node) {
        if (node == null) {
            return 0;
        }
        return 1 + Math.max(height(node.left), height(node.right));
    }

    private boolean contains(T value, Node node) {
        if (node == null) {
            return false;
        }
        int compare = value.compareTo(node.value);
        if (compare == 0) {
            return true;
        }
        if (compare < 0) {
            return contains(value, node.left);
        }
        return contains(value, node.right);
    }

    /**
     * 删除
     *
     * @param value 要删除的节点的值
     * @param node  根节点
     */
    private Node remove(T value, Node node) {
        if (node == null) {
            return null;
        }

        int compare = value.compareTo(node.value);
        if (compare < 0) {
            node.left = remove(value, node.left);
        } else if (compare > 0) {
            node.right = remove(value, node.right);
        } else if (node.left != null && node.right != null) {
            // 找到右子节点中最小的节点, 变为node的右子节点
            node.value = findMinNode(node.right).value;
            node.right = remove(node.value, node.right);
        } else {
            // node为树叶或者只有一个子节点
            node = Optional.ofNullable(node.left).orElse(node.right);
        }
        return node;
    }

    private Node findMaxNode(Node node) {
        if (node.right == null) {
            return node;
        }
        return findMaxNode(node.right);
    }

    private Node findMinNode(Node node) {
        if (node.left == null) {
            return node;
        }
        return findMinNode(node.left);
    }

    private void checkValue(T value) {
        if (value == null) {
            throw new IllegalArgumentException("No soup for u!");
        }
    }

    private class Node {

        private T value;
        private Node left;
        private Node right;

        private Node(T value) {
            this.value = value;
        }
    }

}

4.2.2. 时间复杂度

最坏 \$O(n)\$, 平均 \$O(logn)\$

4.3. AVL树

4.3.1. 性质

  • 可以是空树.

  • 任意左右子树均是AVL树, 且高度之差不能大于1.

4.3.2. 二叉树失衡场景

  • LL: 向左子树的左子树插入元素.

  • LR: 向左子树的右子树插入元素.

  • RR: 向右子树的右子树插入元素.

  • RL: 向右子树的左子树插入元素.

4.3.3. 实现

package me.jy.tree;

/**
 * @author jy
 */
public class AVLTree<T extends Comparable<T>> {

    private Node root;

    private int height(Node node) {
        if (node == null) {
            return 0;
        }
        return Math.max(height(node.left), height(node.right)) + 1;
    }

    private int heightBalanceFactor(Node node) {
        if (node == null) {
            return 0;
        }
        return Math.abs(height(node.left) - height(node.right));
    }

    /**
     * 将Z节点变为X节点的右子节点, X节点原有的右子节点变为Z节点的左子节点.
     */
    public Node llRotate(Node node) {
        Node pivot = node.left;
        node.left = pivot.right;
        pivot.right = node;

        return pivot;
    }

    /**
     * 将Z节点变为Y节点的左子节点, Y节点原有的左子节点变为Z节点的右子节点.
     */
    public Node rrRotate(Node node) {
        Node pivot = node.right;
        node.right = pivot.left;
        pivot.left = node;
        return pivot;
    }

    /**
     * 以X节点的右子节点为轴进行左单旋(用X节点的右子节点替换X节点), 然后以新的X节点为轴进行右单旋.
     */
    private Node lrRotate(Node node) {
        Node left = node.left;
        Node pivot = left.right;

        // ll
        left.right = pivot.left;
        pivot.left = left;
        node.left = pivot;

        // rr
        node.left = pivot.right;
        pivot.right = node;

        return pivot;
    }

    /**
     * 以Y节点的左子节点为轴进行右单旋(用Y节点的左子节点替换Y节点), 然后以新的Y节点为轴进行左单旋.
     */
    private Node rlRotate(Node node) {
        Node right = node.right;
        Node pivot = right.left;

        // rr
        right.left = pivot.right;
        pivot.right = right;
        node.right = pivot;

        // ll
        node.right = pivot.left;
        pivot.left = node;

        return pivot;
    }

    public void insert(T value) {
        root = insert(root, value);
    }

    private Node insert(Node root, T value) {
        Node node = new Node(value);
        if (root == null) {
            root = node;
        } else if (root.value.compareTo(value) < 0) {
            root.right = insert(root.right, value);
            if (heightBalanceFactor(root) > 1) {
                if (root.right.value.compareTo(value) < 0) {
                    root = rrRotate(root);
                } else {
                    root = rlRotate(root);
                }
            }
        } else {
            root.left = insert(root.left, value);
            if (heightBalanceFactor(root) > 1) {
                if (root.left.value.compareTo(value) > 0) {
                    root = llRotate(root);
                } else {
                    root = lrRotate(root);
                }
            }
        }
        return root;
    }

    public int heightFactor() {
        return heightBalanceFactor(root);
    }

    /**
     * 中序遍历
     */
    public void inOrder(Node node) {
        if (node == null) {
            return;
        }
        inOrder(node.left);
        System.out.println(node.value);
        inOrder(node.right);
    }

    public void print() {
        inOrder(root);
    }

    private class Node {

        private final T value;
        private Node left;
        private Node right;

        private Node(T value) {
            this.value = value;
        }
    }
}

5. 图

6. 排序

6.1. 冒泡排序

    /**
     * 冒泡排序
     * 两两比较, 如果左边的元素比右边的大, 则交换位置.
     * O(n^2)
     */
    public static class BubbleSort implements SortTemplate {

        @Override
        public void sort(int[] arr) {
            for (int i = arr.length - 1; i > 0; i--) {
                for (int j = 0; j < i; j++) {
                    if (less(arr[j + 1], arr[j])) {
                        exchange(arr, j, j + 1);
                    }
                }
            }
        }
    }

6.1.1. 时间复杂度

  • 比较次数: \$(n-1) + (n-2) + (n-3)+...+2+1=((n-1+1)*(n-1))/2=(n^2-n)/2\$

  • 交换次数: \$(n-1) + (n-2) + (n-3)+...+2+1=((n-1+1)*(n-1))/2=(n^2-n)/2\$

  • 总: \$O(n^2-n)=O(n^2)\$

6.2. 选择排序

    /**
     * 选择排序
     * 选择数组剩余元素中最小的元素放到首位
     * O(n^2)
     */
    public static class SelectSort implements SortTemplate {

        @Override
        public void sort(int[] arr) {
            for (int i = 0; i < arr.length - 1; i++) {
                int minIndex = i;
                for (int j = i + 1; j < arr.length; j++) {
                    if (less(arr[j], arr[minIndex])) {
                        minIndex = j;
                    }
                }
                exchange(arr, i, minIndex);
            }
        }
    }

6.2.1. 时间复杂度

  • 比较次数: \$(n-1) + (n-2) + (n-3)+...+2+1=((n-1+1)*(n-1))/2=(n^2-n)/2\$

  • 交换次数: \$O(n-1)\$

  • 总: \$O((n^2+n)/2-1)=O(n^2)\$

6.3. 插入排序

    /**
     * 插入排序
     * 保证索引左边的元素排好序
     * O(n^2)
     */
    public static class InsertSort implements SortTemplate {

        @Override
        public void sort(int[] arr) {
            for (int i = 1; i < arr.length; i++) {
                for (int j = i; j > 0; j--) {
                    if (less(arr[j], arr[j - 1])) {
                        exchange(arr, j, j - 1);
                    } else {
                        break;
                    }
                }
            }
        }
    }

6.3.1. 时间复杂度

  • 比较次数: \$(n-1) + (n-2) + (n-3)+...+2+1=((n-1+1)*(n-1))/2=(n^2-n)/2\$

  • 交换次数: \$(n-1) + (n-2) + (n-3)+...+2+1=((n-1+1)*(n-1))/2=(n^2-n)/2\$

  • 总: \$O(n^2-n)=O(n^2)\$

6.4. 希尔排序

    /**
     * 希尔排序(优化过的插入排序)
     * <p>
     * 步骤:
     * 1. 选定一个增长量, 对元素进行分组.
     * 2. 对每组元素进行插入排序.
     * 3. 减小增长量直至1, 重复步骤2.
     */
    public static class ShellSort implements SortTemplate {

        @Override
        public void sort(int[] arr) {
            int gap = 1;
            while (gap < arr.length / 2) {
                gap = gap * 2 + 1;
            }
            for (; gap > 0; gap /= 2) {
                for (int i = gap; i < arr.length; i++) {
                    for (int j = i; j >= gap; j -= gap) {
                        if (less(arr[j], arr[j - gap])) {
                            exchange(arr, j, j - gap);
                        } else {
                            break; // 如果左边比j小, 则不需要继续向左比较了.
                        }
                    }
                }
            }
        }
    }

6.5. 归并排序

    /**
     * 归并排序
     * 将数组切成若干小数组后排序, 最后合并.
     */
    public static class MergeSort implements SortTemplate {

        @Override
        public void sort(int[] arr) {
            sort(arr, 0, arr.length - 1);
        }

        private void sort(int[] arr, int lo, int hi) {
            if (lo >= hi) {
                return;
            }
            int mid = lo + (hi - lo) / 2;
            sort(arr, lo, mid);
            sort(arr, mid + 1, hi);
            merge(arr, lo, mid, hi);
        }

        /*private void sort2(int[] arr) {
            int n = arr.length;
            for (int i = 1; i < n; i += i) {
                for (int lo = 0; lo < n - i; lo += i * 2) { // 每次处理i*2个元素再合并
                    merge(arr, lo, lo + i - 1, Math.min(n - 1, lo + i * 2 - 1));
                }
            }
        }*/

        private void merge(int[] arr, int lo, int mid, int hi) {

            int[] tmpArr = new int[arr.length];

            int i = 0, p1 = lo, p2 = mid + 1;
            while (p1 <= mid && p2 <= hi) {
                if (less(arr[p1], arr[p2])) {
                    tmpArr[i++] = arr[p1++];
                } else {
                    tmpArr[i++] = arr[p2++];
                }
            }
            while (p1 <= mid) {
                tmpArr[i++] = arr[p1++];
            }
            while (p2 <= hi) {
                tmpArr[i++] = arr[p2++];
            }
            System.arraycopy(tmpArr, 0, arr, 0, tmpArr.length);
        }

    }

6.5.1. 时间复杂度

  • 拆分次数: \$O(logn)\$

  • 每次合并比较次数: \$O(n)\$

  • 总: \$O(nlogn)\$

6.6. 快速排序

    /**
     * 快速排序
     * 保证某一元素左边值比该元素值小且有序, 右边值比该元素值大且有序, 则该数组有序.
     * 1. 找到1个分界值, 把比分界值小的放左边, 把比分界值大的放右边.
     * 2. 重复步骤1将分界值左右两边分别排序.
     */
    public static class QuickSort implements SortTemplate {

        @Override
        public void sort(int[] arr) {
            sort(arr, 0, arr.length - 1);
        }

        private void sort(int[] arr, int lo, int hi) {
            if (lo >= hi) {
                return;
            }
            int partitionIndex = partition(arr, lo, hi);
            sort(arr, lo, partitionIndex - 1);
            sort(arr, partitionIndex + 1, hi);
        }

        private int partition(int[] arr, int lo, int hi) {
            int left = lo;
            int partitionValue = arr[hi];

            for (int i = left; i < hi; i++) {
                if (arr[i] < partitionValue) {
                    exchange(arr, i, left++);
                }
            }
            exchange(arr, left, hi);
            return left;
        }

        /*private void sort2(int[] arr, int lo, int hi) {
            if (hi <= lo) {
                return;
            }
            // 随机选择数组中一位作为partition
            exchange(arr, hi, ThreadLocalRandom.current().nextInt(lo, hi));
            // 取到左区间和右区间位置
            int[] indexes = partition(arr, lo, hi);
            sort(arr, lo, indexes[0]);
            sort(arr, indexes[1] + 1, hi);
        }

        private int[] partition(int[] arr, int lo, int hi) {
            int less = lo - 1;
            int more = hi;
            while (lo < more) {
                if (arr[lo] < arr[hi]) {
                    // 左指针加1, 交换lo
                    exchange(arr, ++less, lo++);
                } else if (arr[lo] > arr[hi]) {
                    // 右指针减1
                    exchange(arr, --more, lo);
                } else {
                    lo++;
                }
            }
            exchange(arr, more, hi);
            return new int[]{less, more};
        }*/
    }

6.6.1. 时间复杂度

  • 拆分次数: 最优\$O(logn)\$, 最坏\$O(n)\$

  • 每次比较交换次数: \$O(n)\$

  • 总: 最好\$O(nlogn)\$, 最坏\$O(n^2)\$

6.6.2. 快速排序和归并排序的区别

  • 归并排序将子数组切分后有 归并 的动作, 快速排序将子数组排完序后整个数组就自然而然的有序了.

  • 归并排序每次切分数组都是等分.

6.7. 堆排序

    /**
     * 堆排序
     * 先将数组转成最大堆的形式, 再将第一项放到最后. 逐步重复1..n-1项
     */
    public static class HeapSort implements SortTemplate {

        @Override
        public void sort(int[] arr) {

            int last = arr.length - 1;

            // 构造最大堆
            buildMaxHeap(arr, last);
            while (last > 0) {
                // 交换首尾元素
                exchange(arr, 0, last);
                buildMaxHeap(arr, --last);
            }
        }

        private void buildMaxHeap(int[] arr, int last) {

            if (last == 0) {
                return;
            }
            // 当前尾节点的父节点
            int parent = (last - 1) / 2;

            while (parent >= 0) {
                int left = parent * 2 + 1;
                int right = parent * 2 + 2;
                // 找到两个子节点中的值最大的节点
                int maxChildIndex = right <= last && less(arr[left], arr[right]) ? right : left;
                // 如果父节点值小, 则交换
                if (less(arr[parent], arr[maxChildIndex])) {
                    exchange(arr, parent, maxChildIndex);
                }
                parent--;
            }
        }
    }

6.7.1. 时间复杂度

  • 建堆: \$O(logn)\$

  • 总: \$O(nlogn)\$

6.8. 桶排序

    /**
     * 桶排序
     * 将元素按照大小放入不同的桶中, 对每个桶进行排序. 最后取出所有桶内元素.
     */
    public static class BucketSort implements SortTemplate {

        @Override
        public void sort(int[] arr) {
            List<Integer> list = sort(Arrays.stream(arr).boxed().collect(Collectors.toList()), 5);
            for (int i = 0; i < list.size(); i++) {
                arr[i] = list.get(i);
            }
        }

        private List<Integer> sort(List<Integer> arr, int bucketSize) {

            if (arr.size() <= 1 || bucketSize < 1) {
                return arr;
            }

            IntSummaryStatistics statistics = arr.stream().mapToInt(i -> i).summaryStatistics();
            int min = statistics.getMin();
            int max = statistics.getMax();

            // 计算出桶数量
            int bucketCount = (max - min) / bucketSize + 1;

            List<List<Integer>> buckets = IntStream.range(0, bucketCount).mapToObj(i -> new ArrayList<Integer>()).collect(Collectors.toList());

            for (int value : arr) {
                int bucketIndex = (value - min) / bucketSize;
                buckets.get(bucketIndex).add(value);
            }

            List<Integer> result = new ArrayList<>();

            // 桶内排序
            for (List<Integer> bucket : buckets) {
                if (bucket.isEmpty()) {
                    continue;
                }
                if (bucketCount == 1) {
                    bucketSize--;
                }
                result.addAll(sort(bucket, bucketSize));
            }
            return result;
        }
    }

6.8.1. 时间复杂度

  • 总: \$O(n)\$

6.9. 计数排序

    public static class CountingSort implements SortTemplate {

        @Override
        public void sort(int[] arr) {

            IntSummaryStatistics statistics = Arrays.stream(arr).summaryStatistics();
            int min = statistics.getMin();

            // 计算桶数量
            int bucketCount = statistics.getMax() - min + 1;
            int[] countingArray = new int[bucketCount];

            for (int i : arr) {
                countingArray[i - min]++; // 统计出现次数
            }

            int position = 0;
            for (int i = 0; i < countingArray.length; i++) { // 遍历桶
                for (int j = 0; j < countingArray[i]; j++) { // 桶里值为几, 就代表该桶代表的数出现几次
                    arr[position++] = i + min;
                }
            }
        }
    }

6.9.1. 时间复杂度

  • 总: \$O(n)\$

7.1. 二分查找

package me.jy.search;

/**
 * @author jy
 */
class BinarySearch {

    public int uniqueSearch(int[] arr, int target) {
        int low = 0;
        int high = arr.length - 1;
        while (low < high) {
            int mid = low + (high - low) / 2 + 1;
            if (arr[mid] == target)
                return mid;
            if (arr[mid] < target) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return -1;
    }

}

时间复杂度为 \$O(logn)\$

7.2. BF

package me.jy.search;

/**
 * @author jy
 */
public class BF {

    public int search(String origin, String target) {
        if (origin.length() < target.length()) {
            return -1;
        }
        char[] originChars = origin.toCharArray();
        char[] targetChars = target.toCharArray();

        // 移动双指针来比较两个子串.
        int o = 0, t = 0;
        while (o < originChars.length && t < targetChars.length) {
            if (originChars[o] == targetChars[t]) {
                o++;
                t++;
            } else {
                o = o - t + 1;
                t = 0;
            }
        }

        if (t == targetChars.length) {
            return o - t;
        }
        return -1;
    }
}

7.3. RK

package me.jy.search;

/**
 * @author jy
 */
public class RK {

    public int search(String origin, String target) {
        if (origin.length() < target.length()) {
            return -1;
        }
        int hash = hash(target, 26, 31, 0, target.length());

        for (int i = 0; i < origin.length() - target.length() + 1; i++) {
            // 比较每一个子串的哈希值, 如果哈希值相同则比较两个子串是否完全相等.
            if (hash(origin, 26, 31, i, target.length()) == hash && match(origin, target, i)) {
                return i;
            }
        }
        return -1;
    }

    private boolean match(String origin, String target, int i) {
        for (int j = 0; j < target.length(); j++) {
            if (origin.charAt(i + j) != target.charAt(j)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 计算指定子串hash值
     */
    private int hash(String str, int r, int k, int start, int length) {
        int hash = 0;
        for (int i = start; i < start + length; i++) {
            hash = (r * hash + str.charAt(i) % k);
        }
        return hash;
    }

}

7.4. KMP

package me.jy.search;

/**
 * @author jy
 */
public class KMP {

    public int search(String origin, String target) {

        int[] next = next(target);

        int i = 0;
        int j = 0;
        while (i < origin.length()) {
            while (j > 0 && target.charAt(j) != origin.charAt(i)) {
                j = next[j - 1];
            }
            if (target.charAt(j) == origin.charAt(i)) {
                j++;
            }
            i++;
            if (j == target.length()) {
                return i - j;
            }
        }
        return -1;
    }

    private int[] next(String target) {
        int[] next = new int[target.length()];

        next[0] = 0;

        for (int i = 1, k = 0; i < target.length(); i++) {
            while (k > 0 && target.charAt(i) != target.charAt(k)) {
                k = next[k - 1];
            }
            if (target.charAt(i) == target.charAt(k)) {
                k++;
            }
            next[i] = k;
        }
        return next;
    }
}

8. 算法思想

8.1. 贪心

只考虑局部的利益最大化.

8.1.1. 适用场景

局部最优解能导致产生全局最优解.

8.1.2. 思路

  1. 建立数学模型来描述问题.

  2. 把求解的问题分成若干个子问题.

  3. 对每一个子问题求解, 得到子问题的局部最优解.

  4. 把子问题的局部最优解合成原来解问题的一个解.

8.2. 分治

把一个复杂的问题分解为多个子问题求解.

8.2.1. 适用场景

  • 原问题和子问题有相同的模式.

  • 子问题之间没有相关性.

  • 具有分解终止条件, 即当问题足够小时, 可以直接求解.

  • 可以将子问题合并成原问题.

8.3. 回溯

按优选条件向前搜索, 以达到目标.但当探索到某一步时, 发现原先选择并不优或达不到目标, 就退回一步重新选择.

8.4. 动态规划

把原问题分解为相对简单的子问题的方式求解复杂问题的方法.
记住已经解决过的子问题的解的方法: ① 自顶向下的备忘录法.
② 自底向上.

8.4.1. 适用场景

  • 问题的最优解包含子问题的最优解.

  • 某一子问题的解一旦确定, 就不会受之后子问题的解的影响.

  • 子问题重叠, 一个子问题在下一阶段决策中可能被多次使用到.