zoukankan      html  css  js  c++  java
  • 树的一些定义和基本性质:

    • 一棵树只有唯一的根节点。
    • 子树的个数没有限制,但是它们一定是互不相交的。(一对多的关系,不能是多对多的关系)
    • 1个N节点的树有N-1个边。
    • 节点的度:节点的子树个数(度为0的节点称为叶子节点)。
    • 树的度:树中节点度最大的值。
    • m棵树(一个森林)一共有k条边,那么该森林一共有m+k个节点。(假设每棵树有Ki条边,那么k1+k2+...+km = k。并且每棵树有Ki+1个节点。故(k1+1)+(k2+1)+...+(km+1)为该森林总共的节点数目。化简上式可得k+m。故:一共有k+m个节点)
    • 路径长度:该路径上边的总数即路径长度。
    • 节点层次:从一棵树的根节点开始,定义根为第一层,根的孩子为第二层。一直到最后一层。
    • 树的深度:树中节点最大层次。
    • 任意一棵树都可以采用儿子/兄弟表示法来方便的进行表示。f
    • 二叉树的子树有左右之分(一般度为2的树不区分左右子树)。
    • 完美二叉树(满二叉树):所有分支节点(非叶节点)都有左右两棵子树,并且所有的叶节点都在同一层。
    • 完全二叉树:若一棵完美二叉树的叶节点从右到左有缺失,则可形成完全二叉树。

    二叉树的一些重要性质:

    • 二叉树的第i层最多有2^(i-1)个节点。
    • 深度为k的二叉树最多有2^k - 1个节点。(根据等比数列求和公式求得)
    • 对于任意的二叉树,若n0表示叶节点的个数,n1是度为1的节点个数,n2是度为2的节点个数。那么有n0 = n2 + 1.节点总数是n = n0 + n1 + n2 = 1 + n1 + 2n2。
    • 一棵n节点的完全二叉树深度是[log n] + 1(向下取整)
    • 对于一颗二叉树而言,由中序遍历和先序遍历或者后续遍历可以唯一确定一颗二叉树。(先序遍历和后序遍历不能确定唯一的二叉树,因为这时候没法区分左子树和右子树)

    对于完全二叉树,我们可以使用顺序存储来方便的实现。因为对于下标为i的节点,它的左儿子在下标为2i处,右儿子在下标为2i+1处。(二叉树的元素从下标为1的地方开始存放)。当然,不能超过二叉树的节点总个数N。但是顺序存储的缺点也很明显,不利于插入和删除。这个缺点总是无法避免的。

    实际上,我们经常使用的是链式存储二叉树。下面给出链式存储二叉树的ADT。

    typedef struct BTreeNode *BinTree;
    typedef BinTree Position;
    typedef int ElementType;
    
    struct BTreeNode
    {
    	ElementType data;	//数据域
    	
    	BinTree left;		//左子树
    	BinTree right;		//右子树
    }

    二叉树的操作集:

    1. 创建二叉树:层序创建二叉树,这样可以使得输入二叉树变得简单。(避免了求出二叉树的先序,中序,或者后序序列。)创建过程如下:
      输入第一个数据,若不为0,则赋值,入队;若为0,则返回空树。
      若队列不空,则出队一个元素,并创建该节点的左右子树。从输入序列读入下一数据,若为0,则将出队元素的左子树置空,否则,创建左子树,并赋值,入队。接着从输入序列读取下一个数据,若为0,则将出队元素的右子树置空,否则,创建右子树,并赋值,入队。(在这个过程中记得保存创建的节点。)重复这个步骤,直到队列为空。下面给出C++实现的代码。

      BinTree * CreateBinTree() 
      {
      	ElementType data;
      	PTree BT;
      	PTree T;
      	PTree p[1000];		//存储指针;二叉树的节点指针,这样才能将树连接起来
      	int i = 0, j = 0;	//双下标来控制数组
      	PQueue Q;
      	Q = CreatQueue();
      	BT = new BinTree;
      	cin >> data;
      	if (data != '0')
      	{
      		BT->data = data;
      		BT->Left = NULL;
      		BT->Right = NULL;
      		EnQueue(*BT, *Q);		//根节点入队
      	}
      	else
      	{
      		return NULL;		//空树
      	}
      	p[0] = BT;				//p[0]要保存BT,否则树就找不到
      	while (!IsEmptyQueue(*Q))	//层序建立法,队列不为空就继续
      	{
      		T = DeQueue(*Q);	//弹出一个节点
      		cin >> data;		//读左子树
      		if ('0' == data)
      		{
      			T->Left = NULL;		//左子树空
      		}
      		else
      		{
      			T->Left = new BinTree;		//左子树非空
      			T->Left->data = data;	
      			T->Left->Left = NULL;
      			T->Left->Right = NULL;
      
      			p[i]->Left = T->Left;		//将其加入到弹出节点的左子树
      			p[++j] = T->Left;			//将左子树地址存到数组中
      			EnQueue(*T->Left, *Q);		//左子树入队
      		}
      		//右子树的处理和左子树是类似的。
      		cin >> data;		//读右子树
      		if ('0' == data)
      		{
      			T->Right = NULL;			//右子树空
      		}
      		else
      		{
      			T->Right = new BinTree;
      			T->Right->data = data;
      			T->Right->Left = NULL;
      			T->Right->Right = NULL;
      
      			p[i]->Right = T->Right;		//将其加入到弹出节点的右子树
      			p[++j] = T->Right;			//将右子树地址存到数组中
      			EnQueue(*T->Right, *Q);		//右子树入队
      		}
      		i++;		//i自增,为找到下一个可能弹出的节点的准备
      	}
      	return BT;
      }

      先序创建二叉树:这是一个递归的过程,先创建根节点,然后递归创建左子树,递归创建右子树。代码实现如下。

      BinTree * CreateBinTree(int i)
      {
      	BinTree * BT;
      	ElementType data;
      	cin >> data;
      	BT = new BinTree;
      	if ('0' != data)
      	{
      		BT->data = data;
      		BT->Left = CreateBinTree(1);
      		BT->Right =CreateBinTree(2);
      	}
      	else
      	{
      		return NULL;
      	}
      	return BT;
      }

       

    2. 遍历二叉树:按照访问节点的顺序,我们把对二叉树的遍历分为4中方式:前序遍历,中序遍历,后序遍历,层序遍历。其中前3中遍历方式是按照访问根节点的顺序命名的。层序遍历是根据树的层次来访问节点的,从树根开始,逐层访问树的节点。
      先序遍历递归版实现代码:(访问根节点,先序遍历左子树,先序遍历右子树)

      void PreorderTraversal(PTree BT)
      {
      	if (BT)
      	{
      		cout << BT->data << ' ';
      		PreorderTraversal(BT->Left);
      		PreorderTraversal(BT->Right);
      	}
      }

      中序遍历递归版实现代码:(中序遍历左子树,访问根节点,中序遍历右子树)

      void InorderTraversal(PTree BT)
      {
      	if (BT)
      	{
      		InorderTraversal(BT->Left);
      		cout << BT->data << " ";
      		InorderTraversal(BT->Right);
      	}
      }

      后序遍历递归版实现代码:(后序遍历左子树,后序遍历右子树,访问根节点)

      void PostorderTraversal(PTree BT)
      {
      	if (BT)
      	{
      		PostorderTraversal(BT->Left);
      		PostorderTraversal(BT->Right);
      		cout << BT->data << " ";
      	}
      }

      先序遍历非递归实现代码:

      void PreorderTraversal(PTree BT, int i)
      {
      	PTree T;
      	PStack S = CreatStack();
      	T = BT;
      
      	while (T || !IsEmptyStack(*S))
      	{
      		while (T)
      		{
      			cout << T->data << ' ';			//入栈之前就输出
      			PushStack(*S, *T);				//左子树入栈
      			T = T->Left;
      		}
      		if (!IsEmptyStack(*S))
      		{
      			T = PopStack(*S);				//弹出元素
      			//将输出放在这里就是中序遍历
      			T = T->Right;					//转右子树
      		}
      	}
      }

      中序遍历和前序的最后一句是递归调用,这句递归调用是“尾递归”。可以在不借助堆栈的情况下使用循环语句来完成。所以前序和中序的非递归函数是几乎一致的。都是借助一个堆栈,将左子树入栈,然后弹出转右子树。但是后序遍历的最后一句并不是“尾递归”,使得无法仅通过一个堆栈就完成。所以后序遍历的非递归版本和上面的看起来有些不一样。
      后序遍历非递归实现代码:

      void PostorderTraversal(PTree BT, int i)
      {
      	//前序遍历是:根,左子树,右子树
      	//后序遍历是:左子树,右子树,根
      	//想法是将前序遍历的顺序改为根,右子树,左子树,然后压入堆栈。
      	//最后将元素从堆栈弹出,顺序就变为:左子树,右子树,根
      	//这样就完成了后序遍历二叉树
      	PTree T = BT;
      	PStack S1 = CreatStack();
      	PStack S2 = CreatStack();		//借助这个栈来实现后序遍历输出
      	
      	while (T || !IsEmptyStack(*S1))
      	{
      		while (T)
      		{
      			PushStack(*S1, *T);	//根	
      			PushStack(*S2, *T);
      			T = T->Right;		//右子树入栈
      		}
      		if (!IsEmptyStack(*S1))
      		{
      			T = PopStack(*S1);	//弹出元素
      			T = T->Left;		//左子树入栈
      		}
      	}
      	while (!IsEmptyStack(*S2))	//弹出栈
      	{
      		T = PopStack(*S2);
      		cout << T->data << " ";
      	}
      }

      层序遍历二叉树:层序遍历和层序创建一样,需要借助一个队列来完成。它的操作过程是,访问根节点,访问左儿子,访问右儿子。

      void Levelordertraversal(PTree BT)
      {
      	//从队列中取出一个元素
      	//访问该元素
      	//将该元素的非空左儿子和非空右儿子入队。
      	//重复这个过程,直到队列为空。
      
      	PQueue Q;
      	BinTree* T;	
      	if (!BT)				//空树
      	{
      		return;
      	}
      	else
      	{
      		Q = CreatQueue();		//创建队列
      		EnQueue(*BT, *Q);		//根节点入队
      
      		while (!IsEmptyQueue(*Q))
      		{
      			T = DeQueue(*Q);
      			cout << T->data << " ";
      			if (T->Left)
      			{
      				EnQueue(*T->Left, *Q);		//左子树入队
      			}
      			if (T->Right)
      			{
      				EnQueue(*T->Right, *Q);	//右子树入队
      			}
      		}
      	}
      }

      二叉树是否为空的判断是非常简单的。下面给出。

    3. 二叉树是否为空

      bool IsEmpty(PTree BT)
      {
      	return (NULL == BT) ? true : false;
      }
    4. 输出二叉树的叶节点

      void PreorderPrintLeaves(BinTree * BT)
      {    //这里采用了先序遍历方式
          if (BT)
          {
              if (!BT->Left && !BT->Right)    //左右子树都为空的节点是叶节点
              {
                  cout << BT->data << " ";
              }
              PreorderPrintLeaves(BT->Left);
              PreorderPrintLeaves(BT->Right);
          }
      }

       

    5. 求二叉树的高度
      求一颗二叉树的高度,可以采用后序遍历的方式。根节点的高度就是一棵树的高度,而根节点的高度等于其左子树和右子树高度的最大值加1。所以首先需要求出左子树和右子树的高度,因此,后序遍历的原理就很适合。

      int PostorderGetHeight(BinTree * BT)
      {
      	int L, R, maxsize;
      	if (BT)
      	{
      		L = PostorderGetHeight(BT->Left);
      		R = PostorderGetHeight(BT->Right);
      		maxsize = L > R ? L : R;
      		return maxsize + 1;
      	}
      	else
      	{
      		return 0;		//空树深度为0
      	}
      }

      二叉树的操作基本就这么多了,关于完整代码的实现,我放在了百度云盘里,想看的朋友下载下来看吧!
      百度云盘链接:https://pan.baidu.com/s/1ECw8uo22B06M6wNrP_KfWw
      提取码: j4wt

  • 相关阅读:
    Google搜索引擎如何运作:不会手动调整结果
    一个Ruby脚本
    IE灭绝!?
    除Windows之外的其他操作系统
    刚收到的新书
    奇怪的计算机语言
    小巧的menuetOS
    Ruby学习笔记(1)
    一个通知
    总结 asp.net 和 javascript获取本地IP(MAC)和服务器IP(MAC)的方法
  • 原文地址:https://www.cnblogs.com/zy666/p/10504289.html
Copyright © 2011-2022 走看看