数据结构与算法分析(C++)(第3版)-笔记三(1)-二叉树

定义及主要特性

递归定义

二叉树由结点的有限集合组成,这个组合或者为空,或者由一个根结点及两棵不相交的,分别称作这个根的左子树和右子树的二叉树组成。

特点

1.每个结点至多有两棵子树;
2.二叉树的子树有左、右之分,且其次序不能任意颠倒

基本形态


相关术语

从一个结点到它的两个子结点都有边(edge)相连,此结点称为它的子结点的父结点(parent)

如果一棵树的一串结点n1,n2,…,nk有如下关系:
结点ni是ni+1的父结点(1 <= i < k),就把n1,n2,…,nk称为一条由n1至nk路径(path)

这条路径的长度(length)是k-1(因为k个结点是用k-1条边连接起来的);

如果有一条路径从结点R至结点M,那么R就称为M的祖先(ancestor),而M称为R的子孙(descendant)

结点M的深度(depth)就是从根结点到M的路径的长度;

树的高度(height)等于最深的结点的深度+1;

任何深度为d的结点的层数(level)都为d,根结点深度为0,层数也为0;

没有非空子树的结点称为叶结点(leaf)或终端结点

至少有一个非空子树的结点称为分支结点或内部结点(internal node)

结点度:
结点拥有子结点的数量(二叉树中度最大为2,树中则无限制)

满二叉树:
如果一棵二叉树的任何结点,或者是树叶,或者恰有两个非空子树的分支结点,则称为满二叉树;

完全二叉树:
若一棵二叉树最多只有最下面的两层结点度数可以小于2,并且最下面一层的结点都集中在该层最左边的若干位置上,也就是说,自根结点起每一层从左至右的填充,一棵高度为d的树除了第d-1层,每一层都是满的,底层叶结点集中在左边的若干位置上,则称为完全二叉树;

完全二叉树的例子:


二叉树性质

1. 满二叉树定理:非空满二叉树树叶数等于其分支结点数加1
   证明:
   假设二叉树结点数一共为n,树叶数为l,分支结点数为b,而树叶数+分支结点数=n 即 n=l+b,根据满二叉树的性质有每个分支恰有两个结点,则一共有2b条边,一棵二叉树,除根结点外,每个结点都恰有一条边连接父结点,所以有n-1条边,那么2b=n-1 => 2b= l+b-1 => b+1=l

2. 满二叉树定理的推论:一棵非空二叉树空子树的数目等于其结点数目加1
   证明1:
   设二叉树T,将其所有空子数换为叶结点,把新的二叉树记为T’,所有原来树T的结点现在是树T’的分支结点。
   根据满二叉树定理,新添加的叶结点数目等于树T的结点数目加1,而每个新添加的叶结点对应树T的一棵空子树,因为树T中空子树的数目等于树T中结点数目加1
   证明2:
   根据定义,二叉树T中每个结点都有两个子结点指针(空或非空),因此一个有n个结点的二叉树有2n个子结点指针,除根结点外,共有n-1个结点,它们都由其父结点中相应指针指引而来,换句话说,就有n-1个非空子结点指针,既然子结点指针数为2n,则其中有n+1个为空(指针),也就是一棵非空二叉树空子树的数目等于其结点数目加1

3. 任何一棵二叉树,度为0的结点比度为2的结点多一个
   证明:
   设树的结点数为n,其中度为0、1、2的结点数为n0、n1、n2,有n=n0+n1+n2,设边数为e。因为除根以外,每个结点都有一条边进入,故n=e+1。由于这些边是由度为1和2的结点射出的,因为e=n1+2n2,所以有n=e+1=n1+2n2+1,所以n0=n2+1
4. 二叉树的第i层(根为第0层)最多有2i个结点
5. 高度为k(深度为k-1)的二叉树至多有2k-1个结点
   (只有一个根结点的二叉树的高度为1,深度为0)
6. 有n个结点的完全二叉树的高度为log2n+1(深度为log2n)
   假设该完全二叉树的深度为 k,则根据完全二叉树的定义和性质 2有:
   2 ^(k-1)-1< n ≤2^k-1 或 2^(k-1)≤ n <2^k
   所以有:k-1≤ log2n<k
   又因为 k是整数,所以,k= [log2n]+1

完全二叉树的下标公式

公式中 i 表示结点的索引,n 表示二叉树结点总数
当 i != 0 时,Parent(i)=[(i-1)/2](向下取整);
当 2i + 1 < n 时,LeftChild(i) = 2i + 1;
当 2i + 2 < n 时,RightChild(i) = 2i + 2;
当 r为偶数 且 0 <= r <= n-1 时,LeftSibling(i) = r - 1;
当 r为奇数 且 r + 1 < n 时,RightSibling(i) = r + 1;

周游二叉树

遍历

系统地访问二叉树中地结点,每个结点正好被访问到一次

方法

前序遍历 - preorder traversal – 根 左 右

  访问根结点;前序遍历左子树;前序遍历右子树
  

中序遍历 - inorder traversal – 左 根 右

  中序遍历左子树;访问根结点;中序遍历右子树
  

后序遍历 - inorder traversal – 左 右 根

  后序遍历左子树;后序遍历右子树;访问根结点
  
举例:
  

前序遍历算法

	template <class Elem>
	void preorder(BinNode<Elem>* subroot)
	{
		if(subroot == NULL) return;		//空树,不作处理
		visit(subroot);					// Perform whatever action is desired 即 将subroot结点标记为已遍历
		preorder(subroot->left());
		preorder(subroot->right());
	}

仅知二叉树的先序序列、中序序列或后序序列不足以建树,而已知 先序+中序序列 / 后序+中序序列的话,就可以确定一棵二叉树。

思路:
先序的第一个元素和后序的最后一个元素是当前子树的根,然后遍历中序序列,找到左右子树的分界线,递归建左子树和右子树。

如先序+中序:
  
例:
  

二叉树的实现

二叉树的存储

使用指针实现二叉树

二叉链表(最常用)

		class TNode:public BinNode<E> {
			private:
				Key k;
				E it;
				TNode* lc;
				TNode* rc;
			......
		}

优点:运算方便;
缺点:空指针太多
图例:


带父指针的三重链表

				class TNode:public BinNode<E> {
					private:
						Key k;
						E it;
						TNode* lc;
						TNode* rc;
						TNode* father;
					......
				}

优点:在某些经常要回溯到父结点的应用中很有效
图例:


数组 – 完全二叉树

a. 完全二叉树可使用顺序存储,按照二叉树的层次周游次序存储在一个数组中
    优点:简单,节省空间
    图例:
    
    顺序存储:ABCDEFGHIJKL

b.非完全二叉树可以置空值后转换为完全二叉树存储
    图例(图中蓝色结点均为空):
    
顺序存储:CEDJFX//K/G/I/L

二叉检索树 / 二叉搜索树 / 二叉查找树 / Binary Search Tree

定义

二叉检索树 或者为空,或者是 满足下列条件的非空二叉树:
(1)若左子树非空,则左子树上所有结点的值均小于根结点的值;
(2)若右子树非空,则右子树上所有结点的值均大于或等于根结点的值;
(3)左右子树本身又各是一棵二叉检索树
(也可以是左子树小于等于,右子树大于)

性质

按照中序周游将各节点打印出来,将得到由小到大的序列
也就是说
二叉检索树得到的中序序列是一个从小到大排序好的序列
BST 图例:
    

检索

其检索效率在于只需检索两个子树之一
过程:
· 从根节点开始,在二叉检索树中检索 值 K
· 若根结点存储的值为 K,则检索结束;
· 若 K 小于 根结点的值,则只需要检索左子树;
· 若 K 大于 根结点的值,则只需要检索右子树;
· 持续上述过程直接找到 K 或 找到了一个树叶
· 如果遇上叶子结点还未发现 K,那么 K 就不在该二叉检索树中
代码:

	E find(const Key& k) const {
		return findhelp(root, k);
	}
	E BST<Key,E>::findhelp(BSTNode<Key,E>* root, const Key& k) const {
		if(root == NULL) return NULL;	// Empty Tree, not found
		if(k < root->key())
			return findhelp(root->left(),k);	// check left
		else if(k > root->key())
			return findhelp(root->right(),k);	// check right
		return root->element();			// found
	}
	```
	<br>
	
	**插入:**
	```cpp
	void insert(const Key& k, const E& e) {
		root = inserthelp(root, k, e);
		nodeCount++;
	}
	BSTNode<Key,E>* BST<Key,E>::inserthelp(BSTNode<Key,E>* root, const Key& k, const E& it) {
		if(root == NULL)	// Empty tree: create node
			return new BSTNode<Key,E>(k,it,NULL,NULL);
		if(k < root->key())
			root->setLeft(inserthelp(root->left(),k,it));
		else
			root->setRight(inserthelp(root->right(),k,it);
		return root;		// return tree with node inserted
	}

图例:


删除

原理:

删除子树中最小值图示:

	BSTNode<Key,E>* BST<Key,E>::deletemin(BSTNode<Key,E>* subroot) {
		if (subroot->left() == NULL) {		// found min
			return subroot->right();
		}
		else { 								// Continue left
			subroot->setLeft(deletemin(subroot->left()));
			return subroot;
		}
	}

执行过程如下:
deletemin(10);
10->left() != NULL --> 10->setleft(deletemin(5));
deletemin(5);
5->left() == NULL --> return 5->right(); // 即 return 9
10->setleft(9);


假如要删除的是右子树中最小值结点(值为37):

   E remove(const Key& k) {
	E temp = findhelp(root, k);		// Firstly, find it
		if(temp != NULL) {
			root = removehelp(root, k);
			nodeCouunt--;
		}
		return temp;
	}
	BSTNode<Key,E>* BST<Key,E>::removehelp(BSTNode<Key,E>* subroot, const Key& k) {
		if (subroot == NULL) return NULL;			// k is not in tree
		else if (k < subroot->key())
			subroot->setLeft(removehelp(subroot->left(), e));
		else if (k > subroot->key())
			subroot->setRight(removehelp(subroot->right(), e));
		else { 										// Found it: remove it
			BSTNode<Key,E>* temp = subroot;
			if (subroot->left() == NULL) {			// only a left child
				subroot = subroot->right();
				delete temp;
			}
			else if (subroot->right() == NULL) {	// only a right child
				subroot = subroot->left();
				delete temp;
			}
			else {  								// Both children are non-empty
				temp = getmin(subroot->right());	// 找删除的结点的中序后继结点
				subroot->setElement(temp->element());
				subroot->setKey(temp->Key);
				subroot->setRight(deletemin(subroot->right()));
				delete temp;
			}
		}
		return subroot;
	}
	```
	执行过程如下:
	subroot == 37; //左右均不为空
	temp = getmin(42) = 40;
	subroot = 40;	//根结点的值在此处变为40,即将右子树的最小值换为了其中序遍历的后继结点的值
	subroot -> setLeft(deletemin(42));
	deletemin(42);
	42 -> setLeft(deletemin(40))40->left() == NULL  -->  return 40->right(); // 即 return NULL
	42->setleft(NULL);
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值