Java面试黄金宝典27
1. 红黑树的旋转,2 - node 节点插入和 3 - node 节点插入时旋转的情况
- 定义
红黑树是一种自平衡的二叉搜索树,通过对节点进行染色(红色或黑色)以及旋转操作来维持树的平衡,确保查找、插入和删除操作的时间复杂度为 O(logn)。旋转操作分为左旋和右旋,左旋是将一个节点的右子节点提升为父节点,原父节点变为其左子节点;右旋则是将一个节点的左子节点提升为父节点,原父节点变为其右子节点。
- 红黑树的旋转及插入操作
java
// 红黑树节点类
class RBNode {
int key;
RBNode left, right, parent;
boolean isRed;
public RBNode(int key) {
this.key = key;
this.left = null;
this.right = null;
this.parent = null;
this.isRed = true;
}
}
// 红黑树类
class RedBlackTree {
private RBNode root;
private final RBNode NIL;
public RedBlackTree() {
NIL = new RBNode(0);
NIL.isRed = false;
root = NIL;
}
// 左旋操作
private void leftRotate(RBNode x) {
RBNode y = x.right;
x.right = y.left;
if (y.left != NIL) {
y.left.parent = x;
}
y.parent = x.parent;
if (x.parent == NIL) {
root = y;
} else if (x == x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
// 右旋操作
private void rightRotate(RBNode y) {
RBNode x = y.left;
y.left = x.right;
if (x.right != NIL) {
x.right.parent = y;
}
x.parent = y.parent;
if (y.parent == NIL) {
root = x;
} else if (y == y.parent.right) {
y.parent.right = x;
} else {
y.parent.left = x;
}
x.right = y;
y.parent = x;
}
// 插入操作
public void insert(int key) {
RBNode z = new RBNode(key);
RBNode y = NIL;
RBNode x = root;
while (x != NIL) {
y = x;
if (z.key < x.key) {
x = x.left;
} else {
x = x.right;
}
}
z.parent = y;
if (y == NIL) {
root = z;
} else if (z.key < y.key) {
y.left = z;
} else {
y.right = z;
}
z.left = NIL;
z.right = NIL;
z.isRed = true;
insertFixup(z);
}
// 插入修复操作
private void insertFixup(RBNode z) {
while (z.parent.isRed) {
if (z.parent == z.parent.parent.left) {
RBNode y = z.parent.parent.right;
if (y.isRed) {
z.parent.isRed = false;
y.isRed = false;
z.parent.parent.isRed = true;
z = z.parent.parent;
} else {
if (z == z.parent.right) {
z = z.parent;
leftRotate(z);
}
z.parent.isRed = false;
z.parent.parent.isRed = true;
rightRotate(z.parent.parent);
}
} else {
RBNode y = z.parent.parent.left;
if (y.isRed) {
z.parent.isRed = false;
y.isRed = false;
z.parent.parent.isRed = true;
z = z.parent.parent;
} else {
if (z == z.parent.left) {
z = z.parent;
rightRotate(z);
}
z.parent.isRed = false;
z.parent.parent.isRed = true;
leftRotate(z.parent.parent);
}
}
}
root.isRed = false;
}
}
- 说明
RBNode
类表示红黑树的节点,包含键、左右子节点、父节点和颜色信息。RedBlackTree
类实现了红黑树的基本操作,leftRotate
和rightRotate
方法分别实现了左旋和右旋操作,insert
方法用于插入新节点,insertFixup
方法用于在插入新节点后修复红黑树的性质。
- 2 - node 节点插入
2 - node 指只有一个键的节点。插入新节点时,若未破坏红黑树性质(如黑色高度平衡、红节点不相邻等),通常无需旋转;若导致红节点相邻等情况,就可能要进行旋转和颜色调整。
- 3 - node 节点插入
3 - node 是有两个键的节点。插入新节点后会形成临时的 4 - node(有三个键),为保持红黑树性质,需进行分裂和旋转操作,一般会把中间的键提升到父节点,同时调整左右子树。
- 要点
- 旋转操作是维持红黑树平衡的关键手段。
- 插入操作可能破坏红黑树性质,需通过旋转和颜色调整修复。
- 应用
红黑树的旋转和插入操作是许多高级数据结构和算法的基础,在 Java 的 TreeMap
和 TreeSet
中就使用红黑树实现,能有效优化数据存储和查找效率。
2. 伪代码
- 定义
伪代码是用自然语言和简单编程结构(如循环、条件语句等)描述算法的工具,不依赖具体编程语言,主要用于算法设计和交流,能清晰表达算法逻辑和步骤。
- 要点
- 具有简洁性和可读性,不关注具体语法细节。
- 用自然语言和简单编程结构描述算法核心逻辑。
- 应用
伪代码在算法设计、学术研究和项目开发中广泛应用。在编写大型程序前,先使用伪代码设计算法可提高开发效率,避免过早陷入具体编程细节。
3. 字典树
- 定义
字典树(Trie 树),也叫前缀树,是一种树形数据结构,用于高效存储和检索字符串集合。其每个节点代表一个字符,从根节点到某个节点路径上经过的字符连接起来就是一个字符串。
java
// 字典树节点类
class TrieNode {
TrieNode[] children;
boolean isEndOfWord;
public TrieNode() {
children = new TrieNode[26];
isEndOfWord = false;
}
}
// 字典树类
class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
// 插入单词
public void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
node.children[index] = new TrieNode();
}
node = node.children[index];
}
node.isEndOfWord = true;
}
// 搜索单词
public boolean search(String word) {
TrieNode node = searchPrefix(word);
return node != null && node.isEndOfWord;
}
// 搜索前缀
public boolean startsWith(String prefix) {
return searchPrefix(prefix) != null;
}
private TrieNode searchPrefix(String prefix) {
TrieNode node = root;
for (char c : prefix.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
return null;
}
node = node.children[index];
}
return node;
}
}
- 说明
TrieNode
类表示字典树的节点,包含一个长度为 26 的数组children
用于存储子节点,以及一个布尔值isEndOfWord
用于标记是否为单词的结尾。Trie
类实现了字典树的基本操作,insert
方法用于插入单词,search
方法用于搜索单词,startsWith
方法用于搜索前缀。
- 要点
- 根节点不包含字符,除根节点外每个节点包含一个字符。
- 从根节点到某一节点路径上的字符连接起来是该节点对应的字符串。
- 每个节点可能有多个不同字符的子节点。
- 应用
字典树在搜索引擎、自动补全、拼写检查等领域应用广泛,能快速查找某个字符串是否存在于集合中,以及查找以某个前缀开头的所有字符串。
4. 链表使用的循环链表还是双向链表
- 定义
链表是常见的数据结构,包括单向链表、双向链表和循环链表。单向链表每个节点含一个数据元素和一个指向下一节点的指针;双向链表每个节点含一个数据元素、一个指向前一节点的指针和一个指向后一节点的指针;循环链表的最后一个节点指向第一个节点,形成环。
- 双向链表
java
// 双向链表节点类
class DoublyNode {
int data;
DoublyNode prev;
DoublyNode next;
public DoublyNode(int data) {
this.data = data;
this.prev = null;
this.next = null;
}
}
// 双向链表类
class DoublyLinkedList {
private DoublyNode head;
private DoublyNode tail;
public DoublyLinkedList() {
head = null;
tail = null;
}
// 在链表尾部插入节点
public void insert(int data) {
DoublyNode newNode = new DoublyNode(data);
if (head == null) {
head = newNode;
tail = newNode;
} else {
tail.next = newNode;
newNode.prev = tail;
tail = newNode;
}
}
// 打印链表
public void printList() {
DoublyNode current = head;
while (current != null) {
System.out.print(current.data + " ");
current = current.next;
}
System.out.println();
}
}
- 说明
DoublyNode
类表示双向链表的节点,包含数据、前驱节点和后继节点。DoublyLinkedList
类实现了双向链表的基本操作,insert
方法用于在链表尾部插入节点,printList
方法用于打印链表。
-
循环链表
java
// 循环链表节点类
class CircularNode {
int data;
CircularNode next;
public CircularNode(int data) {
this.data = data;
this.next = null;
}
}
// 循环链表类
class CircularLinkedList {
private CircularNode head;
public CircularLinkedList() {
head = null;
}
// 在链表尾部插入节点
public void insert(int data) {
CircularNode newNode = new CircularNode(data);
if (head == null) {
head = newNode;
newNode.next = head;
} else {
CircularNode current = head;
while (current.next != head) {
current = current.next;
}
current.next = newNode;
newNode.next = head;
}
}
// 打印链表
public void printList() {
if (head == null) {
return;
}
CircularNode current = head;
do {
System.out.print(current.data + " ");
current = current.next;
} while (current != head);
System.out.println();
}
}
- 说明
CircularNode
类表示循环链表的节点,包含数据和后继节点。CircularLinkedList
类实现了循环链表的基本操作,insert
方法用于在链表尾部插入节点,printList
方法用于打印链表。
- 要点
- 单向链表适用于单向遍历场景,空间开销小。
- 双向链表适用于双向遍历场景,空间开销相对大。
- 循环链表适用于循环遍历场景,如操作系统的任务调度。
- 应用
实际应用中需根据具体需求选择链表类型。例如实现 LRU 缓存时,通常用双向链表维护缓存访问顺序。
5. 树和图的区别
- 定义
- 树是无向无环图,有根节点,每个节点可有零个或多个子节点,节点间有明显层次关系,从根节点到任意节点有且仅有一条路径。
- 图由节点(顶点)和连接节点的边组成,可为有向或无向,也可包含环。
- 要点
- 树是特殊的图,具有无环和层次结构特点。
- 图结构更灵活,能表示更复杂关系。
- 应用
树和图在计算机科学中应用广泛,文件系统的目录结构可用树表示,社交网络中的用户关系可用图表示。不同应用场景需选择合适的数据结构解决问题。
6. 哈夫曼树
- 定义
哈夫曼树(Huffman Tree),又称最优二叉树,是带权路径长度(WPL)最短的二叉树。在哈夫曼树中,权值较大的节点离根节点较近,权值较小的节点离根节点较远。
java
import java.util.PriorityQueue;
import java.util.Comparator;
// 哈夫曼树节点类
class HuffmanNode {
int data;
HuffmanNode left, right;
public HuffmanNode(int data) {
this.data = data;
this.left = null;
this.right = null;
}
}
// 哈夫曼树类
class HuffmanTree {
public HuffmanNode buildTree(int[] charFreqs) {
PriorityQueue<HuffmanNode> priorityQueue = new PriorityQueue<>(charFreqs.length, Comparator.comparingInt(node -> node.data));
for (int freq : charFreqs) {
priorityQueue.add(new HuffmanNode(freq));
}
while (priorityQueue.size() > 1) {
HuffmanNode left = priorityQueue.poll();
HuffmanNode right = priorityQueue.poll();
HuffmanNode parent = new HuffmanNode(left.data + right.data);
parent.left = left;
parent.right = right;
priorityQueue.add(parent);
}
return priorityQueue.poll();
}
}
- 说明
HuffmanNode
类表示哈夫曼树的节点,包含数据和左右子节点。HuffmanTree
类实现了哈夫曼树的构建,buildTree
方法使用优先队列(最小堆)来构建哈夫曼树。
- 要点
- 哈夫曼树的构造过程是不断合并权值最小的两个节点,直至形成一棵树。
- 常用于数据压缩算法,如哈夫曼编码。
- 应用
哈夫曼编码是变长编码,通过将出现频率高的字符用短编码表示,出现频率低的字符用长编码表示,实现数据压缩,可有效减少数据存储空间和传输带宽。
7. B+ 树和 B - 树
- 定义
- B - 树是多路平衡搜索树,每个节点可有多个子节点,所有节点都存储数据,节点中的键有序。
- B+ 树是 B - 树的变体,非叶子节点只存储索引信息,不存储数据,所有数据都存储在叶子节点中,叶子节点间通过指针相连形成有序链表。
- 要点
- B - 树适用于文件系统和数据库索引,能高效进行查找、插入和删除操作。
- B+ 树更适合范围查询,因其叶子节点形成有序链表,便于顺序访问。
- 应用
B+ 树在数据库系统中广泛应用,如 MySQL 的 InnoDB 存储引擎用 B+ 树作为索引结构,理解其原理和特点有助于优化数据库查询性能。
8. 栈和队列的区别,如何实现栈
- 定义
- 栈是后进先出(LIFO)的数据结构,只能在栈顶进行插入和删除操作。
- 队列是先进先出(FIFO)的数据结构,只能在队尾进行插入操作,在队头进行删除操作。
- 队列
java
class Queue {
private int[] array;
private int front;
private int rear;
private int capacity;
private int size;
public Queue(int capacity) {
this.capacity = capacity;
this.array = new int[capacity];
this.front = 0;
this.rear = -1;
this.size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == capacity;
}
public void enqueue(int element) {
if (isFull()) {
System.out.println("Queue is full");
return;
}
rear = (rear + 1) % capacity;
array[rear] = element;
size++;
}
public int dequeue() {
if (isEmpty()) {
System.out.println("Queue is empty");
return -1;
}
int element = array[front];
front = (front + 1) % capacity;
size--;
return element;
}
public int peek() {
if (isEmpty()) {
System.out.println("Queue is empty");
return -1;
}
return array[front];
}
}
- 说明
Queue
类使用数组实现了队列的基本操作,enqueue
方法用于入队,dequeue
方法用于出队,peek
方法用于查看队头元素。
-
栈
以下是使用 Java 数组实现栈的代码:
java
class Stack {
private int[] array;
private int top;
private int capacity;
public Stack(int capacity) {
this.capacity = capacity;
this.array = new int[capacity];
this.top = -1;
}
public boolean isEmpty() {
return top == -1;
}
public boolean isFull() {
return top == capacity - 1;
}
public void push(int element) {
if (isFull()) {
System.out.println("Stack is full");
return;
}
array[++top] = element;
}
public int pop() {
if (isEmpty()) {
System.out.println("Stack is empty");
return -1;
}
return array[top--];
}
public int peek() {
if (isEmpty()) {
System.out.println("Stack is empty");
return -1;
}
return array[top];
}
}
- 要点
- 栈和队列的区别在于元素进出顺序不同。
- 栈可用数组或链表实现,要注意栈满和栈空情况。
- 应用
栈在表达式求值、递归调用、回溯算法等方面应用广泛;队列在任务调度、消息队列等方面有重要作用。
9. 有序集合底层数据结构是什么
- 定义
在 Java 中,有序集合如 TreeSet
和 TreeMap
,底层数据结构是红黑树。红黑树是自平衡的二叉搜索树,能保证元素按键的自然顺序或指定比较器顺序排序。
- 要点
- 有序集合能保证元素有序性,插入、删除和查找操作时间复杂度为 O(logn)。
- 红黑树的平衡性保证了有序集合性能稳定。
- 应用
除 Java 的 TreeSet
和 TreeMap
外,其他编程语言也有类似有序集合实现,如 Python 的 sortedcontainers
库。理解有序集合底层数据结构有助于在实际应用中选择合适的数据结构解决问题。
10. 找到二叉树第 m 层的第 n 个节点
以下是使用 Java 实现的代码:
java
import java.util.LinkedList;
import java.util.Queue;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public class Solution {
public TreeNode findNode(TreeNode root, int m, int n) {
if (root == null || m < 1 || n < 1) {
return null;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int currentLevel = 1;
while (!queue.isEmpty()) {
int size = queue.size();
if (currentLevel == m) {
if (n > size) {
return null;
}
for (int i = 1; i < n; i++) {
queue.poll();
}
return queue.poll();
}
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
currentLevel++;
}
return null;
}
}
- 定义
该问题是在给定的二叉树中,找出第 m 层的第 n 个节点。
- 要点
- 使用队列实现广度优先搜索。
- 记录当前遍历的层数,到达第 m 层时,找到第 n 个节点。
- 应用
此问题可拓展,如找到二叉树第 m 层的所有节点、找到二叉树第 m 层节点的和等。也可用深度优先搜索(DFS)解决,但实现相对复杂。
友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读
https://download.csdn.net/download/ylfhpy/90556087