treeNodes) {
for (TreeNode it : treeNodes) {
if (treeNode.getId().equals(it.getParentId())) {
if (treeNode.getChildren() == null) {
treeNode.setChildren(new ArrayList<>());
}
treeNode.getChildren().add(findChildren(it, treeNodes));
}
}
return treeNode;
}
}
```
### 回溯模板
### DFS模板
### BFS模板
## 查找算法
### 顺序查找
就是一个一个依次查找。
### 二分查找
二分查找又叫折半查找,从有序列表的初始候选区`li[0:n]`开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半。如果待查值小于候选区中间值,则只需比较中间值左边的元素,减半查找范围。依次类推依次减半。
- 二分查找的前提:**列表有序**
- 二分查找的有点:**查找速度快**
- 二分查找的时间复杂度为:**O(logn)**
![二分查找](images/Algorithm/二分查找.gif)
JAVA代码如下:
```java
/**
* 执行递归二分查找,返回第一次出现该值的位置
*
* @param array 已排序的数组
* @param start 开始位置,如:0
* @param end 结束位置,如:array.length-1
* @param findValue 需要找的值
* @return 值在数组中的位置,从0开始。找不到返回-1
*/
public static int searchRecursive(int[] array, int start, int end, int findValue) {
// 如果数组为空,直接返回-1,即查找失败
if (array == null) {
return -1;
}
if (start <= end) {
// 中间位置
int middle = (start + end) / 1;
// 中值
int middleValue = array[middle];
if (findValue == middleValue) {
// 等于中值直接返回
return middle;
} else if (findValue < middleValue) {
// 小于中值时在中值前面找
return searchRecursive(array, start, middle - 1, findValue);
} else {
// 大于中值在中值后面找
return searchRecursive(array, middle + 1, end, findValue);
}
} else {
// 返回-1,即查找失败
return -1;
}
}
/**
* 循环二分查找,返回第一次出现该值的位置
*
* @param array 已排序的数组
* @param findValue 需要找的值
* @return 值在数组中的位置,从0开始。找不到返回-1
*/
public static int searchLoop(int[] array, int findValue) {
// 如果数组为空,直接返回-1,即查找失败
if (array == null) {
return -1;
}
// 起始位置
int start = 0;
// 结束位置
int end = array.length - 1;
while (start <= end) {
// 中间位置
int middle = (start + end) / 2;
// 中值
int middleValue = array[middle];
if (findValue == middleValue) {
// 等于中值直接返回
return middle;
} else if (findValue < middleValue) {
// 小于中值时在中值前面找
end = middle - 1;
} else {
// 大于中值在中值后面找
start = middle + 1;
}
}
// 返回-1,即查找失败
return -1;
}
```
### 插值查找
插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找。将折半查找中的求mid索引的公式,low表示左边索引left,high表示右边索引right,key就是前面我们讲的findVal。
![二分查找mid](images/Algorithm/二分查找mid.png) **改为** ![插值查找mid](images/Algorithm/插值查找mid.png)
**注意事项**
- 对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找,速度较快
- 关键字分布不均匀的情况下,该方法不一定比折半查找要好
```java
/**
* 插值查找
*
* @param arr 已排序的数组
* @param left 开始位置,如:0
* @param right 结束位置,如:array.length-1
* @param findValue
* @return
*/
public static int insertValueSearch(int[] arr, int left, int right, int findValue) {
//注意:findVal < arr[0] 和 findVal > arr[arr.length - 1] 必须需要, 否则我们得到的 mid 可能越界
if (left > right || findValue < arr[0] || findValue > arr[arr.length - 1]) {
return -1;
}
// 求出mid, 自适应
int mid = left + (right - left) * (findValue - arr[left]) / (arr[right] - arr[left]);
int midValue = arr[mid];
if (findValue > midValue) {
// 向右递归
return insertValueSearch(arr, mid + 1, right, findValue);
} else if (findValue < midValue) {
// 向左递归
return insertValueSearch(arr, left, mid - 1, findValue);
} else {
return mid;
}
}
```
### 斐波那契查找
黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神奇的数字,会带来意向不大的效果。斐波那契数列{1, 1,2, 3, 5, 8, 13,21, 34, 55 }发现斐波那契数列的两个相邻数的比例,无限接近黄金分割值0.618。
斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1(F代表斐波那契数列),如下图所示:
![斐波那契查找](images/Algorithm/斐波那契查找.png)
JAVA代码如下:
```java
/**
* 因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列
*
* 非递归方法得到一个斐波那契数列
*
* @return
*/
private static int[] getFibonacci() {
int[] fibonacci = new int[20];
fibonacci[0] = 1;
fibonacci[1] = 1;
for (int i = 2; i < fibonacci.length; i++) {
fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2];
}
return fibonacci;
}
/**
* 编写斐波那契查找算法
*
* 使用非递归的方式编写算法
*
* @param arr 数组
* @param findValue 我们需要查找的关键码(值)
* @return 返回对应的下标,如果没有-1
*/
public static int fibonacciSearch(int[] arr, int findValue) {
int low = 0;
int high = arr.length - 1;
int k = 0;// 表示斐波那契分割数值的下标
int mid = 0;// 存放mid值
int[] fibonacci = getFibonacci();// 获取到斐波那契数列
// 获取到斐波那契分割数值的下标
while (high > fibonacci[k] - 1) {
k++;
}
// 因为 fibonacci[k] 值可能大于 arr 的 长度,因此我们需要使用Arrays类,构造一个新的数组
int[] temp = Arrays.copyOf(arr, fibonacci[k]);
// 实际上需求使用arr数组最后的数填充 temp
for (int i = high + 1; i < temp.length; i++) {
temp[i] = arr[high];
}
// 使用while来循环处理,找到我们的数 findValue
while (low <= high) {
mid = low + fibonacci[k] - 1;
if (findValue < temp[mid]) {
high = mid - 1;
k--;
} else if (findValue > temp[mid]) {
low = mid + 1;
k++;
} else {
return Math.min(mid, high);
}
}
return -1;
}
```
## 搜素算法
### 深度优先搜索(DFS)
深度优先搜索(Depth-First Search / DFS)是一种**优先遍历子节点**而不是回溯的算法。
![深度优先搜索](images/Algorithm/深度优先搜索.jpg)
**DFS解决的是连通性的问题**。即给定两个点,一个是起始点,一个是终点,判断是不是有一条路径能从起点连接到终点。起点和终点,也可以指的是某种起始状态和最终的状态。问题的要求并不在乎路径是长还是短,只在乎有还是没有。
**代码案例**
```java
/**
* Depth-First Search(DFS)
*
* 从根节点出发,沿着左子树方向进行纵向遍历,直到找到叶子节点为止。然后回溯到前一个节点,进行右子树节点的遍历,直到遍历完所有可达节点为止。
*
* 数据结构:栈
* 父节点入栈,父节点出栈,先右子节点入栈,后左子节点入栈。递归遍历全部节点即可
*
* @author lry
*/
public class DepthFirstSearch {
/**
* 树节点
*
* @param
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TreeNode {
private V value;
private List> childList;
// 二叉树节点支持如下
public TreeNode getLeft() {
if (childList == null || childList.isEmpty()) {
return null;
}
return childList.get(0);
}
public TreeNode getRight() {
if (childList == null || childList.isEmpty()) {
return null;
}
return childList.get(1);
}
}
/**
* 模型:
* .......A
* ...../ \
* ....B C
* .../ \ / \
* ..D E F G
* ./ \ / \
* H I J K
*/
public static void main(String[] args) {
TreeNode treeNodeA = new TreeNode<>("A", new ArrayList<>());
TreeNode treeNodeB = new TreeNode<>("B", new ArrayList<>());
TreeNode treeNodeC = new TreeNode<>("C", new ArrayList<>());
TreeNode treeNodeD = new TreeNode<>("D", new ArrayList<>());
TreeNode treeNodeE = new TreeNode<>("E", new ArrayList<>());
TreeNode treeNodeF = new TreeNode<>("F", new ArrayList<>());
TreeNode treeNodeG = new TreeNode<>("G", new ArrayList<>());
TreeNode treeNodeH = new TreeNode<>("H", new ArrayList<>());
TreeNode treeNodeI = new TreeNode<>("I", new ArrayList<>());
TreeNode treeNodeJ = new TreeNode<>("J", new ArrayList<>());
TreeNode treeNodeK = new TreeNode<>("K", new ArrayList<>());
// A->B,C
treeNodeA.getChildList().add(treeNodeB);
treeNodeA.getChildList().add(treeNodeC);
// B->D,E
treeNodeB.getChildList().add(treeNodeD);
treeNodeB.getChildList().add(treeNodeE);
// C->F,G
treeNodeC.getChildList().add(treeNodeF);
treeNodeC.getChildList().add(treeNodeG);
// D->H,I
treeNodeD.getChildList().add(treeNodeH);
treeNodeD.getChildList().add(treeNodeI);
// G->J,K
treeNodeG.getChildList().add(treeNodeJ);
treeNodeG.getChildList().add(treeNodeK);
System.out.println("非递归方式");
dfsNotRecursive(treeNodeA);
System.out.println();
System.out.println("前续遍历");
dfsPreOrderTraversal(treeNodeA, 0);
System.out.println();
System.out.println("后续遍历");
dfsPostOrderTraversal(treeNodeA, 0);
System.out.println();
System.out.println("中续遍历");
dfsInOrderTraversal(treeNodeA, 0);
}
/**
* 非递归方式
*
* @param tree
* @param
*/
public static void dfsNotRecursive(TreeNode tree) {
if (tree != null) {
// 次数之所以用 Map 只是为了保存节点的深度,如果没有这个需求可以改为 Stack>
Stack