112 - Tree Summing

Background

LISP was one of the earliest high-level programming languages and, with FORTRAN, is one of the oldest languages currently being used. Lists, which are the fundamental data structures in LISP, can easily be adapted to represent other important data structures such as trees.

This problem deals with determining whether binary trees represented as LISP S-expressions possess a certain property.

The Problem

Given a binary tree of integers, you are to write a program that determines whether there exists a root-to-leaf path whose nodes sum to a specified integer. For example, in the tree shown below there are exactly four root-to-leaf paths. The sums of the paths are 27, 22, 26, and 18.

picture25

Binary trees are represented in the input file as LISP S-expressions having the following form.

 
empty tree 		 ::= 		 ()

tree ::= empty tree tex2html_wrap_inline118 (integer tree tree)

The tree diagrammed above is represented by the expression (5 (4 (11 (7 () ()) (2 () ()) ) ()) (8 (13 () ()) (4 () (1 () ()) ) ) )

Note that with this formulation all leaves of a tree are of the form (integer () () )

Since an empty tree has no root-to-leaf paths, any query as to whether a path exists whose sum is a specified integer in an empty tree must be answered negatively.

The Input

The input consists of a sequence of test cases in the form of integer/tree pairs. Each test case consists of an integer followed by one or more spaces followed by a binary tree formatted as an S-expression as described above. All binary tree S-expressions will be valid, but expressions may be spread over several lines and may contain spaces. There will be one or more test cases in an input file, and input is terminated by end-of-file.

The Output

There should be one line of output for each test case (integer/tree pair) in the input file. For each pairI,T (I represents the integer, T represents the tree) the output is the string yes if there is a root-to-leaf path in T whose sum is I and no if there is no path in T whose sum is I.

Sample Input

22 (5(4(11(7()())(2()()))()) (8(13()())(4()(1()()))))
20 (5(4(11(7()())(2()()))()) (8(13()())(4()(1()()))))
10 (3 
     (2 (4 () () )
        (8 () () ) )
     (1 (6 () () )
        (4 () () ) ) )
5 ()

Sample Output

yes
no
yes
no

本文将从别人的代码和对别人代码思想的解读展开。一步一步理解问题的实质。
包含以下内容:
一.转载自他人博客的代码段
二.自己的理解分析
三.自己的实现
四.小结

一.转载自他人博客的代码段
原文链接:http://www.cnblogs.com/devymex/archive/2010/08/10/1796854.html
#include <iostream>
#include <string>
using namespace std;
//递归扫描输入的整棵树
bool ScanTree(int nSum, int nDest, bool *pNull) {
    static int nChild;
    //略去当前一级前导的左括号
    cin >> (char&)nChild;
    //br用于递归子节点的计算结果,bNull表示左右子是否为空
    bool br = false, bNull1 = false, bNull2 = false;
    //如果读入值失败,则该节点必为空
    if (!(*pNull = ((cin >> nChild) == 0))) {
        //总和加上读入的值,遍例子节点
        nSum += nChild;
        //判断两个子节点是否能返回正确的结果
        br = ScanTree(nSum, nDest, &bNull1) | ScanTree(nSum, nDest, &bNull2);
        //如果两个子节点都为空,则本节点为叶,检验是否达到目标值
        if (bNull1 && bNull2) {
            br = (nSum == nDest);
        }
    }
    //清除节点为空时cin的错误状态
    cin.clear();
    //略去当前一级末尾的右括号
    cin >> (char&)nChild;
    return br;
}
//主函数
int main(void) {
    bool bNull;
    //输入目标值
    for (int nDest; cin >> nDest;) {
        //根据结果输出yes或no
        cout << (ScanTree(0, nDest, &bNull) ? "yes" : "no") << endl;
    }
    return 0;
}


二.自己的理解分析
上述代码是我找到所有的相关题解中,可读性最强、写的也非常简洁优美的一段程序。
理解方式:结合注释读大意、在vs里单步调试、不懂得语法百度&&微微修改再调试

这题的难点在于通过LISP格式的数据文件建立二叉树。建好树之后无非是一个历遍求和的过程,前序、中序、后序都可以。
一颗二叉树,它的LISP数据格式文件长这个样子:
(5(4(11(7()())(2()()))()) (8(13()())(4()(1()()))))
它代表的树长这样:
picture25

仔细观察上面的实现代码,有一个大体逻辑过程是这样的:
bool ScanTree(int nSum, int nDest, bool *pNull) {
    static int nChild;
   <span style="font-size: 13.3333px; font-family: Simsun;">cin >> (char&)nChild;</span>
    。。。
    if (!(*pNull = ((cin >> nChild) == 0))) {
       
        。。。
        br = ScanTree(nSum, nDest, &bNull1) | ScanTree(nSum, nDest, &bNull2);
        。。。
    }
    。。。
}
*pNull是只想当前数据的指针,bNull1  bNull2可以理解为这个结点的左指针和右指针
 <span style="color:#660000;">br = ScanTree(nSum, nDest, &bNull1) | ScanTree(nSum, nDest, &bNull2);</span>
这句话非常关键,相当于读入一个数据到当前结点后,再读入数据到它的左子树,再读入数据到它的右子树。 是一个先序插入数据的过程
我们把原始数据的括号除去,数字序列的顺序为:5 4 11 72 8 13 4.实际上也就是这颗二叉树的先序访问。


因此,我们只需一边读入文件一边按先序建立二叉树即可。


三.自己的实现
为了验证我的想法,我首先根据上面的程序实现了一个一边读取文件一边先序建立二叉树的程序。
代码如下:

//二叉树结点类
class BSTNode {
public:
	int element;
	BSTNode*left;
	BSTNode*right;

	BSTNode() { left = NULL; right = NULL; }
	BSTNode(const int& Element, BSTNode*Left, BSTNode*Right) {
		element = Element;
		left = Left;
		right = Right;
	}
	~BSTNode() { delete left; delete right; }

};

class BST {
private:
	BSTNode*root;//根节点

	//一边读入数据一边按先序建立二叉树
	BSTNode* build(BSTNode*& r) {
		static int Child;
		//忽略左括号
		cin >> (char&)Child;
		//读入数据
		if (cin >> Child) {
			r = new BSTNode(Child, NULL, NULL);
			build(r->left);
			build(r->right);
		}
		cin.clear();
		//忽略右括号
		cin >> (char&)Child;

		return r;
	}

	//先序历遍
	void preorder(BSTNode*r) {
		if (r == NULL)return;
		cout << r->element << " ";
		preorder(r->left);
		preorder(r->right);
	}
public:
	BST() { root = NULL; }
	~BST() { delete root; }

	
	void Build() {
		build(root);
	}
	 
	void pre() {
		preorder(root);
	}
};


#include"bst.h"
using namespace std;


int main() {
	int value;
	cin >> value;

	BST* t = new BST();
	t->Build();
	t->pre();

}

输入:


输出:

说明了恰好是先序建立过程。

现在只需修改先序访问函数,让其一边访问一边求和,如果找到了一条路径立刻返回真即可。

在class BST里增加下面两个函数:
<span style="white-space:pre">	</span>bool scanTree(int num, int Des, BSTNode*r) {
<span style="white-space:pre">		</span>bool b = false;
<span style="white-space:pre">		</span>if (r == NULL) return b;
<span style="white-space:pre">		</span>num += r->element;
<span style="white-space:pre">		</span>if (r->left == NULL&&r->right == NULL)
<span style="white-space:pre">			</span>b = ( num == Des )? true : false;
<span style="white-space:pre">		</span>else
<span style="white-space:pre">		</span>{
<span style="white-space:pre">			</span>bool bl, br;
<span style="white-space:pre">			</span>bl = false;
<span style="white-space:pre">			</span>br = false;
<span style="white-space:pre">			</span>bl= scanTree(num, Des, r->left);
<span style="white-space:pre">		</span>    br=scanTree(num, Des, r->right);
<span style="white-space:pre">			</span>b = bl | br;
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>return b;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>bool ScanTree(int num, int Des) {
<span style="white-space:pre">		</span>return scanTree(num, Des, root);
<span style="white-space:pre">	</span>}

修改主程序如下:
#include"bst.h"
using namespace std;


int main() {
	int value;
	FILE* stream;
	freopen_s(&stream, "C:\\Users\\zgwng\\Desktop\\112data.txt", "r", stdin);
	while (cin >> value) {

		BST* t = new BST();
		t->Build();
		bool result = t->ScanTree(0, value);
		if (result) cout << "yes" << endl;
		else
			cout << "no" << endl;
	}


}

测试数据集:
22 (5(4(11(7()())(2()()))()) (8(13()())(4()(1()()))))
20 (5(4(11(7()())(2()()))()) (8(13()())(4()(1()()))))
10 (3 
     (2 (4 () () )
        (8 () () ) )
     (1 (6 () () )
        (4 () () ) ) )
5 ()
输出结果:




四.小结

1.对比自己的实现和别人的实现,有如下问题需要回答:
a.可不可以一边建树一边求和判断?
   答:显然是可以的,因为都是先序历遍,只要找到一条路满足就立刻返回true

b.建树的时候有没有必要单独记录每个结点的值
   答:不需要,在这一题中,我只需要记录以这个结点为终点的路径和就可以了。
           只需三个指针,一个记录此点是否为空,另外两个记录它的左右子树是否为空,也就是上面的pNull,bNull1,bNull2

因此,得到的实现思路是这样子的:
          有一个扫描函数ScanTree对这个二叉树以先序历遍的方式扫描,其返回值是bool类型,函数的参数有指向当前结点的指针                 pNull,到当前结点的路径累计和num,目标和Des

          在函数内部:
          读取一个数累加到num上,递归判断

2.所学习到的新的语法知识:
              cin结果作为条件、cin.clear()、cin>>(char&)nChild、指针作为函数参数传递什么时候传递指针的引用
cin作为while和if循环的条件。如果正常读取,其返回值是1,否则为0.
而读取失败后, 流的状态标记(failbit orbadbit)就会被设置,必须用cin.clear()去重置一下,否则后面cin是不能继续读取数据的。
另外,cin读取失败的内容还是保留在缓冲区中,下次仍然从读取失败的数据开始读。更全面的内容可以参考这篇博客:
http://blog.csdn.net/bladelyer/article/details/8505912

cin>>(char&)nChild这句话可以把一个char类型都入到nChild中,而nChild原本是int型,强制转换必须写成(char&)而不能是
(char),我看到网上的一个解释是这样的:
强制转换会产生一个临时变量,设为temp,
(char)nChild会产生一个char temp,使其值等于nChild,所以cin >> (char)nChild相当于cin >> temp,而临时变量是不能作为cin的参数的,所以编译会失败(排除nChild是char类型这种情况);
(char&)nChild会产生一个char& temp = nChild,所以cin >> (char&)nChild相当于cin >> temp,temp是对nChild的引用,所以实际作为cin参数的还是nChild,因此会编译通过。
    
关于指针的引用:
我的Build函数原本长这样:
BSTNode* build(BSTNode* r) 
但是运行完发现root指针仍然为空

原因是如果参数原本就是指针类型,而且你想要改变该指针的内容,则应该传递指针的引用。像下面这样:
BSTNode* build(BSTNode*& r) {


3.通过此题对递归的理解

我在写这个递归程序的时候犯了一些错误,现在想想还是对递归理解的不深入,现在对所犯错误进行一个总结:

错误一:if(cin>>Child){...}else{cin.clear();cin>>(char&)Child} 
	//一边读入数据一边按先序建立二叉树
	BSTNode* build(BSTNode*& r) {
		static int Child;
		//忽略左括号
		cin >> (char&)Child;
		//读入数据
		if (cin >> Child) {
			r = new BSTNode(Child, NULL, NULL);
			build(r->left);
			build(r->right);
                   }
		cin.clear();
		//忽略右括号
		cin >> (char&)Child;
		return r;
	}

我多写了一个else把后面对右括号的处理给包含了起来
其结果就是这棵树只递归到第一个左叶子结点
错误代码的思想是:如果没有正确读入数据,则对右括号进行处理,如果正确的读入了数据,则没有了对右括号的处理。
                                这样的读入对应得数据方式应该是如果左括号后面有整数,则不需要右括号里,只有左括号后面是空数据的时                                 候才有右括号。显然这是不对的。
                                正确的思路是不管当前是不是空数据,读了左括号后都应该再读一个右括号。所以不应该用else包含起来。

错误二:发生在我自己写的ScanTree上
错误代码如下:
<span style="white-space:pre">	</span>bool scanTree(int num, int Des, BSTNode*r) {
<span style="white-space:pre">	</span>num += r->element;
<span style="white-space:pre">	</span>if (r->left == NULL&&r->right == NULL)
<span style="white-space:pre">	</span>     return ( num == Des )? true : false;
<span style="white-space:pre">	</span>else
<span style="white-space:pre">	</span>{
<span style="white-space:pre">	</span>            if(r->left!=NULL) return scanTree(num, Des, r->left);
<span style="white-space:pre">	</span>              if (r->left != NULL)return scanTree(num, Des, r->right);
<span style="white-space:pre">	</span>              }
<span style="white-space:pre">	</span>}
这样做的结果是只要第一条路返回了false,最后结果就是false
而正确的应该是在没有找到正确的路前是不会返回false的,除非到了最后一条路仍然是false。
只要有一条路正确,结果就应该返回true,所以正确的是为当前起点设置一个bool量,最后返回这个bool量,如果有一条路正确就更新bool量为true,如下面的程序所示:
bool scanTree(int num, int Des, BSTNode*r) {
		bool b = false;
		if (r == NULL) return b;
		num += r->element;
		if (r->left == NULL&&r->right == NULL)
			b = ( num == Des )? true : false;
		else
		{
			bool bl, br;
			bl = false;
			br = false;
			bl= scanTree(num, Des, r->left);
		    br=scanTree(num, Des, r->right);
			b = bl | br;
		}
		return b;
	}


     
























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值