数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集
第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层完全二叉树:
前
h-1
层为满二叉树第
h
层上的节点都连续排列于第h
层的左侧。满二叉树是完全二叉树的一个特例
有n个元素的完全二叉树的深度为 l o g 2 ( n + 1 ) log_2(n+1) log2(n+1)
特性⑤:设完全二叉树中一元素的序号为i
,1≤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)
(j
是i
的孩子)
- 若i为叶节点,则
下图中假定容忍值为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域,使其指向各自的祖父节点。
数据结构 | 第十一章:二叉树和其他树 | 【前序遍历】【中序遍历】【后序遍历】【层次遍历】 | 并查集相关推荐
- 数据结构(4)树形结构——二叉树(概述、前序、中序、后序、层序遍历JAVA实现)
目录 4.1.树 4.2.二叉树 4.2.1.概述 4.2.3.存储结构 4.2.3.遍历 1.逻辑简介 2.代码示例 4.1.树 树,由n(n≥0)个有限节点和边组成一个具有层次关系的数据结构.树需 ...
- (王道408考研数据结构)第五章树-第三节1:二叉树遍历(先序、中序和后序)
文章目录 一:二叉树遍历概述 二:二叉树深度优先遍历 (1)先序遍历-根左右(NLR) (2)中序遍历-左根右(LNR) (3)后序遍历-左右根(LRN) 总结:三种遍历方式动图演示 三:二叉树的层序 ...
- 数据结构c语言版二叉树的顺序存储表示,数据结构(十一) -- C语言版 -- 树 - 二叉树基本概念...
内容预览 零.读前说明一.二叉树相关概念1.1.定义1.2.性质1.3.满二叉树与完全二叉树1.3.1.满二叉树1.3.2.完全二叉树1.3.3.特点延伸 二.二叉树储存结构2.1.顺序结构存储2.2 ...
- 数据结构(十一) -- C语言版 -- 树 - 二叉树基本概念
内容预览 零.读前说明 一.二叉树相关概念 1.1.定义 1.2.性质 1.3.满二叉树与完全二叉树 1.3.1.满二叉树 1.3.2.完全二叉树 1.3.3.特点延伸 二.二叉树储存结构 2.1.顺 ...
- 树的基本概念和遍历规则 数据结构和算法 二叉树遍历(前序、中序、后序、层次、深度优先、广度优先遍历)
zsychanpin 博客园 首页 新随笔 联系 订阅 管理 树的基本概念和遍历规则 树的递归定义 树是n(n>0)个结点的有限集,这个集合满足下面条件: ⑴有且仅有一个结点没有前驱 ...
- 【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构
本次笔记内容: 3.3.1 先序中序后序遍历 3.3.2 中序非递归遍历 3.3.3 层序遍历 3.3.4 遍历应用例子 小白专场:题意理解及二叉树表示 小白专场:程序框架.建树及同构判别 文章目录 ...
- //数据结构:先序、中序、后序遍历二叉树。输入数据:abd##eg###c#f#h##
//数据结构:先序.中序.后序遍历二叉树.输入数据:abd##eg###c#f#h## #include <stdio.h> #include <stdlib.h> //定义数 ...
- C语言数据结构之二叉树的层次建树及遍历方法(前序,中序,后序,层次遍历)
C语言数据结构之二叉树的层次建树及遍历方法(前序,中序,后序,层次遍历) tips:前些天学习了C语言数据结构链表,栈,队列.今天来学习一下C语言数据结构之二叉树的各种操作. 注意:二叉树的层次建树是 ...
- 【树】二叉树遍历算法(深度优先、广度优先遍历,前序、中序、后序、层次)及Java实现...
[树]二叉树遍历算法(深度优先.广度优先遍历,前序.中序.后序.层次)及Java实现 目录 一.前序遍历 二.中序遍历 三.后序遍历 四.层次遍历 遍历的作用 二叉树是一种非常重要的数据结构,很多其它 ...
- 【练习】树(Tree, UVa 548)给一棵点带权(权值各不相同)的二叉树的中序和后序遍历,找一个叶子使得它到根的路径上的权和最小。
给一棵点带权(权值各不相同,都是小于10000的正整数)的二叉树的中序和后序遍历,找一个叶子使得它到根的路径上的权和最小.如果有多解,该叶子本身的权应尽量小.输入中每两行表示一棵树,其中第一行为中序遍 ...
最新文章
- 原子智库 | 刘伟:人工智能快追上人类思维?答案可能让你失望
- PHP整站迁移空间,discuz整站数据迁移搬家教程
- python列表修改元素_python list 中修改元素
- BZOJ3144: [Hnoi2013]切糕
- 中音萨克斯指法表图_初学萨克斯一定要了解这6点基础知识
- 100行代码搞定抖音短视频App,终于可以和美女合唱了。
- 基于webview的选择滑动控件(PC和wap版)
- DOS下常用网络相关命令解释(华为培训资料)
- mysql api是什么意思_什么是mysql c api? 解析mysql c api简单应用
- javascript 中XMLHttpRequest 实现前台向后台的交互
- Window下利用命令行提交代码到GitHub
- HDU 2674 N!Again
- 如何正确设置同时使用内外网
- linux的php探针使用,php探针在Linux下的安装过程
- J2me调用wap浏览器
- 计算机Excel设置透视图,excel共享表格数据-EXCEL在共享模式中,如何让数据透视表能够自动刷新?...
- 计算机专业英语unit11,计算机专业英语教程
- “死神”百草枯:每年超万人中毒 没有解药
- 使用netstat命令统计established状态的连接数
- 路演 - roadshow
热门文章
- iic通信的深入理解(主从设备通信)
- 清华计算机系开学典礼,清华大学举行2017级本科生新生开学典礼
- 微软edge浏览器花屏_如何玩Microsoft Edge的秘密冲浪游戏
- Python不掉包初探自然语言处理One-Hot编码与解码
- ChinaJoy揭晓十大网游盗号木马黑榜
- springboot+rocketmq(6):实现消息过滤
- stm32f103rct6引脚功能表格
- 性能测试监控工具Server Agent无法监控资源,jmeter报错
- [RK3288]backlight pwm_bl控制双屏背光改写
- 技术干货| 详解AI国际顶会NeurIPS 2020的黑盒优化竞赛冠军算法——HEBO算法