算法学习--二叉树
2016-02-06 20:27
316 查看
二分查找树转化为排序的循环双链表
输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。
例如对于下面的二分查找树:
small pointer 其实也就是指向左孩子,large pointer指向右孩子,转化为双链表之后 small pointer应该指向前一个元素,large pointer应该指向后一个元素。转化为排序的循环双链表则为:
中序遍历改变指针方向
void inOrderBSTree(BSTreeNode* pBSTree) { if (NULL==pBSTree) { return; } if (NULL!=pBSTree->m_pLeft) { inOrderBSTree(pBSTree->m_pLeft); } // if (NULL!=pBSTree) // { // cout<<pBSTree->m_nValue; // } convertToDoubleList(pBSTree); if (NULL!=pBSTree->m_pRight) { inOrderBSTree(pBSTree->m_pRight); } } /************************************************************************/ /************************************************************************/ /* 调整结点指针 */ void convertToDoubleList(BSTreeNode* pCurrent) { pCurrent->m_pLeft=pIndex;//使当前结点的左指针指向双向链表中最后一个结点 if (NULL==pIndex)//若最后一个元素不存在,此时双向链表尚未建立,因此将当前结点设为双向链表头结点 { pHead=pCurrent; } else//使双向链表中最后一个结点的右指针指向当前结点 { pIndex->m_pRight=pCurrent; } pIndex=pCurrent;//将当前结点设为双向链表中最后一个结点 cout<<pCurrent->m_nValue<<" "; }
LCA:
解法1:下面是一个简单的复杂度为 O(n) 的算法,解决LCA问题
1) 找到从根到n1的路径,并存储在一个向量或数组中。
2)找到从根到n2的路径,并存储在一个向量或数组中。
3) 遍历这两条路径,直到遇到一个不同的节点,则前面的那个即为最低公共祖先.
bool findpath(Node * root,vector<int> &path,int key){ if(root == NULL) return false; path.push_back(root->key); if(root->key == key) return true; //左子树或右子树 是否找到,找到的话当前节点就在路径中了 bool find = ( findpath(root->left, path, key) || findpath(root->right,path ,key) ); if(find) return true; //该节点下未找到就弹出 path.pop_back(); return false; } int findLCA(Node * root,int key1,int key2){ vector<int> path1,path2; bool find1 = findpath(root, path1, key1); bool find2 = findpath(root, path2, key2); if(find1 && find2){ int ans ; for(int i=0; i<path1.size(); i++){ if(path1[i] != path2[i]){ break; }else ans = path1[i]; } return ans; } return -1; }
解法2:
上面的方法虽然是O(n),但是操作依然繁琐了一点,并且需要额外的空间来存储路径。其实可以只遍历一次,利用递归的巧妙之处。学好二叉树,其实就是学好递归。
从root开始遍历,如果n1和n2中的任一个和root匹配,那么root就是LCA。 如果都不匹配,则分别递归左、右子树,如果有一个 key(n1或n2)出现在左子树,并且另一个key(n1或n2)出现在右子树,则root就是LCA. 如果两个key都出现在左子树,则说明LCA在左子树中,否则在右子树。
struct Node *findLCA(struct Node* root, int n1, int n2) { if (root == NULL) return NULL; // 只要n1 或 n2 的任一个匹配即可 // (注意:如果 一个节点是另一个祖先,则返回的是祖先节点。因为递归是要返回到祖先的 ) if (root->key == n1 || root->key == n2) return root; // 分别在左右子树查找 Node *left_lca = findLCA(root->left, n1, n2); Node *right_lca = findLCA(root->right, n1, n2); // 如果都返回非空指针 Non-NULL, 则说明两个节点分别出现了在两个子树中,则当前节点肯定为LCA if (left_lca && right_lca) return root; // 如果一个为空,在说明LCA在另一个子树 return (left_lca != NULL)? left_lca: right_lca; }
不使用递归和栈中序遍历二叉树
我们知道,在深度搜索遍历的过程中,之所以要用递归或者是用非递归的栈方式,参考二叉树非递归中序遍历,都是因为其他的方式没法记录当前节点的parent,而如果在每个节点的结构里面加个parent 分量显然是不现实的,那么Morris是怎么解决这一问题的呢?好吧,他用得很巧妙,实际上是用叶子节点的空指针来记录当前节点的位置,然后一旦遍历到了叶子节点,发现叶子节点的右指针指向的是当前节点,那么就认为以当前节点的左子树已经遍历完成。Morris遍历正是利用了线索二叉树 的思想。
以inorder为例,初始化当前节点为root,它的遍历规则如下:
如果当前节点为空,程序退出。
如果当前节点非空,
如果当前节点的左儿子为空,那么输出当前节点,当前节点重置为当前节点的右儿子。
如果当前节点的左儿子非空,找到当前节点左子树的最右叶子节点(此时最右节点的右儿子有两种情况,一种是指向当前节点,一种是为空,你也许感到奇怪,右节点的右儿子怎么可能非空,注意,这里的最右叶子节点只带的是原树中的最右叶子节点。),若其最右叶子节点为空,令其指向当前节点,将当前节点重置为其左儿子,若其最右节点指向当前节点,输出当前节点,将当前节点重置为当前节点的右儿子,并恢复树结构,即将最右节点的右节点再次设置为NULL
void MorrisTraversal(struct tNode *root) { struct tNode *current,*pre; if(root == NULL) return; current = root; while(current != NULL) { if(current->left == NULL) { printf(" %d ", current->data); current = current->right; } else { /* 找到current的前驱节点 */ pre = current->left; while(pre->right != NULL && pre->right != current) pre = pre->right; /* 将current节点作为其前驱节点的右孩子 */ if(pre->right == NULL) { pre->right = current; current = current->left; } /* 恢复树的原有结构,更改right 指针 */ else { pre->right = NULL; printf(" %d ",current->data); current = current->right; } /* End of if condition pre->right == NULL */ } /* End of if condition current->left == NULL*/ } /* End of while */ }
有序链表转化为平衡的二分查找树
比较直观的解法是自顶向下的递归解决,先找到中间节点作为根节点,然后递归左右两部分。所有我们需要先找到中间节点,对于单链表来说,必须要遍历一边,可以使用快慢指针加快查找速度。public class Solution { public TreeNode sortedListToBST(ListNode head) { if(head == null) return null; if(head.next == null){ return new TreeNode(head.val); } //用快慢指针找到中间节点 ListNode slow = head; ListNode fast = head; ListNode preSlow = null; while(fast.next != null && fast.next.next != null){ be6f preSlow = slow; slow = slow.next; fast = fast.next.next; } //分别递归左右两部分 TreeNode mid = new TreeNode(slow.val); if(preSlow != null){ preSlow.next = null; mid.left = sortedListToBST(head); } if(slow.next != null){ mid.right = sortedListToBST(slow.next); } return mid; } }
找出二叉树中某个节点的所有祖先节点
对于一颗普通的二叉树和一个节点key,找出该节点的所有祖先节点。例如树结构如下:给定的key为节点7,则应该打印 4,2,1.
bool printAncestors(struct node *root, int target) { if (root == NULL) return false; if (root->data == target) return true; //子树可以找到,当前节点肯定为祖先节点 if ( printAncestors(root->left, target) || printAncestors(root->right, target) ) { cout << root->data << " "; return true; } /* Else return false */ return false; }
二叉树非递归中序遍历
void InOrderTraverse(BiTree T)//非递归中序遍历 { stack<BiTree> Stack; if(!T) { printf("空树!\n"); return; } while(T || !Stack.empty()) { while(T) { Stack.push(T); T=T->lchild; } T=Stack.top(); Stack.pop(); printf("%c",T->data); T=T->rchild; } }
二叉查找树的后序遍历结果
题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。如果是返回true,否则返回false。例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果:
8
/ \
6 10
/ \ / \
5 7 9 11
因此返回true。
如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。
分析:这是一道trilogy的笔试题,主要考查对二元查找树的理解。
在后续遍历得到的序列中,最后一个元素为树的根结点。从头开始扫描这个序列,比根结点小的元素都应该位于序列的左半部分;从第一个大于跟结点开始到跟结点前面的一个元素为止,所有元素都应该大于跟结点,因为这部分元素对应的是树的右子树。根据这样的划分,把序列划分为左右两部分,我们递归地确认序列的左、右两部分是不是都是二元查找树。
bool verifySquenceOfBST(int squence[], int length) { if(squence == NULL || length <= 0) return false; // root of a BST is at the end of post order traversal squence int root = squence[length - 1]; // the nodes in left sub-tree are less than the root int i = 0; for(; i < length - 1; ++ i) { if(squence[i] > root) break; } // the nodes in the right sub-tree are greater than the root int j = i; for(; j < length - 1; ++ j) { if(squence[j] < root) return false; } // verify whether the left sub-tree is a BST bool left = true; if(i > 0) left = verifySquenceOfBST(squence, i); // verify whether the right sub-tree is a BST bool right = true; if(i < length - 1) right = verifySquenceOfBST(squence + i, length - i - 1); return (left && right); }
相关文章推荐
- LeetCode60. Permutation Sequence
- IntelliJ IDEA创建项目技巧(转)
- 51Nod 1051 最大子矩阵和(二维最大字段和dp)
- U3D物体移动方式小结
- HDU 2894 DeBruijin(欧拉回路)
- android dialog类详解
- python学习之while 和for循环
- Android_Mars学习笔记_S01_001activity初步
- 【3x+1猜想】思考:x一定会缩减成比它小的值
- ios如何在#import方面提升编译性能
- android上的简单hook
- iOS中的HTTP协议 如何使用GET和POST
- JAVA中的接口和抽象类
- FPGA 独立按键消抖
- RunLoop
- Unity GameObject.activeSelf, GameObject.activeInHierarchy,GameObject.SetActive和SetActiveRecursively
- JAVA中的接口和抽象类
- 奋斗的小蜗牛
- 平台架构实践
- OAuth学习笔记