二叉樹應用
2 判斷二叉樹是否平衡二叉樹node
4 二叉樹的廣度遍歷、逐層打印二叉樹節點數據、只打印某層節點數據post
5 在二叉樹中找出和(葉子到根節點路徑上的全部節點的數據和)爲指定值的全部路徑。url
6 將二叉查找樹轉爲有序的雙鏈表.net
9 計算二叉樹高度的非遞歸實現blog
特別說明:
本文中二叉樹結構定義爲:
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 < -1 || 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兩節點之1、其左子樹是否包含A、B兩節點之1、其右子樹是否包含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;}
}
}