二叉树

二叉树:

是由N个结点组成的有限集合,该集合可为空(空二叉树),或者由一个根节点和两个互不相交的子节点组成,也就是说除了根节点,每一个子节点都必须且仅有一个父节点。
二叉树的特点:

  1. 最多有两棵子树,左子树和右子树;

  2. 对于任意一个i层二叉树而言:
    a .它的任意第i层最多有2^(i-1)个节点
    比如; 第一层:最多1个
    第二层:最多2个
    第三层:最多4个
    第四层:最多8个
    最五层:最多16个

    第i层:最多2^(i-1)个
    b.它的所有层节点之和最多有2^i - 1个节点;
    证明如下:
    在这里插入图片描述

  3. 五种基本形态:
    A. 空二叉树;
    B. 只有一个根节点的二叉树;
    C. 根节点只有左子树;
    D. 根节点只有右子树;
    E. 根节点既有左子树又有右子树;
    (树不能带环,子节点不能拥有多个父节点;)
    特殊的二叉树:

  4. 满二叉树:
    在一颗二叉树中,所有的分支节点都存在左子树和右子树,并且所有的叶子都在同一层上,这样的二叉树称为满二叉树;
    如下图:
    在这里插入图片描述

  5. 完全二叉树:
    对一棵有N个结点的二叉树按照层序编号,如果编号为i的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,这样的二叉树称为完全二叉树。(说白了其实就是将结点层序遍历存放在数组中,结点之间没有空缺。)
    在这里插入图片描述
    如果是完全二叉树,对他的每个节点进行顺序编号,任意第 i 个子节点都有以下特性:
    001.每个子节点的父节点的下标是 i / 2;(该特性同样适用于满二叉树)
    比如:上方完全二叉树例图中第三层,
    下标是5的子节点,父节点的下标就是 i / 2 = 2 (舍去小数)
    同理下标是4的子节点,父节点的下标就是 i / 2 = 2 (舍去小数)
    (说明二者拥有共同的父节点)

    002.当父节点的下标是i,左子节点的下标就是 2*i, 右子节点的下标是 2*i + 1  (该特性同样适用于满二叉树)
    
    003.如果左子节点的下标值大于总节点数,代表没有左子节点,右子节点同理
    

通过以上特性可以看出:如果使用完全二叉树按照某种约定(比如从小到大)进行数据的存储,那么在我们进行数据的删除和添加操作时,每一次都要进行一次树结构的调整,除去直接在末尾插入。

  1. 斜树;
    所有结点都在左子树的二叉树为左斜树;
    所有结点都在右子树的二叉树为右斜树;
    二者统称为斜树;

4)二叉搜索树:
要求左子树的所有子节点都比父节点要小,右子树的所有子节点都比父节点大;
如果通过中序遍历(左中右,下面会详细介绍)二叉搜索树会得到一个有序的列表。

5)平衡二叉树:
它是一颗空树或者它的左右两个子树的高度差的绝对值不会超过1,并且左右两个子树都是一颗平衡二叉树,并且满足二叉搜索树的规则。

二叉树的表示方法:

  1. 数组表示:
    缺点:浪费空间,对于没有孩子的也要占用空间,只适用于完全二叉树和满二叉树;
  2. 链表表示:
    A. 二叉链表:有左孩子,右孩子
    B. 三叉链表:有左孩子,右孩子,父节点
    遍历方式:
    前序遍历,中序遍历,后序遍历,主要根据父节点(当前节点)为主,然后左右固定的顺序来区分。
    比如:如果是先输入父节点,然后在左节点,再右节点,就是前序遍历,父亲输入在最前面;
    比如:如果是先输入左节点,然后在父节点,再右节点,就是中序遍历,父亲输入在中间;
    比如:如果是先输入左节点,然后在右节点,再输入父节点,就是后序遍历,父亲在最后输入;
    (以上是个人一个小总结,一种记忆推荐,具体还是根据个人理解)

这里写图片描述
A. 前序遍历:根结点—》左孩子—》右孩子(中左右)
这里写图片描述

B. 中序遍历:左孩子—》根节点—》右孩子(左中右)
这里写图片描述

C. 后序遍历:左孩子—》右孩子—》根节点(左右中)
这里写图片描述

代码表示三种遍历方式:
递归遍历:

	//三种遍历方式
	void printfTreePrev(Node* root)//打印tree,使用前序----根左右
	{
		if(root == NULL)
			return;
		cout<<root->_data<<" ";
		printfTreePrev(root->_leftChild);
		printfTreePrev(root->_rightChild);
	}
	void printfTreeMid(Node* root)//打印tree,使用中序----左根右
	{
		if(root == NULL)
			return ;
		printfTreeMid(root->_leftChild);
		cout<<root->_data<<" ";
		printfTreeMid(root->_rightChild);
	}
	void printfTreeBack(Node* root)//打印tree,使用后序----左右根
	{
		if(NULL == root)
			return;
		printfTreeBack(root->_leftChild);
		printfTreeBack(root->_rightChild);
		cout<<root->_data<<" ";
	}

非递归遍历:
任何算法的递归形式都是可以转换成非递归的形式,因为递归实际是一种不断压栈的过程(这时候要小心栈溢出的问题),所以也可以借助堆栈来帮助我们进行非递归。

思想:这里以先序遍历(中左右)为主我们简单分析一下:
单层循环:
首先我们先将根节点push进入栈中,然后再从栈中将它取出打印,然后再将它的右节点push进入栈中,再将左节点push进入栈中(因为栈的逻辑是先进后出,所以要先打印左节点要最后push左节点),然后循环继续pop出栈的栈顶节点,打印数据,然后继续重复push右节点,左节点,循环往复(直到栈为空),借助一个栈来将我们的数据打印出来。

借个图来描述下:
在这里插入图片描述
代码如下:
在这里插入图片描述

思想:这里以中序遍历(中左右)为主我们简单分析一下:
将当前的节点的左节点一直push进入栈中,直到遇到叶子节点,没有左子节点后,从栈顶pop节点并打印,然后再从它的右子节点为父节点开始,继续push左子节点。
代码如下:
在这里插入图片描述

后序非递归遍历(左右中):

//后序循环遍历和前序循环遍历类似,只不过改变push的顺序,
//我们优先push 左子树,再push右子树
void ListTree::LoopBackOrder(TreeNode* node)
{
	if (nullptr == node)
	{
		return;
	}
	stack<TreeNode*> st1;
	stack<TreeNode*> st2;
	TreeNode* curNode;
	st1.push(node);
	while (!st1.empty())
	{
		curNode = st1.top();
		st1.pop();
		st2.push(curNode);
		if(nullptr != curNode->leftNode)
		{
			st1.push(curNode->leftNode);
		}
		if(nullptr != curNode->rightNode)
		{
			st1.push(curNode->rightNode);
		}
	}
	while (!st2.empty())
	{
		curNode = st2.top();
		cout << curNode->value << " ";
		st2.pop();
	}
}

双层循环:

//采用非递归的方式---利用栈的后进先出的思想---
	//先访问根节点,访问完了以后就push,然后访问左子树,直到访问的左子树为NULL,然后就pop取出栈顶元素访问其右子树,以此类推
	//先序遍历----根左右
	void PreOrder()
	{
		vector<Node*> v;
		cout<<"中序遍历:"<<endl;
		Node* cur = _root;
		while(cur || !v.empty())
		{
			while(cur)
			{
				v.push_back(cur);
				cout<<cur->_data<<" ";
				cur = cur->_leftChild;
			}
			Node* top = v.back();
			cur = top->_rightChild;
			v.pop_back();
		}
		cout<<endl;
	}
	//中序遍历----左根右
	void MidOrder()
	{
		vector<Node*> v;
		cout<<"中序遍历:"<<endl;
		Node* cur = _root;
		while(cur || !v.empty())
		{
			while(cur)
			{
				v.push_back(cur);
				cur = cur->_leftChild;
			}
			Node* top = v.back();
			cout<<top->_data<<" ";
			cur = top->_rightChild;
			v.pop_back();
		}
		cout<<endl;
	}
	//后序遍历----左右根
	void BackOrder()
	{
		vector<Node*> v;
		cout<<"后序遍历:"<<endl;
		Node* cur = _root;
		Node* pre = NULL;
		while(cur || !v.empty())
		{
			while(cur)
			{
				v.push_back(cur);
				cur = cur->_leftChild;
			}
			Node* top = v.back();
			if(top->_rightChild == NULL || top->_rightChild == pre)
			{
				cout<<top->_data<<" ";
				pre = top;
				v.pop_back();
			}
			else
			{
				cur = top->_rightChild;
			}
		}
		cout<<endl;
	}

二叉树代码补充完整:

template <class T>
struct TreeNode
{
	T _data;
	TreeNode<T>* _leftChild;
	TreeNode<T>* _rightChild;

	TreeNode(const T& data)
		:_data(data),_leftChild(NULL),_rightChild(NULL)
	{}
};

template <class T>
class BinaryTree
{
	typedef TreeNode<T> Node;
protected:
	Node* _root;
public:
	BinaryTree()
		:_root(NULL)
	{}
	BinaryTree(T* arr, size_t n, const T& invalid)
	{
		int a = 0;
		_root = CreateTree(arr, n, invalid, a);
	}
	~BinaryTree()//析构函数
	{
		Destory(_root);
	}
	//采用递归的方式

	//销毁结点
	void Destory(Node* root)//删除节点采用后序的遍历方式----左右根
	{
		if(root == NULL)
			return;
		Destory(root->_leftChild);
		Destory(root->_rightChild);
		delete root;
	}
	//创建一颗树
	Node* CreateTree(T* arr, size_t n, const T& invalid, int& index)
	{
		assert(arr);
		Node* root = NULL;
		if(index < n && arr[index] != invalid)
		{
			root = new Node(arr[index]);
			root->_leftChild = CreateTree(arr, n, invalid, ++index);
			root->_rightChild = CreateTree(arr, n, invalid, ++index);
		}
		return root;
	}
	//计算叶子节点的个数=======左 + 右
	size_t CountLeaf()
	{
		return _CountLeaf(_root);
	}
	size_t _CountLeaf(Node* root)
	{
		if(NULL == root)
		{
			return 0;
		}
		if(root->_leftChild == NULL && root->_rightChild == NULL)
			return 1;
		return _CountLeaf(root->_leftChild) + _CountLeaf(root->_rightChild);
	}
	//计算第K层的结点个数
	size_t CountKLeaf(size_t k)
	{
		return _CountKLeaf(_root,k);
	}
	size_t _CountKLeaf(Node* root, size_t k)
	{
		assert(k > 0);
		if(NULL == root)
			return 0;
		if(k == 1)
			return 1;
		return _CountKLeaf(root->_leftChild, k-1) + _CountKLeaf(root->_rightChild, k-1);//切勿使用--k,因为此时k是在同一层的左右子树
	}
	//求tree的深度-----树的高度
	size_t HeightTree()
	{
		return _HeightTree(_root);
	}
	size_t _HeightTree(Node* root)
	{
		if(NULL == root)
			return 0;
		size_t left = _HeightTree(root->_leftChild) + 1;
		size_t right = _HeightTree(root->_rightChild) + 1;
		return left > right ? left : right;
	}
	//找到某个节点
	void Find(const T& data)
	{
		Node* node = _Find(_root,data);
		if(NULL == node)
			cout<<"find number: NULL"<<endl;
		else
		    cout<<"find number: "<<node->_data<<endl;
	}
	Node* _Find(Node* root,const T& data)
	{
		if(NULL == root)
			return NULL;
		if(root->_data == data)
			return root;
		Node* left = _Find(root->_leftChild,data);
		if(left)
			return left;
		return _Find(root->_rightChild,data);	
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值