二叉查找树之二
BST树的经典问题
首先构造如下一棵二元查找树(BST树):
C++代码实现:
typedef struct _BSTreeNode { int value; struct _BSTreeNode *left; struct _BSTreeNode *right; } BSTreeNode; static BSTreeNode* insert(BSTreeNode* q, int x) { BSTreeNode* p = (BSTreeNode*) new(BSTreeNode); p->value = x; p->left = NULL; p->right = NULL; if (q == NULL) { q = p; } else if (x < q->value) { q->left = insert(q->left, x); } else { q->right = insert(q->right, x); } return q; } static BSTreeNode* search(BSTreeNode* p, int x) { int find = 0; while (p && !find) { if (x == p->value) { find = 1; } else if (x < p->value) { p = p->left; } else { p = p->right; } } if (p == NULL) { cout<< "not found" <<endl; } return p; } int main() { int n, key; vector<int> path; BSTreeNode *BT = NULL; int array[11] = {15, 6, 18, 3, 7, 17, 20, 2, 4, 13, 9}; for (n=0; n<11; n++) { key = array[n]; BT = insert(BT, key); } n = 0; return 0; }
对二叉树的遍历除了常见的先序遍历/中序遍历/后序遍历意外,还有广度优先,深度优先遍历。
广度优先遍历(BFS)
沿着树的宽度遍历树的结点,自上而下,从左往右的遍历一棵二叉树。
思路:考虑用队列的数据结构来实现,将根结点放入队列之后。
每次从队列头取出一个结点的同时,将其左孩子、右孩子分别放入队列尾。
void BreadthFirstSearch(BSTreeNode *root, vector<int> &result) { if (!root) return; BSTreeNode *q; deque<BSTreeNode *> queue; queue.push_back(root); while (queue.size()) { // 从队头取出一个节点 q = queue.front(); queue.pop_front(); result.push_back(q->value); if (q->left) queue.push_back(q->left); if (q->right) queue.push_back(q->right); } } int main() { int n, key; vector<int> path; BSTreeNode *p1, *p2, *p3; BSTreeNode *BT = NULL; int array[11] = {15, 6, 18, 3, 7, 17, 20, 2, 4, 13, 9}; for (n=0; n<11; n++) { key = array[n]; BT = insert(BT, key); } BreadthFirstSearch(BT, path); vector<int>::iterator it = path.begin(); for (; it!=path.end(); ++it) { cout<<*it<<"\t"; } cout<<endl; return 0; }
深度优先遍历(DFS)
沿着树的深度遍历树的结点,尽可能的搜索树的分支。
思路:考虑用stack的数据结构来实现,将根结点放入stack之后。
每次从栈顶取出一个结点的同时,将其右孩子、左孩子分别压入stack。
void DepthFirstSearch(BSTreeNode* root, vector<int> &result) { if (!root) return; BSTreeNode *q; stack<BSTreeNode *> nodeStack; nodeStack.push(root); while (!nodeStack.empty()) { q = nodeStack.top(); nodeStack.pop(); result.push_back(q->value); if(q->right) nodeStack.push(q->right); if(q->left) nodeStack.push(q->left); } }
最大深度
int TreeDepth (Node *root) { if (!root) return 0; int nLeft, nRight; nLeft = TreeDepth(root->left); nRight = TreeDepth(root->right); return (nLeft > nRight) ? (nLeft+1) : (nRight+1); }
最大宽度
求一颗二叉树的最大宽度,
思路:假如从根节点开始编号为0,那么编号为n的节点的左孩子就是2n+1,右孩子2n+2,我们需要记录每一层的节点最大、最小编号。
void WidthOfBinayTree(Node* root, int depth, int order, vector<std::pair<int, int>> & ends) { if (!root) return; if (depth == ends.size()) { ends.emplace_back(order, order); } ends[depth].first = std::min(order, ends[depth].first); ends[depth].second = std::max(order, ends[depth].second); WidthOfBinayTree(root->left, depth + 1, 2 * order + 1, ends); WidthOfBinayTree(root->right, depth + 1, 2 * order + 2, ends); } int TreeWidth(Node* root) { if (!root) return 0; vector<pair<int, int>> ends; WidthOfBinayTree(root, 0, 0, ends); int res = 0; for (auto end: ends) { int len = end.second - end.first + 1; if (len > res) res = len; } return res; }
BST树的镜像
输入一颗BST树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点。
例如输入:
8
/ \
6 10
/\ /\
5 7 9 11
输出:
8
/ \
10 6
/\ /\
11 9 7 5
递归解法:
void MirrorRecursively(BSTreeNode *pNode) { if (!pNode) return; // swap the right and left child sub-tree BSTreeNode *pTemp = pNode->left; pNode->left = pNode->right; pNode->right = pTemp; // mirror left child sub-tree if not null if (pNode->left) MirrorRecursively(pNode->left); // mirror right child sub-tree if not null if (pNode->right) MirrorRecursively(pNode->right); }
由于递归的本质是编译器生成了一个函数调用的栈,因此用循环来完成同样任务时最简单的办法就是用一个辅助栈来模拟递归。首先我们把树的头结点放入栈中。在循环中,只要栈不为空,弹出栈的栈顶结点,交换它的左右子树。如果它有左子树,把它的左子树压入栈中;如果它有右子树,把它的右子树压入栈中。这样在下次循环中就能交换它儿子结点的左右子树了。
void MirrorIteratively(BSTreeNode *pTreeHead) { if (!pTreeHead) return; stack<BSTreeNode *> stackTreeNode; stackTreeNode.push(pTreeHead); while (stackTreeNode.size()) { BSTreeNode *pNode = stackTreeNode.top(); stackTreeNode.pop(); // swap the right and left child sub-tree BSTreeNode *pTemp = pNode->left; pNode->left = pNode->right; pNode->right = pTemp; // push left child sub-tree into stack if not null if(pNode->left) stackTreeNode.push(pNode->left); // push right child sub-tree into stack if not null if(pNode->right) stackTreeNode.push(pNode->right); } }
将BST树转为一个有序的双向链表
将上面的BST树转换为双向链表:如2==3==4==6==7==9==13==15==17==18==20。
我们知道,BST树的中序遍历是有序的,所以只要按这种方式将当前访问的节点加到链表末尾就可以了。
void ConvertNode(BSTreeNode *pNode, BSTreeNode** pLastNodeOfList) { if (NULL == pNode) return; if (pNode->left) { ConvertNode(pNode->left, pLastNodeOfList); } pNode->left = *pLastNodeOfList; if (*pLastNodeOfList) { (*pLastNodeOfList)->right = pNode; } *pLastNodeOfList = pNode; if (pNode->right) { ConvertNode(pNode->right, pLastNodeOfList); } } BSTreeNode* Convert_Solution(BSTreeNode* pHeadOfTree) { BSTreeNode *pLastNodeInList = NULL; ConvertNode(pHeadOfTree, &pLastNodeInList); // 找到双向链表的头指针 BSTreeNode *pHeaderOfList = pLastNodeInList; while (pHeaderOfList && pHeaderOfList->left) pHeaderOfList = pHeaderOfList->left; return pHeaderOfList; }
在二叉树中找到和为特定值的所有路径
实现一个FindPath函数,能找到所有节点的和等于50的路径:
void FindPath(BSTreeNode* root, int sum, vector<int>&path, int ¤t) { if (!root) return; current += root->value; path.push_back(root->value); // find one path if (NULL == root->left && NULL == root->right && current == sum) { vector<int>::iterator it = path.begin(); for (; it!=path.end(); ++it) { cout<<*it<<"\t"; } cout<<endl; } if (root->left) FindPath(root->left, sum, path, current); if (root->right) FindPath(root->right, sum, path, current); current -= root->value; path.pop_back(); }
运行结果为:
[root@localhost cq]# ./a.out 15 6 7 13 9 15 18 17
实现思路:
当访问到某一节点时,将该节点添加到路径上,并累加当前节点的值:
1、如果当前节点为叶节点并且当前路径的和刚和等于输入的值,则当前路径就符合要求;
2、如果当前节点不是叶节点,则继续访问它的子节点;
3、当前节点访问结束后,递归函数将自动回到父节点。因此在函数退出之前要在路径上删除当前节点并减去当前节点的值,以确保返回父节点时路径刚好是根节点到父节点的路径。保存路径的数据结构实际上是一个栈结构,因为路径要与递归调用状态一致,而递归调用本质就是一个压栈和出栈的过程。
判断一个数组是不是一棵BST树的后序遍历
二叉树的后序遍历分为3部分: LETF、RIGHT、ROOT,并且,LEFT部分的值都不大于ROOT,RIGHT部分的值都大于ROOT。
因此,对一个数组,从第一个元素开始遍历,把小于最后一个元素(即ROOT)的部分作为LEFT,大于最后一个元素的部分作为RIGHT。
然后再对LEFT和RIGHT递归判断子树是否也符合条件。
C++代码实现如下:
int verifySequenceOfBST(int *seq, int N) { if (seq == NULL || N <= 0) return 0; int n, m; int root = seq[N-1]; // 根结点 for (n=0; n<N-1 && seq[n] <= root; n++); // 左子树 for (m=n; m<N-1 && seq[m] > root; m++); // 右子树 if (m != N-1) return 0; int left = 1, right = 1; if (n > 0) left = verifySequenceOfBST(seq,n); if (n < N-1) right = verifySequenceOfBST(seq+n,N-1-n); return (left && right); }
二叉树两结点的最低共同父结点
如果两个节点A、B有共同的父节点C,那么有4种情况:
1、A和B都在C的左子树中;
2、A和B都在C的右子树中;
3、A在C的左子树,B在C的右子树;
4、B在C的左子树,A在C的右子树;
我们从根节点开始,先判断属于上面哪种情况,如果是情况1,只要A、B的共同父节点肯定在左子树中,继续递归;情况2类似处理;
如果是情况3、4,那么A和B的最低共同父节点就是根节点。
C++代码实现如下:
int HasNode(BSTreeNode *pHead, BSTreeNode *pNode) { if (pHead == pNode) return 1; int has = 0; if (pHead->left) has = HasNode(pHead->left, pNode); if (!has && pHead->right) has = HasNode(pHead->right, pNode); return has; } BSTreeNode *LastCommonParent(BSTreeNode *pHead, BSTreeNode *pNode1, BSTreeNode *pNode2) { if (NULL == pHead || NULL == pNode1 || NULL == pNode2) return NULL; int leftHasNode1 = 0; int leftHasNode2 = 0; if (pHead->left) { leftHasNode1 = HasNode(pHead->left, pNode1); leftHasNode2 = HasNode(pHead->left, pNode2); } if (leftHasNode1 && leftHasNode2) { // 两个结点都在左子树中 if (pHead->left == pNode1 || pHead->left == pNode2) return pHead; return LastCommonParent(pHead->left, pNode1, pNode2); } int rightHasNode1 = 0; int rightHasNode2 = 0; if (pHead->right) { if (!leftHasNode1) rightHasNode1 = HasNode(pHead->right, pNode1); if (!leftHasNode2) rightHasNode2 = HasNode(pHead->right, pNode2); } if (rightHasNode1 && rightHasNode2) { // 两个结点都在右子树中 if (pHead->right == pNode1 || pHead->right == pNode2) return pHead; return LastCommonParent(pHead->right, pNode1, pNode2); } if ((leftHasNode1 && rightHasNode2) // 两个结点一个在左子树中,另一个在右子树中 || (leftHasNode2 && rightHasNode1)) return pHead; return NULL; }
树为另一树的子结构
思路:要在树A中查找是否存在和树B结构一样的子树,可以分为两步:
1、在树A中找到和B的根节点一样的节点N;
2、判断树A中以N为根节点的子树是不是包括和树B一样的结构。
递归的实现方式如下:
int TreeEqual(BSTreeNode* Head1, BSTreeNode* Head2) { if (NULL == Head2) return 1; if (NULL == Head1) return 0; if (Head1->value != Head2->value) return 0; return TreeEqual(Head1->left, Head2->left) && TreeEqual(Head1->right, Head2->right); } int HasSubTreeCore(BSTreeNode* Head1, BSTreeNode* Head2) { int result = 0; if (Head1->value == Head2->value) { result = TreeEqual(Head1, Head2); } if (result == 0 && Head1->left) { result = TreeEqual(Head1->left, Head2); } if (result == 0 && Head1->right) { result = TreeEqual(Head1->right, Head2); } return result; } int HasSubTree(BSTreeNode* Head1, BSTreeNode* Head2) { if ((Head1 == NULL && Head2 != NULL) || (Head1 != NULL && Head2 == NULL)) return 0; if (Head1 == NULL && Head2 == NULL) return 1; return HasSubTreeCore(Head1, Head2); }