C++——红黑树

作者:几冬雪来

时间:2023年12月7日

内容:C++——红黑树讲解

目录

前言: 

红黑树的概念: 

红黑树的性质:

红黑树的路径计算: 

最长路径和最短路径:

AVL树与红黑树的区别: 

红黑树的框架: 

红黑树插入节点: 

插入节点颜色的变化:

颜色变化的特殊情况: 

变色与旋转: 

判断是否为红黑树: 

代码: 

结尾: 


前言: 

在上一篇博客中我们讲解了在C++两棵重要的树中的一棵树,也就是AVL树。而另一棵树就是今天要对其讲解的红黑树。而红黑树也算C++板块的一个重要的知识点,在解题的时候经常被使用。

 

红黑树的概念: 

在上一篇博客讲解AVL树的时候就有谈到过,红黑树和AVL树都属于二叉搜索树的一个延伸,二者的做法差不多,但是其性质却不一样

这里的红黑树,顾名思义就是在每个结点上都增加一个储存为表示该节点的颜色,可以说红或者黑色。而对比起之前讲解的C++的AVL树,红黑树会在一定程度上更优一点。 

红黑树在一定程度上占优,这是因为AVL树过于严格了,它必须保证最高的结点减去最短结点后高度要小于2或者大于-2

这个地方红黑树的做法是,使得每个结点都有红黑两种颜色限制达到最长路径不超过最短路径的两倍

但是这种行为也有它的弊端,那就是对比AVL树,AVL树的平衡是几乎平衡而红黑树的平衡只是近似平衡而且,且在插入相同数量结点的情况下红黑树的高度要较高于AVL树

虽然红黑树的高度相较于AVL树要高,但是其效率却不输AVL树

红黑树的性质:

在讲解完了红黑树的概念之后,接下来就来说一说红黑树的性质

这里看一下红黑树的4个性质,第一个要求每个结点不是黑的就是红色的

接下来就是规定了根节点必须是黑色的

第三条性质讲解了,在红黑树中的任何路径没有连续的两个同为红色的结点

这个地方的最后一条说明了,每条路径都包含了相同数量的黑色结点

红黑树的路径计算: 

在上面最后一条性质讲了,红黑树的每条路径都包含了相同数量的黑色结点

那么这个地方就延伸出一个问题,红黑树要怎么去数它路径的多少,就以刚刚上边的那张红黑树的图为例:

这里路径数量的计算方式是,从根节点到空节点算一条路径,因此要算该红黑树有多少路径的话,最优的方法就是看它空节点的数量是多少

最长路径和最短路径:

在了解完了红黑树路径的计算后,接下来我们就来看看它的最长和最短路径是如何得出的。在AVL树中我们可以得知,最长路径和最短路径是相互限制的,最长路径不能超过最短路径的高度差不超过1

而在红黑树中最长路径和最短路径有了新的变化

首先红黑树的最短路径如上图的左子树,全为黑色结点的路径就为最短路径。而最长路径则是一红一黑相间的路径

AVL树与红黑树的区别: 

在初步了解完了红黑树之后,接下来就是要让红黑树与先前学习的AVL树做对比,对比二者的优缺点

首先就是二者的时间复杂度

从图中可以看出红黑树的时间复杂度是AVL树的两倍,但是先前我们又讲解过红黑树要优于AVL树,这是因为二者处于同一量级,对此一倍与两倍对编译器的效率影响可以忽略不计

话虽如此,但是AVL树看起来时间复杂度是比AVL树小的,同时前面也有说明AVL树是被严格控制平衡的,红黑树只是近似平衡

但是AVL树严格控制平衡会有一定的代价,那就是AVL树在插入和删除的时候需要进行大量的旋转操作,而红黑树对比AVL树不需要大量的旋转操作

红黑树的框架: 

和书写AVL一样,书写红黑树也要有一个它自己的框架,又因为红黑树不用动用到平衡因子,因此也就不需要一个整形来存放平衡因子的值,二者框架的代码也会略微不同。

在这个地方依旧的要创建一个左结点,一个右结点和一个父结点。而后再借用map的pair创建一个_kv

与AVL在这个地方创建一个整形类型的_bf来储存平衡因子不同,红黑树因为没有平衡因子所以不需要创建_bf来使用

但是红黑树在这里涉及到了结点的颜色,因此此处要用一个枚举函数枚举出RED和BLACK两种颜色

再然后对它们进行初始化,这里的col就从RED与BLACK中随机选一个颜色进行初始化即可。、 

红黑树插入节点: 

在书写完了框架之后,接下来就是红黑树节点的插入

因为红黑树和AVL树都是二叉搜索树的延伸,因此在书写插入代码的时候二者十分的相似,这里我们就可以使用CV操作,将AVL树插入的代码拷贝过来

但是在红黑树的性质那里有规定,根节点的颜色必须为黑色,因此当根节点为空第一次插入结点的时候,这里要将这个节点的颜色设计为黑色

而在插入首个节点,接下来每个新插入节点的颜色会带来一些问题,也就是我们它的节点颜色是红色还是黑色

要解决这个问题,这里需要返回到之前红黑树性质的4条规则那里

在这里着重要关注的是第3条与第4条规则

如果插入的结点颜色为黑色的话,这个地方可能会使得第四条规则违规,因为规则里面有说要保证每个路径的黑色节点的个数相同,要是增加的节点颜色为黑色的话,最终会导致该路径的黑节点的个数与其他路径的不一致

如果新插入节点为红色,那这个地方要保证期父节点的颜色不为红色

因此插入的新节点颜色只能为红色,并且要对新节点的父节点进行判断

插入节点颜色的变化:

在上文我们讲解到红黑树插入的新节点的颜色必然是红色,而且如果插入的新节点的父节点也会红色的话,这里就要进行节点变色的操作

首先就是将新插入的节点定义为cur,接下来将它父节点定义为parent同时将parent的父节点的另一边节点定义为uncle

如果新插入的节点cur与parent的颜色同为红色的话,这里就违反了规则不能有两个红色的节点连续

因此parent结点就需要更改为黑色,又因为在红黑树中每条路径黑色结点的数量必须都一样,所以uncle要换为黑色,同时也因为每条路径黑色节点的数量不能改变,所以uncle的父节点要换为红色,如果parent的父节点为红且不为根节点的话,我们就继续向上处理。 

因此这个地方就总结出来了几个情况: 

1.如果grandfather没有父亲,也就是grandfather为根,只需要将其变黑即可

2.如果grandfather有父亲,且它的父亲为黑色,则结束运行

3. 如果grandfather有父亲,它的颜色为红色,这里就要向上处理,cur为第一次修改的grandfather位置

颜色变化的特殊情况: 

在什么我们得知了插入节点后节点颜色变化的规律,插入的必然是红节点,如果其父节点与uncle也为红节点的话,就要将它与uncle节点都改为黑色

接下来再判断它们的grandfather的颜色与有无父节点来决定要不要继续向上处理

但是这里可能会出现一些特殊的情况

就如同上图,新添加的cur节点它的父节点为红色,但是这里并没有uncle节点

这样就意味着,这个地方我们不能直接去修改parent与grandfather的颜色,如果修改后就会和上图一样,有一条路径的黑色节点的个数变少了。

这个地方的另一种特殊情况则是增加节点导致了最长路径超过了最短路径的两倍,在这种情况下我们也不能只是单纯的对其进行换色操作

而且这里为了解决这些问题就要利用到AVL树中的旋转操作了,同时这里的旋转也是有单旋和双旋的做法

就拿最长路径超过了最短路径的两倍的特殊情况来举例,这个地方要使这棵错误的红黑树变为正常的红黑树

首先就是以值为25的结点为旋转点进行一次右旋的操作,使得最长路径与最短路径在两倍或者两倍以内,再旋转后才能判断后对节点进行上色操作

同样的红黑树没有uncle节点也是通过旋转再上色的操作完成的

因此这里我们就得出了一些特殊情况:

1.uncle节点不存在的情况

2.uncle节点存在,且该节点颜色为黑

3.uncle节点存在,且该节点颜色为红

同时也对上面特殊情况的处理做了总结:

1.如果uncle存在且为红,则进行变色操作后再向上处理

2.如果uncle不存在或者存在且为黑,则先进行旋转操作后再进行换色操作 

这里我们也能看出红黑树插入后的旋转或者变色操作,关键还是要看uncle。 

变色与旋转: 

在进行完了节点插入的操作,并且也清楚的了解了红黑树的节点颜色的一些规矩,并且也知道了红黑树的变色与旋转操作,关键是在于uncle节点的情况

但是使得上下两个连接的节点都呈现红色的情况不单单只有parent为红节点,cur为新插入的节点这一种操作而已

下来我们就来讲解在哪些情况会出现下上下两个连接节点都为红色的情况

在这里的c,d,e处如果是只有一个黑节点的局部树的话有4种节点形状的插入,外加上新插入的节点有4个位置能插入,因此能计算出来这里最后能有256种(4*4*4*4)插入的结果

并且从上图我们也可以看出来使得上下两个节点都变为红色的不止只有新插入节点的情况,也可能是新插入的节点使得grandfather变为红色,让grandfather和它的父节点颜色相同。 

因此红黑树在这里有无数种组合情况。 

知道红黑树在什么情况下要对其节点进行变色,在什么情况下要对其节点进行旋转操作后,接下来我们就来书写它的代码。

因为在红黑树中我们可能要不断的向上处理,首先就是一个循环判断新插入节点的父节点存不存在存在的同时它的颜色是不是为红色,只有同时满足两个条件才能进入循环

接下来就是确定父节点的父节点,接下来如果parent节点为grandfather节点的左边的话,这个地方我们就能确定uncle节点的位置

再下来还是判断,如果uncle节点存在且为红色的话,这个地方我们就将parent与uncle节点更改为黑色,再将grandfather节点更改为红色

如果第一次变色结束后修改后的parent节点不存在或者存在但是为黑的话,这里的做法就是将根节点的颜色改为黑色

接下来就是几种比较特殊的情况,上图这种就是uncle节点不存在的情况,这种情况就需要进行旋转操作

而下面的则是存在uncle,但是parent节点为红uncle节点的颜色为黑色的情况。在这种情况下cur节点不可能为新插入的节点,因为这会导致它们路径上的黑节点不同

与此同时,c处应该是有一个黑色节点的局部树,d与e两个位置可以为红节点也可以为空

并且这里旋转的代码可以从AVL树那边CV过来,唯一需要更改的就是将平衡因子删除

这个地方在原parent为grandfather的左节点的基本上进行判断,这里如果uncle不存在或者uncle节点为黑,对其进行分类讨论

如果parent为grandfather的左节点,cur也为parent的左节点,这里就像AVL树一样,以grandfather为旋转点进行一次右旋,而后将parent与grandfather的节点颜色进行更改

如果cur为parent的右节点,这里也是和AVL树一样进行双旋后再对节点颜色进行更改

在上面我们就以parent为grandfather的左节点写了变色和处理的办法

这个地方则继续写出当parent为grandfather的右节点的代码,其变化和旋转的方法不变,唯一变化的则是方向可能会发生改变

判断是否为红黑树: 

在书写完了红黑树之后,接下来就是一个重点知识——如何判断该树是否为红黑树,或者说改红黑树有没有错误

像书写AVL树一样,在代码的在最后我们也需要一个判断,判断在AVL树中它插入节点后的平衡因子会不会发生异常

同样的红黑树也要这个操作,不同与AVL的是红黑树并没有平衡因子,而它的判断的内容则是红黑树性质的4大规则

这里要判断的就是以上的规则:根节点是否为黑色,是否有两个红节点相连接,它每条路径的黑节点个数是否一样

在了解完了详情之后,接下来就是对代码的书写了。

首先就是进行基础的判断,如果插入后的红黑树它的根节点为空这里就返回true,如果颜色不为黑色,这个地方就返回false

接下来定义一个整形用来存储某条路径黑色节点的个数,这里选择的就是最左路径,统计它最左路径黑色节点的个数

然后上面的代码就跳转到这里,也是一样先看看根节点是否为空,然后再判断里面还需要判断路径的黑色节点数是否相同(递归结束回到根节点处)

如果root处的节点颜色为黑色,这里就对计算黑色节点的整形进行++操作

然后就是if条件判断是否会出现连续的红节点,如果这里的root存在且为红,root的parent存在且为红,这种情况就是出现了连续的红节点,就需要报错后返回false。(这里不能用parent为红,parent的cur节点也为红的条件来判断,因为这里可能存在空节点)

最后这里要递归root节点的左子树与右子树,递归到根节点的parent,回到开头判断各个路径黑色节点的个数是否相同。  

这个地方就是我们的红黑树的判断

代码: 

RBTree.cpp

#include "RBTree.h"int main()
{int a[] = { 16,3,7,11,9 };RBTree<int, int> t;for (auto e : a){t.Insert(make_pair(e, e));cout << "Insert:" << e << "->" << t._IsBalance() << endl;}return 0;
}

RBTree.h 

#pragma once
#include<iostream>
using namespace std;enum Colour
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}
};template<class K, class V>
struct RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED; cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else//	if (parent == grandfather->_right){Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;Node* ppnode = parent->_parent;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;Node* ppnode = parent->_parent;parent->_left = cur->_right;if (curright){curright->_parent = parent;}cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}bool CheckColour(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark){return false;}return true;}if (root->_col == BLACK){++blacknum; }if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << "出现连续红节点" << endl;return false;}return CheckColour(root->_left, blacknum, benchmark) && CheckColour(root->_right, blacknum, benchmark);}bool _IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr){return true;}if (root->_col != BLACK){return false; }int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++benchmark;}cur = cur->_left;}return CheckColour(root, 0, benchmark);}private:Node* _root = nullptr; 
};

结尾: 

到这里我们的红黑树代码的基本讲解就告一段落了,但是这并不意味着红黑树就结束了,在以后学习的日子中我们会学习然后对红黑树进行封装,如何用AVL树和红黑树去解决一下K,V问题,最后希望这篇博客能给各位带来一些帮助。 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

让你在组建企业级项目时手到擒来——浅谈各类常用工具和框架概述

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作…

分布式系统理论基础

目录 引言 CAP定理 CAP的工程启示 1、关于 P 的理解 2、CA非0/1的选择 3、跳出CAP 小结 本文转自&#xff1a;https://www.cnblogs.com/bangerlee/p/5328888.html 该系列博文会告诉你什么是分布式系统&#xff0c;这对后端工程师来说是很重要的一门学问&#xff0c;我们会逐步了…

Node.js版本管理工具NVM(Node Version Manager)的使用

nvm简介 nvm&#xff08;Node Version Manager&#xff09;是一个用于管理 Node.js 版本的工具。它可以让你在同一台计算机上安装并切换多个 Node.js 版本&#xff0c;非常方便。 如何安装 nvm 下载 nvm 安装包 访问 nvm下载地址 &#xff0c;根据你的操作系统选择对应的安…

开源,可商业化!性能比Stable Difusion强2.5倍

文生图平台Playground宣布开源Playground V2版本&#xff0c;允许商业化&#xff0c;用户通过文本就能生成3D、动漫、素描、朋克、暗黑等多种类型1024x1024图片&#xff0c;同时提供免费在线体验。 Playground V2是基于Stable Diffusion XL开发而成&#xff0c;还从Midjourney…

台灯应该买什么样的才能护眼?学生护眼必备护眼台灯推荐

10月26日&#xff0c;教育部召开新闻发布会&#xff0c;介绍综合防控儿童青少年近视工作情况。全国综合防控儿童青少年近视工作联席会议机制办公室主任、教育部体育卫生与艺术教育司司长王登峰介绍&#xff0c;2018年全国儿童青少年的总体近视率53.6%&#xff0c;2019年总体近视…

1.2 C语言简介

一、为什么要讲C语言 C语言是编程界的长青藤&#xff0c;可以查看语言排名发现&#xff0c;虽然现在语言很多&#xff0c;但是C语言一直占有一定地址 来源网站&#xff1a;https://www.tiobe.com/tiobe-index/ 在系统、嵌入式、底层驱动等领域存在一定的唯一性&#xff08;C语…

使用GPT-4V解决Pycharm设置问题

pycharm如何实现关联&#xff0c;用中文回答 在PyCharm中关联PDF文件类型&#xff0c;您可以按照以下步骤操作&#xff1a; 1. 打开PyCharm设置&#xff1a;点击菜单栏中的“File”&#xff08;文件&#xff09;&#xff0c;然后选择“Settings”&#xff08;设置&#xff09;。…

【目标检测】进行实时检测计数时,在摄像头窗口显示实时计数个数

这里我是用我本地训练的基于yolov8环境的竹签计数模型&#xff0c;在打开摄像头窗口增加了实时计数显示的代码&#xff0c;可以直接运行&#xff0c;大家可以根据此代码进行修改&#xff0c;其底层原理时将检测出来的目标的个数显示了出来。 该项目链接&#xff1a;【目标检测…

SQL注入漏洞的检测及防御方法

SQL注入&#xff08;SQL Injection&#xff09;是一种广泛存在于Web应用程序中的严重安全漏洞&#xff0c;它允许攻击者在不得到授权的情况下访问、修改或删除数据库中的数据。这是一种常见的攻击方式&#xff0c;因此数据库开发者、Web开发者和安全专业人员需要了解它&#xf…

tomcat控制台中文信息显示乱码

问题现象 我的tomcat版本是10.1版本。 在cmd下启动tomcat&#xff0c;会新打开控制台输出窗口&#xff1a; 控制台窗口输出的中文信息是乱码&#xff1a; 问题原因 产生这个问题的原因是&#xff1a;控制台窗口的编码和输出到控制台窗口的日志信息编码不一致。 查看tomc…

【开发技能】-解决visio交叉线(跨线)交叉点弯曲问题

问题 平时工作中使用visio作图时&#xff0c;经常会遇到交叉线在相交时会形成一个弯曲弓形&#xff0c;这十分影响视图效果。可以采用下面的方法消除弓形。 方法 第一步&#xff1a;菜单栏--设计---连接线 第二步&#xff1a;选中这条交叉线---点击显示跨线 最终问题得到解决…

【Java】实现顺序表基本的操作(数据结构)

文章目录 前言顺序表1、打印顺序表2、增加元素3、在任意位置增加元素4、判断是否包含某个元素5、查找某个元素对于的位置6、获取任意位置的元素7、将任意位置的元素设为value8、删除第一次出现的关键字9、获取顺序表长度10、清空顺序表总结 前言 在了解顺序表之前我们要先了解…

编织魔法世界——计算机科学的奇幻之旅

文章目录 每日一句正能量前言为什么当初选择计算机行业计算机对自己人生道路的影响后记 每日一句正能量 人生就像赛跑&#xff0c;不在乎你是否第一个到达尽头&#xff0c;而在乎你有没有跑完全程。 前言 计算机是一个神奇的领域&#xff0c;它可以让人们创造出炫酷的虚拟世界…

gpt3、gpt2与gpt1区别

参考&#xff1a;深度学习&#xff1a;GPT1、GPT2、GPT-3_HanZee的博客-CSDN博客 Zero-shot Learning / One-shot Learning-CSDN博客 Zero-shot&#xff08;零次学习&#xff09;简介-CSDN博客 GPT-2 模型由多层单向transformer的解码器部分构成&#xff0c;本质上是自回归模型…

【Android】查看keystore的公钥和私钥

前言&#xff1a; 查看前准备好.keystore文件&#xff0c;安装并配置openssl、keytool。文件路径中不要有中文。 一、查看keystore的公钥&#xff1a; 1.从keystore中获取MD5证书 keytool -list -v -keystore gamekeyold.keystore 2.导出公钥文件 keytool -export -alias …

vue+echarts实现桑吉图的效果

前言&#xff1a; 在我们项目使用图形的情况下&#xff0c;桑吉图算是冷门的图形了&#xff0c;但是它可以实现我们对多级数据之间数据流向更好的展示的需求&#xff0c;比如&#xff0c;我们实际数据流向中&#xff0c;具有1对多&#xff0c;多对多的情况下&#xff0c;如果用…

IDEA加载阿里Java规范插件

IDEA加载阿里巴巴Java开发手册插件&#xff0c;在写代码的时候会自动扫描代码规范。 1、打开Settings 2、打开Plugins 3、搜索Alibaba Java Code Guidelines&#xff08;XenoAmess TPM&#xff09;插件&#xff0c;点击Install进行安装&#xff0c;然后重启IDE生效。 4、鼠标右…

多人聊天Java

服务端 import java.io.*; import java.net.*; import java.util.ArrayList; public class Server{public static ServerSocket server_socket;public static ArrayList<Socket> socketListnew ArrayList<Socket>(); public static void main(String []args){try{…

大数据可视化项目——基于Python豆瓣电影数据可视化分析系统的设计与实现

大数据可视化项目——基于Python豆瓣电影数据可视化分析系统的设计与实现 本项目旨在通过对豆瓣电影数据进行综合分析与可视化展示&#xff0c;构建一个基于Python的大数据可视化系统。通过数据爬取收集、清洗、分析豆瓣电影数据&#xff0c;我们提供了一个全面的电影信息平台…

系统思考与啤酒游戏经营沙盘

结束一家汽车零配件公司《系统思考与啤酒游戏经营沙盘》的内训课&#xff0c;4个小组基本上都有共同的心智模式&#xff0c;这也代表团队有一些集体的盲点。不仅仅对啤酒游戏经营沙盘做了复盘&#xff0c;同时也借用学员画出的系统环路图完成真实案例的研讨以及团队共识&#x…