一.概念
边:一棵n个结点树有n-1条边
结点深度:从根到当前结点的路径的深度。
结点高度:从当前结点到叶子结点最长路径的长度。
树的性质
- 树中的结点总数等于所有结点的度+1;
 - m叉树中第i(i>=1)层上至多可以有m^(i-1)个节点;
 

- 高度为h(h>=1)的m叉树至多有(m^h -1)/(m-1)
 - 具有n个结点的m叉树的最小高度为[logm(n(m-1)+1)]。取上限,向上取整。
 
![]()
二叉树
二叉树每个结点只能有两棵子树,分别被称为左子树和右子树。
满二叉树:一棵高度为h,且含有2^h-1个结点的二叉树被称之为满二叉树
对于编号为i的结点,左子节点的编号为2i,右子节点的编号为2i+1,双亲结点的编号为[i/2](向下取整)
完全二叉树:一颗高度为h,有n个结点的完全二叉树,它的每个结点都与高度相同的满二叉树中的结点编号一一对应。
二叉排序树:(二叉查找树)树上任意结点如果存在左子树和右子树,则左子树上所有结点元素的值都小于该结点,右子树上所有结点元素的值都大于该结点。
二叉树的性质:
- 一棵非空的二叉树,n表示结点总数,n0表示度为0的结点树,n1表示度为1的结点树,n2表示度为2 的节点数
n=n0+n1+n2
n=n1+2n2+1
n0=n2+1 - 一棵非空的二叉树的第k层最多只能有2^(k-1)个结点
 - 高度为h的二叉树最多只能有2^h-1个结点
 
二.完全二叉树的存储
完全二叉树的顺序存储

普通二叉树的顺序存储,添加空结点,补全成为完全二叉树,数组中用0或者其他特殊值填充。可能造成浪费。

二叉树的遍历
层次遍历:利用队列先进先出的性质
// 二叉树层次遍历。
void LevelOrder(BiTree TT)
{
    SeqQueue QQ;     // 创建循环队列。
    InitQueue(&QQ);  // 初始化循环队列。
    ElemType ee = TT;  // 队列的元素是二叉树。
    InQueue(&QQ, &ee);  // 把根结点当成队列的元素入队。
    while (IsEmpty(&QQ) != 1)  // 队列不为空。
    {
        OutQueue(&QQ, &ee);  // 队头元素出队。
        visit(ee);  // 访问出队元素。
        if (ee->lchild != NULL) InQueue(&QQ, &ee->lchild);  // 如果出队元素有左结点,左结点入队。
        if (ee->rchild != NULL) InQueue(&QQ, &ee->rchild);  // 如果出队元素有右结点,右结点入队。
    }
} 
先序遍历:根左右
中序遍历:左根右
后序遍历:左右根
最深的子树开始向上遍历
二叉树的实现
递归实现
// 二叉树的先序遍历。
void PreOrder(BiTree TT)
{
    if (TT == NULL) return;
    visit(TT);               // 访问子树TT的根结点。
    PreOrder(TT->lchild);    // 遍历左子树。
    PreOrder(TT->rchild);    // 遍历右子树。
} 
非递归实现:利用栈的性质
前序遍历:

由遍历序列构造二叉树
由一种遍历序列不能还原出来唯一的二叉树。

线索二叉树:
在含有n个结点的二叉链表中,有n+1个空指针,能否利用这些空指针来存放前驱和后继结点的地址?然后像遍历链表一样方便的遍历二叉树的序列?
线索二叉树可以部分解决上述问题,加快了在序列中查找前驱和后继结点的速度,但增加了在树中插入和删除结点的难度。

typedef struct TBTNode
{
    char   data;               // 存放结点的数据元素。
    struct TBTNode* lchild;    // 指向左子结点地址的指针。
    struct TBTNode* rchild;    // 指向右子结点地址的指针。
    unsigned char ltag, rtag;   // 左右指针的类型,0-非线索指针,1-线索指针。
}TBTNode, * TBTTree; 
先序线索树求后继
 
 后续线索树求前驱?????
  
二叉排序树

二叉排序树(二叉搜索树,二叉查找树BST)一棵非空的二叉排序树具有如下性质:
- 1.如果左子树不空,则左子树上所有结点的值都小于根节点的值
 - 2.如果右子树不为空,右子树上所有节点的值都大于根节点的值
 - 3.左右子树也分别是二叉排序树
 
中序排列的结果:结点时升序
创建二叉排序树
左子树<根<右子树
1.相同的序列创建的二叉排序树是唯一的
2.同一集合创建的二叉排序树是不同的
3.用二叉树的先序遍历序列创建的二叉排序树与原数相同
删除二叉树的结点

ASL (average Search Length)平均查找长度:

平衡二叉树:任意结点的左右高度不超过1.

平衡因子为左子树高减去右子树高
AVL树规定每个结点的平衡因子的绝对值不大于1
LL型状态:右旋

 

LR双旋:先左转B结点,再右旋A结点

节点的度:子树的个数
树的度:所有节点度中的最大值
叶子节点:度为0的节点
非叶子节点:度不为0的节点
层数:根节点在第一层
节点的深度:从根节点到当前节点的唯一路径上的节点总数
节点的高度:从当前节点到最远叶子节点的路径上的节点总数
有序树:树中任意节点的子节点之间有顺序关系
二叉树的特点:
- 每个节点的度最大为2
 - 左子树和右子树是有顺序的
 
二叉树是有序树还是无序树?有序树
非空二叉树的性质:

 
 
 
 
n0=floor((n+1)/2);

 
 
 
层序遍历:利用队列的性质
    层序遍历
     1.将根节点入队
     2.循环执行以下操作,直到队列为空
         a.将队头节点A出队,访问
         b.将A的左子节点入队
         c.将A的右子节点入队
求二叉树的高度:
法一:利用递归
	int heightByRecursion(Node<T>* node)
	{
		if (node == nullptr) return 0;
		return 1 + std::max(height(node->m_left), height(node->m_right));
	} 
法二:迭代版本 每一层访问完后,下一层的长度就是队列的长度
int heightByIterator(Node<T>* node)
	{
		if (node == nullptr) return 0;
		int iHeight = 0;
		int iLaySize = 1;// 每一层的元素数量  初始化第一层的长度为1
		//先将头节点入队
		std::queue<T> q;
		q.push(node);
		//循环操作,直到队列为空
		while (!q.empty()) {
			Node<T>* head = q.front();
			std::cout << head->element;
			q.pop();
			iLaySize--;
			if (head->m_left != nullptr)
				q.push(head->m_left);
			if (head->m_right != nullptr)
				q.push(head->m_right);
			if (0 == iLaySize)//意味着即将要访问下一层
			{
				iLaySize = q.size();
				iHeight++;
			}
		}
		return iHeight;
	} 
如何判断一棵树是否为完全二叉树

bool isCompleteBT()
	{
		std::queue<Node<T>*> q;
		q.push(m_root);
		bool bLeaf = false;
		while (!q.empty()) {
			Node<T>* head = q.front();
			q.pop();
			if (bLeaf && head->isLeaf())
			{
				return false;
			}
			/*************************/
			if (head->m_left != nullptr && head->m_right != nullptr)
			{
				q.push(head->m_left);
				q.push(head->m_right);
			}
			else if (head->m_left == nullptr && head->m_right != nullptr)
			{
				return false;
			}
			else if 
				(
					(head->m_left != nullptr && head->m_right == nullptr) ||
					(head->m_left == nullptr && head->m_right == nullptr)
				)//后面遍历的节点必须是叶子节点
			{
				if(head->m_left != nullptr) q.push(head->m_left);
				bLeaf = true;
			}
			/*************************/
		}
		return true;
	} 
反转二叉树(所有左右子节点交换)
	//利用前序遍历访问每一个节点  递归方式 中序方式有问题
	Node<T>* inverTree(Node<T>* root)
	{
		if (root == nullptr) return root;
		Node<T>* node = root->m_left;
		root->m_left = root->m_right;
		root->m_right = node;
		inverTree(root->m_left);
		inverTree(root->m_right);
	}
	//利用中序遍历访问每一个节点  递归方式 
	Node<T>* inverTreePreOrder(Node<T>* root)
	{
		if (root == nullptr) return root;
		inverTree(root->m_left);
		Node<T>* node = root->m_left;
		root->m_left = root->m_right;
		root->m_right = node;
		inverTree(root->m_left);//这里仍然要传左子节点,因为左右已经交换
	}
	//利用迭代方式 层序遍历 反转二叉树
	bool inverTreeLayOrder(Node<T>* root)
	{
		Node<T>* head = root;
		std::queue<T> q;
		q.emplace(head);
		while (!q.empty())
		{
			head = q.front();
			q.pop();
			Node<T>* tempNode = head->m_left;
			head->m_left = head->m_right;
			head->m_right = tempNode;
			if (head->m_left != nullptr)
				q.emplace(head->m_left);
			if (head->m_right != nullptr)
				q.emplace(head->m_right);
		}
	} 
根据遍历结果复原唯一的一棵二叉树
1.前序遍历+中序遍历
2.后序遍历+中序遍历
前序遍历+后序遍历,如果它是一棵真二叉树,结果是唯一的。不然,结果不唯一。
原因:如果左右子树有一个为空,通过前序遍历和后序遍历不能确定是左子树为空还是右子树为空。如果是一棵真二叉树,则左右子树要么都存在,要么为空。
前驱节点
前驱节点指 中序遍历时的前一个结点
对于二叉排序树,前驱结点是该结点的左子树的最大结点。


	/*查找前驱结点*/
	Node<T>* preDecessor(Node<T>* node)
	{
		if (node == nullptr) return nullptr;
		Node<T>* tempNode = nullptr;
		
		if (node->m_left != nullptr)
		{
			Node<T>* p = node->m_left;
			while (p != nullptr)
			{
				p = p->m_right;
			}
			return p;
		}
		//从父节点 祖父结点中寻找前驱结点
		while(node->m_parent != nullptr && node == node->m_parent->m_left)
			//父节点不为空并且该节点是父节点的左子树
		{
			node = node->m_parent;
		}
		//
		return node->m_parent;
	} 
后继结点:中序遍历时的后一个结点,如果是二叉搜索树,后继结点就是后一个比它大的结点
删除结点
a.如果是叶子结点,直接删除
b.度为1的结点,用子结点替代原结点的位置。
1.如果node是左子结点
child.parent = node.parent;
node->parent->left = child;
2.如果node是右子结点
child.parent = node.parent;
node->parent->right = child;
3.如果node是root
root = child;child.parent=nullptr;
c.度为2的结点
找前驱或者后继结点覆盖该节点,删除前驱或者后继结点。
注意:如果一个结点的度为2,那么它的前驱,后继节点的度只能是1和0。


















