6.3.3 二叉树的递归遍历

第六章中树的内容较多,因此特地多开一篇来进行总结。
6.3.3 二叉树的递归遍历
对于二叉树T,可以递归定义它的先序遍历,中序遍历和后序遍历
PreOrder(T) = T的根节点+PreOrder(T的左子树)+PreOrder(T的右子树)
InOrder(T) = InOrder(T的左子树)+T的根结点+InOrder(T的右子树)
PostOrder(T) = PostOrder(T的左子树)+PostOrder(T的右子树)+T的根节点
其中加号表示字符串连接运算
前面的运算式表示的是对于树的先序遍历,中序遍历以及后序遍历
很明显先,中,后所指的就是T的根节点的相对方位
这三种遍历都属于递归遍历,或者说深度优先遍历(Depth-First Search, DFS),因为他们总是优先往深处访问,这边就需要注意其与BFS的区别
当然这边的先中后序遍历,与递归中尾递归的定义类似

二叉树中的根,结点,叶子的简要理解
根是没有前驱的结点,叶子是没有后继的结点,结点就是其中的各个数据元素,即结点包括一般节点,根节点,叶结点

preorder先序遍历
inorder中序遍历
postorder后序遍历
题6-8作者代码如下:

点击查看代码
#include<iostream>
#include<string>
#include<sstream>
#include<algorithm>
using namespace std;

//因为每个结点的权值各不相同且都是正整数,直接用权值作为结点编号
const int maxv = 10000 + 10;
int in_order[maxv], post_order[maxv], lch[maxv], rch[maxv];
int n;

bool read_list(int* a) {
  string line;
  if(!getline(cin, line)) return false;//“getline (cin, stri)返回cin,cin再转换为bool值true(读入成功)或者false(读入失败)” 
  stringstream ss(line);
  n = 0;
  int x;
  while(ss >> x) a[n++] = x;
  return n > 0;
} 

//把in_order[L1..R1]和post_order[L2..R2]建成一棵二叉树,返回树根
int build(int L1, int R1, int L2, int R2) {
  if(L1 > R1) return 0;
  int root = post_order[R2];
  int p = L1;
  while(in_order[p] != root) p++;
  int cnt = p-L1; //左子树的结点个数
  lch[root] = build(L1, p-1, L2, L2+cnt-1);
  rch[root] = build(p+1, R1, L2+cnt, R2-1);
  return root; 
} 

int best, best_sum;//目前为止的最优解和对应的权和

void dfs(int u, int sum) {
  sum += u;
  if(!lch[u] && !rch[u]) {//叶子 
  	if(sum < best_sum || (sum == best_sum && u < best)) {
  		best = u;
  		best_sum = sum;
	  }
  }
  if(lch[u]) dfs(lch[u], sum);
  if(rch[u]) dfs(rch[u], sum);
} 

int main() {
  while(read_list(in_order)) {
  	read_list(post_order);
  	build(0, n-1, 0, n-1);
  	best_sum = 1000000000;
  	dfs(post_order[n-1], 0);
  	cout << best << "\n";
  }
  return 0;
}

本题实现的关键在于理解对于树型结构来说,根在树中的地位和链表的头节点在链表中的地位相当。也就是说找到了根,那么一棵树就已经掌握咋我们手中。
所以这边的中序遍历一定要搭配上先序遍历或者后序遍历才可以,注意这边需要又整体思想,需要将根,左子树,右子树看成是一个整体,这样才会准确的发现,如果题目给出的是先序遍历和后序遍历那么这棵树的确认难度将会更大

接下来就不断从后序遍历的最后一个字符作为根节点,去分割中序遍历,再由中序遍历中的左右子树的长度区分个后序遍历中的左右子树,循环往复,构成一个递归

这边主要是利用整体的思想来建树,同时明确根是一棵树的灵魂,这样才能不断地循环递归
同时该段程序中尤为需要注意对于stringstream的使用,笔者已经在另外一片中讲解了stringstream需要注意的问题

点击查看笔者代码
#include<iostream>
#include<sstream>
using namespace std;

const int maxl = 10000+5;
struct Node{
  int value;
  Node* left, *right;
  Node() : value(0), left(NULL), right(NULL) {}
}tree[maxl];
int cnt = 0, ans = 0x7fffffff, leave = maxl, inorder[maxl], postorder[maxl];

Node* getTree(int in1, int in2, int post1, int post2) {
//  cout << in1 << " " << in2 << " " << post1 << " " << post2 << endl;
  if(in1==in2-1) {
  	Node* temp = &tree[cnt++];
  	temp->value = inorder[in1];
  	temp->left = NULL;
  	temp->right = NULL;
  	return temp;
  }
  else if(in1 >= in2) return NULL;
  int mid = in1;
  for(int i = in1; i < in2; i++) {
  	if(inorder[i] == postorder[post2-1]) {
       mid = i;
       break;
	}
  }
  Node* root = &tree[cnt++];
  root->value = inorder[mid];
  root->left = getTree(in1, mid, post1, post1+mid-in1);
  root->right = getTree(mid+1, in2, post1+mid-in1, post2-1);
  return root;  
}

void getMin(Node* root, int val) { 
  if(root->right == NULL && root->left == NULL) {
//  	cout << root->value << " " << val+root->value << " " << ans << endl;
    if(ans > val+root->value) {
      ans = val+root->value;
      leave = root->value;
	}
	else if(ans == val+root->value && root->value < leave) leave = root->value;
    return;
  }
  if(root->left != NULL) getMin(root->left, val+root->value);
  if(root->right != NULL) getMin(root->right, val+root->value);
}

int main() {
//	freopen("test.in", "r", stdin);
//	freopen("test.out", "w", stdout);
  string line;
  while(getline(cin, line)) {
    cnt = 0, ans = 0x7fffffff, leave = maxl;
  	stringstream ss(line);
    int sum = 0;
    while(ss>>inorder[sum]) sum++;
    getline(cin, line);
    ss.str("");
	ss.clear();
	ss << line;  
    for(int i = 0; i < sum; i++) ss >> postorder[i];
	Node* root = getTree(0, sum, 0, sum);
    getMin(root, 0);
    cout << leave << endl;
  }  
  return 0;
}

作者在这边抛出问题,认为可以在递归的同时统计最优解
笔者认为其中的大致思路如下,此时递归函数,还需要传递从叶子结点到当前节点的最小值,以及该叶子的值,通过不断传递该值,因为传递的过程该最小值并不会改变,因此到最后树的根上的时候,仍然是从叶子节点到根结点的最小值并且叶子权重最小

Not_so_Mobile题解

点击查看笔者代码
#include<iostream>
using namespace std;

const int maxl = 10000 + 5;
int cnt = 0;

struct Node{
  int lw, rw, ld, rd;
  Node* left, *right;
  Node() : lw(0), rw(0), ld(0), rd(0), left(NULL), right(NULL) {}
}node[maxl];

Node* build() {
  Node* temp = &node[cnt++];
  cin >> temp->lw >> temp->ld >> temp->rw >> temp->rd;
  temp->right = temp->left = NULL;
  if(temp->lw == 0) temp->left = build();
  if(temp->rw == 0) temp->right = build(); 
  return temp;
}

bool check(Node* root) {
  if(root->right == NULL && root->left == NULL) {
    if(root->ld*root->lw == root->rd*root->rw) return true;
    else return false;
  }
  if(root->left != NULL) {
  	if(check(root->left)) root->lw = root->left->lw+root->left->rw;	
	else return false;
  }
  if(root->right != NULL) {
  	if(check(root->right)) root->rw = root->right->lw+root->right->rw;
	else return false;	
  }
  if(root->ld*root->lw == root->rd*root->rw) return true;
  else return false;
}

int main() {
  int t;
  cin >> t;
  while(t--) {
    cnt = 0;
	Node* root = build();  
	if(check(root)) cout << "YES" << endl;
	else cout << "NO" << endl;	
	if(t) cout << endl;
  }	
  return 0;
}

这是一道较为简单的模拟题,通过深搜即可完成,不过笔者的代码较为繁琐,其实本题的树型结构的存储较为无用,可以在递归建树的时候直接开始判断是否平衡,这样会使得代码以及效率提高数倍

简单的在递归处理的代码如下

点击查看笔者代码
#include<iostream>
using namespace std;

int build() {
  int lw=0, ld=0, rw=0, rd=0;
  cin >> lw >> ld >> rw >> rd;
  if(lw == 0) lw = build(); 
  if(rw == 0) rw = build();	
  if(lw*ld != rw*rd || lw == 0 || rw == 0) return 0;
  else return lw+rw;
}

int main() {
  int t;
  cin >> t;
  while(t--) {
	if(build()) cout << "YES" << endl;
	else cout << "NO" << endl;	
	if(t) cout << endl; 
  }	
  return 0;
}

对于此类题目,当其中的形式较为复杂的时候,建议先手算样例或者至少把样例的图示画出来,以免误解提议。
这道题目的输入就采取了递归方式定义,因此编写一个递归过程进行输入比较自然,事实上,在输入过程中就能完成判断。由于使用引用传值,代码非常精简
作者的代码如下

点击查看代码
#include<iostream>
using namespace std;
//输入一个子天平,返回子天平是否平衡,参数W修改了子天平的总重量
bool solve(int& W) {//最精彩的处理部分 
  int W1, D1, W2, D2;
  bool b1 = true, b2 = true;
  cin >> W1 >> D1 >> W2 >> D2;//正常读入 
  if(!W1) b1 = solve(W1);//判断是否左边为子天平 
  if(!W2) b2 = solve(W2);
  W = W1 + W2;//调用前面已经修改过值的W1和W2 
  return b1 && b2 && (W1 * D1 == W2 * D2); //这边是保证只要有一处错误,那么传递回去的布尔表达式一定也是false,可以参考与运算 
} 

int main() {
  int T, W;
  cin >> T;
  while(T--) {
  	if(solve(W)) cout << "YES\n"; else cout << "NO\n";//多样例的读取处理,老经典了 
    if(T) cout << "\n"; 
  }
  return 0;
}

The_Falling_Leaves题解

点击查看笔者代码
#include<iostream>
#include<cstring>
using namespace std;

const int mm = 10000+5, maxl = 200, base = 80, inf = 0x7fffffff;
int cnt = 0, start = inf, End = -inf, ans[maxl];

struct Node{
  int value;
  Node* left, *right;
  Node() : value(0), left(NULL), right(NULL) {}
}node[mm];

Node* build() {
  int val;
  cin >> val;
  if(val == -1) return NULL;
  Node* temp = &node[cnt++];
  temp->value = val;
  temp->left = build();
  temp->right = build();
  return temp;
}

void getVal(Node* root, int col) {
  if(start > col) start = col;
  if(End < col) End = col;
  ans[col] += root->value;
  if(root->left != NULL) getVal(root->left, col-1);
  if(root->right != NULL) getVal(root->right, col+1);
}

int main() {
  Node* root;
  int kase = 0;
  while((root=build())!=NULL) {
  	cnt = 0; start = End = base;
	cout << "Case " << ++kase << ":\n";
    memset(ans, 0, sizeof(ans));
    getVal(root, base);
    for(int i = start; i < End; i++) cout << ans[i] << " ";
    cout << ans[End] << endl << endl;
  }
  return 0;
}

笔者刚遇到这种题目,第一个想法就是暴力模拟,实在想不出如何构造左右对正这种操作,因为题目提示数据输出不会超过80字符,因此就采用开大数组的暴力方式解决了

作者的代码是一如既往的简洁,同时也提示笔者如果下次遇见输入为递归输入的树的时候,未必一定要建立树型结构再来处理,可以边读入便处理,简化代码,提高效率

点击查看代码
#include<iostream>
#include<cstring>
using namespace std;

const int maxn = 200;
int sum[maxn]; 

//输入并统计一棵子树,树根水平位置为p
void build(int p) {//建立树木,很常见的二叉树写法了 
  int v; cin >> v;
  if(v == -1) return;
  sum[p] += v;
  build(p-1); build(p+1);
} 

//边读入便统计
bool init() {
  int v; cin >> v;
  if(v == -1) return false;//对于数据是否合法的判断 
  memset(sum, 0, sizeof(sum));
  int pos = maxn/2;//树根的水平位置
  sum[pos] = v;
  build(pos-1); build(pos+1); //建造左右子树,间接递归 
  return true;
} 

int main() {
  int kase = 0;
  while(init()) {
  	int p = 0;
  	while(sum[p] == 0) p++;//找最左边的叶子,这边应该是默认题目中不会出现0的情况 
	cout << "Case " << ++kase << ":\n" << sum[p++];//因为要避免行末多余空格
	while(sum[p] != 0) cout << " " << sum[p++];
	cout << "\n\n";  
  }
  return 0;
}
posted @   banyanrong  阅读(88)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示