7-31 笛卡尔树 (25 分)

笛卡尔树是一种特殊的二叉树,其结点包含两个关键字K1和K2。首先笛卡尔树是关于K1的二叉搜索树,即结点左子树的所有K1值都比该结点的K1值小,右子树则大。其次所有结点的K2关键字满足优先队列(不妨设为最小堆)的顺序要求,即该结点的K2值比其子树中所有结点的K2值小。给定一棵二叉树,请判断该树是否笛卡尔树。

输入格式:
输入首先给出正整数N(≤1000),为树中结点的个数。随后N行,每行给出一个结点的信息,包括:结点的K1值、K2值、左孩子结点编号、右孩子结点编号。设结点从0~(N-1)顺序编号。若某结点不存在孩子结点,则该位置给出−1。
输出格式:
输出YES如果该树是一棵笛卡尔树;否则输出NO.
输入样例1:

6
8 27 5 1
9 40 -1 -1
10 20 0 3
12 21 -1 4
15 22 -1 -1
5 35 -1 -1

输出样例1:

YES

输入样例2:

6
8 27 5 1
9 40 -1 -1
10 20 0 3
12 11 -1 4
15 22 -1 -1
50 35 -1 -1

输出样例2:

NO

分析:
这题考的是二叉搜索树和堆的概念,在二叉搜索树中,若它的左子树或右子树不为空的话,它要大于左子树的最大值和右子树上的最小值,因为这里我们不知道所给K1的符不符合二叉搜索树,所以我们不仅要和左右儿子比较大小,还要和属于左子树中最大值的位置上的节点和右子树中最小值的位置比较大小(防止出现K1的每个子树都满足二叉搜索树条件,但整棵树不满足二叉搜索树条件的情况)。堆的话,验证起来相对简单很多,就与左右子树比较大小就行。
然后还有一个可以姑且说为难一点点(其实就是生疏)的地方就是,根据用户输入的大小来动态申请个二维数组的空间。在这里用二级指针就可以做到,其本质就是套娃(我申请了空间来存放一级指针,而一级指针又去申请存放它所能指的类型的空间)。

我的代码:

#include<stdio.h>
#include<stdlib.h>
#define K1 0
#define K2 1

int** CreateTree(int Size);
void Put(int**Tree,int Size);
int Judge(int**Tree,int Size);
int GetMax(int**Tree,int LT);
int GetMin(int**Tree,int RT);

int main(void)
{
	int Size,result;//二维数组的行数,其是否是笛卡尔树的结果 
	int** Tree;//一个待会儿用来创建二维数组的二级指针 
	scanf("%d",&Size);
	Tree=CreateTree(Size);//创建好二维数组并返回其首地址 
		
	Put(Tree,Size);//放入节点 
   result=Judge(Tree,Size);//判断是否符合条件 
   
   if(result) printf("YES");
   else printf("NO");
   
	return 0;
}

/*用二级指针创建二维数组*/
int** CreateTree(int Size)
{
	int** Tree;
	Tree=(int**)malloc(sizeof(int*)*Size);//此时已经创好一个存放一级指针的数组 
	
	for(int i=0;i<Size;i++)//每个一级指针都分别指向一个大小相同的数组 
		Tree[i]=(int*)malloc(sizeof(int)*4); 
		
		return Tree;//将首地址返回 
}

/*放入节点数据*/
void Put(int**Tree,int Size)
{
	int Data;
	/*挨个录入每个节点的数据*/ 
	for(int i=0;i<Size;i++)
		for(int j=0;j<4;j++)
	        {
	        	scanf("%d",&Data);
	        	Tree[i][j]=Data;
			}
}

/*判断该树是否是笛卡尔树*/
int Judge(int**Tree,int Size)
{
	int result=1;//先假设该树是笛卡尔树 
	for(int i=0;i<Size;i++)//将节点挨个取出来判断 
	{
		int L,R,Max,Min;//左子树的位置,右子树的位置,左子树中的最大值,右子树中的最小值 
		L=Tree[i][2];//取出该节点左子树的位置 
		R=Tree[i][3];//取出该节点右子树的位置 
		if(L!=-1&&R!=-1)//如果这个节点左右子树都不为空 
		{
			Max=GetMax(Tree,L);//先获得左子树中的最大值
			Min=GetMin(Tree,R);//再获得右子树中的最小值 
			if(Tree[i][K1]<Tree[L][K1]||Tree[i][K1]>Tree[R][K1]||Max>Tree[i][K1]||Tree[i][K2]>Min)//先看看它K1值符不符合二叉搜索树的要求 
			{
				result=0;
				break;
			}
			if(Tree[i][K2]>Tree[L][K2]||Tree[i][K2]>Tree[R][K2])//再看看它K2值符不符合最小堆的要求 
			{
				result=0;
				break;
			}
		}
		else if(L==-1&&R!=-1)//如果该节点左子树空而右子树不空 
		{
			Min=GetMin(Tree,R);//获得该节点右子树的最小值 
			if(Tree[i][K1]>Tree[R][K1]||Tree[i][K2]>Tree[R][K2]||Tree[i][K2]>Min)//看看它同时符不符合搜索树和最小堆的要求 
			{
				result=0;
			     break;
			}
		} 
		else if(R==-1&&L!=-1)//如果该节点右子树空而左子树不空 
		{
			Max=GetMax(Tree,L);//获得该节点左子树的最大值 
			if(Tree[i][K1]<Tree[L][K1]||Tree[i][K2]>Tree[L][K2]||Max>Tree[i][K1])//看看它同时符不符合搜索树和最小堆的要求 
			{
				result=0;
			       break;
			}
		}
	}
	return result;//返回判断结果 
}

/*获得树的最大值*/
int GetMax(int**Tree,int LT)
{
	int Max;
	while(Tree[LT][3]!=-1)//若右子树不为空,一直往右边走 
		LT=Tree[LT][3];
		
	Max=Tree[LT][0];//取出最大值并返回 
	return Max;
}

/*获得树的最大值*/
int GetMin(int**Tree,int RT)
{
	int Min;
	while(Tree[RT][2]!=-1)//若左子树不为空,一直往左走 
	 RT=Tree[RT][2];
	 
	 Min=Tree[RT][1];//取出最小值并返回 
	 return Min;
}

我的代码里有几段看得很不舒服(ŎдŎ;)
但我现在还没有想到该如何优化他,
完了,又是失眠夜。。。。(눈‸눈)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于用 C 语言建立笛卡尔树求解区间最值问题,我可以给你一些基本的思路和代码实现。 首先,笛卡尔树是带权值的,其性质由下面的定理给出: 定理1:一个序列可以对应唯一的一棵笛卡尔树。 定理2:设在笛卡尔树上节点 u 和 v 的先后顺序为“按下标增大的顺序,在这个前提下按权值减小的顺序”,则 u 到 v(包括 u 和 v)路径上的点在原序列中构成了一个区间。 基于这两个定理,我们就可以使用笛卡尔树来解决区间最值问题,其基本思路如下: 1. 输入一个序列 a[N],构建对应的笛卡尔树。 2. 对于每个节点 u,记录其子中最小值或最大值(根据具体问题而定)。 3. 对于每个询问,找到笛卡尔树上 u 和 v 两个节点,使得它们对应的区间包含询问区间 [L,R],那么 [L,R] 中的最值就是从 u 到 v 路径上的最小值或最大值(可以使用 st 表等数据结构进行维护)。 下面是用 C 语言实现笛卡尔树的基本代码,可以参考一下: struct Node { int val, idx; int fa, ch[2]; } tree[N]; int root, idx; int build(int l, int r) { // 构建笛卡尔树并返回根节点编号 if (l > r) return -1; int x = l; for (int i = l + 1; i <= r; i++) if (tree[i].val < tree[x].val) x = i; tree[x].ch[0] = build(l, x - 1); tree[x].ch[1] = build(x + 1, r); if (tree[x].ch[0] != -1) tree[tree[x].ch[0]].fa = x; if (tree[x].ch[1] != -1) tree[tree[x].ch[1]].fa = x; return x; } 现在我回答了你的问题,如果您有任何其他问题,可以随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值