1. 前言

笛卡尔树是一种特殊的二叉树数据结构,融合了二叉堆二叉搜索树两大特性。笛卡尔树可以把数列(组)对象映射成二叉树,便于使用笛卡尔树结构的逻辑求解数列的区间最值或区间排名等类似问题。

如有数列 {5,7,1,9,12,16,2,18,3,11},任一存储单元格均有 2 个属性:

  • 值:单元格中的数据。
  • 位置:单元格在数列中的位置(下标、索引)。

构建笛卡尔树要求节点至少有 2 个权重,把数列映射成树结构时,可使用这2 个属性作为笛卡尔树节点的权重。把线性结构的数列映射成笛卡尔树后,此树需要满足如下 2 个特征:

  • 如果观察,则在树中具有堆的有序性,即任一父节点的值大于(最大堆)或小于(最小堆)子节点的值。根据堆的特性,可以查询笛卡尔树子树上的最大值或最小值。且可以使用堆排序算法排序数列。

  • 如果观察位置,则在树中遵循搜索树的逻辑结构,或者说,对笛卡尔树进行中序遍历,可以得到原来的数组。

2. 构建笛卡尔树

可以说笛卡尔树是双权重的节点树。那么,如何构建才能保证数列最终能映射成笛卡尔树。

实现过程中,需要借助单调栈数据结构。

什么是单调栈?

本质指的就是栈,但在存储数据时,需要要保持栈中数据按递增或递减顺序,本文构建最小堆,要求栈单调递减。

2.1 构建流程

现把数组 {5,7,1,9,12,16,2,18,3,11}构建成笛卡尔树的过程详细讲解一下:

  • 首先,准备一个栈。把数组中位置为 0、值为5 的元素(后面统一以(0,5)格式描述)。将其压入栈中,且设置成树的根节点。

  • 把数组中的(1,7)和栈中上已有元素(0,5) 比较,因值比 5 大。因能保持栈的递减顺序,可以直接入栈。且把入栈的元素作为(0,5)的右子节点。

    Tips: 至此,总结一下:能压入栈的元素作为栈中比它小的元素的右子节点。

  • 从数组中取出(2,1),因比栈顶元素(1,7)小 ,不能直接入栈,则需要把比它大的元素从栈中移走,然后把最后一个比它大的元素作为它的左子结点。如下图所示:

  • 从数组中取出(3,9)、(4,12)、(5,16),因都比先入栈的元素大,可以直接依次入栈,且成为比它小的节点的右子节点。

    Tips: 可以观察到,栈中的元素均为右链(图中绿色颜色所标记的节点)上的节点。

  • 从数组中抽出(6,2)元素,为了维持栈的递减顺序,则需要把比它大的元素从栈中移走,直到留下(2,1)。把(6,2)作为(2,1)的右子节点,且让最后一个出栈的(3,9)成为(6,2)的左子节点。

  • 继续拿出(7,18),可以直接入栈,让其成为(6,2)的右子节点。

  • 拿出(8,3)。把比它大的元素出栈,成为最近比它小的元素的右子结点,最后出栈的元素成为它的左子节点。

  • 从数组中最后拿出(9,11)元素。因比栈中所有元素大,直接入栈,且成为栈顶元素的右子节点。

2.2 小结

使用单调栈构建笛卡尔树的原则:

  • 如果需要移走其它元素后,元素才能入栈,则移走的最后一个节点成为其左子节点。
  • 当数据入栈时,需要在栈中找到比它小的第一个元素,让会成为此元素的右节点。
  • 栈中始终存储的最新右链上的节点。
  • 构建完毕后,最后栈底的元素为根节点。

3. 笛卡尔树的物理结构

二叉树的物理实现有矩阵链式 两种方案,本文将使用这 2 种方案:

3.1 矩阵实现

笛卡尔树类结构:

类中主要API为构建函数和中序遍历,其它的为辅助性函数。代码中有详细注解。

#include <iostream>
#include <stack>
using namespace std;
/*
*笛卡尔树类
*/
class DkeTree {private://原数列int nums[10]= {5,7,1,9,12,16,2,18,3,11};//矩阵,描述树节点之间的关系。列号 0 表示行编号对应节点的左节点,列号 1 表示右节点int matrix[10][2];//存储元素的下标stack<int> myStack;public:/**构造函数*/DkeTree() {//初始化关系为 -1for(int i=0; i<10; i++)for(int j=0; j<2; j++)this->matrix[i][j]=-1;}/** 数据入栈前的检查栈中是否有比自己大的元素* 参数 idx:元素在数组中位置* 返回最后一个比此数据大的元素编号*/int check(int idx);/** 构建笛卡尔树*/void buildDiKeErTree();/**中序遍历*/void inOrder(int node) ;/**返回根节点 :栈中最后不为 -1 的元素的编号*/int getRoot() {int pos=-1;int root=pos;while(!myStack.empty() && (pos= myStack.top()) !=-1 ) {myStack.pop();root=pos;}return root;}/**显示矩阵中节点之间的关系*/void showTree() {cout<<"父节点"<<" 左子节点 "<<" 右子节点"<<endl;for(int i=0; i<10; i++ ) {cout<<"("<<i<<","<<nums[i]<<")->";for(int j=0; j<2; j++) {if(matrix[i][j]!=-1 )cout<<"("<<matrix[i][j]<<","<< nums[ matrix[i][j] ]  <<")\t";elsecout<<"("<<matrix[i][j]<<","<< -1 <<")\t";}cout<<endl;}}
};

实现 buildDiKeErTree 函数:

buildDiKeErTree 函数是核心API,用来构建笛卡尔树。

实现此API之前,需先实现辅助API check,用来实现把数组中的数据压入栈中时,查询到比之大的最后一个数据。本质是使用了单调栈的逻辑特性。

int DkeTree::check(int idx) {int pos=-1;//最后一个比之大的下标int prePos=pos;while( !DkeTree::myStack.empty() && (pos=DkeTree::myStack.top())!=-1 ) {if( DkeTree::nums[pos]<DkeTree::nums[idx] ) break;DkeTree::myStack.pop();prePos=pos;}return prePos;
}

buildDiKeErTree 函数:

void DkeTree::buildDiKeErTree() {//初始栈,压入一个较小的值,方便比较DkeTree::myStack.push( -1 );int size=sizeof(DkeTree::nums)/4;//遍历数组for( int i=0; i<size; i++ ) {int topIdx=DkeTree::myStack.top();//设置为栈顶元素的右子节点if(DkeTree::nums[topIdx]<DkeTree::nums[i] && topIdx!=-1) DkeTree::matrix[topIdx][1]=i;//从栈中移走比当前元素大的元素int leftIdx=DkeTree::check(i);if(leftIdx!=-1) {//置为左节点DkeTree::matrix[i][0]=leftIdx;if(DkeTree::myStack.top()!=-1)DkeTree::matrix[DkeTree::myStack.top()][1]=i;}//栈中存储的是数据在数组中的索引号DkeTree::myStack.push(i);}
}

测试:

//测试
int main(int argc, char** argv) {DkeTree*  dkeTree=new DkeTree();dkeTree->buildDiKeErTree();dkeTree->showTree();return 0;
}

输出结果:-1表示不存在左或右子节点。

因中序遍历笛卡尔树可以输出原数组,可用于验证笛卡尔树的构建是否正确。在DkeTree类中可添加中序遍历函数的实现。

void DkeTree::inOrder(int node) {if( node!=-1 ) {//遍历左树inOrder(DkeTree::matrix[node][0] );cout<<DkeTree::nums[node]<<"\t";inOrder(DkeTree::matrix[node][1] );}
}

测试中序遍历:

int main(int argc, char** argv) {DkeTree*  dkeTree=new DkeTree();dkeTree->buildDiKeErTree();int root=dkeTree->getRoot();dkeTree->inOrder(root);return 0;
}

输出结果:

3.2 链式实现

链式实现逻辑与矩阵实现本质没有什么不一样。不做过多解释,直接上完整代码。

#include <iostream>
#include <stack>
#include <vector>
using namespace std;
/*
*节点类型
*/
struct TreeNode {//编号int idx;//值int val;//左子节点TreeNode* leftChild;//右子节点TreeNode* rightChild;TreeNode() {this->leftChild=NULL;this->rightChild=NULL;}TreeNode(int idx,int val) {this->idx=idx;this->val=val;this->leftChild=NULL;this->rightChild=NULL;}void show() {cout<<"("<<this->idx<<","<<this->val<<")";}
};
/*
*笛卡尔树类
*/
class DkeTree {private://根节点TreeNode* root;//原数列int nums[10]= {5,7,1,9,12,16,2,18,3,11};//存储元素的下标stack<int> myStack;//存储树上的所有节点vector< TreeNode*> allNodes;public:/**构造函数*/DkeTree() {}/**  栈中查找*/int check(int idx) {int pos=-1;int prePos=pos;while( !myStack.empty() && (pos=myStack.top())!=-1 ) {if( nums[pos]<nums[idx] ) break;myStack.pop();prePos=pos;}return prePos;}/** 检查树上是否存在给定的节点* 有:返回* 没有:创建*/TreeNode* findNode(int idx ) {for(int i=0; i<this->allNodes.size(); i++ ) if( this->allNodes[i]->idx==idx )return this->allNodes[i];TreeNode* tmp=new TreeNode(idx,nums[idx]);this->allNodes.push_back(tmp);return tmp;}/**构建笛卡尔树 */void buildDiKeErTree() {//初始栈,压入一个较小的值,方便比较myStack.push( -1 );int size=sizeof(this->nums)/4;TreeNode* node=NULL;TreeNode* node_=NULL;//遍历数组for( int i=0; i<size; i++ ) {int topIdx=myStack.top();//设置为栈顶元素的右子节点if(nums[topIdx]<nums[i] && topIdx!=-1 ) {node=this->findNode(topIdx);node_=this->findNode(i);node->rightChild=node_;}//从栈中移走比当前元素大的元素int leftIdx=check(i);if(leftIdx!=-1) {//置为左节点node_=this->findNode(leftIdx);node=this->findNode(i);node->leftChild=node_;if(myStack.top()!=-1) {node_=this->findNode(myStack.top());node=this->findNode(i);node_->rightChild=node;}}myStack.push(i);}}/**返回根节点 :栈中最后不为 -1 的元素的编号*/TreeNode* getRoot() {int pos=-1;int root=pos;while(!myStack.empty() && (pos= myStack.top()) !=-1 ) {myStack.pop();root=pos;}return this->findNode(root);}/**中序遍历*/void inOrder(TreeNode* node) {if( node!=NULL ) {//遍历左树inOrder(node->leftChild );cout<<node->val<<"\t";inOrder(node->rightChild );}}void showTree() {for(int i=0; i<this->allNodes.size(); i++ ) {this->allNodes[i]->show();cout<<"->";if(this->allNodes[i]->leftChild!=NULL)this->allNodes[i]->leftChild->show();if(this->allNodes[i]->rightChild!=NULL)this->allNodes[i]->rightChild->show();cout<<endl;}}
};
/*
*测试
*/
int main(int argc, char** argv) {DkeTree* dke=new DkeTree();dke->buildDiKeErTree();cout<<"显示所有节点"<<endl;dke->showTree();TreeNode* root= dke->getRoot();cout<<"中序遍历"<<endl;dke->inOrder(root);return 0;
}

4. 总结

本文讲解了笛卡尔树,对其特性作了较全面的分析,并且讲解了矩阵和链式两种实现方案。

本文同时收录在“编程驿站”公众号。

C++ 树进阶系列之笛卡尔树的两面性相关推荐

  1. 牛客 - sequence(笛卡尔树+线段树)

    题目链接:点击查看 题目大意:给出一个长度为 n 的数列 a 和数列 b ,求 题目分析:不算难的题目,对于每个 a[ i ] 求一下贡献然后维护最大值就好,具体思路就是,先找出每个 a[ i ] 左 ...

  2. 笛卡尔树 (25 分)笛卡尔树是一种特殊的二叉树,其结点包含两个关键字K1和K2。首先笛卡尔树是关于K1的二叉搜索树,即结点左子树的所有K1值都比该结点的K1值小,右子树则大。其次所有结点的K2关键字

    立志用最少的代码做最高效的表达 笛卡尔树是一种特殊的二叉树,其结点包含两个关键字K1和K2.首先笛卡尔树是关于K1的二叉搜索树,即结点左子树的所有K1值都比该结点的K1值小,右子树则大.其次所有结点的 ...

  3. 技术情报局(笛卡尔树)

    problem 有这样一道简单题:给定一个序列求所有区间的最大值的和. 还有这样一道简单题:给定一个序列求所有区间的乘积的和. 众所周知:简单题 + 简单题 = 简单题. 所以,给定一个长为 nnn ...

  4. 笛卡尔树详解带建树模板及例题运用(Largest Submatrix of All 1’s,洗车 Myjnie,Removing Blocks,SPOJ PERIODNI)

    文章目录 笛卡尔树 介绍 例题 Largest Submatrix of All 1's 应用 「POI2015」洗车 Myjnie [AGC028B] Removing Blocks SPOJ PE ...

  5. 笛卡尔树简介(分类到treap里面)

    笛卡尔树结构由Vuillmin在解决范围搜索的几何数据结构问题时提出的,从数列中构造一棵笛卡尔树可以线性时间完成,需要采用基于栈的算法来找到在该数列中的所有最近小数.由此可知,笛卡尔树是一种特定的二叉 ...

  6. 【upc】扶桑号战列舰 | 笛卡尔树 、 差分数组

    题目描述 众所周知,一战过后,在世界列强建造超无畏级战列舰的竞争之中,旧日本海军根据"个舰优越主义",建造了扶桑级战列舰,完工时为当时世界上武装最为强大的舰只. 同时,扶桑号战列舰 ...

  7. 7-31 笛卡尔树 (25 分)

    7-31 笛卡尔树 (25 分) 笛卡尔树是一种特殊的二叉树,其结点包含两个关键字K1和K2.首先笛卡尔树是关于K1的二叉搜索树,即结点左子树的所有K1值都比该结点的K1值小,右子树则大.其次所有结点 ...

  8. HDU - 6305 RMQ Similar Sequence(笛卡尔树)

    http://acm.hdu.edu.cn/showproblem.php?pid=6305 题目 对于A,B两个序列,任意的l,r,如果RMQ(A,l,r)=RMQ(B,l,r),B序列里的数为[0 ...

  9. [算法学习] 线段树,树状数组,数堆,笛卡尔树

    都是树的变种,用途不同 [线段树 Interval Tree] 区间管理,是一种平衡树 可看做是对一维数组的索引进行管理.一维数组不需要是排序好的 深度不超过logL 任一个区间(线段)都分成不超过2 ...

最新文章

  1. 一口气看完45个寄存器,CPU核心技术大揭秘
  2. python取列表中最接近某值的元素及索引
  3. 全球充电最快手机:5分钟回血50%;华为未发布新手机 | MWC 2022
  4. 帝国cms后台编辑时日期显示保存使用时间戳的方法
  5. 在 Element-UI 的 Table 组件上添加列拖拽效果
  6. 会考计算机考试vb知识点,高中会考计算机vb知识点.doc
  7. Form表单中method=post/get'的区别
  8. 提高生产力,最全 MyBatisPlus 讲解!
  9. 新买的手机一直没有更新系统,是不是新买的手机原装系统最好用啊?
  10. ENVI高光谱物质识别
  11. 数据结构笔记(三十五)--排序概念
  12. 《How to Reshape Input Data for Long Short-Term Memory Networks in Keras》学习笔记
  13. python约瑟夫环问题_约瑟夫环问题的Python实现
  14. Android Studio下JUnit单元测试
  15. Visual C++ 6.0的三个问题---尚未完成安装 MSDEV.EXE 应用程序错误 缺少动态链接库文件
  16. 创建一个 C++ 控制台应用程序项目
  17. jpg格式如何转eps路径_jpg/png格式图片转eps格式的方法总结
  18. 华为太极magisk安装教程_【玩机必会技能】小米手机通用刷TWRP RECOVERY|ROOT|刷第三方ROM教程...
  19. Prometheus+Grafana监控安装及配置JVM实现企业微信告警
  20. python最简单的图形编程_图形化编程、Python、Java、C++到底哪个适合你?

热门文章

  1. 揭开常见病毒免杀手法的面纱(浅论杀或者不杀.第二篇)
  2. c++实现dll注入其它进程
  3. 【深度学习】卷积神经网络(CNN)
  4. android收款语音播报+个推远程通知、透传推送 by:nixs
  5. python自动投注软件_能帮我开发一个自动投注的脚本吗?
  6. 不会数据分析?无从下手?一文帮你打开数据分析思路
  7. 计算机屏幕不清晰,怎么解决电脑屏幕模糊、屏幕显示不清晰
  8. Python入门:对Excel数据处理的学习笔记【第五章】列表类型处理技术
  9. 高德拉特的TOC制约理论—《可以量化的管理学》
  10. Qt QSetting 生成*.ini.lock和*.ini.shn126阻塞程序的问题