二叉树应用

1     求二叉树中相距最远的两个节点之间的距离

2     判断二叉树是否平衡二叉树

3     指定二叉树,给定两节点求其最近共同父节点

4     二叉树的广度遍历、逐层打印二叉树节点数据、只打印某层节点数据

5     在二叉树中找出和(叶子到根节点路径上的所有节点的数据和)为指定值的所有路径。

6     将二叉查找树转为有序的双链表

7     求二叉树的镜像

8     二叉树前序、中序、后序遍历的非递归实现

9     计算二叉树高度的非递归实现

10    连接二叉树同一层上的结点

特别说明:

 

本文中二叉树结构定义为:

struct Node {

Node* left;

Node* right;

int data;

};

定义:空二叉树的高度为-1,只有根节点的二叉树高度为0,根节点在0层,深度为0。

1     求二叉树中相距最远的两个节点之间的距离

两个节点的距离为两个节点间最短路径的长度。

求两节点的最远距离,实际就是求二叉树的直径。假设相距最远的两个节点分别为A、B,它们的最近共同父节点(允许一个节点是其自身的父节点)为C,则A到B的距离 =  A到C的距离 + B到C的距离

节点A、B分别在C的左右子树下(假设节点C的左右两子树均包括节点C),不妨假设A在C的左子树上,由假设“A到B的距离最大”,先固定B点不动(即B到C的距离不变),根据上面的公式,可得A到C的距离最大,即点A是C左子树下距离C最远的点,即:

A到C的距离 = C的左子树的高度

同理,    B到C的距离 = C的右子树的高度

因此,本问题可以转化为:“二叉树每个节点的左右子树高度和的最大值”。

static int tree_height(const Node* root, int& max_distance)

{

const int left_height  = root->left  ? tree_height(root->left,  max_distance)  + 1 : 0;

const int right_height = root->right ? tree_height(root->right, max_distance)  + 1 : 0;

const int distance = left_height + right_height;

if (max_distance < distance) max_distance = distance;

return (left_height > right_height ? left_height : right_height);

}

int tree_diameter(const Node* root)

{

int max_distance = 0;

if (root) tree_height(root, max_distance);

return max_distance;

}

2     判断二叉树是否平衡二叉树

根据平衡二叉树的定义:每个结点的左右子树的高度差小等于1,只须在计算二叉树高度时,同时判断左右子树的高度差即可。

static int tree_height(const Node* root, bool& balanced)

{

const int left_height = root->left ? tree_height(root->left, balanced) + 1 : 0;

if (!balanced) return 0;

const int right_height = root->right ? tree_height(root->right, balanced) + 1 : 0;

if (!balanced) return 0;

const int diff = left_height - right_height;

if (diff < -|| diff > 1) balanced = false;

return (left_height > right_height ? left_height : right_height);

}

bool is_balanced_tree(const Node* root)

{

bool balanced = true;

if (root) tree_height(root, balanced);

return balanced;

}

3     指定二叉树,给定两节点求其最近共同父节点

遍历二叉树时,只有先访问给定两节点A、B后,才可能确定其最近共同父节点C,因而采用后序遍历。

可以统计任一节点的左右子树“是否包含A、B中的某一个”(也可以直接统计“包含了几个A、B”)。当后序遍历访问到某个节点D时,可得到三条信息:节点D是否是A、B两节点之一、其左子树是否包含A、B两节点之一、其右子树是否包含A、B两节点之一。当三条信息中有两个为真时,就可以确定节点D的父节点(或节点D,如果允许一个节点是自身的父节点的话)就是节点A、B的最近共同父节点。另外,找到最近共同父节点C后应停止遍历其它节点。

① 允许节点是其自身的父节点(若B是A的孩子,A、B的最近共同父节点为A):

//代码1:

static bool lca(const Node* root, const Node* va, const Node* vb, const Node*& result)

{

const bool left = root->left ? lca(root->left, va, vb, result) : false;

if (result) return false;  //剪枝,随便返回一个值

const bool right = root->right ? lca(root->right, va, vb, result) : false;

if (result) return false;

//由于va可能等于vb,不要写成: const int mid = (root == va) | (root == vb);

const int mid = (root == va) + (root == vb);

int ret = left + right + mid;

if (ret == 2) result = root;

return (bool)ret;

}

const Node* lca(const Node* root, const Node* va, const Node* vb)

{

const Node* result = NULL;

if (root) lca(root, va, vb, result);

return result;

}

上面的代码中需要特别注意的是:判断所访问时节点是否是两节点A、B时要写成

constint mid = (root == va) + (root == vb);

而不是:  const int mid = (root == va) | (root == vb);

这样当va等于vb时,可以得到正确结果。

若采用第二种方法,代码可以改写为:

//代码2:

static int lca(const Node* root, const Node* va, const Node* vb, const Node*& result)

{

const int N = 2;

const int left = root->left ? lca(root->left, va, vb, result) : 0;

if (left == N) return N;

const int right = root->right ? lca(root->right, va, vb, result) : 0;

if (right == N) return N;

const int mid = (root == va) + (root == vb);

const int ret = left + right + mid;

if (ret == N) result = root;

return ret;

}

const Node* lca(const Node* root, const Node* va, const Node* vb)

{

const Node* result = NULL;

if (root) lca(root, va, vb, result);

return result;

}

② 节点不能是其自身的父节点(若B是A的孩子,A、B的最近共同父节点为A的父节点):

只要再增加一个变量保存父节点即可。

static bool lca(const Node* root, const Node* va, const Node* vb,

const Node* parrent, const Node*& result)

{

bool left = false;

if (root->left) {

left = lca(root->left, va, vb, root, result);

if (result) return false;

}

bool right = false;

if (root->right) {

right = lca(root-> right, va, vb, root, result);

if (result) return false;

}

const int mid = (root == va) + (root == vb);

const int ret = left + right + mid;

if (ret == 2) result = (mid != 0 ? parrent : root);

return (bool)ret;

}

const Node* lca(const Node* root, const Node* va, const Node* vb)

{

const Node* result = NULL;

if (root) lca(root, va, vb, NULL, result);

return result;

}

 

4      二叉树的广度遍历、逐层打印二叉树节点数据、只打印某层节点数据

广度遍历可以用一个队列保存中间结果。每访问一个节点时,将不为空的的左右孩子分别放入队列中,然后从队列头部取出下一个节点,重复前面的操作直到队列为空。

若需要对同一层的节点数据进行一些特殊操作(比如:打印完一层后换行、只打印某一层),可以记录某一层的最后一个节点,当遍历完该节点时(此时,队列的中的最后一个元素恰好就是下一层的最后一个节点),再进行这些特殊操作。

//简单的广度遍历

void bfs(const Node* root)

{

if (root == NULL) return;

std::deque<const Node*> dq;

while (true) {

if (root->left)  dq.push_back(root->left);

if (root->right) dq.push_back(root->right);

std::cout << root->data << " ";

if (dq.empty()) break;

root = dq.front();

dq.pop_front();

}

}

//逐层打印

void bfs_level(const Node* root)

{

if (root == NULL) return;

std::deque<const Node*> dq;

const Node* end = root;

while (true) {

if (root->left)  dq.push_back(root->left);

if (root->right) dq.push_back(root->right);

std::cout << root->data;

if (root != end) { std::cout << " "; }

else {

std::cout << "\n";

if (dq.empty()) break;

end = dq.back();

}

root = dq.front();

dq.pop_front();

}

}

//只打印某层

void bfs_nth_level(const Node* root, int level)  //root node is at level 0

{

if (root == NULL || level < 0) return;

std::deque<const Node*> dq;

const Node* end = root;

while (true) {

if (root->left)  dq.push_back(root->left);

if (root->right) dq.push_back(root->right);

if (level == 0)  std::cout << root->data << (root == end ?  "\n" : " ");

if (root == end) {

if (--level < 0 || dq.empty()) break;

end = dq.back();

}

root = dq.front();

dq.pop_front();

}

}

5     在二叉树中找出和(叶子到根节点路径上的所有节点的数据和)为指定值的所有路径。

要输出所有的路径,必须额外用一个栈来保存当前路径信息。

当访问到节点A时,节点A的信息要在访问A的左右子树时用到,因而,该信息必须在遍历A的左右子树前加入到栈中,而在遍历完A的左右子树后从栈中移除。

每访问一个节点,就计算当前路径值(可直接利用父节点的路径值),当其等于给定值且当前节点是叶子节点时,就打印路径信息。

static void node_path(const Node* root, const int value, int sum, std::deque<int>& dq)

{

sum += root->data;

if (root->left == NULL && root->right == NULL) {

if (sum != value) return;

std::copy(dq.begin(), dq.end(), std::ostream_iterator<int>(std::cout, " "));

std::cout << root->data << "\n";

return;

}

dq.push_back(root->data);

if (root->left)  node_path(root->left,  value, sum, dq);

if (root->right) node_path(root->right, value, sum, dq);

dq.pop_back();

}

void print_node_path(const Node *root, int value)

{

if (root == NULL) return;

std::deque<int> dq;

node_path(root, value, 0, dq);

}

//非递归解法

void print_path_by_value(const Node *root, int value)

{

typedef std::vector<const Node*> Container;

Container node;

node.reserve(64);

int sum = 0;

while (true) {

for ( ; root != NULL; root = root->left) {

sum += root->data;

if (sum == value && root->left == NULL && root->right == NULL) {

Container::const_iterator first = node.begin(), last = node.end();

for ( ; first != last; ++first) printf("%d ", (*first)->data);

printf("%d\n", root->data);

sum -= root->data;

break;

}

node.push_back(root);

}

while (true) {

if (node.empty())  return;

const Node* parrent = node.back();

if (root != parrent->right) { root = parrent->right; break; }

root = parrent;

sum -= root->data;

node.pop_back();

}

}

}

6     将二叉查找树转为有序的双链表

实际上就是对二叉查找树进行中序遍历。可以用两个变量分别保存刚访问的结点、新链表的头结点,访问某一个结点A时,设置该节点时left成员指向刚访问过的结点B,再设置结点B的right成员指向结点A。经过这样处理,得到的新双链表,除了头结点的left成员、尾结点的right成员没有设置外,其它的结点成员都被正确设置。而中序遍历的特点决定了第一个访问的数据节点的left成员一定为空指针,最后一个访问的数据节点的right成员也一定为空指针。因而不需要再对这两个成员进行额外的设置操作。

static void tree2list_inorder(Node* root, Node*& prev, Node*& list_head)

{

if (root->left) tree2list_inorder(root->left, prev, list_head);

root->left = prev;

if (prev) prev->right = root;

prev = root;

if (list_head == NULL) list_head = root;

if (root->right) tree2list_inorder(root->right, prev, list_head);

}

Node* tree2list(Node* root)

{

Node* list_head = NULL;

Node* prev = NULL;

if (root) tree2list_inorder(root, prev, list_head);

return list_head;

}

7     求二叉树的镜像

①     在原来的二叉树上进行修改。

static void mirror(Node* root)

{

Node* const left  = root->left;

Node* const right = root->right;

root->left = right;

root->right = left;

if (left)  mirror(left);

if (right) mirror(right);

}

Node* mirror_node(Node* root)

{

if (root) mirror(root);

return root;

}

② 创建一个二叉树的镜像,注意内存分配失败时的处理

static void clear_node(Node* root)

{

Node* const left  = root->left;

Node* const right = root->right;

delete root;

if (left)  clear_node(left);

if (right) clear_node(right);

}

static void clone_mirror(const Node* root, Node*& position)

{

Node *node = new Node;

*node = *root;

position = node;

if (root->left)  clone_mirror(root->left,  node->right);

if (root->right) clone_mirror(root->right, node->left);

}

Node* clone_mirror(const Node* root)

{

Node* new_root = NULL;

if (root) {

try {

clone_mirror(root, new_root);

} catch (...) {

if (new_root) clear_node(new_root);

new_root = NULL;

}

}

return new_root;

}

8     二叉树前序、中序、后序遍历的非递归实现

三种遍历相同点是:从某节点出发向左走到头(边走边记录访问过的节点),然后退回到该节点,再进入右子树,再重复前面操作。

①  对前序遍历,先访问节点数据、以后再访问该节点右孩子的数据,因而可以不记录该节点,而直接记录该节点的右孩子。

②  对前序、中序遍历,同一个节点可能要被访问两次:从上往下、(沿着左子树)从下往上。

③  对后序遍历,同一个节点可能要被访问三次:从上往下、(沿着左子树)从下往上、(沿着右子树)从下往上。

后序遍历相对麻烦的地方是:从下往上时,要判断是沿着左子树向上,还是沿着右子树向上,若是后者(或父节点的右孩子为空节点)才访问父节点数据。方向的判断,只须判断当前节点是否是其父节点的右孩子,不需要对每个结点都设一个标志!另外,若当前节点是某个叶子节点的左孩子(此时当前节点是空节点),可以把当前节点当作是该叶子节点的右孩子处理,而不影响结果。

void preorder(const Node* root)

{

std::deque<const Node*> dq;

while (true) {

while (root) {

std::cout << root->data << " ";

if (root->right) dq.push_back(root->right);

root = root->left;

}

if (dq.empty()) break;

root = dq.back();

dq.pop_back();

}

}

void inorder(const Node* root)

{

std::deque<const Node*> dq;

while (true) {

for ( ; root != NULL; root = root->left) dq.push_back(root);

if (dq.empty()) break;

root = dq.back();

dq.pop_back();

std::cout << root->data << " ";

root = root->right;

}

}

void postorder(const Node* root)

{

std::deque<const Node*> dq;

while (true) {

for ( ; root != NULL; root = root->left) dq.push_back(root);

while (true) {

if (dq.empty()) return;

const Node* parrent = dq.back();

//可以不检查parrent->right是否为空指针

const Node* right = parrent->right;

if (right && root != right) { root = right; break;}

std::cout << parrent->data << " ";

root = parrent;

dq.pop_back();

}

}

}

9     计算二叉树高度的非递归实现

计算二叉树的高度,一般都是用后序遍历,先算出左子树的高度,再算出右子树的高度,最后取较大者。但若直接将该算法改成非递归形式是非常麻烦的。考虑到二叉树高度与深度的关系,可以有下面两种方法:

①     先将算法改成前序遍历再改写非递归形式。前序遍历算法:遍历一个节点前,先算出当前节点是在哪一层,层数的最大值就等于二叉树的高度

②     修改上面提到的后序遍历迭代写法,上面的代码中,所用到辅助栈(或双端队列),其大小达到的最大值减去1 就等于二叉树的高度。因而只须记录在往辅助栈放入元素后(或者在访问结点数据时),辅助栈的栈大小达到的最大值。

int tree_height_preorder(const Node* root)

{

struct Info {

const Node* node;

int level;

};

std::deque<Info> dq;

int level  = -1;

int height = -1;

while (true) {

while (root) {

++level;

if (root->right)  {

Info info = {root->right, level};

dq.push_back(info);

}

root = root->left;

}

height = max(height, level);

if (dq.empty()) break;

const Info& info = dq.back();

root  = info.node;

level = info.level;

dq.pop_back();

}

return height;

}

int tree_height_postorder(const Node* root)

{

std::deque<const Node*> dq;

int height = -1;

while (true) {

for ( ; root != NULL; root = root->left) dq.push_back(root);

height = max(height, (int)dq.size() - 1);

while (true) {

if (dq.empty()) return height;

const Node* parrent = dq.back();

//可以不检查parrent->right是否为空指针

const Node* right = parrent->right;

if (right && root != right) { root = right; break;}

root = parrent;

dq.pop_back();

}

}

}

 

 

10     连接二叉树同一层上的结点

若二叉树结构定义为:

struct Node {

Node *left;

Node *right;

Node *right_sibling; //

int data;

};

其中,right_sibling指向同一层上右侧的第一个结点(没有的话则设为空指针)。

要求设置各结点的right_sibling成员(其它成员已经初始化)。

本题可以用递归,也可以使用迭代,两种方法都是时间复杂度O(n),空间复杂度O(1)(递归解法可能会栈溢出)。

递归法:访问一个结点前,事先算出它的right_sibling。访问该结点时,利用该结点的right_sibling指针,算出其左右孩子的right_sibling(找出该结点右侧第一个不是叶子的结点B,结点B的某个孩子,就是该结点某个孩子的right_sibling)。需要特别注意的是:访问二叉树可以采用前序遍历,要先访问右子树,再访问左子树,这样可以保证访问到某个结点,该结点及其右侧的结点的right_sibling指针已被正确设置。

迭代法:访问某一层前,先设置好该层所有节点的right_sibling,访问该层时,利用已经设置好的right_sibling信息,设置下一层节点的right_sibling。

//递归解法:

static void set_sibling(Node* root, Node* sibling)

{

root->right_sibling = sibling;

Node* const left  = root->left;

Node* const right = root->right;

if (left == NULL && right == NULL) return;

while (sibling) {

if (sibling->left)  { sibling = sibling->left;  break;}

if (sibling->right) { sibling = sibling->right; break;}

sibling = sibling->right_sibling;

}

if (right) {

set_sibling(right, sibling);

sibling = right;

}

if (left) set_sibling(left, sibling);

}

void set_sibling(Node* root)

{

if (root) set_sibling(root, NULL);

}

//非递归解法:

void set_right_sibling2(Node* root)

{

if (root == NULL) return;

root->right_sibling = NULL;

Node* level_start = NULL;

while (root) {

Node* const left  = root->left;

Node* const right = root->right;

if (level_start == NULL)  level_start = (left ? left : right);

Node* right_sibling = NULL;

while (1) {

root = root->right_sibling;

if (root == NULL) { root = level_start; level_start = NULL; break; }

if (root->left)   { right_sibling = root->left;  break;}

if (root->right)  { right_sibling = root->right; break;}

}

if (right) { right->right_sibling = right_sibling;  right_sibling = right; }

if (left)  { left->right_sibling  = right_sibling;}

}

}

时间: 2024-10-12 05:14:13

二叉树应用的相关文章

C#实现二叉树的遍历

C#实现二叉树的前序.中序.后序遍历. public class BinaryTreeNode     {         int value;         BinaryTreeNode left;         BinaryTreeNode right;         /// <summary>         /// 前序遍历         /// </summary>         /// <param name="tree">&l

【树4】二叉树的遍历

简介 遍历二叉树就是按照某种顺序,将树中的结点都枚举一遍,且每个结点仅仅访问一次.因为树不是线性的结构,遍历不像线性表那样简单,因此他的遍历需要特点的算法来完成. 从某种角度讲,对二叉树的遍历就是将树形结构转换为线性结构的操作. 二叉树的遍历方法主要有如下几种: 先序遍历:先访问root结点,再先序遍历左子树,再先序遍历右子树. 中序遍历:先中序遍历左子树,再访问root结点,再中序遍历右子树. 后序遍历:先后序遍历左子树,再后序遍历右子树,再访问root结点. 层遍历:从上到下,从左到右,一层

按之字形顺序打印二叉树

题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推 /* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } }; */ class Solution { public: vector<vect

【数据算法】Java实现二叉树存储以及遍历

二叉树在java中我们使用数组的形式保存原数据,这个数组作为二叉树的数据来源,后续对数组中的数据进行节点化操作. 步骤就是原数据:数组 节点化数据:定义 Node节点对象 存储节点对象:通过LinkedList保存Node节点对象 在操作过程中我们需要将当前结点和前一节点.后一节点进行关系绑定 package tree; import java.util.LinkedList; import java.util.List; /** * 功能:把一个数组的值存入二叉树中,然后进行3种方式的遍历 *

二叉树的后序遍历(暴力版) 小白菜oj 1034

给出二叉树的前序遍历和中序遍历,求二叉树的后序遍历-- 作为一个搜索蒟蒻,我真的没有办法很和谐的A掉,但估计过几天就会写有关这个题的和谐的解法--但只是估计-- 下面讲述我的超暴力解法-- 首先,先由前序遍历得到一个父亲节点,然后再由中序遍历得到这个父亲节点的左子树和右子树中的元素(中序遍历中,该点的左边的所有点,都在它的左子树,右边的都在它的右子树,子树中的根节点是在这些节点的前序遍历中排名最靠前的),然后递归建树,之后在递归求后序遍历即可. 但这个方法有两个比较--&¥--&的问题:1

二叉树的序列化和反序列化

http://blog.csdn.net/qq_27703417/article/details/70958692 先序遍历二叉树,如果遇到空节点,就在str的末尾加上"#!","#"表示这个节点为空,节点值不存在,当然你也可以用其他的特殊字符,"!"表示一个值的结束.如果遇到不为空的节点,假设节点值为3,就在str的末尾加上"3!".现在请你实现树的先序序列化. 先序遍历 import java.util.*; //使用递归

【二叉树】 二叉树基础

在计算机科学中,二叉树是每个节点最多有两个子树的树结构.通常子树被称作"左子树"(left subtree)和"右子树"(right subtree).二叉树常被用于实现二叉查找树和二叉堆. 二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒.二叉树的第i层至多有个结点:深度为k的二叉树至多有个结点:对任何一棵二叉树T,如果其终端结点数为,度为2的结点数为,则. 树和二叉树的三个主要差别: 树的结点个数至少为1,而二叉树的

二叉树的深度

输入一棵二叉树,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度. 思路:使用递归的方法分别计算左右子树的深度 public class Solution { public int TreeDepth(TreeNode pRoot){ return pRoot == null? 0 : Math.max(TreeDepth(pRoot.left),TreeDepth(pRoot.right)) + 1;     } }

226反转二叉树 Invert Binary Tree

Invert a binary tree. 4 / 2 7 / \ / 1 3 6 9 to 4 / 7 2 / \ / 9 6 3 1 Trivia:This problem was inspired by this original tweet by Max Howell: Google: 90% of our engineers use the software you wrote (Homebrew), but you can't invert a binary tree on a wh

树、二叉树、森林的转换

树转换为二叉树 (1)加线.在所有兄弟结点之间加一条连线. (2)去线.树中的每个结点,只保留它与第一个孩子结点的连线,删除它与其它孩子结点之间的连线. (3)层次调整.以树的根节点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明.(注意第一个孩子是结点的左孩子,兄弟转换过来的孩子是结点的右孩子) 森林转换为二叉树 (1)把每棵树转换为二叉树. (2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来. 二叉树转换为树 是树转换为二