zoukankan      html  css  js  c++  java
  • 二叉树



    一:术语、定义、特点

    1、树

    树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。

    树结构中的术语

    1. 节点的度:节点拥有的子树的数目;
    2. 叶子节点:度为0的节点;
    3. 分支结点:度不为0的节点;
    4. 树的度:树中节点的最大的度;
    5. 层次:根结点的层次为1,其余节点的层次等于该结点的双亲节点的层次加1;
    6. 树的高度:树中节点的最大层次;
    7. 森林:0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。

    2、二叉树

    二叉树也是树;
    二叉树有五种基本形态,如下图:

    二叉树结构特点

    1. 每个节点最多有两个子树(即二叉树的度不能大于2),并且二叉树的子树有左右之分,其次序不能颠倒;
    2. 没有父节点的节点称为根节点;
    3. 没有子节点的节点称为叶子节点;
    4. 每一个非根节点有且只有一个父节点;
    5. 除了根节点外,每个子节点可以分为多个不相交的子树;
    6. 树里面没有环路。

    二叉树性质

    性质1:在二叉树的第 i 层上最多有 2^(i – 1) 个节点; (i>=1)
    性质2:深度为 k 的二叉树至多有 2^k – 1 个节点;(k>=1)
    性质3:包含n个节点的二叉树的高度至少为(log2n)+1;
    性质4:在任意一棵二叉树中,若终端节点的个数为n0,度为2的节点数为n2,则n0=n2+1;

    考试时常考性质4的证明

    性质4:在任意一棵二叉树中,若终端节点的个数为n0,度为2的节点数为n2,则n0=n2+1。
    证明:因为二叉树中所有节点的度数均不大于2,不妨设n0表示度为0的节点个数,n1表示度为1的节点个数,n2表示度为2的节点个数。三类节点加起来为总结点个数,于是便可得到:n=n0+n1+n2 (式1)
    由度之间的关系可得第二个等式:n=n00+n11+n2*2+1即n=n1+2n2+1 (式2)
    合(式1)(式2),移项可得:n0=n2+1,证毕。

    3、几种特殊形态的二叉树

    3.1 满二叉树

    深度为k 且有2^k -1 个节点的二叉树。


    已知某二叉树深度为k,则该树最多有2^k - 1个节点;

    每层有2^k+1 个节点;

    3.2 完全二叉树

    深度为k,有n个节点的二叉树,其每一个节点都与深度为k 的满二叉树中编号从1 至n 的节点一一对应。

    特点:叶子节点只能出现在最下层和次下层,且最下层的叶子节点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。

    面试题:如果一个完全二叉树的节点总数为768个,求叶子节点的个数。
    由二叉树的性质知:n0=n2+1,将之带入768=n0+n1+n2中得:768=n1+2n2+1,因为完全二叉树度为1的节点个数要么为0,要么为1,那么就把n1=0或者1都代入公式中,很容易发现n1=1才符合条件。所以算出来n2=383,所以叶子节点个数n0=n2+1=384。

    总结:如果一棵完全二叉树的节点总数为n,那么叶子节点等于n/2(当n为偶数时)或者(n+1)/2(当n为奇数时)

    3.3 平衡二叉树

    当且仅当两个子树的高度差不超过1时,这个树是平衡二叉树。

    3.4 二叉搜索树

    又称二叉查找树,设x为二叉查找树中的一个节点,x节点包含关键字key,节点x的key值计为key[x]。如果y是x的左子树中的一个节点,则key[y]<=key[x];如果y是x的右子树的一个节点,则key[y]>=key[x]。即满足:左子树<=根节点<=右子树。

    (1)若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值。

    (2)任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值。

    (3)任意节点的左、右子树也分别为二叉查找树。

    二:二叉树的代码实现

    typedef struct node{
        type_t data;
        struct node *left;
        struct node *right;
    }Node;
    

    1、创建节点

    Node *node_create(type_t data){
        Node *p = (Node *)malloc(sizeof(Node));
        p->data = data;
        p->left = NULL;
        p->right = NULL;
        return p;
    }
    

    2、递归实现二叉树的三序遍历

    2.1 前序遍历

    void tree_pre_order(Node *tree){
        if(tree == NULL)
            return;
        else{
            printf("pre: %d
    ", tree->data);
            tree_pre_order(tree->left);
            tree_pre_order(tree->right);
        }
    }
    
    

    2.2 中序遍历

    void tree_in_order(Node *tree){
        if(tree == NULL)
            return;
        else{
            tree_in_order(tree->left);
            printf("in: %d
    ", tree->data);
            tree_in_order(tree->right);
        }
    }
    

    2.3 后序遍历

    void tree_post_order(Node *tree){
        if(tree == NULL)
            return;
        else{
            tree_post_order(tree->left);
            tree_post_order(tree->right);
            printf("next: %d
    ", tree->data);
        }
    }
    

    3、层序遍历

    思路:相当于广度优先搜索,使用队列实现。队列初始化,将根节点压入队列。当队列不为空,进行如下操作:弹出一个节点,访问,若左子节点或右子节点不为空,将其压入队列。

    void LevelTraverse(BinaryTreeNode * pRoot){
    	if(pRoot == NULL)
    		return;
    	queue<BinaryTreeNode *> q;
    	q.push(pRoot);
    	while(!q.empty()){
    		BinaryTreeNode * pNode = q.front();
    		q.pop();
    		Visit(pNode); // 访问节点
    		if(pNode->m_pLeft != NULL)
    			q.push(pNode->m_pLeft);
    		if(pNode->m_pRight != NULL)
    			q.push(pNode->m_pRight);
    	}
    	return;
    }
    

    4、求二叉树中的节点数

    递归思路:

    (1)如果二叉树为空,节点个数为0;

    (2)如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1;

    int GetNodeNum(BinaryTreeNode * pRoot){
    	// 递归出口
    	if(pRoot == NULL) 
    		return 0;
    	return GetNodeNum(pRoot->m_pLeft) + GetNodeNum(pRoot->m_pRight) + 1;
    }
    

    5、求二叉树的深度

    递归思路:

    (1)如果二叉树为空,二叉树的深度为0;

    (2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1;

    int GetDepth(BinaryTreeNode * pRoot){
    	// 递归出口
    	if(pRoot == NULL) 
    		return 0;
    	int depthLeft = GetDepth(pRoot->m_pLeft);
    	int depthRight = GetDepth(pRoot->m_pRight);
    	return depthLeft > depthRight ? (depthLeft + 1) : (depthRight + 1); 
    }
    

    python版:

    def treeDepth(self,root):
    	if not root:
    		return 0
    	depthLeft = self.treeDepth(root.left)
    	depthRight = self.treeDepth(root.right)
    return depthLeft+1 if depthLeft > depthRight else depthRight+1
    

    6、判断二叉树是否是平衡二叉树

    递归思路:

    (1)如果二叉树为空,返回真;

    (2)如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假;

    bool IsAVL(BinaryTreeNode * pRoot, int & height){
    	// 空树,返回真
    	if(pRoot == NULL) {
    		height = 0;
    		return true;
    	}
    	int heightLeft;
    	bool resultLeft = IsAVL(pRoot->m_pLeft, heightLeft);
    	int heightRight;
    	bool resultRight = IsAVL(pRoot->m_pRight, heightRight);
    	// 左子树和右子树都是AVL,并且高度相差不大于1,返回真
    	if(resultLeft && resultRight && abs(heightLeft - heightRight) <= 1) {
    		height = max(heightLeft, heightRight) + 1;
    		return true;
    	}
    	else{
    		height = max(heightLeft, heightRight) + 1;
    		return false;
    	}
    }
    

    python版:

    # 判断平衡二叉树
    def isBalanced(self,root):
    	"""
    	:typeroot:TreeNode
    	:rtype:bool
    	"""
    	if not root:
    		return True
    	resultLeft = self.isBalanced(root.left)
    	heightLeft = self.treeDepth(root.left)
    	resultRight = self.isBalanced(root.right)
    	heightRight = self.treeDepth(root.right)
    	#左子树和右子树都是AVL,并且高度相差不大于1,返回真
    	if resultLeft and resultRight and abs(heightLeft-heightRight) <= 1:
    		return True
    	else:
    		return False
    
    #求二叉树的深度
    def treeDepth(self,root):
    	if not root:
    		return 0
    	depthLeft = self.treeDepth(root.left)
    	depthRight = self.treeDepth(root.right)
    return depthLeft + 1 if depthLeft > depthRight else depthRight + 1
    

    7、判断两棵二叉树结构是否相同

    不考虑数据内容。结构相同意味着对应的左子树和对应的右子树都结构相同。

    递归思路:

    (1)如果两棵二叉树都为空,返回真;

    (2)如果两棵二叉树一棵为空,另一棵不为空,返回假;

    (3)如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假;

    bool StructureCmp(BinaryTreeNode * pRoot1, BinaryTreeNode * pRoot2){
    	// 都为空,返回真
    	if(pRoot1 == NULL && pRoot2 == NULL) 
    		return true;
    	// 有一个为空,一个不为空,返回假
    	else if(pRoot1 == NULL || pRoot2 == NULL) 
    		return false;
    	// 比较对应左子树
    	bool resultLeft = StructureCmp(pRoot1->m_pLeft, pRoot2->m_pLeft);  
    	bool resultRight = StructureCmp(pRoot1->m_pRight, pRoot2->m_pRight); // 比较对应右子树
    	return (resultLeft && resultRight);
    }
    

    8、求二叉树第k层节点个数

    递归思路:

    (1)如果二叉树为空或者k<1返回0;

    (2)如果二叉树不为空并且k==1,返回1;

    (3)如果二叉树不为空且k>1,返回左子树中k-1层的节点个数与右子树k-1层节点个数之和;

    int GetNodeNumKthLevel(BinaryTreeNode * pRoot, int k){
    	if(pRoot == NULL || k < 1)
    		return 0;
    	if(k == 1)
    		return 1;
    	// 左子树中k-1层的节点个数
    	int numLeft = GetNodeNumKthLevel(pRoot->m_pLeft, k-1);  
    	// 右子树中k-1层的节点个数
    	int numRight = GetNodeNumKthLevel(pRoot->m_pRight, k-1); 
    	return (numLeft + numRight);
    }
    

    9、求二叉树的叶子节点个数

    递归思路:

    (1)如果二叉树为空,返回0;

    (2)如果二叉树不为空且左右子树为空,返回1;

    (3)如果二叉树不为空,且左右子树不同时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数;

    int GetLeafNodeNum(BinaryTreeNode * pRoot){
    	if(pRoot == NULL)
    		return 0;
    	if(pRoot->m_pLeft == NULL && pRoot->m_pRight == NULL)
    		return 1;
    	int numLeft = GetLeafNodeNum(pRoot->m_pLeft); // 左子树中叶节点的个数
    	int numRight = GetLeafNodeNum(pRoot->m_pRight); // 右子树中叶节点的个数
    	return (numLeft + numRight);
    }
    

    10、求二叉树的镜像

    递归思路:

    (1)如果二叉树为空,返回空;

    (2)如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树和右子树;

    BinaryTreeNode * Mirror(BinaryTreeNode * pRoot){
    	if(pRoot == NULL) // 返回NULL
    		return NULL;
    	BinaryTreeNode * pLeft = Mirror(pRoot->m_pLeft); // 求左子树镜像
    	BinaryTreeNode * pRight = Mirror(pRoot->m_pRight); // 求右子树镜像
          // 交换左子树和右子树
    	pRoot->m_pLeft = pRight;
    	pRoot->m_pRight = pLeft;
    	return pRoot;
    }
    

    11、求二叉树中两个节点的最近公共祖先节点

    递归思路

    (1)如果两个节点分别在根节点的左子树和右子树,则返回根节点;

    (2)如果两个节点都在左子树,则递归处理左子树;如果两个节点都在右子树,则递归处理右子树;

    bool FindNode(BinaryTreeNode * pRoot, BinaryTreeNode * pNode){
    	if(pRoot == NULL || pNode == NULL)
    		return false;
    
    	if(pRoot == pNode)
    		return true;
     
    	bool found = FindNode(pRoot->m_pLeft, pNode);
    	if(!found)
    		found = FindNode(pRoot->m_pRight, pNode);
    	return found;
    }
    BinaryTreeNode * GetLastCommonParent(BinaryTreeNode * pRoot, BinaryTreeNode * pNode1,           BinaryTreeNode * pNode2){
    	if(FindNode(pRoot->m_pLeft, pNode1)){
    		if(FindNode(pRoot->m_pRight, pNode2))
    			return pRoot;
    		else
    			return GetLastCommonParent(pRoot->m_pLeft, pNode1, pNode2);
    	}
    	else{
    		if(FindNode(pRoot->m_pLeft, pNode2))
    			return pRoot;
    		else
    			return GetLastCommonParent(pRoot->m_pRight, pNode1, pNode2);
    	}
    }
    

    非递归思路

    先求从根节点到两个节点的路径,然后再比较对应路径的节点就行,最后一个相同的节点也就是他们在二叉树中的最低公共祖先节点。

    bool GetNodePath(BinaryTreeNode * pRoot, BinaryTreeNode * pNode, 
                     list<BinaryTreeNode *> & 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->m_pLeft, pNode, path);
    	if(!found)
    		found = GetNodePath(pRoot->m_pRight, pNode, path);
    	if(!found)
    		path.pop_back();
    	return found;
    }
    BinaryTreeNode * GetLastCommonParent(BinaryTreeNode * pRoot, BinaryTreeNode * pNode1, BinaryTreeNode * pNode2){
    	if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
    		return NULL;
    	list<BinaryTreeNode*> path1;
    	bool bResult1 = GetNodePath(pRoot, pNode1, path1);
    	list<BinaryTreeNode*> path2;
    	bool bResult2 = GetNodePath(pRoot, pNode2, path2);
    	if(!bResult1 || !bResult2) 
    		return NULL;
    	BinaryTreeNode * pLast = NULL;
    	list<BinaryTreeNode*>::const_iterator iter1 = path1.begin();
    	list<BinaryTreeNode*>::const_iterator iter2 = path2.begin();
    	while(iter1 != path1.end() && iter2 != path2.end()){
    		if(*iter1 == *iter2)
    			pLast = *iter1;
    		else
    			break;
    		iter1++;
    		iter2++;
    	}
    	return pLast;
    }
    

    12、判断二叉树是否为完全二叉树

    首先考虑完全二叉树的特点:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

    可得思路:按层次(从上到下,从左到右)遍历二叉树,当遇到一个节点的左子树为空时,则该节点右子树必须为空,且后面遍历的节点左右子树都必须为空,否则不是完全二叉树。

    bool IsCompleteBinaryTree(BinaryTreeNode * pRoot){
    	if(pRoot == NULL)
    		return false;
    	queue<BinaryTreeNode *> q;
    	q.push(pRoot);
    	bool mustHaveNoChild = false;
    	bool result = true;
    	while(!q.empty()){
    		BinaryTreeNode * pNode = q.front();
    		q.pop();
    		 // 已经出现了有空子树的节点了,后面出现的必须为叶节点(左右子树都为空)
    		if(mustHaveNoChild){
    			if(pNode->m_pLeft != NULL || pNode->m_pRight != NULL){
    				result = false;
    				break;
    			}
    		}
    		else{
    			if(pNode->m_pLeft != NULL && pNode->m_pRight != NULL){
    				q.push(pNode->m_pLeft);
    				q.push(pNode->m_pRight);
    			}
    			else if(pNode->m_pLeft != NULL && pNode->m_pRight == NULL){
    				mustHaveNoChild = true;
    				q.push(pNode->m_pLeft);
    			}
    			else if(pNode->m_pLeft == NULL && pNode->m_pRight != NULL){
    				result = false;
    				break;
    			}
    			else{
    				mustHaveNoChild = true;
    			}
    		}
    	}
    	return result;
    }
    
  • 相关阅读:
    HDU 2844 Coins(多重背包)
    HDU 4540 威威猫系列故事——打地鼠(DP)
    Codeforces Round #236 (Div. 2)
    FZU 2140 Forever 0.5
    HDU 1171 Big Event in HDU(DP)
    HDU 1160 FatMouse's Speed(DP)
    ZOJ 3490 String Successor
    ZOJ 3609 Modular Inverse
    ZOJ 3603 Draw Something Cheat
    ZOJ 3705 Applications
  • 原文地址:https://www.cnblogs.com/panweiwei/p/13065661.html
Copyright © 2011-2022 走看看