Java 中常见数据结构之哈夫曼树、红黑树、B + 树和跳表详解及手撕代码实现

目录

一、哈夫曼树

(一)概念

(二)应用场景

(三)Java 代码实现及解析

二、红黑树

(一)概念

(二)应用场景

(三)Java 代码实现及解析(简化版,仅展示关键部分)

三、B + 树

(一)概念

(二)应用场景

(三)Java 代码实现及解析(简化版,仅展示关键部分)

四、跳表

(一)概念

(二)应用场景

(三)Java 代码实现及解析


在 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 中的TreeMapTreeSet内部就是使用红黑树实现的。

(三)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方法通过一系列的判断和旋转操作来调整树的结构。如果插入节点的父节点是红色,根据父节点的位置和叔父节点的颜色进行不同的处理。如果叔父节点是红色,则进行重新着色操作;如果叔父节点是黑色,则进行旋转操作。
  • rotateLeftrotateRight方法分别用于左旋和右旋操作,通过调整节点的位置来保持树的平衡。

三、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 + 树和跳表这四种数据结构的介绍和代码实现,我们可以更好地理解它们的特点和应用场景。在实际编程中,根据具体需求选择合适的数据结构可以提高程序的性能和效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值