《红黑树最少和最多红节点》贪心法题解分析

【C++详解】——红黑树 本文介绍了红黑树的概念、性质,详细解释了红黑树插入节点的过程。 阅读详情

 题目:

来源:  洛谷P5566


 题解:

这位大佬的题解:

#include "cstdio"
int n, ans, k;
int main()
{
    scanf("%d", &n);
    k = n + 1;
    while (k > 1)
    {
        ans += k & 1;
        k >>= 1;
    }
    printf("%d\n", ans);
    k = n + 1;
    ans = 0;
    while (k > 1)
    {
        if (k == 2)
            ans++, k--;
        else if ((k & 3) == 1)
            ans += ((k >> 2) << 1) - 1, k >>= 2, k++;
        else if ((k & 3) == 2)
            ans += ((k >> 2) << 1), k >>= 2, k++;
        else if ((k & 3) == 3)
            ans += ((k >> 2) << 1) + 1, k >>= 2, k++;
        else
            ans += (k >> 1), k >>= 2;
    }
    printf("%d", ans);
    return 0;
}

题解分析: 

这个贪心法的确非常巧妙。可是大佬并没有给出详细严谨的解释。不过我基本上把代码中每一行的意思都“翻译”了出来。

(只不过有一点前提还没有严谨地证明:满足要求的红黑树一定可以是完全二叉树。

因为我发现只有基于这个前提,以上代码才有具体的意义,才可以解释通。)

此外还要注意一点:题目里面的N不包括空节点数!这里要注意!

下面分别针对最小值和最大值进行分析:

  

对于红色节点最小值:

首先根据红黑树的性质可以得到这样的一个理解:

将一个黑色节点涂成红色,可以使得经过这个节点的从根到叶的黑路径长度-1(黑路径就是这条路径上黑色节点的个数)。

所以这个地方贪心的意思就慢慢出来了。

不如假设N个节点都是黑色,然后我们要做的是把黑色涂成红色,问题就变成了最少要涂多少个。为了使得涂的个数最少,则:

1.应当使这棵树尽量平衡。(比如一个满二叉树,每条路径的长度都一样——为树的高度)

2.所涂的节点尽量高度要高(因为越高的节点影响的黑路径就越多)

关于第一点,因为,比如一个节点的左右子树的高度不一样,那么可以通过给较高的子树中的某个或某些节点涂红色,从而达到降低其高度的效果。

所以我们考虑的范围就可以缩小到只考虑完全二叉树了。(因为完全二叉树叶子节点的深度最多相差1,而且相同高度的叶子结点都互相邻近(靠在一起这样可以更便于涂红、即更利于第二点的实现)。

emm可能我表述得并不是很好,下面举个例子来加深理解。

比如这样一棵二叉树(空节点没画出来,后面也都不会画出来):

 

显然,经过节点2的路径要比经过3的长1,所以我们来把一些黑色节点涂成红色来使其符合每条从根要叶子节点的黑路径相同(红黑树的条件也是题目里面的条件)。

我们可以涂4、5:

 也可以涂2:

显然,为了使红色节点数最少,此处就会选择涂2号。

慢慢就会发现,每次涂尽量涂高处,这样红色节点影响的路径数才多,红色节点用的自然也就更少了。这里就用了贪心的思想)。

再看个例子:

 

 

两棵树看起来都挺平衡的(子树高度差不超过1)(左边为完全二叉树,右边不是)

但是左边的树只要涂一个节点就行了,右边的至少要涂两个。

这也是为什么我们选择完全二叉树来考虑的原因。

然后我们来思考如何求出要涂红的最少的节点数。

不如从例子出发,有这样一棵完全二叉树:

 

我们要做的就是通过将1、2、3节点或其祖先节点涂红,把节点1、2、3的深度调整到和右半棵树相同(即深度-1)。

为了贪心,我们从上往下考虑:

A节点:不行,因为他会影响到不需要调整的节点。

B节点:不可行,因为他会影响到不需要调整的节点(图中看不出来是因为,它影响的是空节点(空节点算是黑色,但是上图我没把他画出来))。

C节点:可行

D节点:可行

此时以及全部符合要求

综上只最少需要涂两个。

通过自己举大量的例子,可以发现这样一个规律:完全二叉树,设最下面一层节点数为m,且最下面一层未满。

当m=1时,只需要涂1个,

当m=2时,只需要涂1个,

当m=4时,只需要涂1个,

······

当m = 2的n次方时,只需要涂1个。

此时可以停下来,慢慢结合满二叉树的性质来思考思考这件事情。

没错,这里就利用到了,满二叉树的叶子节点数等于2的幂。

例如下面的例子,此时红色涂的就是这连续的2^2个节点作为叶子节点的一个满二叉树的根节点:

那么下面就可以考虑m不为2的幂的情况了。

例如前一个例子,m=3;

此时可以发现3=1+2 = 2^0 + 2^1   ,此时的答案是  涂2个

再举个例子,这棵树m=7:

 

7 = 1 + 2+7  = 2^0 + 2 ^ 1 + 2^2  ,而此时的答案是3;

所以,看到这里,即使我的废话可能表述不清,这么多的例子应该能让你有个大概的理解了。

 那就是:最少涂的红色节点数量= 未满层的叶子节点数能够分解为2的不同次幂的个数。

熟悉二进制的话,上面这句话就能很轻松地翻译成:最少红色节点数量=未满层叶子节点数的二进制中1的个数。

这也就是大佬代码中求第一问的简洁过程的来源了。

不过也许你会疑惑为什么代码中令k =n+1,这里稍微解释一下:

满二叉树节点数=2^h-1

而完全二叉树节点数=一个满二叉树节点数+未满层节点数

所以:完全二叉树节点数+1 = 2^h + 未满层节点数

而2^h在 k的二进制中的表达便是最高位的那个1,所以循环的条件是k>1

好的,第一小问就这么愉快的结束了。

第二问,对于红色节点数的最大值:

说实话这一小问的贪心做法挺难理解的。

首先我无法证明为什么这一问同样考虑完全二叉树即可(还是说是我的一厢情愿实际上对非完全二叉树也有说得通的说法?)。

我姑且从完全二叉树的前提去考虑这一小问了。

(实际上,多举几个例子,发现其它的情况都可以变换为完全二叉树的情况)。。

然后,对于这一问我们制定的贪心策略是:

尽量从叶子节点开始涂,能涂上的叶子都涂上。

(为什么没有考虑每条路径的黑长度都相等呢?慢慢看看例子就懂了)

为啥呢?多举几个例子就懂了(啪!) 

但是尽管知道了策略,代码依然很让人费解。

没关系,从例子出发就行,

说是这边来了个20个节点的完全二叉树:

  那我们来看看第一回合涂了哪些叶子节点:

  去掉红色节点和与红色节点上下相邻的黑色节点后(因为一条路径上不能出现相邻的红色节点),二叉树为:

第二回合涂完后,二叉树为:

去掉节点后,第三次,最后一次涂了:

所以结果为:

结束

所以我们的涂法策略归结为:

当最下面一层叶子节点深度>=3时,涂掉尽可能多的叶子节点,

同时要保证:整个二叉树左右的黑深度总是相差1,且不破坏红黑树正确性

(因为不是把所有叶子节点都涂起来就是正确的做法,要从四个情况考虑,

而这四个情况需要当前叶子节点深度>=3)。

下面四个情况的图,分别是将二叉树从上到下从左到右编号中,编号最后的几个节点的局部图:

  

       同时,这四种情况分别对应代码中的对(k&3)的讨论的操作。

   (其实代码为什么那么写可能和作者的思维方式有关,讨论部分的代码可以等效为:   

while (k > 1)
    {
        if (k == 2)
            ans++, k--;
        else if ((k & 3) == 1)
            ans += (k >> 1) - 1, k >>= 2, k++;
        else if ((k & 3) == 2)
            ans += (k >> 1) - 1, k >>= 2, k++;
        else if ((k & 3) == 3)
            ans += (k >> 1) , k >>= 2, k++;
        else
            ans += (k >> 1), k >>= 2;
    }

这就时对四种情况的讨论了。

其中k>>=2的操作就是将涂色的红节点和无法涂色的黑节点删去的过程(文章最后的代码就是按照这个过程用减法写的,可行)。这里巧妙地利用了完全二叉树叶子节点数等于(总结点数+1)/2,所以除以了2次(常数由于整数除法,除以完了就变成了0)。或者理解为给整个树修剪了两次叶子。

其他一些小细节,举一举例子就能够写出来了。


总结:

关于这条题目的贪心方法相较于常规的动态规划解法可以说是非常简洁了。但是其中许多地方的正确性证明起来并不容易。能力有限,只能靠直觉意会了。

顺便附上我依照我所理解得到的思路写下的代码(已AC):

#include<iostream>
using namespace std;
int main() {
    int N;
    cin >> N;
    int a = N + 1;
    int ans1 = 0;
    int ans2 = 0;
    while (a > 1) {
        if (a & 1) {
            ans1++;
        }
        a = a >> 1;
    }
    cout << ans1 << endl;//问题一的解答
    a = N + 1;
    while (a>1) {
        //cout << '=' << a << '=' << endl;
        if (a == 2) {
            ans2++;
            break;
        }
        else if ((a & 3) == 3 || (a & 3)== 0) {
            ans2 += a >> 1;
            a = a - (a >> 1);
            a = a >> 1;
            //a++;
        }
        else {//a & 3 == 2 ||a & 3 ==  1
            ans2 += (a >> 1) - 1;
            a = a - ((a >> 1) - 1);
            a = a >> 1;
            //a++;
        }
        //cout << '-' << a << '-' << endl;
    }
    cout << ans2 << endl;
    return 0;
}
JDK1.8的HashMap底层红黑树实现 今天看了小刘老师讲的红黑树,看完后茅塞顿开,受益匪浅,于是我打算写一篇博客记录一下今天所学,本文章大多来自小刘老师的笔记,以及部分自己的见解 什么是树 在学习红黑树之前,我们先来复习一下什么是树 **树(tree)**是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点通过连接它们的边组成一个具有层次关系的集合。 把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 树有很多种,向上面的一个节点有多余两个的子节点的树,称为多路. 阅读详情

相关推荐

算法4第3章红黑树及习题讲解

二叉查找树在最坏的情况下性能很差时间复杂度是O(N),本节我们学习一种新的数据结构红黑树 它能保证操作在最坏情况下时间复杂度也是O(logN),无论怎么构造,它都能到达平衡,红黑树结构很复杂,首先我们学习一种叫2-3树的数据结构来帮忙对红黑树的理解。 一般的二叉查找树一个节点只能有一个键两个子节点,在2-3树中一个节点最多可以有两个键3个子节点,左子树中的键都比左边的键小, 中间子树的键大于左边的...

cyj88jyc的专栏 2397

算法设计与分析: 3-29 红黑树的红色内结点问题

3-29 红黑树的红色内结点问题 问题描述 红黑树是一类特殊的二叉搜索树,其中每个结点被“染成”红色或黑色。若将二叉搜索树结点中的空指针看作是指向一个空结点,则称这类空结点为二叉搜索树的前端结点。并规定所有前端结点的高度为-1。 一棵红黑树是满足下面“红黑性质”染色二叉搜索树: (1)每个结点被染成红色或黑色; (2)每个前端结点为黑色结点; (3)任一红结点的儿子结点均为黑结点...

d3y1`5 81_09 1109

红黑树的插入与删除

红黑树的性质: 红黑树每一个节点都是有颜色的,不是黑色就是红色。 根节点必须为黑色。 所有叶子节点都为黑色,叶子节点是null,不存储实际数据。 每个红色的节点必须要有两个黑色的子节点,或者说每个叶子结点到根节点的所有路径上不能有连续的红色节点。 从根节点开始到任意叶子节点的路径上,黑色节点的数量必须是一致的。 节点的左右高度差最多不能超过两倍。 ...

qq_41078889的博客 564

BZOJ 3227 [Sdoi2008] 红黑树(tree)

(神奇的)贪心

依然一笑作春温。 1197

8分钟,复习一遍红黑树

*** 红黑树节点定义**/// 颜色T key;// 关键字(键值)// 左孩子// 右孩子// 父结点​}​}​"(R)":"B");}}复制代码。

m0_67322837的博客 571

【信息科学与工程学】【数据科学】数据科学领域 第六篇 算法设计 01

算法设计

weixin_49199313的博客 1500

红黑树详细分析

RBT----set和map底层原理 结构: 1、每个节点非红即黑 2、根节点必须为黑 3、不允许两个红节点互相为父子关系 4、我们认为终端节点(NULL/NIL节点)是黑色的,也称之为黑哨兵 5、从树中任意节点向下出发,到其所有可能到达的各个终端节点的各个路径上,黑节点的数目必须是相同的。 由于以上5个性质,使得红黑上的路径长度不会有一条路径长度超过其他路径长度的两倍。也使得其添加、删除、查找...

冯同学的博客 322

红黑树内部结点数量分析

红黑树是一种自平衡的二叉搜索树,它通过一系列复杂的性质和操作来维持平衡,从而确保各种动态集合操作的高效性。红黑树的平衡性是通过黑高(从根节点到最远叶子节点的黑色节点数量)来衡量的。在本文中,我们将探讨在一棵黑高为k的红黑树中,内部结点的最大和最小数量,并提供伪代码和C语言代码实例来进一步阐释这一概念。

笔者从事电信媒体开发多年,愿意将多年的开发经验分享给同行 1048

【C++】红黑树

红黑树 红黑树的性质: 每各节点不是黑色就是红色 根节点是黑色 如果一个节点是红色的,则它的两个孩子节点是黑色的 对于每个节点,从节点到其所有后代叶节点的路径上,包含相同数目的黑色节点 每个叶子节点都是黑色的(叶子节点是指空节点) 我们根据上面五条性质,可以知道红黑树中最主要的性质是: 红黑树中不会出现连续的红节点(第3条性质) 每一条路径上的黑色节点的数量相同(第4条性质) 还能由这五条...

进阶之路 3027

python3.12 百分百解决安装dlib失败问题【研究原理后Bug已解决】

python3.12 百分百解决安装dlib失败问题【研究原理后Bug已解决】 python3.12版本下安装dlib失败。截图如下。试了网上很多方法都失败,耗费了很多时间终于找到适用于python3.12版本安装dlib的方法,亲测百分百成功。几乎尝试了所有的方法都不太行。终于找到安装的方法了。 原因分析: 版本匹配问题。我们现在是python3.12 使用pip根本无法安装。下面我们一起手动安装,1分钟搞定。

一键难忘的博客 7129

【OpenClaw企业级智能体实战】第13篇:1+N架构实战!OpenClaw多Agent数字员工军团搭建指南(含完整配置+路由策略+代码落地)

单Agent因“记忆负担重、Token消耗高、响应不精准”已难以满足复杂场景需求,而多Agent团队通过“1个编排者+N个执行者”的分工协作模式,能让效率提升300%以上。本文基于OpenClaw 2026稳定版,结合阿里云开发者社区实战案例,从核心认知、准备工作、实操流程、协作机制、安全配置五大维度,完整拆解多Agent团队搭建全流程。文中包含可直接复制执行的代码、详细配置模板、路由策略设置及常见问题排查,新手可在1-2小时内落地属于自己的“数字员工”军团,覆盖内容创作、技术支持、多平台运营等多场景。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 745

红黑树-题解

红黑树题解

playedgames的博客 427

huggingface下载提示huggingface_hub.errors.EntryNotFoundError:404 Client Error: (Request 1D: Root...如何解决?

🏆 本文收录于《全栈Bug调优(实战版)》专栏,致力于分享我在项目实战过程中遇到的各类Bug及其原因,并提供切实有效的解决方案。无论你是初学者还是经验丰富的开发者,本文将为你指引出一条更高效的Bug修复之路,助你早日登顶,迈向财富自由的梦想🚀!同时,欢迎大家关注、收藏、订阅本专栏,更多精彩内容正在持续更新中。让我们一起进步,Up!Up!Up!    备注: 部分问题/难题源自互联网,经过精心筛选和整理,结合数位十多年大厂实战经验资深大佬经验总结所得,数条可行方案供所需之人参考。

**My Coding Family** 1232

6.红黑树

一、红黑树 1.红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构,在以前被称为平衡二叉B树。它是一种特殊的二叉查找树,红黑树的每一个结点上都有存储位表示结点的颜色,每一个结点可以是红或者黑;红黑树不是高度平衡的(平衡而二叉树是高度平衡的),它的平衡是通过“红黑规则”进行实现的。 ...

zhaozhiwei314的博客 246

强化学习优化多目标冲突推理的决策过程设计

1. 背景介绍 强化学习(Reinforcement Learning,RL)作为机器学习的一个重要分支,近年来在计算机科学和人工智能领域取得了显著的进展。其核心思想是通过试错来学习最优策略,从而在复杂的环境中实现目标。然而,在多目标冲突推理(Multi-Objective Conflict Reasoning,MOCR)问题上,强化学习面临着诸多挑

AI天才研究院 1511

分享一个我自用的 Python 消息发送模块,支持邮件、钉钉、企业微信

🔥 分享一个我自用的 Python 消息发送模块,支持邮件、钉钉、企业微信

XiaoqiangClub的博客 477

《数据结构》红黑树

今日内容: 泛型的通配符 泛型的上下限 数据结构 List集合 Set集合 Collections(Comparator 接口、Comparable接口) 学习目标 了解泛型的通配符 能够理解泛型的上下限 能够表达泛型通配符的作用 能够说出List集合的特点 能够说出常见的数据结构 能够说出队列结构特点 能够说出单向链表结构特点

weixin_51621893的博客 380

PCL Alpha Shapes平面点云边界特征提取【2026最新版】

alpha shapes算法用于平面点云的边界特征提取。博客长期更新,本文最近一次更新时间为:2026年4月20日。

点云侠的博客 27万+

[数据结构] 红黑树的详解

红黑树

weixin_43717520的博客 2197

基于YOLOv8的建筑材料分类系统:从数据集构建到UI界面实现

建筑材料分类数据集需要包含各种常见建筑材料的图像,并对每种材料进行标注。砖块(Brick)混凝土(Concrete)钢筋(Rebar)木材(Wood)瓷砖(Tile)玻璃(Glass)钢材(Steel)塑料(Plastic)石膏板(Drywall)绝缘材料(Insulation)如果没有找到合适的公开数据集,可以构建自己的数据集。使用智能手机或专业相机拍摄各种建筑材料的照片确保在不同光照条件、角度和背景下拍摄每类材料至少收集300-500张图像。

YOLO 489

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

树采用孩兄弟链表作为存储结构,设计非递归算法求树的叶子结点数

#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char class_[20]; int score; }CLASS; int main() { printf("开始写入2\n"); int i,j,num=0; CLASS t,a[30]; FILE *fp; int no,type,score; int rank[] = {7,5,4,3,2,1

qq_46504622的博客 502

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

数据结构——红黑树基础(博文笔记)

0.满足BST的要求1.根节点和叶子节点是黑色的(叶子节点是空节点,下图中将null节点省略)2.对于任意节点,从此节点到任何叶子节点的简单路径上的黑色节点数量一致。3.只有红色和黑色两种颜色的节点4.任意红色节点不相邻顶端为黑,非红即黑,红不相邻,叶路黑同。

R的博客 1851

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

算法设计技巧: 贪心算法(Greedy Algorithm)

贪心算法是一种启发式(Heuristic)算法, 它的基本思想是在每一步决策时选择局部最优的策略. 贪心算法一般在设计和实现上比较容易, 因此在求解实际问题中应用广泛. 编码问题 考虑如下的编码问题: 给定字符的集合CCC, 例如C={a,b,c,d,e}C=\{a, b, c, d, e\}C={a,b,c,d,e}. 每个字符α∈C\alpha\in Cα∈C的使用频次为fαf_{\alpha...

胡拉哥 2367

【数据结构与算法】红黑树 详解

简介 红黑树是一种特殊的二叉搜索树,满足如下三个性质: 1.颜色:节点要么红要么黑,根节点和叶子节点为黑色; 2.红色节点不能相邻,或者说红色节点的孩子节点必为黑色; 3.任意节点到其叶子结点的所有路径包含相同数目的黑色节点; 好,红黑树的定义就是这么简单,那么这么定义的意义何在?我们知道普通的二叉搜索树已经具备了一定的查询效率,但是由于插入和删除操作,树的高度可能变高,甚至退化为链表,这种情...

u010900754的专栏 1963

数据结构-红黑树相关问题

红黑树的数据结构的定义。enum Color { RED = 0, BLACK = 1 }; struct RBTreeNode { struct RBTreeNode*left, *right, *parent; int key; int data;

草根学僧 915
上一篇: 非负整数大数高精度加法乘法【c++】
下一篇: 卡特兰数在括号串中的应用
咖啡咖_CoffCa
博客等级 码龄6年 116粉丝 58原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡咖_CoffCa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值