目录
在 Java 编程中,数据结构的选择对于程序的性能和效率至关重要。本文将详细介绍哈夫曼树、红黑树、B + 树和跳表这四种常见的数据结构,并提供 Java 代码实现及解析。
一、哈夫曼树
(一)概念
哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。它通过构建二叉树的方式,将权值较大的节点靠近根节点,权值较小的节点远离根节点,从而使得整棵树的带权路径长度最小。
(二)应用场景
哈夫曼树主要用于数据压缩,通过将出现频率较高的字符用较短的编码表示,出现频率较低的字符用较长的编码表示,可以有效地减少数据的存储空间。
(三)Java 代码实现及解析
import java.util.PriorityQueue;
class HuffmanNode implements Comparable<HuffmanNode> {
int frequency;
char data;
HuffmanNode left;
HuffmanNode right;
HuffmanNode(int frequency, char data) {
this.frequency = frequency;
this.data = data;
}
@Override
public int compareTo(HuffmanNode other) {
return this.frequency - other.frequency;
}
}
public class HuffmanTree {
public static HuffmanNode buildHuffmanTree(char[] data, int[] frequencies) {
PriorityQueue<HuffmanNode> pq = new PriorityQueue<>();
for (int i = 0; i < data.length; i++) {
pq.add(new HuffmanNode(frequencies[i], data[i]));
}
while (pq.size() > 1) {
HuffmanNode left = pq.poll();
HuffmanNode right = pq.poll();
HuffmanNode parent = new HuffmanNode(left.frequency + right.frequency, '\0');
parent.left = left;
parent.right = right;
pq.add(parent);
}
return pq.poll();
}
}
解析:
- 首先定义了一个
HuffmanNode
类,用于表示哈夫曼树的节点,包含频率、数据以及左右子节点。节点实现了Comparable
接口,以便在优先级队列中进行比较。 buildHuffmanTree
方法接受字符数组和对应的频率数组作为参数。通过优先级队列(最小堆)来存储节点,每次取出频率最小的两个节点,合并成一个新的节点,将新节点的频率设置为两个子节点频率之和,并将新节点加入优先级队列。重复这个过程,直到队列中只剩下一个节点,即为哈夫曼树的根节点。
二、红黑树
(一)概念
红黑树是一种自平衡的二叉搜索树,它在插入和删除节点时通过旋转和重新着色操作来保持树的平衡,确保最长路径不超过最短路径的两倍。红黑树的节点颜色为红色或黑色,通过颜色规则来维持平衡。
(二)应用场景
红黑树广泛应用于各种需要高效查找、插入和删除操作的场景,如 Java 中的TreeMap
和TreeSet
内部就是使用红黑树实现的。
(三)Java 代码实现及解析(简化版,仅展示关键部分)
class RBTreeNode<T extends Comparable<T>> {
boolean isRed;
T data;
RBTreeNode<T> left;
RBTreeNode<T> right;
RBTreeNode<T> parent;
RBTreeNode(T data) {
this.data = data;
isRed = true;
}
}
class RedBlackTree<T extends Comparable<T>> {
private RBTreeNode<T> root;
public void insert(T data) {
RBTreeNode<T> node = new RBTreeNode<>(data);
if (root == null) {
root = node;
node.isRed = false;
return;
}
insertNode(root, node);
fixInsert(node);
}
private void insertNode(RBTreeNode<T> parent, RBTreeNode<T> node) {
if (node.data.compareTo(parent.data) < 0) {
if (parent.left == null) {
parent.left = node;
node.parent = parent;
} else {
insertNode(parent.left, node);
}
} else {
if (parent.right == null) {
parent.right = node;
node.parent = parent;
} else {
insertNode(parent.right, node);
}
}
}
private void fixInsert(RBTreeNode<T> node) {
while (node!= root && node.parent.isRed) {
if (node.parent == node.parent.parent.left) {
RBTreeNode<T> uncle = node.parent.parent.right;
if (uncle!= null && uncle.isRed) {
node.parent.isRed = false;
uncle.isRed = false;
node.parent.parent.isRed = true;
node = node.parent.parent;
} else {
if (node == node.parent.right) {
node = node.parent;
rotateLeft(node);
}
node.parent.isRed = false;
node.parent.parent.isRed = true;
rotateRight(node.parent.parent);
}
} else {
RBTreeNode<T> uncle = node.parent.parent.left;
if (uncle!= null && uncle.isRed) {
node.parent.isRed = false;
uncle.isRed = false;
node.parent.parent.isRed = true;
node = node.parent.parent;
} else {
if (node == node.parent.left) {
node = node.parent;
rotateRight(node);
}
node.parent.isRed = false;
node.parent.parent.isRed = true;
rotateLeft(node.parent.parent);
}
}
}
root.isRed = false;
}
private void rotateLeft(RBTreeNode<T> node) {
RBTreeNode<T> rightChild = node.right;
node.right = rightChild.left;
if (rightChild.left!= null) {
rightChild.left.parent = node;
}
rightChild.parent = node.parent;
if (node.parent == null) {
root = rightChild;
} else if (node == node.parent.left) {
node.parent.left = rightChild;
} else {
node.parent.right = rightChild;
}
rightChild.left = node;
node.parent = rightChild;
}
private void rotateRight(RBTreeNode<T> node) {
RBTreeNode<T> leftChild = node.left;
node.left = leftChild.right;
if (leftChild.right!= null) {
leftChild.right.parent = node;
}
leftChild.parent = node.parent;
if (node.parent == null) {
root = leftChild;
} else if (node == node.parent.left) {
node.parent.left = leftChild;
} else {
node.parent.right = leftChild;
}
leftChild.right = node;
node.parent = leftChild;
}
}
解析:
RBTreeNode
类表示红黑树的节点,包含数据、颜色、左右子节点和父节点。RedBlackTree
类包含红黑树的根节点。insert
方法用于插入节点,首先创建一个新节点,然后根据数据大小找到合适的位置插入。插入后,调用fixInsert
方法来调整红黑树的结构,以保持平衡。fixInsert
方法通过一系列的判断和旋转操作来调整树的结构。如果插入节点的父节点是红色,根据父节点的位置和叔父节点的颜色进行不同的处理。如果叔父节点是红色,则进行重新着色操作;如果叔父节点是黑色,则进行旋转操作。rotateLeft
和rotateRight
方法分别用于左旋和右旋操作,通过调整节点的位置来保持树的平衡。
三、B + 树
(一)概念
B + 树是一种平衡的多路查找树,它的特点是所有的数据都存储在叶子节点中,非叶子节点只存储关键字和指向子节点的指针。B + 树的叶子节点之间通过链表连接,便于进行范围查询。
(二)应用场景
B + 树主要用于数据库索引,能够高效地支持范围查询和顺序遍历。
(三)Java 代码实现及解析(简化版,仅展示关键部分)
class BPlusTreeNode<K extends Comparable<K>, V> {
boolean isLeaf;
int numKeys;
K[] keys;
V[] values;
BPlusTreeNode<K, V>[] children;
BPlusTreeNode<K, V> next;
BPlusTreeNode(int degree) {
isLeaf = true;
numKeys = 0;
keys = (K[]) new Comparable[2 * degree - 1];
values = (V[]) new Object[2 * degree - 1];
children = (BPlusTreeNode<K, V>[]) new BPlusTreeNode[2 * degree];
}
}
class BPlusTree<K extends Comparable<K>, V> {
private int degree;
private BPlusTreeNode<K, V> root;
public BPlusTree(int degree) {
this.degree = degree;
root = new BPlusTreeNode<>(degree);
}
public void insert(K key, V value) {
if (root.numKeys == 2 * degree - 1) {
BPlusTreeNode<K, V> newRoot = new BPlusTreeNode<>(degree);
newRoot.children[0] = root;
splitChild(newRoot, 0);
root = newRoot;
}
insertNonFull(root, key, value);
}
private void insertNonFull(BPlusTreeNode<K, V> node, K key, V value) {
int i = node.numKeys - 1;
if (node.isLeaf) {
while (i >= 0 && key.compareTo(node.keys[i]) < 0) {
node.keys[i + 1] = node.keys[i];
node.values[i + 1] = node.values[i];
i--;
}
node.keys[i + 1] = key;
node.values[i + 1] = value;
node.numKeys++;
} else {
while (i >= 0 && key.compareTo(node.keys[i]) < 0) {
i--;
}
i++;
if (node.children[i].numKeys == 2 * degree - 1) {
splitChild(node, i);
if (key.compareTo(node.keys[i]) > 0) {
i++;
}
}
insertNonFull(node.children[i], key, value);
}
}
private void splitChild(BPlusTreeNode<K, V> parent, int index) {
BPlusTreeNode<K, V> child = parent.children[index];
BPlusTreeNode<K, V> newChild = new BPlusTreeNode<>(degree);
newChild.isLeaf = child.isLeaf;
for (int j = 0; j < degree - 1; j++) {
newChild.keys[j] = child.keys[j + degree];
newChild.values[j] = child.values[j + degree];
}
if (!child.isLeaf) {
for (int j = 0; j < degree; j++) {
newChild.children[j] = child.children[j + degree];
}
}
child.numKeys = degree - 1;
for (int j = parent.numKeys; j > index; j--) {
parent.keys[j] = parent.keys[j - 1];
parent.values[j] = parent.values[j - 1];
parent.children[j + 1] = parent.children[j];
}
parent.keys[index] = child.keys[degree - 1];
parent.values[index] = child.values[degree - 1];
parent.children[index + 1] = newChild;
parent.numKeys++;
}
}
解析:
BPlusTreeNode
类表示 B + 树的节点,包含是否为叶子节点标志、关键字数量、关键字数组、值数组、子节点数组和指向下一个叶子节点的指针。BPlusTree
类包含 B + 树的度和根节点。insert
方法用于插入关键字和值,首先判断根节点是否已满,如果已满则创建新的根节点并进行分裂操作。然后调用insertNonFull
方法,在非满节点中插入关键字和值。如果插入的节点是叶子节点,则直接插入;如果不是叶子节点,则找到合适的子节点继续插入。insertNonFull
方法根据节点是否为叶子节点进行不同的处理。如果是叶子节点,则在合适的位置插入关键字和值;如果不是叶子节点,则找到合适的子节点继续插入。如果子节点已满,则进行分裂操作,然后继续插入。splitChild
方法用于分裂满节点,将满节点分成两个节点,并将中间关键字上移到父节点。
四、跳表
(一)概念
跳表是一种可以替代平衡树的数据结构,它通过在链表的基础上增加多层索引来提高查找效率。跳表的查找、插入和删除操作的时间复杂度均为 O (log n)。
(二)应用场景
跳表适用于需要高效查找、插入和删除操作的场景,如数据库索引、有序集合等。
(三)Java 代码实现及解析
import java.util.Random;
class SkipListNode<K extends Comparable<K>, V> {
K key;
V value;
SkipListNode<K, V>[] next;
SkipListNode(K key, V value, int level) {
this.key = key;
this.value = value;
next = new SkipListNode[level + 1];
}
}
class SkipList<K extends Comparable<K>, V> {
private static final int MAX_LEVEL = 16;
private int level;
private SkipListNode<K, V> head;
public SkipList() {
level = 0;
head = new SkipListNode<>(null, null, MAX_LEVEL);
}
public void insert(K key, V value) {
int[] update = new int[MAX_LEVEL];
SkipListNode<K, V> current = head;
for (int i = level; i >= 0; i--) {
while (current.next[i]!= null && current.next[i].key.compareTo(key) < 0) {
current = current.next[i];
}
update[i] = current;
}
current = current.next[0];
if (current == null || current.key.compareTo(key)!= 0) {
int newLevel = randomLevel();
if (newLevel > level) {
for (int i = level + 1; i < newLevel + 1; i++) {
update[i] = head;
}
level = newLevel;
}
SkipListNode<K, V> newNode = new SkipListNode<>(key, value, newLevel);
for (int i = 0; i <= newLevel; i++) {
newNode.next[i] = update[i].next[i];
update[i].next[i] = newNode;
}
}
}
private int randomLevel() {
int level = 0;
Random random = new Random();
while (random.nextInt(2) == 1 && level < MAX_LEVEL - 1) {
level++;
}
return level;
}
}
解析:
SkipListNode
类表示跳表的节点,包含关键字、值和指向下一个节点的数组(每个元素对应一层索引)。SkipList
类包含跳表的当前最高层、头节点。insert
方法用于插入关键字和值,首先从最高层开始遍历,找到每个层中小于插入关键字的最后一个节点,并记录在update
数组中。然后判断插入的关键字是否已经存在,如果不存在,则生成一个随机的层数。如果新生成的层数大于当前最高层,则需要更新头节点的索引。最后创建新节点,并将其插入到合适的位置。randomLevel
方法用于生成随机的层数,通过不断随机生成 0 或 1,直到生成 0 或者达到最大层数为止。
通过对哈夫曼树、红黑树、B + 树和跳表这四种数据结构的介绍和代码实现,我们可以更好地理解它们的特点和应用场景。在实际编程中,根据具体需求选择合适的数据结构可以提高程序的性能和效率。