数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集

第5-10章:线性结构,元素之间存在线性次序(线性表、数组与矩阵、栈、队列、跳表和散列表

第11-15章:层次结构(二叉树和树、优先队列、竞赛树、搜索树)

文章目录

    • 11.1 树
    • 11.2 二叉树
    • 11.3 二叉树的特性
    • 11.4 二叉树的描述
      • 11.4.1 数组描述
      • 11.4.2 链表描述
    • 11.5 二叉树常用操作
    • 11.6 二叉树遍历(重要)
      • 前序遍历
        • 递归实现
        • 非递归实现(了解思想)
      • 中序遍历
        • 递归实现
        • 非递归实现(了解思想)
      • 后序遍历
        • 递归实现
        • 非递归实现(了解思想)
      • 层次遍历
      • 小结
    • 11.7 抽象数据类型`BinaryTree`
      • ADT
      • 二叉树抽象类
    • 11.8 类`linkedBinaryTree`
      • 查找
      • 建立树
      • 计算高度
      • 计算节点数目
    • 11.9 应用
      • 11.9.1 设置信号放大器
      • 11.9.2 并查集

11.1 树

具有层次结构的数据一般不适合于用线性数据结构描述

定义数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第1张图片

  • 树(tree)t是一个非空有限元素的集合
  • 其中一个元素为根(root)
  • 其余的元素(如果有的话)分成不相交的集合,组成t的子树(subtrees)

术语大集合

  • 对应一个层次结构

  • 根(root):层次中最高层的元素

  • 孩子(children):根的孩子是根的下一层元素,是树的子树的根

  • 叶子(leaves):树中没有孩子的元素

  • 数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第2张图片

  • 级(level):一个元素的级 = 其父母的级 + 1;树根的级为1数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第3张图片

  • 高度(height)深度(depth):树的级数(或层数)

  • 元素的度:指其孩子的个数。如上图:Joe的度为3;Mary的度为2;Ann的度为0

  • 树的度:是其元素度的最大值。如上图那棵树的度即3

11.2 二叉树

定义二叉树

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第4张图片

  • 二叉树(binary tree)t是有限个元素的集合(可以为空)。
  • 当二叉树非空时,其中有一个称为根(root)的元素,余下的元素(如果有的话)被组成2个二叉树,分别称为t的左子树和右子树。

二叉树和树的区别

二叉树
可以为空 不能为空
每个元素都恰好有两棵子树(其中一个或两个可能为空) 每个元素可有任意多个子树
在二叉树中每个元素的子树都是有序的,也就是说,可以用左、右子树来区别 子树间是无序的

补充:表达式树数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第5张图片

11.3 二叉树的特性

特性①:包含n(n>0)个元素的二叉树边数n-1

证明:二叉树中每个元素(除了根)有且只有一个父母,在孩子与其父母间有且只有一条边,因此,边数为n-1

特性②:若二叉树的高度为h(h≥0),则该二叉树最少有h个元素,最多有 2 h − 1 2^h-1 2h1个元素。

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第6张图片

特性③:包含n(n≥0)个元素的二叉树的高度最大为n,最小为 l o g 2 ( n + 1 ) log_2(n+1) log2(n+1)

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第7张图片

特性④:二叉树中度为0的元素数 = 度为2的元素数+1

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第8张图片


满二叉树

当高度为h的二叉树恰好有 2 h − 1 2^h-1 2h1个元素时,称其为满二叉树(full binary tree)。对高度为h的满二叉树中的元素按从第上到下,从左到右的顺序从1到 2 h − 1 2^h-1 2h1进行编号。

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第9张图片

完全二叉树

从满二叉树中删除k个元素,所得到的二叉树被称为完全二叉树数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第10张图片

h层完全二叉树

  1. h-1层为满二叉树

  2. h层上的节点都连续排列于第h层的左侧。数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第11张图片

  3. 满二叉树是完全二叉树的一个特例

  4. 有n个元素的完全二叉树的深度为 l o g 2 ( n + 1 ) log_2(n+1) log2(n+1)


特性⑤:设完全二叉树中一元素的序号为i1≤i≤n。则有以下关系成立:数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第12张图片

  • i=1时,该元素为二叉树的根。若i>1,则该元素父母的编号为(i/2)
  • 2i>n时,该元素无左孩子。否则,其左孩子的编号为2i
  • 2i+1>n,该元素无右孩子。否则,其右孩子编号为2i+1

11.4 二叉树的描述

11.4.1 数组描述

完全二叉树:按照从上到下同层从左到右对元素编号,将二叉树的元素按照编号存储在数据中相应位置

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第13张图片

二叉树可以看作是缺少了部分元素的完全二叉树数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第14张图片

右斜二叉树存储空间达到最大数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第15张图片

一个有n个元素的二叉树需要的存储空间:n+1到 2 n 2^n 2n

当缺少的元素数目比较少时,数组描述方法是有效的。

11.4.2 链表描述

二叉树最常用的描述方法。

每个元素都存储在一个节点内,每个节点:

leftChild element rightChild

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第16张图片

在n个结点的二叉链表中,有n+1个空指针域

//链表二叉树的节点结构
template<class T>
struct binaryTreeNode
{
    T element;
    binaryTreeNode<T> *leftChild;//指向左孩子节点的指针
    binaryTreeNode<T> *rightChild;//指向右孩子节点的指针
    
    //第一个构造函数——无参数
    binaryTreeNode(){leftChild = rightChild = NULL;}
    //第二个构造函数——有一个参数用来初始化element,而指针域被置为NULL
    binaryTreeNode(const T& theElement)
    {
        element(theElement)
        leftChild = rightChild = NULL;
    }
    //第三个构造函数——三个参数用来初始化三个域
    binaryTreeNode(const T& the Element, 
                   binaryTreeNode *theLeftChild,
                   binaryTreeNode *theRightChild)
    {
        element(theElement)
        leftChild = theLeftChild;
        rightChild = theRightChild;
    }
};

11.5 二叉树常用操作

都是基于11.6将提到的遍历

  • 确定其高度指路11.8

  • 确定其元素数目指路11.8

  • 复制

  • 在屏幕或纸上显示二叉树

  • 确定两棵二叉树是否一样

  • 删除整棵树

    用递归的后序遍历,将访问根结点改为释放根结点空间即可

  • 若为数学表达式树,计算该数学表达式指路11.6

  • 若为数学表达式树,给出对应的带括号的表达式指路11.6

11.6 二叉树遍历(重要)

visit函数

template <class T>
void visit(binaryTreeNode<T> *x) 
{//访问节点*x,仅输出element域
 	cout << x->element <<' ';
}

前序遍历

先访问一个节点,再访问该节点的左右子树(根、左、右)

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第17张图片

递归实现
template <class T>
void preOrder(binaryTreeNode<T> *t)
{//前序遍历二叉树*t
    if(t != Null)
    {
        visit(t);//访问树根
        preOrder(t->leftChild);//前序遍历左子树
        preOrder(t->rightChild);//前序遍历右子树
    }
}
非递归实现(了解思想)
  • 每遇到一个节点,先访问该节点,并把该节点的非空右孩子入栈,然后遍历其左子树
  • 左子树遍历完后,从栈中弹出节点(右子树的根),继续遍历。
  • 为了算法的简洁,最开始推入一个空指针入栈作为监视哨;当这个空指针被弹出时,遍历结束
template<class T>
void preOrder(binaryTreeNode<T>*t)
{
    arrayStack <binaryTreeNode<T>*> S(MaxLength);
    S.push(NULL);
    binaryTreeNode<T>*p=t;
    while (p != NULL)
	{
        visit(p);
		if (p->rightChild != NULL) 
           S.push(p->rightChild);
        if(p->leftChild != NULL)
            p = p->leftChild;
    	else 
        {
            p = S.top();
			S.pop();
        }
    }
}

中序遍历

先访问一个节点的左子树,然后访问该节点,最后访问右子树(左、根、右)

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第18张图片

递归实现
template <class T>
void inOrder(binaryTreeNode<T> *t)
{//中序遍历二叉树*t
    if(t != Null)
    {
        inOrder(t->leftChild);//中序遍历左子树
        visit(t);//访问树根
        inOrder(t->rightChild);//中序遍历右子树
    }
}
  • 输出完全括号化的中缀表达式

    对一棵数学表达式树分别进行中序、前序和后序遍历,结果便是表达式的中缀、前缀和后缀形式。中缀形式是我们通常的书写形式。

    数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第19张图片

    template <class T>
    void infix(binaryTreeNode<T> *t)
    {//输出中缀表达式
        if(t != NULL)
        {
          cout << '(';
          infix(t->leftChild);//左操作数
          cout << t->element;//操作符
          infix(t->rightChild);//右操作数
          cout << ')';
        }
    }
    
非递归实现(了解思想)
  • 每遇到一个节点就把它入栈,然后去遍历其左子树
  • 遍历完左子树后,弹出栈顶节点并访问
  • 按照其右链接指示的地址再去遍历该节点的右子树
template<class T>
void inOrder(binaryTreeNode<T>*t)
{ 
    arrayStack <binaryTreeNode<T>*> S(MaxLength);
    binaryTreeNode<T>*p=t;
    do
    {
		while (p!=NULL)
		{
            S.push(p);
            p=p->leftChild;
        }
        if(!S.empty())
		{
            p=S.top();
            S.pop();
            visit(p);
            p=p->rightChild;
        }
	}while(p!=NULL||!S.empty())
}

后序遍历

先访问一个节点的左右子树,再访问该节点(左、右、根)

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第20张图片

递归实现
template <class T>
void postOrder(binaryTreeNode<T> *t)
{//后序遍历二叉树*t
    if(t != Null)
    {
        postOrder(t->leftChild);//后序遍历左子树
        postOrder(t->rightChild);//后序遍历右子树
        visit(t);//访问树根
    }
}
非递归实现(了解思想)
  • 后序遍历的非递归实现主要思想:

    • 每遇到一个节点就把它入栈,然后去遍历其左子树
    • 遍历完左子树后,回到栈顶节点
    • 按照其右链接指示的地址再去遍历该节点的右子树
    • 遍历完右子树后,弹出栈顶节点并访问
  • 给栈中的每个元素加一个标志位tag

    • 用枚举类型表示,tag为left表示已进入该节点的左子树;tag为right表示已进入该节点的右子树
  • 栈中的元素类型stackElement

    pointer tag
//定义枚举类型:Tag
enum Tag{left,right};
//自定义新的类型,把二叉树节点和标记封装在一起
typedef struct
{
    binaryTreeNode<T>* node;
    Tag tag;
}TagNode;    
//后序遍历  
void postOrder(binaryTreeNode<T> *t)
{
    if (t == NULL)
        return;
    arrayStack<TagNode> s;
    TagNode tagnode;
    binaryTreeNode<T>* p = t;
    while (!s.empty() || p)
    {
        while (p)
        {
            tagnode.node = p;
            //该节点的左子树被访问过
            tagnode.tag = Tag::left;
            s.push(tagnode);
            p = p->leftchild;
        }
        tagnode = s.top();
        s.pop();
        //左子树被访问过,则还需进入右子树
        if (tagnode.tag == Tag::left)
        {
            //置换标记
            tagnode.tag = Tag::right;
            //再次入栈
            s.push(tagnode);
            p = tagnode.node;
            //进入右子树
            p = p->rightchild;
        }
        else//右子树已被访问过,则可访问当前节点
        {
            tagnode.node->element;
            //置空,再次出栈(这一步是理解的难点)
            p = NULL;
        }
    }
}

层次遍历

从上往下逐层,同层从左到右的次序访问各元素

参考理解博客

借助队列来实现,核心思想:每次出队一个元素,就将该元素的孩子节点加入队列中,直至队列中元素个数为0时,出队的顺序就是该二叉树的层次遍历结果

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第21张图片

template <class T>
void levelOrder(binaryTreeNode<T> *t)
{//层次遍历二叉树*t
    arrayQueue<binaryTreeNode<T>*> q;//链队列的应用
    while (t != NULL)
    {
        visit(t);//访问t
        //将t的孩子插入队列
        if(t->leftChild != Null)
            q.push(t->leftChild);
        if(t->rightChild != Null)
            q.push(t->rightChild);
        
        //提取下一个要访问的节点
        try {t = q.front();}
        catch(queueEmpty) {return;}
        q.pop();
    }
}

小结

  • 设二叉树中元素数目为n,四种遍历算法:数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第22张图片

  • 数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第23张图片

  • 若二叉树中各节点的值均不相同

    • 由二叉树的前序序列和中序序列,或由其后序序列和中序序列唯一地确定一棵二叉树
    • 而由前序序列和后序序列不一定能唯一确定一棵二叉树

11.7 抽象数据类型BinaryTree

ADT

抽象数据类型 binaryTree
{
    实例:
		元素集合;如果不空,则被划分为根、左子树和右子树;
        每个子树仍是一个二叉树;
    操作:
		empty():如果二叉树为空,则返回true,否则返回false 
        size():返回二叉树的节点/元素个数
		preorder(visit):前序遍历二叉树,visit是访问函数
       	inOrder(visit):中序遍历二叉树
        postOrder(visit):后序遍历二叉树
        levelOrder(visit):层次遍历二叉树
}

二叉树抽象类

template<class T>
class binaryTree
{
    public:
		virtual ~binaryTree(){}
    	//二叉树为空时返回true,否则返回false
    	virtual bool empty() const = 0;
    	//返回二叉树中元素的个数	
 		virtual int size() const = 0;
		//前序遍历二叉树
		virtual void preOrder(void(*)(T*) = 0;
 		//中序遍历二叉树
        virtual void inOrder(void(*)(T*) = 0;
     	//后序遍历二叉树
    	virtual void postOrder(void(*)(T*) = 0;
     	//层序遍历二叉树
    	virtual void levelOrder(void(*)(T*) = 0;
}

11.8 类linkedBinaryTree

template<class T>
class linkedBinaryTree:public binaryTree<binaryTreeNode<E>>
{
    public:
    	//基础操作(代码见上方)
        linkedBinaryTree(){root = NULL; treeSize = 0;}//构造函数
        ~linkedBinaryTree(){}; //析构函数
    	bool empty() const {return treeSize == 0};
    	void visit(binaryTreeNode<T> *x);//遍历辅助
        void preOrder(binaryTreeNode<T>*t);//前序遍历
        void inOrder(binaryTreeNode<T>*t);//中序遍历
        void postOrder(binaryTreeNode<T>*t);//后序遍历
        void levelOrder(); //层次遍历
    	
    	//补充操作
    	binaryTreeNode<T>* find(binaryTreeNode<T>*t, int k);//查找 
        void makeTree(int n); //建立树
        int height(binaryTreeNode<T>*t);//计算二叉树的高度 
        int number(binaryTreeNode<T>*t);//计算二叉树节点数目 
    private:
        binaryTreeNode<T> *root;//指向根的指针
        int treeSize;//树的节点个数
};

查找

借助于队列应用,流程同层次遍历每次出队一个元素,且在出队时检查其是否为所找的元素,并将该元素的孩子节点加入队列中,直至队列中元素个数为0时,

template<class T>
binaryTreeNode<T>*linkedBinaryTree<T>::find(binaryTreeNode<T>*t, int k)
{
    queue <binaryTreeNode<T>*>q;
	while (t!=NULL)
	{
		if (t->element==k)
			return t;
		if (t->leftChild!=NULL)
			q.push(t->leftChild);
		if (t->rightChild!=NULL)
			q.push(t->rightChild);
		if (q.empty())
			return NULL;
		t=q.front();
		q.pop();
	}
}

建立树

根节点为1,编号为 i 的节点的左孩子节点为 a,右孩子节点为 b,-1 表示该位置没有节点。

template<class T>
void linkedBinaryTree<T>::makeTree(int n)
{
    root = new binaryTreeNode<T>(1);
    for(int i = 1; i <= n; i++)
	{
        binaryTreeNode<T>*p = find(root, i);
        int a,b;
	  	cin >> a >> b;
	  	if (a != -1)
		{
	  		p->leftChild = new binaryTreeNode<T> (a); 
		}
		if (b != -1)
		{
			p->rightChild = new binaryTreeNode<T> (b);
		}
    }
}

计算高度

template<class T>
int linkedBinaryTree<T>::height(binaryTreeNode<T>*t)
{
    if(t == NULL)
        return 0;
    int h1 = height(t->leftChild);
    int h2 = height(t->rightChild);
    if(h1 > h2)
        return ++h1;
    else
        return ++h2;
}

计算节点数目

template<class T>
int linkedBinaryTree<T>::number(binaryTreeNode<T>*t)
{
    int x = 0;
    if(t != NULL)
    {
        x =  number(t->leftChild) + number(t->rightChild) + 1;
    }
    return x;
}

11.9 应用

11.9.1 设置信号放大器

优秀原理解释博客

  • degradeFromParent(i)——节点i与其父节点间的衰减量
    • if degradeFromParent(i) > 容忍值,则不可能通过放置放大器来时信号的衰减不超过容忍值
  • degradeToLeaf(i)——从节点i到以i为根节点的子树的任一叶子的衰减量的最大值。
    • 若i为叶节点,则degradeToLeaf(i) = 0
    • 对于其他节点i,degradeToLeaf(i) = max{degradeToLeaf(j) + degradeFromParent(j)(ji的孩子)

下图中假定容忍值为3

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第24张图片


树的二叉树描述

对于树 t 的每个节点x,x节点的leftChild指针指向x的第一个孩子,x节点的rightChild指针指向x的下一个兄弟数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第25张图片

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第26张图片

森林的二叉树表示

首先得到树林中每棵树(设有m棵树)的二叉树描述

然后,第i棵作为第i-1棵树的右子树

数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第27张图片


11.9.2 并查集

并查集优质博客①

并查集优质博客②

问题描述:数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第28张图片

  • 初始时有n个元素,每个元素都属于一个独立的等价类

  • 向R中添加新关系(a,b),执行combine(a,b)

    classA = find(a);
    classB = find(b);
    if(classA != classB)
    	unite(classA, classB);
    
  • 查询(Find):查询两个元素是否在同一个集合中。

    int find(int x)			//查找x的教主
    {
    	while(pre[x] != x)	//如果x的上级不是自己(则说明找到的人不是教主)
    		x = pre[x];		//x继续找他的上级,直到找到教主为止
    	return x;			//教主驾到~~~
    }
    
  • 合并(Union):把两个不相交的集合合并为一个集合。

    //寻找x的代表元(即教主);
    //寻找y的代表元(即教主);
    //如果x和y不相等,则选一个人作为另一个人的上级,如此一来就完成了x和y的合并。
    void union(int x,int y) //我想让虚竹和周芷若做朋友
    {
        int fx=find(x), fy=find(y);//虚竹的老大是玄慈,芷若MM的老大是灭绝
        if(fx != fy)               //玄慈和灭绝显然不是同一个人
            pre[fx]=fy;            //方丈只好委委屈屈地当了师太的手下啦
    }
    
  • 性能改进

    • 重量规则】若树i节点数少于树j节点数,则将j作为i的父节点。否则,将i作为j的父节点数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第29张图片

    • 高度规则】若树i的高度小于树j的高度,则将j作为i的父节点。否则,将i作为j的父节点数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第30张图片

  • 提高最坏情况下的性能的方法

    路径的缩短可以通过称为路径压缩(path compression)的过程实现。

    • 紧凑路径法(path compaction)

      • 改变从e到根节点路径上所有节点的parent指针,使其指向根节点。数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第31张图片
    • 路径分割法(path splitting)

      • 改变从e到根节点路径上每个节点(除了根和其子节点)的parent指针,使其指向各自的祖父节点。数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第32张图片
    • 路径对折法(path halving)

      • 改变从e到根节点路径上每隔一个节点(除了根和其子节点)的parent域,使其指向各自的祖父节点。数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集_第33张图片

你可能感兴趣的:(数据结构,算法与应用,#,数据结构笔记合集,数据结构,c++,算法)