循环队列满空条件
Creato il: 23 gennaio 2025
Creato il: 23 gennaio 2025
分别给出循环队列队首指针指向队首元素,队尾指针指向队尾元素,队首指针指向队首元素,队尾指针指向队尾元素后一个位置,队首指针指向队首元素前一个位置,队尾指针指向队尾元素,队首指针指向队首元素前一个位置,队尾指针指向队尾元素后一个位置的队满和队空的条件
在使用循环数组实现队列时,由于“队首指针”(front)和“队尾指针”(rear)的含义不尽相同,不同教材或不同代码中对这两个指针的“指向”有四种常见约定。相应地,如何判定“队空”和“队满”也会随之变化。下面分别列出这四种约定下常见的队空、队满条件(假设循环队列的容量为 )。
front 指向队首“元素本身”,rear 指向队尾“元素本身”。
队空(Queue Empty)条件
常见两种处理方式:
front = -1
,rear = -1
;插入第一个元素后令 front = 0
,rear = 0
。队满(Queue Full)条件
如果允许队列中实际存储 个元素(即“满”时确实占满整个数组),那么一旦再插入就会冲突,因此
或更常见的说法是:当再插一个元素,会使得 “队首”和“队尾”在环上重合(不可区分空满)。所以在不使用额外标志而又想存满 个元素的设计中,通常就会采用 “队空 = ”,而“队满”用额外标志位加以区分。
实际上,约定一 若想区分空和满,往往需要一个额外标志(或者额外计数变量);否则单靠模运算容易出现“空”和“满”条件冲突。
front 指向队首“元素本身”,rear 指向队尾“元素的后一个位置”(也可理解为“下一个可插入的位置”)。
这是很多教材里“循环队列”最常见的定义方式。它有个特点:只能最多存放 个元素(因为需要留下一个空位区分空和满)。相应条件:
队空
当“队首”和“队尾”指针在环状意义上重合时,表示队中没有任何元素。
队满
即如果再插入一个元素就会使得 rear
与 front
相等,从而无法区分空或满,因此此时认为已经“满”了。
front 指向队首“元素的前一个位置”,rear 指向队尾“元素本身”。**
可以把“front + 1 (mod n)”理解为真正的队首元素位置。相应的空、满判断常见写法如下:
队空
因为此时“front的下一个位置”与“rear”重合,意味着队中没有实际元素。
队满
当队满时,如果再让 rear
后移一格,就会追上“front”的位置(即“front的前一个位置”和“队尾再往后一个位置”重合),因而不能再插入。
front 指向队首“元素的前一个位置”,rear 指向队尾“元素的后一个位置”。**
这与约定二类似,也是很多实现里较常见的写法,只是把“front”后移了一个位置,使得真正的队首元素在 (front + 1) mod n
处。它的特点同样是:能区分空和满,但最多只能存放 个元素。判定条件与约定二非常相似:
如果采取“队首指针正好指向队首元素、队尾指针正好指向队尾元素”(约定一),为了能够真正放满 个元素,往往需要额外的“队列状态标记”(或大小计数)来区分“空”和“满”,不然单纯用“模运算”易产生冲突。常见写法是
如果采取“队尾指针永远指向下一个可插入位置”的方式(约定二/约定四),则只需用 front == rear
表示空,用 (rear + 1) % n == front
表示满,优点是“判空、判满”条件非常简单,缺点是实际只能用掉 个存储单元。
如果“front”定义在队首元素“前一个位置”,则空、满条件和“rear 指向队尾或下一个位置”配合,也可以用相同的公式去判断(约定三/约定四),核心思路也类似。
这四种约定本质上只是指针所代表的含义不同,要点是在自己实现循环队列时,务必先明确“front”和“rear”到底各自指向哪里,然后相应写出一致的入队、出队和判空、判满条件。只要前后一致,都能正确工作。
一棵有124个叶结点的完全二叉树,最多有多少个节点 A247 B248 C249 D250,请给我详细的思考步骤与过程
下面给出一个比较详细、循序渐进的思路,说明为什么在一棵具有 124 个叶子的完全二叉树中,其节点总数的最大可能值是 248(选项 B)。
**完全二叉树(Complete Binary Tree)**的定义:
叶子(Leaf):没有任何孩子的结点。
本题给定:
选项:A. 247 B. 248 C. 249 D. 250
有些同学会先想到:“如果这棵树的高度是 ,那么一棵满(完全)二叉树(也称完满二叉树或完全充满的二叉树)若最底层满,则叶子数是 。要得到 124 这个叶子数,就接近满二叉树的 个叶子,于是从高度 8 的那棵完满二叉树(它有 128 个叶子、总节点数 255)里‘去掉’ 4 个最右边的叶子,就能得到 124 个叶子,看起来节点数就是 。”
然而,在“完全二叉树”里,不是简单地“减掉 4 个叶子”就行了,因为一旦在最右侧减去一些孩子,其父结点是否依然存在?是否会变成叶子?以及“层次遍历编号”会不会出现中间空洞?这一系列细节会导致有的父结点也变成叶子,从而使实际叶子数又发生变化,或者层次编号出现“跳号”。所以,“255 - 4=251” 并不一定能同时保证“恰好 124 片叶”和“整棵树依然是完全二叉树”。
接下来用更系统的办法来分析。
对于一棵“完全二叉树”来说:
除了最后两层外,其它更上层的结点一定是“有 2 个孩子”的内部结点。因为如果在上层有结点缺孩子,那最后几层就不可能排得那么满、那么靠左,不符合“完全”定义。
因此,只有第倒数第一层(即“高度倒数第二层”)和最后一层的结点才有可能成为叶子:
令整棵树的高度为 。本题里要想有接近 128 个叶子,就可以断定 至少是 8(因为高度 7 的满二叉树叶子只有 64 个,远小于 124)。实际也可以推知:这棵树的高度就是 8,最后一层“接近满”但未必全满。
那么显然:
故可得叶子总数 :
题目要求 ,所以:
又由 ,可得
将它代入上式:
从而
在“完全二叉树”里,倒数第二层的 64 个结点从左到右编号 。一旦某个结点出现“缺右孩子”(即只有 1 个孩子)或“没有孩子”(0 个孩子),那么它右边编号更大的那些结点不能再有 2 个孩子——否则就会造成 BFS 编号的“空洞”。
因此可以把第 7 层的 64 个结点视为:
并且
同时我们还有 。于是就有两种可能的搭配:
这两种都满足 以及 。
对于任意给定 :
因此,整棵树节点总数 =
由于 必然,故:
所以
而 只能是 0 或 1:
两种形状下都能保证“叶子总数 =124 且整棵树依然符合完全二叉树的逐层填充规则”;但显然 ,所以能达到的最大节点数是 248。
也就是说, 正是满足“有 124 个叶子且为完全二叉树”时的最大可能节点数。
很多人可能还会想:“能不能再多放几个底层结点,让第 7 层再出现 2~3 个拥有一个孩子的结点……”——但正如前述,“一次出现两个或以上‘只带一个孩子’的结点”,就会在它们之间或之后形成 BFS 编号的‘跳号’,违反‘完全性’。或“干脆不缺孩子,让第 7 层更多结点都有两孩子”——那会让叶子超过 124 个。等等一切尝试,都无法兼顾既要 124 个叶子又要保持完全性。
因此,248 确实是这道题中的最大值。
若一棵二叉树有126个节点,在第七层(根节点在第一层)至多有多少个节点 A32 B64 C63,如果一共有80个节点,那这道题还能判断出来最后一层最多有多少个吗,分别给出详细解析
下面分两部分来回答这个问题:
题目给出的三个选项分别是:
我们要找的是“在第 7 层可能出现的最大节点数”。
满二叉树(也叫完全“充满”的二叉树)高度若为 ,其总节点数为
且它的第 层恰好有
个节点。
如果某棵二叉树的高度是 7,那么一棵“满二叉树”就会有总节点数 ,并且它的第 7 层有 个节点。
如果把那 1 个结点从第 7 层删去,那么第 7 层只剩 63 个。这样总数就是
但此时第 7 层变为 个。
如果把那 1 个结点从更上层删去(例如第 6 层),要小心:
因此,想要把“缺的那一个结点”放在上层而又不损失更多孩子,往往行不通(或者会减少第 7 层的数量更多)。
综合而言,在一棵总节点数 = 126、且高度至少能到第 7 层的二叉树里,第 7 层最多放得下 63 个结点,而无法达到 64(那需要总数 127)。所以正确答案是:
C. 63
这一问的背景更开放:题目只说“总共有 80 个结点”,并未固定要看哪一层,而是问“它的最后一层(即整棵树的最高层)最多能放多少个结点?”很多同学疑惑:高度可以不同,排布方式也可以不同,是否还能得出一个唯一的“最大可能值”?
要使“最后一层”尽量多,就意味着:
在二叉树里,若整棵树的高度是 ,那么最顶上 层至少要有 1 个节点(根) + 1 个节点(第二层) + … 直到第 层,每一层最少也要有 1 个;但要想在层 上出现很多父节点(以便给第 层生很多孩子),就得在更前面各层多分叉……这就会“用掉”更多节点。
然而一个更简便、几乎可以“锁定答案”的方法是:
这样一来,最后一层(即第 7 层)的结点数便是这 17 个。这是目前已知能让“最下一层”结点数最多的排布方式,而且确实能构成一棵合法的二叉树。
所以,如果总节点数是 80,则“它的最后一层最多可以有 17 个节点”。
126 个节点时,在第 7 层最多有多少? 答案是 63。
若总共有 80 个节点,能否判断“最后一层”最多有多少?
这两个问题都体现了一个通用思想:
- 要想让某一层(或最后一层)的节点数达到最大,往往需要把上面各层“恰当地”充满(以便有足够多的父节点),但又不能浪费太多节点在更高层的冗余分支上。
- 对于给定的总节点数 ,通常“从上到下尽量满”到某层,然后把剩余的节点尽数投入下一层,就是使“那一层”出现最大容量的典型排布方案。
生成1-16有序顺序表折半查找的生成树
下面给出一个详细示例,演示对有序顺序表 (假设按下标从 1 到 16 编号,或者说元素值恰好就是 1~16)进行折半查找时所产生的递归划分树(也常被称作“折半查找生成树”或“决策树”)。
注意:此“生成树”并不是指把这 16 个数直接插入到一棵二叉搜索树中,而是指折半查找(Binary Search)在区间 上不断取“中点”、向左右子区间递归的过程所形成的那棵“递归调用树”。
假设我们使用1-based 下标,初始 low = 1
, high = 16
。
每次取中点 mid = (low + high) // 2
(向下取整),与目标值比较后决定去左半区或右半区继续查找。
在不考虑“具体查找的目标值是多少”的情况下,如果把每一次“取中点”都看做在树上生成一个节点,并把它的“左、右子节点”分别表示对左侧区间和右侧区间的继续折半,那么最终会产生如下完整的“递归划分树”:
[1..16]
,其中点下标 mid = (1+16)//2 = 8
[1..7]
,中点 mid = (1+7)//2 = 4
[9..16]
,中点 mid = (9+16)//2 = 12
向下依次递归,直到区间长度为 1 或出现“low>high”的空区间。
下面以“节点中写 mid
值”的形式画出最终的递归树。
[k..k]
时,对应一个叶节点 mid = k
,再无法继续拆分。[15..14]
这类 low>high
),不会生成节点,递归直接结束。用树状排布可以示意为(可从上到下阅读):
text(8) <-- 区间 [1..16] mid=8 / \ / \ (4) (12) <-- 左: [1..7], 右: [9..16] / \ / \ (2) (6) (10) (14) <-- 依次细分 / \ / \ / \ / \ (1) (3)(5)(7) (9)(11)(13)(15) \ (16)
根 (8) :区间 [1..16]
,mid=8
[1..7]
→ mid=4[9..16]
→ mid=12左子树:
[1..7]
[1..3]
→ mid=2[5..7]
→ mid=6[1..3]
[1..1]
→ mid=1[3..3]
→ mid=3[5..7]
[5..5]
→ mid=5[7..7]
→ mid=7右子树:
[9..16]
[9..11]
→ mid=10[13..16]
→ mid=14[9..11]
[9..9]
→ mid=9[11..11]
→ mid=11[13..16]
[13..13]
→ mid=13[15..16]
→ mid=15
[15..14]
空,不生成节点[16..16]
→ mid=16最终我们可以看出:最底层叶子(单点区间)分别是
1, 3, 5, 7, 9, 11, 13, 16
以及其余也都是叶子,只不过在上面某些位置上成对出现(如 2 的左右孩子是 1 和 3,4 的左右孩子是 2 和 6……);其中 (15) 还带了一个右孩子 (16),因为 [15..16]
被继续一分为二。
如果想看得更清楚,可以把每个结点标注为“正要处理的 [low..high]
区间”而非仅仅 mid
:
text[1..16] / \ [1..7] [9..16] / \ / \ [1..3] [5..7] [9..11] [13..16] / \ / \ / \ / \ [1..1][3..3][5..5][7..7] [9..9][11..11][13..13][15..16] \ [16..16]
mid = (L+R)//2
;[1..16]
(mid=8)开始,将区间不断二分,一直到子区间长度变为 1 或空,形成了这棵深度为 左右的树结构(实际最深处会到 5 层左右,因为 16 是 )。[1..7]
);若目标 > 8,则进右子树([9..16]
);若刚好等于 8,就命中。这棵“折半查找生成树”不但能帮助理解二分搜索的递归结构,也能直观地看出查找路径是如何在对半分的区间中逐步收缩的。
请列出长度为10个元素的升序数组的所有折半查找判定树可能的样子,再给出9,11的
下面给出一个比较系统的说明,来回答这道题中包含的两个部分:
“请列出长度为 10 个元素的升序数组的所有折半查找判定树可能的样子”
这里所谓的“所有可能”,是指在区间长度为偶数时,可以有两种不同的“中点”选择(例如对区间 ,既可以取中点下标 5
也可以取 6
),从而导致不同形状的决策树。
“再给出 9,11 的…”
通常是指“若我们要查找元素值 9 或元素值 11(或者下标 9、11)时,在这些不同的判定树中,其搜索路径或比较次数如何”。下面会在示例中演示。
**折半查找(Binary Search)**在有序表上工作:给定一个区间 ,取某个“中点” mid
与目标值比较,若目标小于中点值则进入左子区间 ,若大于则进入右子区间 ,若等于则查找成功。对每个区间,都要决策“取哪个位置作为 mid”;对于长度为偶数的区间,有两种常见做法:
mid = floor((L+R)/2)
mid = ceil((L+R)/2)
(L+R)//2
与 (L+R+1)//2
的区别)在“折半查找判定树”中:
mid
进行比较),当区间长度为偶数时,如果我们允许“取左中点或右中点”两种做法,那么整棵决策树便可能出现分歧,从而形成多种不同形状的“判定树”。
先设定一个数组下标为 的升序区间,初始 low=1
, high=10
,长度 = 10(为偶数)。那么顶层就有 2 种可能的中点选择:
mid = 5
mid = 6
所以,从根开始就会分成两大类决策树。
mid=5
mid=8
),没有分歧。mid=6
mid=3
),无分歧。这样,我们在“根节点”就已经产生了 2 种可能;进入左或右子区间后,如果子区间长度为偶数,就再产生 2 种分支;如果为奇数,就只有 1 种中点。
为了真正把所有 “二分”决策树 列出来,需要沿着每个偶数长度的子区间分别画出“左中点 / 右中点”两棵子树,然后继续往下对其子区间递归……这会形成一个“分支大爆炸”的过程。下面给一个示例性展开,帮助理解如何 systematically 生成所有树。
提示:完全写出所有细节的最终大树,节点很多且层次深,往往需要画多张子图或采用“树形”+“文字递归”相结合的方式进行描述。这里我们给出核心方法和部分关键分支示例,不再逐一把所有终端叶子都写到最细。
mid=2
(左中点)mid=3
(右中点)mid=8
(无分歧)[1..4]
→ mid=2
[1..1]
→ 长度1,只有1个点(mid=1),到达叶[3..4]
→ 长度2为偶数 → 又可选 mid=3 或 mid=4(1a)-右子再分:
[3..2]
空,右子 [4..4]
为单点[3..3]
,右子 [5..4]
空[1..4]
→ mid=3
[1..2]
→ 长度2为偶数 → 又可选 mid=1 或 mid=2[4..4]
→ 单点(1b)-左子 [1..2]
再分:
[1..0]
空,右 [2..2]
单点[1..1]
单点,右 [3..2]
空[6..7]
→ 长度2 → 再有 2 种选法 (mid=6 或 mid=7)[9..10]
→ 长度2 → 再有 2 种选法 (mid=9 或 mid=10)所以仅在顶层 mid=5 的情况下,左区间 4 会带来 2 分叉,右区间 5 固定 mid=8 又会带来 2×2 = 4 种分叉。故整体就有 2 × (4) = 8 种不同的完整决策树(仅针对“顶层 mid=5”的大分支)。
[1..5]
→ 长度5,取 mid=3
(无分歧)[7..10]
→ 长度4 → 可能 mid=8
或 mid=9
[1..5], mid=3
[1..2]
→ 长度2 → 有 2 种选法 (mid=1 或 mid=2)[4..5]
→ 长度2 → 有 2 种选法 (mid=4 或 mid=5)故左区间部分会产生 2×2=4 种展开。
[7..10]
:[7..7]
单点,右 [9..10]
长度2 → 再 2 种[7..8]
长度2 → 2 种,右 [10..10]
单点所以在右区间 [7..10]
内,也会衍生 2(顶层选择) × 2(进一步长度2的选择) = 4 种。
合并:左区间 4 种 × 右区间 4 种 → 16 种完整决策树(仅针对“顶层 mid=6”的大分支)。
把上面两大类汇总:
因此,对长度为 10 的数组,若在每一个偶数长度的区间都允许“左中点 / 右中点”两种选择,则所有可能的折半查找判定树共有
当然,每一种里又要继续把所有长度 2 的子区间、4 的子区间等展开到叶子,就得到 24 棵最终完全展开的“决策树”。由于篇幅原因,一一画出会非常庞大。
题目最后一句“再给出 9,11 的…”通常理解为:
下面演示几个典型树的顶层对比;后续子区间同理。
查找 9
[6..10]
[6..10]
长度 5,mid=8
[9..10]
[9..10]
长度 2 → 如果这里再次选 mid=9,则
[9..9]
查找 11
[6..10]
[6..10]
, mid=8
[9..10]
[9..10]
, 长度2
[10..10]
[11..10]
空 → 未找到[11..10]
空 → 未找到
查找 9
[7..10]
[7..10]
, 长度4 → 假设 mid=8
[9..10]
[7..10]
里选 mid=9
查找 11
[7..10]
[7..10]
, 长度4 => mid=8 或 9
[9..10]
…[10..10]
…长度为10的升序数组,在“允许每个偶数子区间选左/右中点”时,确实会产生多棵不同形状的折半查找判定树。
针对“查找 9 或 11”,每一棵决策树会对应不同的比较路径、也就有不同的搜索深度或比较次数(可能在 2~4 次之间不等)。
小结:这道题主要是为了说明:
- 折半查找判定树取决于每次如何选择“mid”在偶数区间时的分歧;
- “列出所有可能的判定树”实际上可以用“递归 + 分支”系统生成,但展开后非常庞大;
- “查找某个给定值”在不同判定树中的搜索过程、比较次数可能不同,这就是代码实现中
(L+R)//2
还是(L+R+1)//2
等差异所带来的影响。
给出一个红黑树最长路径等于最短路径两倍的情况
下面给出一个最简单、但又“恰好让最长根到叶路径等于最短路径的两倍”的红黑树示例。这个小例子也常被用来说明红黑树高度最多是其“黑高(black‐height)”的两倍。
在红黑树中有一条重要性质:
对于一棵红黑树,任一路径上“红”节点都不能相邻,同时每条从根到外部 NIL 的路径都包含相同数量的黑节点(称为黑高 black-height)。
由此可以推出一条常见结论:
其直观含义是:
在极端情况下,最长路径恰好可以达到最短路径的 2 倍。
最简单能“实现最长=2×最短”的红黑树只需要 1 个根 + 1 个红孩子 + 若干 NIL 叶,即下图所示(假设左子为空,右子为红):
textRoot (Black) / \ NIL (Red) / \ NIL NIL
由此可见它完全满足红黑树的五条性质。
因此,这里
这正是“最长路径等于最短路径的两倍”的最小化例子。
在实际大型红黑树中,若“黑高 = h”,则最短根叶路径大约含“h”个黑节点(边数大约是 ),而最长根叶路径能插入最多的红节点(但不能两个红相连),使总边数最多增至 ,故出现“最短 、最长 ”的 2 倍关系。
上面这棵“小树”则是把“黑高=2”(数根和外部叶都黑)压缩到最小规模来演示,恰好出现“最短 1 → 最长 2”。再大的树只是在层数上扩张,但都遵循同样的规律。