@myecho
2018-03-29T13:35:35.000000Z
字数 10076
阅读 747
数据结构
题目均来自:
1. 题目链接: http://blog.csdn.net/u011412619/article/details/44540431
2. 数据结构的ppt
树的概念,从数据结构上来看是基于递归定义的,树由根节点(没有父节点)和许多子树组成,除了根节点外,其他节点都只能有一个父节点。
从图论的角度看,只要没有回路的连通图就是树。森林是指互相不交并树的集合。还有如下的等价的说法:
1. G 是没有回路的连通图。
2. G 没有回路,但是在G内添加任意一条边,就会形成一个回路。
3. G 是连通的,但是如果去掉任意一条边,就不再连通。
4. G 是连通的,并且3顶点的完全图 K_3 不是G的子图。
5. G内的任意两个顶点能被唯一路径所连通。
如果无向简单图G有有限个顶点(设为n个顶点),那么G 是一棵树还等价于:
G是连通的,有n − 1条边,并且G没有简单回路。
如果一个无向简单图G中没有简单回路,那么G是森林。
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推
节点的度:一个节点含有的子树的个数称为该节点的度
叶节点:节点的度为0的节点
树的度:一棵树中,最大的节点的度称为树的度;
二叉树:每个节点最多含有两个子树的树称为二叉树;
完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
满二叉树:对于上述的完全二叉树,如果去掉其第d层的所有节点,那么剩下的部分就构成一个满二叉树(此时该满二叉树的深度为d-1);
线索二叉树(保留遍历时结点在任一序列的前驱和后继的信息):若结点有左子树,则其lchild域指示其左孩子,否则令lchild域指示其前驱;若结点有右子树,则其rchild域指示其右孩子,否则令rchild指示其后继。还需在结点结构中增加两个标志域LTag和RTag。LTag=0时,lchild域指示结点的左孩子,LTag=1时,lchild域指示结点的前驱;RTag=0时,rchild域指示结点的右孩子,RTag=1时,rchild域指示结点的后继。
二叉查找树:左节点小于根节点、右节点大于根节点的树。
创建一颗二叉树,可以创建先序二叉树,中序二叉树,后序二叉树。但是关于空节点如何表示,也是个很严重的问题。
包括前、中、后三种基本的方式,还包括层次遍历。
struct BinaryNode{
int value;
BinaryNode* left;
BinaryNode* right;
};
void PreOrderTraverse(BinaryNode* root) {
if (root == NULL)
return;
cout << root -> value << endl;
PreOrderTraverse(root -> left);
PreOrderTraverse(root -> right);
}
void InOrderTraverse(BinaryNode* root) {
if (root == NULL)
return;
InOrderTraverse(root -> left);
cout << root -> value << endl;
InOrderTraverse(root -> right);
}
void PostOrderTraverse(BinaryNode* root) {
if (root == NULL)
return;
PostOrderTraverse(root -> left);
PostOrderTraverse(root -> right);
cout << root -> value << endl;
}
void LevelTraverse(BinaryNode* root) {
if (root == NULL)
return;
queue<BinaryNode*> q;
q.push(root);
while (!q.empty()) {
BinaryNode* root = q.front();
q.pop();
cout << root -> value << endl;
if (root -> left != NULL) {
q.push(root -> left);
}
if (root -> right != NULL) {
q.push(root -> right);
}
}
return;
}
Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)
http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html
int GetNodeNum(BinaryNode* root) {
if (root == NULL)
return 0;
return GetNodeNum(root -> left) + GetNodeNum(root -> right) + 1;
}
int GetDepth(BinaryNode* root) {
if (root == NULL)
return 0;
int depthLeft = GetDepth(root -> left);
int depthRight = GetDepth(root -> right);
return depthLeft > depthRight ? (depthLeft + 1) : (depthRight + 1);
}
int GetLeafNode(BinaryNode* root) {
if (root == NULL)
return 0;
if (root -> left == NULL && root -> right == NULL)
return 1;
return GetLeafNode(root -> left) + GetLeafNode(root -> right);
}
//判断一个节点是否在树中
bool IsInTree(BinaryNode* root, BinaryNode* t) {
if (root == NULL) {
return false;
} else if (r == t) {
return true;
} else {
bool has = false;
if (r -> left != NULL)
has = IsInTree(r -> left, t);
if (!has && r -> right != NULL)
has = IsInTree(r -> right, t);
return has;
}
}
//求第k层的节点的个数
int GetNodeNumKthLevel(BinaryNode * Root, int k)
{
if(Root == NULL || k < 1)
return 0;
if(k == 1)
return 1;
int numLeft = GetNodeNumKthLevel(Root -> left, k-1); // 左子树中k-1层的节点个数
int numRight = GetNodeNumKthLevel(Root -> right, k-1); // 右子树中k-1层的节点个数
return (numLeft + numRight);
}
//判断两棵二叉树的结构是否相同
bool StructureCmp(BinaryNode * root1, BinaryNode * root2)
{
if(root1 == NULL && root2 == NULL) // 都为空,返回真
return true;
else if(root1 == NULL || root2 == NULL) // 有一个为空,一个不为空,返回假
return false;
bool resultLeft = StructureCmp(root1->left, root2->left); // 比较对应左子树
bool resultRight = StructureCmp(root1->right, root2->right); // 比较对应右子树
return (resultLeft && resultRight);
}
//判断是否为AVL树
bool IsAVL(BinaryNode * root, int & height)//height为树的高度
{
if(root == NULL) // 空树,返回真
{
height = 0;
return true;
}
int heightLeft;
bool resultLeft = IsAVL(root->left, heightLeft);
int heightRight;
bool resultRight = IsAVL(root->right, heightRight);
if(resultLeft && resultRight && abs(heightLeft - heightRight) <= 1) // 左子树和右子树都是AVL,并且高度相差不大于1,返回真
{
height = max(heightLeft, heightRight) + 1;
return true;
}
else
{
height = max(heightLeft, heightRight) + 1;
return false;
}
}
//二叉树的镜像
BinaryNode * Mirror(BinaryNode * root)
{
if(root == NULL) // 返回NULL
return NULL;
BinaryNode * pLeft = Mirror(root->left); // 求左子树镜像
BinaryNode * pRight = Mirror(root->right); // 求右子树镜像
// 交换左子树和右子树
root->left = pRight;
root->right = pLeft;
return root;
}
void Convert(BinaryNode* root, BinaryNode* & phead, BinaryNode* & ptail) {
BinaryNode *FirstLeft, * LastLeft, * FirstRight, *LastRight;
if (root == NULL) {
phead = NULL;
ptail = NULL;
return;
}
if (root -> left == NULL) {
phead = root;
} else {
Convert(root -> left, FirstLeft, LastLeft);
phead = FirstLeft;
root -> left = LastLeft;
LastLeft -> right = root;
}
if (root -> right == NULL) {
ptail = root;
} else {
Convert(root -> right, FirstRight, LastRight);
ptail = LastRight;
root -> right = FirstRight;
FirstRight -> left = root;
}
return;
}
//遍历双向有序链表时
BinaryNode* start = phead;
while (start -> right != ptail) {
//output
}
output(ptail);
LCA
//递归版本,有很多重复的遍历
bool FindNode(BinaryNode * pRoot, BinaryNode * pNode)//判断节点是否在某棵树中
{
if(pRoot == NULL || pNode == NULL)
return false;
if(pRoot == pNode)
return true;
bool found = FindNode(pRoot->left, pNode);
if(!found)
found = FindNode(pRoot->right, pNode);
return found;
}
BinaryNode * GetLastCommonParent(BinaryNode * pRoot,
BinaryNode * pNode1,
BinaryNode * pNode2)
{
if(FindNode(pRoot->left, pNode1))
{
if(FindNode(pRoot->right, pNode2))
return pRoot;
else
return GetLastCommonParent(pRoot->left, pNode1, pNode2); //都在左节点中
}
else
{
if(FindNode(pRoot->left, pNode2))
return pRoot;
else
return GetLastCommonParent(pRoot->right, pNode1, pNode2); //都在右节点中
}
}
//非递归版本,得到到两个节点的路径
bool GetNodePath(BinaryNode * pRoot, BinaryNode * pNode,
list<BinaryNode *> & path)
{
if (pRoot == NULL) {
return false;
}
if(pRoot == pNode) {
path.push_back(pRoot);
return true;
}
path.push_back(pRoot);
bool found = false;
found = GetNodePath(pRoot->left, pNode, path);
if(!found)
found = GetNodePath(pRoot->right, pNode, path);
if(!found)
path.pop_back(); //把pRoot删除
return found;
}
BinaryNode * GetLastCommonParent(BinaryNode * pRoot, BinaryNode * pNode1, BinaryNode * pNode2)
{
if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
return NULL;
list<BinaryNode*> path1;
bool bResult1 = GetNodePath(pRoot, pNode1, path1);
list<BinaryNode*> path2;
bool bResult2 = GetNodePath(pRoot, pNode2, path2);
if(!bResult1 || !bResult2)
return NULL; //有可能没有找到这个节点
BinaryNode * pLast = NULL;
list<BinaryNode*>::const_iterator iter1 = path1.begin();
list<BinaryNode*>::const_iterator iter2 = path2.begin();
while(iter1 != path1.end() && iter2 != path2.end())
{
if(*iter1 == *iter2)
pLast = *iter1;
else
break;
iter1++;
iter2++;
}
return pLast;
}
此类问题的变种:求树上两个节点的最近距离或最短路径
其实也是求最近公共祖先,求出最近祖先节点之后,由最近祖先节点到这两个节点的距离之和就是两个节点的最近距离。
/**
*
* 如果树为二叉查找树,并已知根节点
*
* 二叉查找树的性质:节点值的大小关系满足:左<根<右
*
* 那么从树根开始:
*
* ①如果当前结点t 小于结点u、v,说明u、v都在t 的右侧,所以它们的共同祖先必定在t 的右子树中,故从t 的右子树中继续查找;
* ②如果当前结点t 满足 u <t < v,说明u和v分居在t 的两侧,故当前结点t 即为最近公共祖先;
* ③而如果u是v的祖先,那么返回u的父结点,同理,如果v是u的祖先,那么返回v的父结点。
*
* @param root 树的根节点
* @param node1
* @param node2
* @return
*/
public Node getLCA1(Node root, Node node1, Node node2){
//计算出这两个节点的值
int left = node1.value > node2.value ? node2.value : node1.value;
int right = node1.value > node2.value ? node1.value : node2.value;
//根据注释中的关系,进行迭代
while (true){
if(root.value < left){
root = root.right;
}else if(root.value > right){
root = root.left;
}else {
return root;
}
}
}
/**
*
* 如果树为普通二叉查找树,并已知根节点
*
* 二叉树的性质:只知道节点间的关系
*
*
* 递归入口:以root为根节点寻找node1和node2的公共祖先
* 递归出口为:node1或者node2成为了根节点(子树的),或者已经到了最底层还没有遇到node1或者node2;
*
* @param root 树的根节点
* @param node1
* @param node2
* @return
*/
public Node getLCA2(Node root, Node node1, Node node2){
if(root == null || root == node1 || root == node2){
return root;
}
//以root.left为根节点进行递归寻找
Node left = getLCA2(root.left, node1, node2);
//以root.right为根节点进行递归寻找
Node right = getLCA2(root.right, node1, node2);
if(left != null && right != null){
return root;
}else if(left != null){
return left;
}else if(right != null){
return right;
}else {
return null;
}
}
//上边这种写法是错误的,该函数当一个节点是另一个节点的祖先时,返回的是离根节点最近的那个节点,要想返回最近公共祖先节点需进行判断两节点是否有祖孙关系
另外有离线tarjan算法可以解决LCA问题
//详见<剑指offer笔记>
思路:按层次(从上到下,从左到右)遍历二叉树,当遇到一个节点的左子树为空时,则该节点右子树必须为空,且后面遍历的节点左
右子树都必须为空,否则不是完全二叉树。
bool IsCompleteBinaryTree(BinaryNode * pRoot)
{
if(pRoot == NULL)
return false;
queue<BinaryNode *> q;
q.push(pRoot);
bool mustHaveNoChild = false;
bool result = true;
while(!q.empty())
{
BinaryNode * pNode = q.front();
q.pop();
if(mustHaveNoChild)
{
if(pNode->left != NULL || pNode->right != NULL)
{
result = false;
break;
}
}
else
{
if(pNode->left != NULL && pNode->right != NULL)
{
q.push(pNode->left);
q.push(pNode->right);
}
else if(pNode->left != NULL && pNode->right == NULL)
{
mustHaveNoChild = true;
q.push(pNode->left);
}
else if(pNode->left == NULL && pNode->right != NULL)
{
result = false;
break;
}
else //左右节点都为NULL
{
mustHaveNoChild = true;
}
}
}
return result;
}
leetcode题解中有更为简略的版本。Maxinimal path sum
int GetMaxDistance(BinaryNode * pRoot, int & maxLeft, int & maxRight)
{
// maxLeft, 左子树中的节点距离根节点的最远距离
// maxRight, 右子树中的节点距离根节点的最远距离
if(pRoot == NULL)
{
maxLeft = 0;
maxRight = 0;
return 0;
}
int maxLL, maxLR, maxRL, maxRR;
int maxDistLeft, maxDistRight;
if(pRoot->left != NULL) //很重要不要遗漏!
{
maxDistLeft = GetMaxDistance(pRoot->left, maxLL, maxLR);
maxLeft = max(maxLL, maxLR) + 1;
}
else
{
maxDistLeft = 0;
maxLeft = 0;
}
if(pRoot->right != NULL)
{
maxDistRight = GetMaxDistance(pRoot->right, maxRL, maxRR);
maxRight = max(maxRL, maxRR) + 1;
}
else
{
maxDistRight = 0;
maxRight = 0;
}
return max(max(maxDistLeft, maxDistRight), maxLeft+maxRight);
} //maxDistLeft没有利用到root这个节点的距离
路径一定指的是到叶节点,所以递归停止时不只要判断sum==0而且还要判断是否到达了叶节点
void Print_Sum(BinaryNode* root, list<BinaryNode*>& path, int sum) {
if (root == NULL) {
return;
}
path.push_back(root);
sum -= root -> value;
if (sum == 0) {
list<BinaryNode*>::const_iterator iter = path.begin();
cout << (*iter) -> value << endl;
//return?取决于value的值可否允许有负值存在
}
if (root -> left != NULL) {
Print_Sum(root -> left, path, sum);
}
if (root -> right != NULL) {
Print_Sum(root -> right, path, sum);
}
sum += root -> value;
path.pop_back();
return;
}
步骤如下:
1. 给定n个权值的节点用来构造一棵haffman树
2. 在森林中选取两棵节点权值较小的二叉树构造一棵二叉树,新二叉树的节点的权值为两棵子树的权值之和
3. 将2中2个旧节点删除,添加入新生成的节点
4. 重复2、3,直到只剩下一个根节点
编程时从(m+1)~2*m生成数组元素即可
为什么要使用前缀编码?答:可以得到在遍历时知道到哪个字符时终止扫描并开始解码。
huffman编码如何解码? 对应着huffman树往下走即可解码。
主要的内容包括线索二叉树的创建和遍历,其中中序遍历对应的线索二叉树既可以得到前驱也可以得到后继,而如前序遍历得到的线索二叉树只能得到后继,后序遍历得到的线索二叉树只能得到前序。
http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html
题目大意:对于n叉树,给出先序遍历和后续遍历,求可能的个数。
主要就是利用递归和组合数的思想.
题解:http://www.acmerblog.com/jiudu-1044-2225.html
要注意题解中的两个地方?
1. 组合数的计算 算法竞赛入门经典中给出了更简便的计算方式
2. 是否是同一层的判断?在left与right中字母出现的顺序相同则为同一层
3. getcnt函数接受的输入都是去除了头节点的,在递归过程中要注意