44 二叉搜索树中第K个小的元素

news2024/7/6 17:33:31

二叉搜索树中第K个小的元素

    • 题解1 中序遍历
    • 题解2 AVL(手撕平衡二叉树:谢谢力扣官方)

给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。

在这里插入图片描述
提示:

  • 树中的节点数为 n 。
  • 1 <= k <= n <= 1 0 4 10^4 104
  • 0 <= Node.val <= 1 0 4 10^4 104

进阶:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?

题解1 中序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int kthSmallest(TreeNode* root, int k) {
        vector<int> vals;
        stack<TreeNode*> kk;
        while(vals.size() < k){
            while(root){
                kk.push(root);
                root = root->left;
            }
            root = kk.top();
            kk.pop();
            vals.emplace_back(root->val);
            root = root->right;
        }
        return vals.back();
    }
};

在这里插入图片描述

题解2 AVL(手撕平衡二叉树:谢谢力扣官方)

// 平衡二叉搜索树结点
struct Node {
    int val;
    Node * parent;
    Node * left;
    Node * right;
    int size;
    int height;

    Node(int val) {
        this->val = val;
        this->parent = nullptr;
        this->left = nullptr;
        this->right = nullptr;
        this->height = 0; // 结点高度:以node为根节点的子树的高度(高度定义:叶结点的高度是0)
        this->size = 1; // 结点元素数:以node为根节点的子树的节点总数
    }

    Node(int val, Node * parent) {
        this->val = val;
        this->parent = parent;
        this->left = nullptr;
        this->right = nullptr;
        this->height = 0; // 结点高度:以node为根节点的子树的高度(高度定义:叶结点的高度是0)
        this->size = 1; // 结点元素数:以node为根节点的子树的节点总数
    }

    Node(int val, Node * parent, Node * left, Node * right) {
        this->val = val;
        this->parent = parent;
        this->left = left;
        this->right = right;
        this->height = 0; // 结点高度:以node为根节点的子树的高度(高度定义:叶结点的高度是0)
        this->size = 1; // 结点元素数:以node为根节点的子树的节点总数
    }
};


// 平衡二叉搜索树(AVL树):允许重复值
class AVL {
public:
    AVL(vector<int> & vals) {
        if (!vals.empty()) {
            root = build(vals, 0, vals.size() - 1, nullptr);
        }
    }

    // 根据vals[l:r]构造平衡二叉搜索树 -> 返回根结点
    Node * build(vector<int> & vals, int l, int r, Node * parent) {
        int m = (l + r) >> 1;
        Node * node = new Node(vals[m], parent);
        if (l <= m - 1) {
            node->left = build(vals, l, m - 1, node);
        }
        if (m + 1 <= r) {
            node->right = build(vals, m + 1, r, node);
        }
        recompute(node);
        return node;
    }

    // 返回二叉搜索树中第k小的元素
    int kthSmallest(int k) {
        Node * node = root;
        while (node != nullptr) {
            int left = getSize(node->left);
            if (left < k - 1) {
                node = node->right;
                k -= left + 1;
            } else if (left == k - 1) {
                break;
            } else {
                node = node->left;
            }
        }
        return node->val;
    }

    void insert(int v) {
        if (root == nullptr) {
            root = new Node(v);
        } else {
            // 计算新结点的添加位置
            Node * node = subtreeSearch(root, v);
            bool isAddLeft = v <= node->val; // 是否将新结点添加到node的左子结点
            if (node->val == v) { // 如果值为v的结点已存在
                if (node->left != nullptr) { // 值为v的结点存在左子结点,则添加到其左子树的最右侧
                    node = subtreeLast(node->left);
                    isAddLeft = false;
                } else { // 值为v的结点不存在左子结点,则添加到其左子结点
                    isAddLeft = true;
                }
            }

            // 添加新结点
            Node * leaf = new Node(v, node);
            if (isAddLeft) {
                node->left = leaf;
            } else {
                node->right = leaf;
            }

            rebalance(leaf);
        }
    }

    // 删除值为v的结点 -> 返回是否成功删除结点
    bool Delete(int v) {
        if (root == nullptr) {
            return false;
        }

        Node * node = subtreeSearch(root, v);
        if (node->val != v) { // 没有找到需要删除的结点
            return false;
        }

        // 处理当前结点既有左子树也有右子树的情况
        // 若左子树比右子树高度低,则将当前结点替换为右子树最左侧的结点,并移除右子树最左侧的结点
        // 若右子树比左子树高度低,则将当前结点替换为左子树最右侧的结点,并移除左子树最右侧的结点
        if (node->left != nullptr && node->right != nullptr) {
            Node * replacement = nullptr;
            if (node->left->height <= node->right->height) {
                replacement = subtreeFirst(node->right);
            } else {
                replacement = subtreeLast(node->left);
            }
            node->val = replacement->val;
            node = replacement;
        }

        Node * parent = node->parent;
        Delete(node);
        rebalance(parent);
        return true;
    }

private:
    Node * root;

    // 删除结点p并用它的子结点代替它,结点p至多只能有1个子结点
    void Delete(Node * node) {
        if (node->left != nullptr && node->right != nullptr) {
            return;
            // throw new Exception("Node has two children");
        }
        Node * child = node->left != nullptr ? node->left : node->right;
        if (child != nullptr) {
            child->parent = node->parent;
        }
        if (node == root) {
            root = child;
        } else {
            Node * parent = node->parent;
            if (node == parent->left) {
                parent->left = child;
            } else {
                parent->right = child;
            }
        }
        node->parent = node;
    }

    // 在以node为根结点的子树中搜索值为v的结点,如果没有值为v的结点,则返回值为v的结点应该在的位置的父结点
    Node * subtreeSearch(Node * node, int v) {
        if (node->val < v && node->right != nullptr) {
            return subtreeSearch(node->right, v);
        } else if (node->val > v && node->left != nullptr) {
            return subtreeSearch(node->left, v);
        } else {
            return node;
        }
    }

    // 重新计算node结点的高度和元素数
    void recompute(Node * node) {
        node->height = 1 + max(getHeight(node->left), getHeight(node->right));
        node->size = 1 + getSize(node->left) + getSize(node->right);
    }

    // 从node结点开始(含node结点)逐个向上重新平衡二叉树,并更新结点高度和元素数
    void rebalance(Node * node) {
        while (node != nullptr) {
            int oldHeight = node->height, oldSize = node->size;
            if (!isBalanced(node)) {
                node = restructure(tallGrandchild(node));
                recompute(node->left);
                recompute(node->right);
            }
            recompute(node);
            if (node->height == oldHeight && node->size == oldSize) {
                node = nullptr; // 如果结点高度和元素数都没有变化则不需要再继续向上调整
            } else {
                node = node->parent;
            }
        }
    }

    // 判断node结点是否平衡
    bool isBalanced(Node * node) {
        return abs(getHeight(node->left) - getHeight(node->right)) <= 1;
    }

    // 获取node结点更高的子树
    Node * tallChild(Node * node) {
        if (getHeight(node->left) > getHeight(node->right)) {
            return node->left;
        } else {
            return node->right;
        }
    }

    // 获取node结点更高的子树中的更高的子树
    Node * tallGrandchild(Node * node) {
        Node * child = tallChild(node);
        return tallChild(child);
    }

    // 重新连接父结点和子结点(子结点允许为空)
    static void relink(Node * parent, Node * child, bool isLeft) {
        if (isLeft) {
            parent->left = child;
        } else {
            parent->right = child;
        }
        if (child != nullptr) {
            child->parent = parent;
        }
    }

    // 旋转操作
    void rotate(Node * node) {
        Node * parent = node->parent;
        Node * grandparent = parent->parent;
        if (grandparent == nullptr) {
            root = node;
            node->parent = nullptr;
        } else {
            relink(grandparent, node, parent == grandparent->left);
        }

        if (node == parent->left) {
            relink(parent, node->right, true);
            relink(node, parent, false);
        } else {
            relink(parent, node->left, false);
            relink(node, parent, true);
        }
    }

    // trinode操作
    Node * restructure(Node * node) {
        Node * parent = node->parent;
        Node * grandparent = parent->parent;

        if ((node == parent->right) == (parent == grandparent->right)) { // 处理需要一次旋转的情况
            rotate(parent);
            return parent;
        } else { // 处理需要两次旋转的情况:第1次旋转后即成为需要一次旋转的情况
            rotate(node);
            rotate(node);
            return node;
        }
    }

    // 返回以node为根结点的子树的第1个元素
    static Node * subtreeFirst(Node * node) {
        while (node->left != nullptr) {
            node = node->left;
        }
        return node;
    }

    // 返回以node为根结点的子树的最后1个元素
    static Node * subtreeLast(Node * node) {
        while (node->right != nullptr) {
            node = node->right;
        }
        return node;
    }

    // 获取以node为根结点的子树的高度
    static int getHeight(Node * node) {
        return node != nullptr ? node->height : 0;
    }

    // 获取以node为根结点的子树的结点数
    static int getSize(Node * node) {
        return node != nullptr ? node->size : 0;
    }
};

class Solution {
public:
    int kthSmallest(TreeNode * root, int k) {
        // 中序遍历生成数值列表
        vector<int> inorderList;
        inorder(root, inorderList);
        // 构造平衡二叉搜索树
        AVL avl(inorderList);

        // 模拟1000次插入和删除操作
        vector<int> randomNums(1000);
        std::random_device rd;
        for (int i = 0; i < 1000; ++i) {
            randomNums[i] = rd()%(10001);
            avl.insert(randomNums[i]);
        }
        shuffle(randomNums); // 列表乱序
        for (int i = 0; i < 1000; ++i) {
            avl.Delete(randomNums[i]);
        }

        return avl.kthSmallest(k);
    }

private:
    void inorder(TreeNode * node, vector<int> & inorderList) {
        if (node->left != nullptr) {
            inorder(node->left, inorderList);
        }
        inorderList.push_back(node->val);
        if (node->right != nullptr) {
            inorder(node->right, inorderList);
        }
    }

    void shuffle(vector<int> & arr) {
        std::random_device rd;
        int length = arr.size();
        for (int i = 0; i < length; i++) {
            int randIndex = rd()%length;
            swap(arr[i],arr[randIndex]);
        }
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1060612.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

免杀对抗-成品EXE免杀-反特征码-通用跳转

一、exe程序生成 1.使用如下shellcode加载器&#xff0c;生成c/c语言的exe程序 加载器&#xff1a;1.c #include <Windows.h> #include <stdio.h>#pragma comment(linker,"/section:.data,RWE")unsigned char shellcode[] 生成的shellcode;int main() { …

【一、灵犀考试系统项目设计、框架搭建】

一、创建数据库 1、打开power designer&#xff0c;新建数据库模型 2、新建数据表&#xff0c;以及关系 【注意】 图片的类型有两种&#xff1a;varbinary 和 image varbinary : 二进制字节流&#xff0c;可以自动控制长度 image : 最大可放2G图片 3、创建数据库&#…

基于郊狼优化的BP神经网络(分类应用) - 附代码

基于郊狼优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于郊狼优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.郊狼优化BP神经网络3.1 BP神经网络参数设置3.2 郊狼算法应用 4.测试结果&#xff1a;5.M…

国庆假期作业day2

作业&#xff1a;创建一个双向链表&#xff0c;将26个英文字母通过头插的方式插入到链表中&#xff0c;通过尾删的方式将数据读取出来并删除 #ifndef _TEXT_H #define _TEXT_H #include<myhead.h> typedef int datatype; typedef struct dblist {union {datatype data;/…

逆强化学习

1.逆强化学习的理论框架 1.teacher的行为被定义成best 2.学习的网络有两个&#xff0c;actor和reward 3.每次迭代中通过比较actor与teacher的行为来更新reward function&#xff0c;基于新的reward function来更新actor使得actor获得的reward最大。 loss的设计相当于一个排序问…

Java基于SSM的校园一卡通系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

解决stm32驱动LCD1602A时不显示第二行的问题

根据这个教程&#xff0c;用仿真测试可以成功&#xff0c;但实际上上真机只显示第一行。 考虑到可能是电压的问题&#xff0c;我外接的是和stm32一样的3.3v&#xff0c;但实际上lcd的额定电压是5v&#xff0c;于是换为5V&#xff0c;结果变这样了&#xff1a; 只能恢复电压3.…

Linux信号解析

文章目录 Linux信号概念信号种类Linux信号产生异步 LInux信号阻塞递达、未决、阻塞、忽略信号集操作函数阻塞信号集操作函数未决信号集操作函数 Linux信号捕捉signal函数sigaction函数 总结 Linux信号概念 信号在我们的生活中无处不在&#xff0c;常见的如电话铃声&#xff0c…

openstack-ansible部署zed版本all-in-one

目录 部署架构部署节点准备安装Rocky linux 9配置rocky 目标节点配置网络配置rocky linux网卡的创建永久网桥的方法&#xff1a; 部署前配置 部署架构 可用的操作系统&#xff1a; Debian11&#xff08;bullseye&#xff09; Ubuntu 22.04或20.04 CentOS Stream 9 或 Rocky Lin…

【Golang】网络编程

网络编程 网络模型介绍 OSI七层网络模型 在软件开发中我们使用最多的是上图中将互联网划分为五个分层的模型&#xff1a; 物理层数据链路层网络层传输层应用层 物理层 我们的电脑要与外界互联网通信&#xff0c;需要先把电脑连接网络&#xff0c;我们可以用双绞线、光纤、…

CocosCreator3.8研究笔记(二十五)CocosCreator 动画系统-2d骨骼动画spine

大家都知道&#xff0c;在游戏中 一般用帧动画或者骨骼动画&#xff0c;实现 人物的行走、奔跑、攻击等动作。 帧动画&#xff0c;在上一篇已经做了介绍&#xff0c;感兴趣的朋友可以前往阅读&#xff1a; CocosCreator3.8研究笔记&#xff08;二十四&#xff09;CocosCreator …

10.5作业

磕磕绊绊还是差不多完成了,tcp多客户端在线词典 代码&#xff1a; 数据库导入&#xff1a;有点粗糙&#xff0c;不知道怎么搞成两列&#xff0c;一个单词中间还是空格卧槽难搞 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <s…

NPDP产品经理知识(产品创新流程)

1.复习组合管理: 组合管理的目标 ===> 价值最大化,项目平衡,战略一致,管道平衡(资源需求和供给),盈利充分 (实现财务目标) 产品创新流程就是管理风险的过程。 模糊前端: 产品创新章程:PIC 包含 =====> 背景,聚焦舞台,目标和目的,特别准则,可持续性 新产…

mysql面试题13:MySQL中什么是异步复制?底层实现?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:讲一讲mysql中什么是异步复制?底层实现? MySQL中的异步复制(Asynchronous Replication)是一种复制模式,主服务器将数据写入二进制日志后,无…

计组——I/O方式

一、程序查询方式 CPU不断轮询检查I/O控制器中“状态寄存器”&#xff0c;检测到状态为“已完成”之后&#xff0c;再从数据寄存器取出输入数据。 过程&#xff1a; 1.CPU执行初始化程序&#xff0c;并预置传送参数&#xff1b;设置计数器、设置数据首地址。 2. 向I/O接口发…

星际争霸之小霸王之小蜜蜂(十六)--狂奔的花猫

系列文章目录 星际争霸之小霸王之小蜜蜂&#xff08;十五&#xff09;--剧将终场 星际争霸之小霸王之小蜜蜂&#xff08;十四&#xff09;--资本家的眼泪 星际争霸之小霸王之小蜜蜂&#xff08;十三&#xff09;--接着奏乐接着舞 星际争霸之小霸王之小蜜蜂&#xff08;十二…

视频讲解|含可再生能源的热电联供型微网经济运行优化(含确定性和源荷随机两部分代码)

1 主要内容 该视频为《含可再生能源的热电联供型微网经济运行优化》代码讲解内容&#xff0c;对应的资源下载链接为考虑源荷不确定性的热电联供微网优化-王锐matlab&#xff08;含视频讲解&#xff09;&#xff0c;对该程序进行了详尽的讲解&#xff0c;基本做到句句分析和讲解…

路径问题【动态规划】

一、不同路径 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m1,vector<int>(n1));dp[0][1] 1;for(int i 1;i < m;i){for(int j 1;j < n;j){dp[i][j] dp[i-1][j]dp[i][j-1];}}return dp[m][n];} }; 二、不同路…

三、thymeleaf基本语法

3.1、基本语法 3.1.1变量表达式&#xff1a;${...} 变量表达式用于在页面中输出指定的内容&#xff0c;此内容可以是变量&#xff0c;可以是集合的元素&#xff0c;也可以是对象的属性。主要用于填充标签的属性值&#xff0c;标签内的文本&#xff0c;以及页面中js变量的值等…

OpenCV项目开发实战--CUDA 模块使用详细介绍--附完整代码

如果您已经使用 OpenCV 一段时间,您应该已经注意到,在大多数情况下 OpenCV 使用 CPU,这并不总能保证您获得所需的性能。为了解决这个问题,2010 年 OpenCV 中添加了一个使用 CUDA 提供 GPU 加速的新模块。您可以在下面找到展示 GPU 模块优势的基准测试: 图 1: CPU 上的 Op…