diff --git a/README.md b/README.md index c9d1107..bc5223b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,18 @@ ### :lollipop::lollipop::lollipop:全文持续更新中 ... :recycle::recycle::recycle: + + +### 零、:rocket::rocket::rocket:数据结构与算法 + +* [数据结构与算法 (第 01 篇) Java版:常见基础排序算法及复杂度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BasicSorting.md) +* [数据结构与算法 (第 02 篇) Java版:二叉树遍历的n种方法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BinaryTreeTraversal.md) +* [数据结构与算法 (第 03 篇) Java版:二叉树深度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BinaryTreeDepth.md) +* [数据结构与算法 (第 04 篇) Java版:LRU缓存机制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/LRUCache.md) + + ### 一、:bullettrain_side::railway_car::railway_car::railway_car:集合框架源码分析 + * [集合框架 (第 01 篇) 源码分析:Collection 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaCollections.md) * [集合框架 (第 02 篇) 源码分析:Map 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaMaps.md) * [集合框架 (第 03 篇) 源码分析:ArrayList](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/ArrayList.md) @@ -190,6 +201,5 @@ ### 联系作者:flags:: -> :postbox:aboutcloud@163.com -> -> :dizzy:[https://www.FooVv.com](https://www.foovv.com) \ No newline at end of file +> :postbox:linkedme@qq.com +> \ No newline at end of file diff --git a/resource/markdown/algorithm/BasicSorting.md b/resource/markdown/algorithm/BasicSorting.md new file mode 100644 index 0000000..c37e6a2 --- /dev/null +++ b/resource/markdown/algorithm/BasicSorting.md @@ -0,0 +1,302 @@ +### 2常见基础排序算法 + + + +#### 排序算法分类 + +![排序算法分类](http://ww2.sinaimg.cn/large/006y8mN6ly1g68uopou69j30ko0dzwgb.jpg) + + +#### 时间复杂度 + +| 排序算法 | 最好(时间复杂度) | 平均(时间复杂度) | 最坏(时间复杂度) | 稳定性 | 空间复杂度 | +| ------------ | ------------------------- | ------------------------- | ------------------------- | ------ | -------------------------------- | +| 冒泡排序 | **O**(n) | **O**(n2) | **O**(n2) | 稳定 | **O**(1) | +| **快速排序** | **O**(n*log2n) | **O**(n*log2n) | **O**(n2) | 不稳定 | **O**(log2n)~**O**(n) | +| 直接插入排序 | **O**(n) | **O**(n2) | **O**(n2) | 稳定 | **O**(1) | +| **希尔排序** | **O**(n) | **O**(n1.3) | **O**(n2) | 不稳定 | **O**(1) | +| 简单选择排序 | **O**(n) | **O**(n2) | **O**(n2) | 不稳定 | **O**(1) | +| **堆排序** | **O**(n*log2n) | **O**(n*log2n) | **O**(n*log2n) | 不稳定 | **O**(1) | +| **归并排序** | **O**(n*log2n) | **O**(n*log2n) | **O**(n*log2n) | 稳定 | **O**(n) | +| 基数排序 | **O**(d*(r+n)) | **O**(d*(r+n)) | **O**(d*(r+n)) | 稳定 | **O**(r*d+n) | + + + +#### 各种复杂度效率比较图 + +```java +O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(2^n) < O(n^3) < O(n^n) +``` + +![各种时间复杂度效率比较图](https://i.loli.net/2019/06/11/5cff235ba9b9a93703.jpg) + +**说明:** n 越大,越能体现算法效率。当 n 比较小时,复杂度会有一波小交叉,上图不考虑 n 比较小的情况。 + + + +##### 1. 冒泡排序 ★☆☆☆☆ + +```java +public void bubbleSort(int[] array) { + if (array == null) { + return; + } + + int temp; + // 冒泡次数 + for (int i = array.length - 1; i > 0; i--) { + // 冒泡排序 + for (int j = 0; j < i; j++) { + // 将大值交换到后面 + if (array[j] > array[j + 1]) { + temp = array[j]; + array[j] = array[j + 1]; + array[j + 1] = temp; + } + } + } +} +``` + + + +##### 2. 快速排序 ★★★★★ + +基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 + +```java +public void quickSort(int[] array, int left, int right) { + if (array == null) { + return; + } + + if (left < right) { + int i = left; + int j = right; + int temp = array[i]; // 选取一端值为基准值 + + while (i < j) { + // 如果 j 处值大于等于基准值,那么不用交换数据,直接将 j 向前移动, + // 直到 i 等于 j 或者 j 处值比基准值小 + while (i < j && array[j] >= temp) { + j--; + } + // 如果 i < j,说明 j 处值比基准值小(根据上面循环判断) + if (i < j) { + // 交换 j 与 i 处的值,并将 i 向后移动 + array[i++] = array[j]; + } + + + // 如果 i 处值小于等于基准值,那么将i向后移动就可以了 + while (i < j && array[i] <= temp) { + i++; + } + // 如果 i < j,说明 i 处值比基准值大(根据上面循环判断) + if (i < j) { + // 交换 i 与 j 处的值,并将 i 向前移动 + array[j--] = array[i]; + } + + // 最后将临时基准值填充到 i 处 + array[i] = temp; + // 对两段各自快速排序 + } + + quickSort(array, left, i - 1); + quickSort(array, i + 1, right); + } +} +``` + + + +##### 3. 直接插入排序 ★★★☆☆ + +```java +public void insertionSort(int[] array) { + if (array == null) { + return; + } + // 和冒泡排序有些类似,这里是遍历趟数 + for (int i = 0; i < array.length; i++) { + // 精髓是从局部有序,到整体有序 + int temp = array[i]; // 当前基准元素 + int j; + for (j = i; j > 0 && array[j - 1] > temp; j--) { + array[j] = array[j - 1]; // 下一个元素比基准元素大,下一个元素向后移动 + } + // 最后比较当前元素和基准元素大小 + if (array[j] > temp) { + array[j] = temp; + } + } +} +``` + + + +##### 4. 希尔排序(缩写增量-直接插入排序) ★★★☆☆ + +```java +public void shellSort(int[] array) { + if (array == null) { + return; + } + // 计算增量 + for (int d = array.length / 2; d > 0; d /= 2) { + // 分组 + for (int g = 0; g < d; g++) { + // 插入排序(第 x 组的第 d 个增量元素起步)(直接插入排序的增量是 1,这里是 d,需注意下) + for (int i = g + d; i < array.length; i += d) { + int temp = array[i]; + int j; + for (j = i; j > d && array[j - d] > temp; j -= d) { + array[j] = array[j - d]; + } + if (array[j] > temp) { + array[j] = temp; + } + } + } + } +} +``` + + + +##### 5. 简单选择排序 ★★☆☆☆ + +```java +public void selectionSort(int[] array) { + if (array == null) { + return; + } + + int index; + int temp; + // 做出的选择次数 + for (int i = array.length - 1; i > 0; i--) { + index = 0; + for (int j = 1; j < i; j++) { + // 选择一个最大的值(记录索引) + if (array[j] > array[index]) { + index = j; + } + } + // 将选出的最大值换到一端 + if (array[index] > array[i]) { + temp = array[index]; + array[index] = array[i]; + array[i] = temp; + } + } +} +``` + + + +##### 6. 堆排序 ★★★★☆ + +```java +public void heapSort(int[] array) { + if (array == null) { + return; + } + + for (int i = array.length / 2 - 1; i >= 0; i--) { + // 先调整堆(选择一个最大值放到堆顶) + adjustHeap(array, i, array.length); + } + + for (int i = array.length - 1; i > 0; i--) { + // 将堆顶的元素与其他元素比较并交换 + swap(array, 0, i); + // 再调整堆 + adjustHeap(array, 0, i); + } +} + +// 调整堆,使得堆顶元素值大于等于其子节点值 +private void adjustHeap(int[] array, int top, int length) { + int temp = array[top]; + + for (int i = top * 2 + 1; i < length; i = i * 2 + 1) { + // (如果存在的化)从左右子节点找出值最大的子节点 + if (i + 1 < length && array[i + 1] > array[i]) { + i++; + } + if (array[i] > temp) { + array[top] = array[i]; + top = i; + } else { + break; + } + } + + if (array[top] > temp) { + array[top] = temp; + } +} + +private void swap(int[] array, int a, int b) { + int temp = array[a]; + array[a] = array[b]; + array[b] = temp; +} +``` + + + +##### 7. 归并排序 ★★★★☆ + +```java +public void mergeSort(int[] array) { + if (array == null) { + return; + } + + int[] aux = new int[array.length]; + sort(array, 0, array.length - 1, aux); +} + +private void sort(int[] array, int left, int right,int[] aux) { + if (left < right) { + int mid = (left + right) / 2; + // 先分后合 + sort(array, left , mid, aux); + sort(array, mid + 1, right, aux); + merge(array, left, mid, right, aux); + } +} + +private void merge(int[] array, int left, int mid, int right, int[] aux){ + int t = 0; + int l = left; + int m = mid + 1; + + // 判断元素值大小,按大小排序到辅助数组上 + while (l <= mid && m <= right) { + if (array[l] <= array[m]) { + aux[t++] = array[l++]; + } else { + aux[t++] = array[m++]; + } + } + + // 把剩余元素填充到辅助数组上 + while (l <= mid) { + aux[t++] = array[l++]; + } + while (m <= right) { + aux[t++] = array[m++]; + } + + // 将辅助线数组上的元素复制到需要排序的数组上 + t = 0; + while (left <= right) { + array[left++] = aux[t++]; + } +} +``` + diff --git a/resource/markdown/algorithm/BinaryTreeDepth.md b/resource/markdown/algorithm/BinaryTreeDepth.md new file mode 100644 index 0000000..38cef68 --- /dev/null +++ b/resource/markdown/algorithm/BinaryTreeDepth.md @@ -0,0 +1,89 @@ +### 二叉树的最小深度 + + + +> 给定一个二叉树,找出其最小深度。 +> +> 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 + +**说明:** 叶子节点是指没有子节点的节点。 + + + +##### 0. 定义一个二叉树节点 + +```java +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode(int x) { + val = x; + } +} +``` + + + +##### 1. 递归 + +```java +public class Solution { + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + // 递归计算 + int leftMinDepth = minDepth(root.left); + int rightMinDepth = minDepth(root.right); + + // 有子节点为空的,则返回另一个节点的深度。否则返回两则最小的深度。 + return leftMinDepth == 0 || rightMinDepth == 0 + ? leftMinDepth + rightMinDepth + 1 + : Math.min(leftMinDepth, rightMinDepth) + 1; + } +} +``` + + + +##### 2. 非递归(宽度优先搜索) + +> 出现第一个无子节点的节点,则该节点的深度为树的最小深度 + +```java +public class Solution { + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + Queue queue = new LinkedList<>(); + queue.offer(root); + + int minDepth = 0; + while (!queue.isEmpty()) { + ++minDepth; + + // 逐层遍历,判断一层是否存在没有子节点的节点,则该节点的深度为树的最小深度 + int size = queue.size(); + for (int i = 0; i < size; i++) { + root = queue.poll(); + if (root.left == null && root.right == null) { + return minDepth; + } + if (root.left != null) { + queue.offer(root.left); + } + if (root.right != null) { + queue.offer(root.right); + } + } + } + + return minDepth; + } +} +``` + diff --git a/resource/markdown/algorithm/BinaryTreeTraversal.md b/resource/markdown/algorithm/BinaryTreeTraversal.md new file mode 100644 index 0000000..d55a2bb --- /dev/null +++ b/resource/markdown/algorithm/BinaryTreeTraversal.md @@ -0,0 +1,164 @@ +### 二叉树的遍历 ★★★★★ + + + +##### TreeNode 节点 + +```java +/* Definition for a binary tree node. */ +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int x) { + val = x; + } +} +``` + + + +##### 1. 递归调用 (深度优先遍历) ★☆☆☆☆ + +```java +public void preorderTraversal(TreeNode root) { + if (root == null) { + return; + } + // 可调整前、中、后序遍历 + System.out.print(root.val); + preorderTraversal(root.left); + preorderTraversal(root.right); +} +``` + + + +##### 2.1 非递归遍历 - 基于栈 (前序遍历、深度优先遍历) ★★★☆☆(不推荐) + +```java +public List preorderTraversalWithStack(TreeNode root) { + + LinkedList output = new LinkedList<>(); + + // 用于回溯的栈 + Stack stack = new Stack<>(); + + while (root != null || !stack.isEmpty()) { + // 访问结点的数据,并且移动到左子结点,直到无左子结点 + while (root != null) { + output.add(root.val); + stack.push(root); // 将当前节点 push 到栈中,方便后续回溯 + root = root.left; // 遍历左子结点 + } + // 回溯,遍历右子结点 + if (!stack.isEmpty()) { + root = stack.pop().right; + } + } + + return output; +} +``` + + + +##### 2.2非递归遍历 - 基于栈改进版(基于队列的栈模式)(执行时间较上者提升1倍) ★★★★☆ + +```java +public List preorderTraversalWithStack(TreeNode root) { + + LinkedList output = new LinkedList<>(); + + // 用于回溯的栈(基于链表实现,不用担心栈的扩容问题) + LinkedList stack = new LinkedList<>(); + + while (root != null || !stack.isEmpty()) { + // 访问结点的数据,并且移动到左子结点,直到无左子结点 + while (root != null) { + output.add(root.val); + stack.add(root); // 将当前节点 add 到栈中,方便后续回溯 + root = root.left; // 遍历左子结点 + } + // 回溯,遍历右子结点 + if (!stack.isEmpty()) { + root = stack.pollLast().right; + } + } + + return output; +} +``` + + + + + +##### 3. 非递归遍历 - 基于队列 (层次遍历、广度优先遍历、O(n)) ★★★★☆ + +```java +public List levelorderTraversal(TreeNode root) { + LinkedList output = new LinkedList<>(); + if (root == null) { + return output; + } + + Queue queue = new LinkedList<>(); + queue.add(root); + + // 遍历队列 + while (!queue.isEmpty()) { + // 从队列头部取出一个结点 + root = queue.poll(); + output.add(root.val); + // 将左右子结点放入队列尾部 + if (root.left != null) { + queue.add(root.left); + } + if (root.right != null) { + queue.add(root.right); + } + } + + return output; +} +``` + + + +##### 4. Morris Traversal(莫里斯遍历、O(n)) ★★★★☆ + +```java +public List morrisTraversal(TreeNode root) { + LinkedList output = new LinkedList<>(); + + TreeNode prev; + TreeNode curr = root; + while (curr != null) { + // 向左子节点遍历 + if (curr.left != null) { + prev = curr.left; + while (prev.right != null && prev.right != curr) { + prev = prev.right; + } + // 右子节点的回溯指针绑定 + if (prev.right == null) { + prev.right = curr; + curr = curr.left; + } else { + output.add(curr.val); + prev.right = null; + curr = curr.right; + } + // 向右子节点遍历 + } else { + output.add(curr.val); + curr = curr.right; + } + } + + return output; +} +``` + diff --git a/resource/markdown/algorithm/LRUCache.md b/resource/markdown/algorithm/LRUCache.md new file mode 100644 index 0000000..953911a --- /dev/null +++ b/resource/markdown/algorithm/LRUCache.md @@ -0,0 +1,158 @@ +### LRUCache 缓存算法 + +##### 最少最近使用算法 + +```java +public class LRUCache { + /** + * 默认容量 + */ + private static final int DEFAULT_CAPACITY = 1024; + /** + * 缓存容量 + */ + private int capacity; + /** + * 实际存储/使用的元素大小 + */ + private int size = 0; + /** + * 高效访问的散列表 + */ + private Map> map; + + private Node head, tail; + + /** + * 自定义双向链表中的节点 + */ + private static class Node { + K key; + V value; + Node prev; + Node next; + + Node(Node prev, K key, V value, Node next) { + this.key = key; + this.value = value; + this.prev = prev; + this.next = next; + } + } + + public LRUCache() { + this.capacity = DEFAULT_CAPACITY; + this.map = new HashMap<>(DEFAULT_CAPACITY, 0.75F); + this.head = null; + this.tail = null; + } + + public LRUCache(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be positive integer"); + } + + this.capacity = capacity; + this.map = new HashMap<>(capacity, 0.75F); + this.head = null; + this.tail = null; + } + + public V get(K key) { + Node node = this.map.get(key); + if (node != null) { + this.moveToHead(node); + return node.value; + } else { + return null; + } + } + + public V put(K key, V value) { + Node node = this.map.get(key); + if (node != null) { + node.value = value; + moveToHead(node); + return value; + } + + if (size == capacity) { + node = removeLast(); + map.remove(node.key); + } + + node = addFirst(key, value); + map.put(key, node); + + return value; + } + + /** + * 对于新添加的元素,应将新元素添加到链表的头部 + */ + private Node addFirst(K key, V value) { + final Node h = head; + final Node newNode = new Node<>(null, key, value, h); + head = newNode; + if (h == null) { + tail = newNode; + } else { + h.prev = newNode; + } + size++; + + return newNode; + } + + /** + * 对于被访问的元素,将该元素移动到头部 + */ + private Node moveToHead(Node node) { + final Node prev = node.prev; + final Node next = node.next; + + if (prev == null) { // 如果是首节点,无需移动 + return node; + } + + prev.next = next; + if (next == null) { // 如果是尾节点,需要移动tail + tail = prev; + } else { + next.prev = prev; + } + + node.prev = null; + node.next = head; + head.prev = node; + head = node; + + return node; + } + + /** + * 缓存满时,应删除(淘汰)最后一个节点 + */ + private Node removeLast() { + final Node t = tail; + if (t == null) { + return null; + } + + t.value = null; // help GC + Node prev = t.prev; + t.prev = null; // help GC + tail = prev; // 移动 tail + if (prev == null) { // 如果尾节点的前一个节点也为空,说明尾节点也是首节点 + head = null; + } else { + prev.next = null; + } + size--; + return t; + } +} +``` + + +