C++面试:二叉树和红黑树

目录

二叉树

1. 二叉树的定义

2. 二叉树的遍历

3. 二叉树的应用

4. 实现细节

5. C++中的实现

面试准备

红黑树

红黑树的原理

红黑树的用途

示例代码

面试准备

1. 红黑树的工作原理及其规则

2. 红黑树的优势及与其他二叉搜索树(如AVL树)的比较

3. 红黑树操作的时间复杂度

4. 红黑树的基本操作编写代码

代码

红黑树节点定义和基本结构

辅助函数实现

插入操作和违规修正

遍历函数

测试用例


二叉树

1. 二叉树的定义

  • 基本概念:二叉树是一种树形结构,其中每个节点最多有两个子节点,分别称为“左子节点”和“右子节点”。
  • 特殊类型
    • 完全二叉树:除了最后一层外,每一层都被完全填满,且所有节点都尽可能地向左对齐。
    • 满二叉树:所有非叶子节点都有两个子节点,所有叶子节点都在同一层级。
    • 平衡二叉树(AVL树):任何节点的两个子树的高度差不超过1。

2. 二叉树的遍历

  • 前序遍历(根-左-右):先访问根节点,然后递归地对左子树进行前序遍历,最后对右子树进行前序遍历。
  • 中序遍历(左-根-右):先递归地对左子树进行中序遍历,然后访问根节点,最后对右子树进行中序遍历。
  • 后序遍历(左-右-根):先递归地对左子树进行后序遍历,然后对右子树进行后序遍历,最后访问根节点。
  • 层次遍历(广度优先遍历):按照树的层次从上到下,从左到右遍历所有节点。

3. 二叉树的应用

  • 二叉搜索树(BST):一种特殊的二叉树,其中每个节点的值都大于其左子树上任何节点的值,且小于其右子树上任何节点的值。
  • :特殊的完全二叉树,用于实现优先队列。
  • 哈夫曼编码树:用于数据压缩的二叉树。

4. 实现细节

  • 节点结构:通常包含数据部分和指向左、右子节点的指针。
  • 操作函数:包括创建、遍历(递归或迭代)、插入、删除、查找等。

5. C++中的实现

        在C++中,二叉树可以通过结构体或类来实现。例如:

 

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

面试准备

  • 理解不同遍历方法的应用场景
  • 熟练实现二叉树的基本操作,如插入、删除、查找等。
  • 解决实际问题:例如,如何在二叉搜索树中找到第K小的元素,如何判断一棵树是否为平衡二叉树等。
  • 掌握递归和非递归遍历方法,以及它们的时间和空间复杂度。

红黑树

红黑树的原理

        红黑树的关键特性是保持树的平衡,这通过以下规则实现:

  1. 节点颜色:每个节点被涂成红色或黑色。
  2. 根节点规则:根节点总是黑色的。
  3. 红色节点规则:红色节点的子节点必须是黑色的(即不允许有连续的红色节点)。
  4. 黑色高度规则:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
  5. 新插入节点规则:新插入的节点是红色的。

        通过这些规则,红黑树保持大致的平衡,从而在插入、删除和查找操作中提供接近O(log n)的最坏情况时间复杂度。

红黑树的用途

红黑树在许多高级数据结构中都有应用,如:

  • 关联数组:在诸如C++ STL中的mapset中被用作底层结构。

        在C++的标准模板库(STL)中,mapset通常是基于红黑树实现的。这些数据结构提供了高效的查找、插入和删除操作。

        map:

#include 
#include 
using namespace std;

int main() {
    map exampleMap;

    // 插入键值对
    exampleMap[1] = "Tencent";
    exampleMap[2] = "Alibaba";
    exampleMap[3] = "ByteDance";

    // 遍历map
    for (auto &pair : exampleMap) {
        cout << pair.first << " => " << pair.second << endl;
    }

    return 0;
}

        set:

#include 
#include 
using namespace std;

int main() {
    set exampleSet;

    // 插入元素
    exampleSet.insert(10);
    exampleSet.insert(30);
    exampleSet.insert(20);
    exampleSet.insert(5);

    // 遍历set
    for (int element : exampleSet) {
        cout << element << endl;
    }

    return 0;
}
  • 优先队列:可以构建以支持快速的元素插入和删除。

        虽然C++ STL中的priority_queue通常是基于二叉堆实现的,红黑树也可以用来实现一个功能类似的结构,支持快速元素插入和删除。

        注意:这里提供的是一个简化的示例,仅用于演示概念。

#include 
#include 
using namespace std;

class PriorityQueue {
    set pq;

public:
    void insert(int value) {
        pq.insert(value);
    }

    int top() {
        if (!pq.empty())
            return *pq.begin();
        return -1; // 或抛出异常
    }

    void pop() {
        if (!pq.empty())
            pq.erase(pq.begin());
    }

    bool isEmpty() {
        return pq.empty();
    }
};

int main() {
    PriorityQueue pq;

    pq.insert(10);
    pq.insert(5);
    pq.insert(20);

    while (!pq.isEmpty()) {
        cout << pq.top() << endl;
        pq.pop();
    }

    return 0;
}
  • CPU调度系统:用于维护任务的优先级队列。

        在CPU调度系统中,红黑树可以用来维护一个任务的优先级队列。以下示例展示了一个简化的任务调度系统,其中任务按优先级排序。

#include 
#include 
using namespace std;

struct Task {
    int priority;
    string taskName;

    bool operator<(const Task& t) const {
        return priority < t.priority;
    }
};

int main() {
    set schedule;

    // 添加任务
    schedule.insert({1, "Task A"});
    schedule.insert({3, "Task C"});
    schedule.insert({2, "Task B"});

    // 按优先级执行任务
    for (const auto& task : schedule) {
        cout << "执行任务: " << task.taskName << " (优先级: " << task.priority << ")" << endl;
    }

    return 0;
}

示例代码

        以下是C++中红黑树节点的基本结构示例:

enum Color { RED, BLACK };

struct Node {
    int data;
    bool color;
    Node *left, *right, *parent;

    // Constructor
    Node(int data) : data(data) {
        parent = left = right = nullptr;
        color = RED;
    }
};

class RedBlackTree {
private:
    Node *root;

protected:
    void rotateLeft(Node *&, Node *&);
    void rotateRight(Node *&, Node *&);
    void fixViolation(Node *&, Node *&);

public:
    RedBlackTree() { root = nullptr; }
    void insert(const int &n);
    // ... 其他函数,例如遍历和删除
};

        在这个结构中,每个节点都有一个数据值、一个颜色标记(红色或黑色)、指向左子节点、右子节点和父节点的指针。红黑树类包含用于维护树平衡的旋转和修复函数。

        这只是一个非常简化的示例,红黑树的完整实现需要包括详细的插入、删除和平衡调整逻辑。

面试准备

在面试中,你可能需要:

  • 解释红黑树的工作原理及其各个规则。
  • 讨论红黑树的优势及其与其他二叉搜索树(如AVL树)的区别。
  • 分析红黑树操作的时间复杂度。
  • 可能需要对红黑树的基本操作编写代码(特别是插入和删除)。

1. 红黑树的工作原理及其规则

红黑树是一种自平衡二叉搜索树,它通过以下规则确保高效的操作时间复杂度(主要是插入和删除):

  • 节点颜色:每个节点要么是红色,要么是黑色。
  • 根节点:根节点总是黑色的。
  • 红色节点规则:红色节点的两个子节点都是黑色的(即红色节点不相邻)。
  • 每个节点到叶子节点的路径上黑色节点的数量相同:从任一节点到其每个叶子节点的所有路径上的黑色节点数目相同。
  • 新插入节点为红色:新插入的节点是红色的(除非它是根节点)。

2. 红黑树的优势及与其他二叉搜索树(如AVL树)的比较

红黑树的主要优势在于它提供了一种平衡插入、删除和查找操作的高效方式。与AVL树相比,红黑树在插入和删除操作中更加高效,因为它们需要更少的重新平衡操作。

  • 插入和删除:红黑树在插入和删除操作时通常只需要O(log n)时间,并且不需要像AVL树那样频繁的旋转来维持平衡。
  • 查找操作:AVL树由于是更加严格平衡的,对于查找密集型应用可能稍微优于红黑树。

3. 红黑树操作的时间复杂度

红黑树的所有基本操作(插入、删除、查找)的时间复杂度都是O(log n)。由于树是大致平衡的,所以这些操作的最坏情况性能也是很好的。

  • 查找:由于红黑树是二叉搜索树,查找操作的时间复杂度为O(log n)。
  • 插入和删除:尽管插入和删除可能需要额外的颜色更改和旋转来保持红黑树的性质,但这些操作的时间复杂度依然是O(log n)。

4. 红黑树的基本操作编写代码

编写红黑树的代码需要注意其性质的维持。插入和删除操作特别复杂,因为它们可能会破坏红黑树的性质,所以需要通过旋转和重新着色来修复。由于代码实现较长,我之前的回答已经提供了插入操作的一个示例。删除操作的代码更加复杂,但基本思路是在删除节点后通过一系列的旋转和颜色更改来保持树的平衡和性质。

代码

红黑树节点定义和基本结构

        首先,定义红黑树的节点结构和基本的红黑树类:       

 

#include 
using namespace std;

enum Color { RED, BLACK };

struct Node {
    int data;
    bool color;
    Node *left, *right, *parent;

    Node(int data) : data(data) {
        parent = left = right = nullptr;
        color = RED;
    }
};

class RedBlackTree {
private:
    Node *root;

protected:
    void rotateLeft(Node *&root, Node *&pt);
    void rotateRight(Node *&root, Node *&pt);
    void fixViolation(Node *&root, Node *&pt);

public:
    RedBlackTree() { root = nullptr; }
    void insert(const int &n);
    void inorder();
    void levelOrder();
};

辅助函数实现

        接下来,实现旋转和修正违规的辅助函数:

void RedBlackTree::rotateLeft(Node *&root, Node *&pt) {
    Node *pt_right = pt->right;
    pt->right = pt_right->left;
    if (pt->right != nullptr)
        pt->right->parent = pt;
    pt_right->parent = pt->parent;
    if (pt->parent == nullptr)
        root = pt_right;
    else if (pt == pt->parent->left)
        pt->parent->left = pt_right;
    else
        pt->parent->right = pt_right;
    pt_right->left = pt;
    pt->parent = pt_right;
}

void RedBlackTree::rotateRight(Node *&root, Node *&pt) {
    Node *pt_left = pt->left;
    pt->left = pt_left->right;
    if (pt->left != nullptr)
        pt->left->parent = pt;
    pt_left->parent = pt->parent;
    if (pt->parent == nullptr)
        root = pt_left;
    else if (pt == pt->parent->right)
        pt->parent->right = pt_left;
    else
        pt->parent->left = pt_left;
    pt_left->right = pt;
    pt->parent = pt_left;
}

插入操作和违规修正

        插入操作和违规修正是红黑树最核心的部分:

void RedBlackTree::fixViolation(Node *&root, Node *&pt) {
    Node *parent_pt = nullptr;
    Node *grand_parent_pt = nullptr;

    while ((pt != root) && (pt->color != BLACK) && (pt->parent->color == RED)) {
        parent_pt = pt->parent;
        grand_parent_pt = pt->parent->parent;

        /* Case : A
           Parent of pt is left child of Grand-parent of pt */
        if (parent_pt == grand_parent_pt->left) {
            Node *uncle_pt = grand_parent_pt->right;

            /* Case : 1
               The uncle of pt is also red
               Only Recoloring required */
            if (uncle_pt != nullptr && uncle_pt->color == RED) {
                grand_parent_pt->color = RED;
                parent_pt->color = BLACK;
                uncle_pt->color = BLACK;
                pt = grand_parent_pt;
            } else {
                /* Case : 2
                   pt is right child of its parent
                   Left-rotation required */
                if (pt == parent_pt->right) {
                    rotateLeft(root, parent_pt);
                    pt = parent_pt;
                    parent_pt = pt->parent;
                }

                /* Case : 3
                   pt is left child of its parent
                   Right-rotation required */
                rotateRight(root, grand_parent_pt);
                swap(parent_pt->color, grand_parent_pt->color);
                pt = parent_pt;
            }
        }

        /* Case : B
           Parent of pt is right child of Grand-parent of pt */
        else {
            Node *uncle_pt = grand_parent_pt->left;

            /* Case : 1
               The uncle of pt is also red
               Only Recoloring required */
            if ((uncle_pt != nullptr) && (uncle_pt->color == RED)) {
                grand_parent_pt->color = RED;
                parent_pt->color = BLACK;
                uncle_pt->color = BLACK;
                pt = grand_parent_pt;
            } else {
                /* Case : 2
                   pt is left child of its parent
                   Right-rotation required */
                if (pt == parent_pt->left) {
                    rotateRight(root, parent_pt);
                    pt = parent_pt;
                    parent_pt = pt->parent;
                }

                /* Case : 3
                   pt is right child of its parent
                   Left-rotation required */
                rotateLeft(root, grand_parent_pt);
                swap(parent_pt->color, grand_parent_pt->color);
                pt = parent_pt;
            }
        }
    }
    root->color = BLACK;
}

void RedBlackTree::insert(const int &data) {
    Node *pt = new Node(data);

    // Do a normal BST insert
    root = BSTInsert(root, pt);

    // fix Red Black Tree violations
    fixViolation(root, pt);
}

Node* BSTInsert(Node* root, Node* pt) {
    /* If the tree is empty, return a new node */
    if (root == nullptr)
        return pt;

    /* Otherwise, recur down the tree */
    if (pt->data < root->data) {
        root->left = BSTInsert(root->left, pt);
        root->left->parent = root;
    } else if (pt->data > root->data) {
        root->right = BSTInsert(root->right, pt);
        root->right->parent = root;
    }

    /* return the (unchanged) node pointer */
    return root;
}

遍历函数

        遍历函数用于验证树的结构:

void inorderHelper(Node *root) {
    if (root == nullptr)
        return;
    inorderHelper(root->left);
    cout << root->data << " ";
    inorderHelper(root->right);
}

void RedBlackTree::inorder() { inorderHelper(root); }

void levelOrderHelper(Node *root) {
    if (root == nullptr)
        return;
    std::queue q;
    q.push(root);
    while (!q.empty()) {
        Node *temp = q.front();
        cout << temp->data << " ";
        q.pop();
        if (temp->left != nullptr)
            q.push(temp->left);
        if (temp->right != nullptr)
            q.push(temp->right);
    }
}

void RedBlackTree::levelOrder() { levelOrderHelper(root); }

测试用例

        最后,定义一些测试用例以验证红黑树的功能:

int main() {
    RedBlackTree tree;

    tree.insert(7);
    tree.insert(6);
    tree.insert(5);
    tree.insert(4);
    tree.insert(3);
    tree.insert(2);
    tree.insert(1);

    cout << "Inorder Traversal of Created Tree\n";
    tree.inorder();
    cout << "\n\nLevel Order Traversal of Created Tree\n";
    tree.levelOrder();

    return 0;
}

你可能感兴趣的:(c++,面试,数据结构)