关于树的练习(一)

1. 不同的二叉搜索树(中等)

LeetCode题号:96
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

示例

输入: 3
输出: 5

解释

给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

  1         3     3      2      1
   \       /     /      / \      \
    3     2     1      1   3      2
   /     /       \                 \
  2     1         2                 3
题解

这题……感觉主要是用了二叉搜索树的定义,如果不用按序输出所有二叉搜索树的话,关于树的成分就更少了。
先说下二叉搜索树,基本原则是:对于一个节点来说,左子树任意节点的值都比它的值小,而右子树任意节点的值都比它的值大。建立时就是小的值向左分流,大的值向右分流,空节点可以存储任一流过来的数值。
纸上画了n=1,2,3,4的情形,看到4作为根节点时,子树的情况跟n=3时一样。想到这题可以分解问题直到最简情形求解,典型的递归 / 递推
其实这也是有实现基础的。
1,2,…,n是有序的,如果我们选定一个数作为当前根,那么左侧的数自然进入左子树,右侧的数自然进入右子树。当前根的子节点又是子树的新的“当前”根。
(问题可分解性)
实际操作为:k从1到n遍历,分别计算以k为根的情形数,累加。
以k为根的情形数=(n=k-1)的情形数×(n=n-k)的情形数。
1,2,…,n是连续的,如果我们选定一段建构成树,那么情形等价于以1为起点的连续数的建构成树。
(解法可移植性)
实际操作为:(k+1)->n等价于1->(n-k)
思路是综合各方面得出来的,最后选用了递归去处理,另加了记忆化去优化时间。其实也可以用递推去做。

源代码
#include<stdio.h>
long long mem[1005],n;//其实不必开这么大
long long s(long long n)
{
	if(mem[n]) return mem[n];//记忆化处理
	if(n==0) return mem[0]=1;//初值
	for(int i=1;i<=n;i++)
	mem[n]+=s(i-1)*s(n-i);//原理式,递归并累加
	return mem[n];
}
int main(void)
{
	scanf("%lld",&n);
	printf("%lld",s(n));
	return 0;
}

如果我们再考虑一下树的输出……还没想好……orz

2. 对称二叉树(简单)

LeetCode题号: 101
给定一个二叉树,检查它是否是镜像对称的。

示例

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
  /  \
 2    2
/ \  /  \
3  4 4  3

但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

     1
   /   \
  2     2
 /  \  /  \
    3      3
题解

问题的核心大概在于处理不同父节点的子节点间的比较,要求指针同时分别指向两个对称位置的节点。
单纯观察判断较为容易,从根节点向“下”检查即可。
由此转向算法分析,树的路径只有向下的单向路,从根节点向下,左右指针做对称的移动,左指针向右则右指针向左。
考虑到从根节点开始,问题处理的相似性(比较左右指针所指的对称节点,再比较左->左,右->右,比较左->右,右->左),采用递归的方法遍历,判断是否镜像对称并返回BOOL值。

源代码
/*
 Definition for a binary tree node.
 struct TreeNode {
     int val;
     struct TreeNode *left;
     struct TreeNode *right;
 };
 //LEETCODE提供的树节点,单向指向子树
 */

bool isSymmetric(struct TreeNode* root){
//只能处理一个节点,即为根节点。新定义一个处理两个节点的函数,用来向下对称遍历。
    if(root==NULL) return true;//空树对称
    return check(root->left,root->right);//开始左右对称检查
}
//用了int是因为bool不知为啥编译没过
int check(struct TreeNode* left,struct TreeNode* right)
{
    if(left==NULL&&right==NULL) return true;//皆空,对称
    if(left==NULL&&right!=NULL||left!=NULL&&right==NULL) return false;//半空,不对称
    if(left->val!=right->val) return false;//均不空时取值才有意义
    return check(left->left,right->right)*check(left->right,right->left);//对称向下移动指针检查
    //真假值1和0,×来代替&
}

3.恢复二叉搜索树(困难)

LeetCode题号: 99
二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。

示例
1

输入: [1,3,null,null,2]

   1
  /
 3
  \
   2

输出: [3,1,null,null,2]

   3
  /
 1
  \
   2
2

输入: [3,1,4,null,null,2]

  3
 / \
1   4
   /
  2

输出: [2,1,4,null,null,3]

  2
 / \
1   4
   /
  3
题解

开始理解二叉搜索树不深,算是没什么思路。想过递归深搜,找到错误节点之后回头去找到另一个错误节点,交换数值。但是这棵树没有回头路,实现这个功能也不容易。
另一个想法是,仍然递归深搜,添加两个参数,一个是当前节点的父节点的值,一个是左/右节点(应该存储比父节点小/大的值)。搜到错误的节点之后存到一个容量为2的栈里面,再遍历一遍修复。但是有个问题(个人没有暂时解决):没法确认大小错序的两个数是谁错了。如示例1,2和3大小错序,但其实是3错了,应该由3和1交换。如果处理不好肯定是不行的。而如果回溯或者继续深搜去检查,最糟糕的情况是从一端找到另一端,一个错误节点的两个边构成的节点对都要判断。判断的准则也很乱。写不出来。(一方面是本人想不清楚这个的处理方法,可能这个处理方案本身就不够有效)
场面一度十分尴尬然后跑去评论区白嫖借鉴。看到“中序遍历”恍然大悟(虽然现在不是最优解):一棵正确的二叉搜索树进行中序遍历,访问的节点的值是有序递增的。
无聊的说教时间:我们要先打好基础然后好好做题,知识体系是互通的……orz。不仅是树,特殊的遍历方法本身的特性就可以在一定程度上简化算法的实现,或者影响解题的实际思路。
下面讲下本菜鸡写的代码:
第一次中序遍历,数值存到栈里面,转存到一个新的栈然后排序,第二次中序遍历中修正。
实际操作中,修正的方式有两种:

1.找出错误节点并交换值

这个思路非常直白,找错误节点的判断原则不太容易,但是一共只有两个错误节点,也就是在有序的数组中,这两个节点的值互换了位置,只要把排序前后的数组元素一一比较就可以得出错误交换的值。
再次遍历(其实无所谓哪种顺序了,遍历就完事了)
错误经历

  1. 误以为存储的值就是1,2,…,n,直接比较tre[i]!=i,试图省掉排序,然后被数据打脸……
  2. 交换数值的代码直接两个if,没有打else,然后这个if满足了,改过去,结果下个if又满足了,又改回来,自爆行为……
2.直接全部按排序后数组全部赋值

之前考虑了有序性,只要这次遍历还是中序遍历,仍然会有序访问节点,此时直接把排序之后的值按遍历顺序赋给各个节点即可,相当于新建一个正确的二叉搜索树,错误的节点也就被更正了。

提交结果都是24ms,11.6MB左右。
数组开的大小没影响这个内存。
直接赋值,不再去遍历对照排序前后的数组找错误节点,结果也没影响运行时间。
迷惑…orz…

源代码
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };//又是单向的节点
 */

int top,tre[1005],soret[1005],temp;//,t[2],te;
//栈指针,栈,转存栈,冒泡的临时变量,错误节点栈,栈指针
void recoverTree(struct TreeNode* root){
//    te=0;//指针初始化
    top=1;    //其实可以赋0的,后面再解释
//    tre[0]=-1e9;//试图直接比较得出错误节点然后弃疗的痕迹
    visit(root);//第一次中序遍历
//    tre[top]=1e9;//试图直接比较得出错误节点然后弃疗的痕迹
//       if(tre[i]!=i)//错误经历
//           t[te++]=i;
    for(int i=1;i<top;i++)
    soret[i]=tre[i];//转存
    for(int i=1;i<top;i++)
        for(int j=i+1;j<top;j++)
            if(soret[i]>soret[j])
            {
                temp=soret[i];
                soret[i]=soret[j];
                soret[j]=temp;
            }//不知道LEETCODE提供了啥样的sort,渣渣的冒泡
//    for(int i=1;i<top;i++)
//    if(tre[i]!=soret[i])
//        t[te++]=tre[i];//找错误节点的思路
    top=1;//重置栈指针
    c_visit(root);//修复,另写了一个中序遍历
    return;
}
void visit(struct TreeNode* rot){
    if(rot->left==NULL&&rot->right==NULL)//无子树
    {
        tre[top++]=rot->val;//进栈
        return;
    }
    if(rot->left!=NULL)//存在则访问
        visit(rot->left);
    tre[top++]=rot->val;//左-中-右的中序遍历
    if(rot->right!=NULL)//存在则访问
        visit(rot->right);
    return;
}
void c_visit(struct TreeNode* rot){
//    if(rot->val==t[0]) rot->val=t[1];//修正错误节点
//    else if(rot->val==t[1]) rot->val=t[0];//当初忘了else的地方
    if(rot->left!=NULL) c_visit(rot->left);
    rot->val=soret[top++];//直接赋值更正
    if(rot->right!=NULL) c_visit(rot->right);
    return;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值