第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 树

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

定义

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

术语大集合

  • 对应一个层次结构

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

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

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

  • 级(level):一个元素的级 = 其父母的级 + 1;树根的级为1

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

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

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

11.2 二叉树

定义二叉树

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

二叉树和树的区别

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

补充:表达式树

11.3 二叉树的特性

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

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

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

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

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


满二叉树

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

完全二叉树

从满二叉树中删除k个元素,所得到的二叉树被称为完全二叉树

h层完全二叉树

  1. h-1层为满二叉树

  2. h层上的节点都连续排列于第h层的左侧。

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

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


特性⑤:设完全二叉树中一元素的序号为i1≤i≤n。则有以下关系成立:

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

11.4 二叉树的描述

11.4.1 数组描述

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

二叉树可以看作是缺少了部分元素的完全二叉树

右斜二叉树存储空间达到最大

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

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

11.4.2 链表描述

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

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

leftChild element rightChild

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

//链表二叉树的节点结构
template<class T>
struct binaryTreeNode
{T element;binaryTreeNode<T> *leftChild;//指向左孩子节点的指针binaryTreeNode<T> *rightChild;//指向右孩子节点的指针//第一个构造函数——无参数binaryTreeNode(){leftChild = rightChild = NULL;}//第二个构造函数——有一个参数用来初始化element,而指针域被置为NULLbinaryTreeNode(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 <<' ';
}

前序遍历

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

递归实现

template <class T>
void preOrder(binaryTreeNode<T> *t)
{//前序遍历二叉树*tif(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();}}
}

中序遍历

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

递归实现

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

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

    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())
}

后序遍历

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

递归实现

template <class T>
void postOrder(binaryTreeNode<T> *t)
{//后序遍历二叉树*tif(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时,出队的顺序就是该二叉树的层次遍历结果

template <class T>
void levelOrder(binaryTreeNode<T> *t)
{//层次遍历二叉树*tarrayQueue<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,四种遍历算法:

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

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

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,否则返回falsevirtual 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;elsereturn ++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


树的二叉树描述

对于树 t 的每个节点x,x节点的leftChild指针指向x的第一个孩子,x节点的rightChild指针指向x的下一个兄弟

森林的二叉树表示

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

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


11.9.2 并查集

并查集优质博客①

并查集优质博客②

问题描述:

  • 初始时有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的父节点

    • 【高度规则】若树i的高度小于树j的高度,则将j作为i的父节点。否则,将i作为j的父节点

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

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

    • 紧凑路径法(path compaction)

      • 改变从e到根节点路径上所有节点的parent指针,使其指向根节点。
    • 路径分割法(path splitting)

      • 改变从e到根节点路径上每个节点(除了根和其子节点)的parent指针,使其指向各自的祖父节点。
    • 路径对折法(path halving)

      • 改变从e到根节点路径上每隔一个节点(除了根和其子节点)的parent域,使其指向各自的祖父节点。

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

  1. 数据结构(4)树形结构——二叉树(概述、前序、中序、后序、层序遍历JAVA实现)

    目录 4.1.树 4.2.二叉树 4.2.1.概述 4.2.3.存储结构 4.2.3.遍历 1.逻辑简介 2.代码示例 4.1.树 树,由n(n≥0)个有限节点和边组成一个具有层次关系的数据结构.树需 ...

  2. (王道408考研数据结构)第五章树-第三节1:二叉树遍历(先序、中序和后序)

    文章目录 一:二叉树遍历概述 二:二叉树深度优先遍历 (1)先序遍历-根左右(NLR) (2)中序遍历-左根右(LNR) (3)后序遍历-左右根(LRN) 总结:三种遍历方式动图演示 三:二叉树的层序 ...

  3. 数据结构c语言版二叉树的顺序存储表示,数据结构(十一) -- C语言版 -- 树 - 二叉树基本概念...

    内容预览 零.读前说明一.二叉树相关概念1.1.定义1.2.性质1.3.满二叉树与完全二叉树1.3.1.满二叉树1.3.2.完全二叉树1.3.3.特点延伸 二.二叉树储存结构2.1.顺序结构存储2.2 ...

  4. 数据结构(十一) -- C语言版 -- 树 - 二叉树基本概念

    内容预览 零.读前说明 一.二叉树相关概念 1.1.定义 1.2.性质 1.3.满二叉树与完全二叉树 1.3.1.满二叉树 1.3.2.完全二叉树 1.3.3.特点延伸 二.二叉树储存结构 2.1.顺 ...

  5. 树的基本概念和遍历规则 数据结构和算法 二叉树遍历(前序、中序、后序、层次、深度优先、广度优先遍历)

    zsychanpin 博客园 首页 新随笔 联系 订阅 管理 树的基本概念和遍历规则 树的递归定义 树是n(n>0)个结点的有限集,这个集合满足下面条件:       ⑴有且仅有一个结点没有前驱 ...

  6. 【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构

    本次笔记内容: 3.3.1 先序中序后序遍历 3.3.2 中序非递归遍历 3.3.3 层序遍历 3.3.4 遍历应用例子 小白专场:题意理解及二叉树表示 小白专场:程序框架.建树及同构判别 文章目录 ...

  7. //数据结构:先序、中序、后序遍历二叉树。输入数据:abd##eg###c#f#h##

    //数据结构:先序.中序.后序遍历二叉树.输入数据:abd##eg###c#f#h## #include <stdio.h> #include <stdlib.h> //定义数 ...

  8. C语言数据结构之二叉树的层次建树及遍历方法(前序,中序,后序,层次遍历)

    C语言数据结构之二叉树的层次建树及遍历方法(前序,中序,后序,层次遍历) tips:前些天学习了C语言数据结构链表,栈,队列.今天来学习一下C语言数据结构之二叉树的各种操作. 注意:二叉树的层次建树是 ...

  9. 【树】二叉树遍历算法(深度优先、广度优先遍历,前序、中序、后序、层次)及Java实现...

    [树]二叉树遍历算法(深度优先.广度优先遍历,前序.中序.后序.层次)及Java实现 目录 一.前序遍历 二.中序遍历 三.后序遍历 四.层次遍历 遍历的作用 二叉树是一种非常重要的数据结构,很多其它 ...

  10. 【练习】树(Tree, UVa 548)给一棵点带权(权值各不相同)的二叉树的中序和后序遍历,找一个叶子使得它到根的路径上的权和最小。

    给一棵点带权(权值各不相同,都是小于10000的正整数)的二叉树的中序和后序遍历,找一个叶子使得它到根的路径上的权和最小.如果有多解,该叶子本身的权应尽量小.输入中每两行表示一棵树,其中第一行为中序遍 ...

最新文章

  1. 原子智库 | 刘伟:人工智能快追上人类思维?答案可能让你失望
  2. PHP整站迁移空间,discuz整站数据迁移搬家教程
  3. python列表修改元素_python list 中修改元素
  4. BZOJ3144: [Hnoi2013]切糕
  5. 中音萨克斯指法表图_初学萨克斯一定要了解这6点基础知识
  6. 100行代码搞定抖音短视频App,终于可以和美女合唱了。
  7. 基于webview的选择滑动控件(PC和wap版)
  8. DOS下常用网络相关命令解释(华为培训资料)
  9. mysql api是什么意思_什么是mysql c api? 解析mysql c api简单应用
  10. javascript 中XMLHttpRequest 实现前台向后台的交互
  11. Window下利用命令行提交代码到GitHub
  12. HDU 2674 N!Again
  13. 如何正确设置同时使用内外网
  14. linux的php探针使用,php探针在Linux下的安装过程
  15. J2me调用wap浏览器
  16. 计算机Excel设置透视图,excel共享表格数据-EXCEL在共享模式中,如何让数据透视表能够自动刷新?...
  17. 计算机专业英语unit11,计算机专业英语教程
  18. “死神”百草枯:每年超万人中毒 没有解药
  19. 使用netstat命令统计established状态的连接数
  20. 路演 - roadshow

热门文章

  1. iic通信的深入理解(主从设备通信)
  2. 清华计算机系开学典礼,清华大学举行2017级本科生新生开学典礼
  3. 微软edge浏览器花屏_如何玩Microsoft Edge的秘密冲浪游戏
  4. Python不掉包初探自然语言处理One-Hot编码与解码
  5. ChinaJoy揭晓十大网游盗号木马黑榜
  6. springboot+rocketmq(6):实现消息过滤
  7. stm32f103rct6引脚功能表格
  8. 性能测试监控工具Server Agent无法监控资源,jmeter报错
  9. [RK3288]backlight pwm_bl控制双屏背光改写
  10. 技术干货| 详解AI国际顶会NeurIPS 2020的黑盒优化竞赛冠军算法——HEBO算法