Skip to content

Commit

Permalink
Merge pull request #31 from shipiyouniao/main
Browse files Browse the repository at this point in the history
feat: 添加数据结构与算法八股中的二叉树和搜索树相关内容
  • Loading branch information
shipiyouniao authored Sep 15, 2024
2 parents fdc5832 + 73e9d03 commit ef5c005
Show file tree
Hide file tree
Showing 9 changed files with 470 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,51 @@ AOP 主要包含如下几个概念:
1. JDK 动态代理:基于接口的代理,通过`java.lang.reflect.Proxy`类实现,要求目标对象必须实现接口。
2. CGLIB 动态代理:基于类的代理,通过`net.sf.cglib.proxy.Enhancer`类实现,不要求目标对象必须实现接口。

其原理很简单,调用`Method.invoke()`方法时,会先调用`MethodInterceptor.intercept()`方法,最后将参数传递给目标对象。
在 SpringBoot 中实现 AOP,一般需要经过以下几个步骤:

1. 定义切面和通知
```java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod() {
System.out.println("Method execution started");
}
}
```
2. 配置 Spring AOP(由 SpringBoot 自动完成)
3. 使用目标对象
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

public void addUser() {
System.out.println("User added");
}
}
```
4. 启动 SpringBoot 应用

此时,当调用`UserService`的`addUser`方法时,会自动执行`LoggingAspect`的`logBeforeMethod`方法。

通过这种方式,我们可以很轻易地为目标对象添加横切关注点,提高代码的可读性、可维护性、可扩展性。

## 什么是 IOCDI

IOC(Inversion of Control)是一种控制反转的思想,主要是将对象的创建和管理交给容器,提高代码的可读性、可维护性、可扩展性
IOCInversion of Control)是一种控制反转的思想,主要是将对象的创建和管理交给容器。

DI(Dependency Injection)是一种依赖注入的方式,主要是将对象的依赖关系交给容器,提高代码的可读性、可维护性、可扩展性。
DIDependency Injection)是一种依赖注入的方式,主要是将对象的依赖关系交给容器。

实际上很多人认为 IOCDI 是一码事,因为 IOCDI 的一种实现方式,但是从概念上来说,IOC 是一种思想,DI 是一种方式,目的都是为了解耦对象的创建和使用,提高代码的可读性、可维护性、可扩展性。

SpringBootIOCDI 主要有以下几个概念:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@
3. 计算索引:通过哈希值和数组长度计算索引,具体是返回`hash & (length - 1)`
4. 存储元素:将键值对存储到索引位置,如果索引位置已经有元素,则存储为链表,当链表长度大于 8 时,链表转换为红黑树,当红黑树节点小于 6 时,红黑树转换为链表。

## 为什么 Java 的 HashMap 底层使用红黑树,而非 AVL 树、B 树或 B+ 树?

Java 的 HashMap 是一种基于哈希表的 Map 集合,底层使用数组和链表或红黑树实现。在 JDK 8 中,当链表长度超过阈值 8 时,链表会转换为红黑树,以提高检索效率。

HashMap 使用红黑树而非 AVL 树、B 树或 B+ 树的原因主要有以下几点:

1. **平衡性**:红黑树是一种近似平衡的二叉搜索树,插入和删除操作的平均时间复杂度为 O(log N),适合动态数据结构。红黑树的旋转操作较少,插入和删除的性能较好。AVL 树的平衡性更好,但旋转次数较多,性能较差。B 树和 B+ 树适合磁盘存储,不适合内存存储。
2. **实现复杂度**:红黑树的实现相对简单,旋转次数较少,适合内存存储。AVL 树的实现复杂,旋转次数较多,性能较差。B 树和 B+ 树的实现复杂度较高,适合磁盘存储,不适合内存存储。
3. **适用场景**:HashMap 的键值对数量通常较小,红黑树的平衡性和实现复杂度都比较适合 HashMap 的使用场景。AVL 树适合静态数据结构,不适合频繁插入和删除的场景。B 树和 B+ 树适合磁盘存储,不适合内存存储。

因此,Java 的 HashMap 底层使用红黑树而非 AVL 树、B 树或 B+ 树,以提高检索效率和性能。

## 讲讲`Collection``Map`的区别

`Collection``Map`是 Java 中的容器类,它们之间的区别如下:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,20 @@ MySQL 和 MongoDB 是两种不同类型的数据库,主要区别如下:
7. 数据库引擎:MySQL 支持多种存储引擎,MongoDB 只支持一种存储引擎。
8. 数据库性能:MySQL 读取速度快,写入速度慢,MongoDB 读取速度慢,写入速度快。

MySQL 和 MongoDB 的最大区别是数据库类型,MySQL 是关系型数据库,MongoDB 是非关系型数据库。
MySQL 和 MongoDB 的最大区别是数据库类型,MySQL 是关系型数据库,MongoDB 是非关系型数据库。

## 为什么 InnoDB 选择使用 B+ 树,而非 AVL 树、红黑树或 B 树等其他树?

InnoDB 是 MySQL 数据库的存储引擎之一,采用 B+ 树索引结构,而非 AVL 树、红黑树或 B 树等其他树。B+ 树是一种多路平衡搜索树,每个节点可以有多个子节点,叶子节点之间有指针相连,适合磁盘存储,具有以下优点:

1. **减少磁盘 I/O**:B+ 树的非叶子节点只存储键值信息,而所有的实际数据都存储在叶子节点中。这使得每个节点可以包含更多的键值,从而减少树的高度,减少磁盘 I/O 次数。
2. **适合范围查询**:B+ 树的叶子节点通过指针相连,形成一个有序链表。这使得范围查询非常高效,可以通过顺序扫描叶子节点快速获取结果。
3. **适合磁盘存储**:B+ 树的节点是顺序存储的,适合磁盘存储。顺序存储可以利用磁盘的预读特性,提高检索效率。

相比之下,其他树结构在数据库索引中的应用有以下缺点:

1. **AVL 树**:AVL 树是一种高度平衡的二叉搜索树,每次插入和删除操作都需要进行旋转以保持平衡。这些旋转操作在磁盘存储中会导致大量的随机 I/O,性能较差。
2. **红黑树**:红黑树是一种近似平衡的二叉搜索树,插入和删除操作的旋转次数较少,但仍然存在随机 I/O 的问题,不适合磁盘存储。
3. **B 树**:B 树和 B+ 树类似,但 B 树的非叶子节点也存储数据,这使得每个节点能存储的键值数量减少,树的高度增加,导致更多的磁盘 I/O。但并不证明 B 树不适合磁盘存储,只是相对 B+ 树而言,B+ 树更适合磁盘存储,MongoDB 就是使用 B 树作为索引结构。

因此,InnoDB 选择使用 B+ 树作为索引结构,以提高数据库的检索效率和性能。
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**本模块是公共类课程,适合绝大部分计算机岗位。由于 Java 受众最广且易读性强,本模块示例代码使用 Java 编写,其他语言也可参考学习。**
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
**本模块是公共类课程,适合绝大部分计算机岗位。由于 Java 受众最广且易读性强,本模块示例代码使用 Java 编写,其他语言也可参考学习。**

## 什么是二叉树?二叉树有哪些常见遍历方法?

二叉树是一种树形结构,每个节点最多有两个子节点,分别是左子节点和右子节点。

二叉树有如下几个特点:

1. 第 i 层最多有 2^(i-1) 个节点。
2. 深度为 k 的二叉树最多有 2^k-1 个节点。
3. 叶子节点数等于度为 2 的节点数加 1。(度的定义:一个节点的子节点个数称为度)

上面的定义 3 还可以推导出,在完全二叉树中,最大非叶子节点索引为 n/2 - 1,其中 n 为节点总数。

二叉树的常见遍历方法主要有以下几种:

1. 前序遍历(Preorder Traversal):根节点 -> 左子树 -> 右子树。

```java
// 递归实现
public void preorderTraversal(TreeNode root) {
if (root == null) {
return;
}
System.out.println(root.val);
preorderTraversal(root.left);
preorderTraversal(root.right);
}

// 迭代实现
public void preorderTraversal(TreeNode root) {
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
System.out.println(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
```

2. 中序遍历(Inorder Traversal):左子树 -> 根节点 -> 右子树。

```java
// 递归实现
public void inorderTraversal(TreeNode root) {
if (root == null) {
return;
}
inorderTraversal(root.left);
System.out.println(root.val);
inorderTraversal(root.right);
}

// 迭代实现
public void inorderTraversal(TreeNode root) {
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
while (node != null || !stack.isEmpty()) {
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
System.out.println(node.val);
node = node.right;
}
}
```

3. 后序遍历(Postorder Traversal):左子树 -> 右子树 -> 根节点。

```java
// 递归实现
public void postorderTraversal(TreeNode root) {
if (root == null) {
return;
}
postorderTraversal(root.left);
postorderTraversal(root.right);
System.out.println(root.val);
}

// 迭代实现
public void postorderTraversal(TreeNode root) {
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
Stack<Integer> output = new Stack<>();
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
output.push(node.val);
if (node.left != null) {
stack.push(node.left);
}
if (node.right != null) {
stack.push(node.right);
}
}
while (!output.isEmpty()) {
System.out.println(output.pop());
}
}
```

4. 层序遍历(Levelorder Traversal):逐层遍历,从上到下,从左到右。

```java
public void levelorderTraversal(TreeNode root) {
if (root == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
System.out.println(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
```

其中,前序遍历、中序遍历、后序遍历是深度优先遍历 DFS,层序遍历是广度优先遍历 BFS。

## 有哪些常见的特殊二叉树?

常见的特殊二叉树主要有以下几种:

1. 满二叉树:每个节点都有两个子节点,除了叶子节点,其他节点都有两个子节点。
2. 完全二叉树:除了最后一层,其他层都是满的,最后一层的节点都靠左排列。
3. 平衡二叉树:左子树和右子树的高度差不超过 1,可以保持树的平衡,提高检索效率。
4. 二叉搜索树:左子树的所有节点都小于根节点,右子树的所有节点都大于根节点,可以提高检索效率。
5. 红黑树:一种近似平衡的二叉搜索树,每个节点是红色或黑色,可以提高检索效率。
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ef5c005

Please sign in to comment.