当前位置: 首页 > news >正文

应用场景举例

没有应用场景,我们谈论某个技术也没有意义。 首先我们要谈一下树的应用: 应该主要用于,索引+ 查找:

  • 一维空间:各种平衡树,包括红黑树,AVL
  • 高维空间: KD-tree R-tree PST等
  • 外存索引: B-tree String B-tree, 等
  • 字符串索引: patricia tree, trie , suffix tree, wavelet tree等
  • 搜索: 可以采用树实现各种搜索策略,其各种遍历方法。
  • 表示: 很多事物可以用一棵树来表示,堆,语法树等。

抽象地说,基本上有序列的地方就可以应用树,因为树结构即是一种序列索引结构。 序列的核心接口就是三个cha:插、查、X(增查删)。

对于这个三个接口,我们要解决的核心问题是:

  1. 效率:怎么查得快
  2. 稳定:如果不支持增删,那么序列就是静态结构,用处不大。而支持增删之后,需要考虑如何保证序列内部结构不会被增删操作破坏,导致查询效率受到影。

树和森林的定义

树 (tree) 是包括 n 个结点的有限集合 T(n ≥ 1):

  • 有且仅有一个特定的结点,称为 根 (root)
  • 除根以外的其他结点被分成 m 个 (m ≥ 0)不相交的有限集合T1,T2,...,Tm,而每一个集合又都是树 ,称为 T 的子树。

树的逻辑结构

从定义上看,树就是一个集合,并且在集合上定义了节点之间的关系。

树的相关术语

  • 度数:一个结点的子树的个数 D
  • 层数:根为第 0 层
    • 其他结点的层数等于其父结点的层数加 1
  • 深度:层数最大的叶结点的层数
  • 高度:层数最大的叶结点的层数加 1

树形结构的各种表示法

  • 树形表示法
  • 形式语言表示法
  • 文氏图表示法(高中的集合表示)
  • 凹入表表示法(课本目录)
  • 嵌套括号表示法

森林定义及与二叉树的等价转换

森林(forest):零棵或多棵 不相交 的 树的集合(通常是有序)

森林与二叉树之间可以相互转化,而 且这种转换是一一对应的

森林转化成二叉树的形式定义

有序集合 F = {T1,T2,...,Tn} 是树 T1,T2,...,Tn 组成的森林,递归转换成二叉树B(F):

  • 若F为空,即n=0,则B(F)为空。
  • 若 F 非空,即 n > 0,则 B(F) 的根是森林中第一棵树 T1 的根 W1, B(F) 的左子树是树 T1 中根结点 W1的子树森林 F = {T11,...,T1m} 转换成的二叉树B(T11,...,T1m); B(F)的右子树是从森林 F’ = {T2,...,Tn} 转换而成的二叉树

二叉树转化成森林或树的形式定义

设B是一棵二叉树,root是 B 的根,BL 是 root 的左子树,BR 是 root 的右子树,则对应于二叉树B的森林或树 F(B) 的形式定义是:

  • 若 B 为空,则 F(B) 是空的森林
  • 若 B 不为空,则 F(B)是一棵树T1加上森林 F(BR), 其中树 T1 的根为 root,root 的子树为 F(BL) 也就是说转换成了空森林,或者包含两颗树的森林

树的ADT

template<class T> 
class TreeNode { 
public:
    TreeNode(const T& value); 
    virtual ~TreeNode() {}; 
    bool isLeaf();
    T Value();
    TreeNode<T> *LeftMostChild(); 
    TreeNode<T> *RightSibling();
    void setValue(const T& value);
    void setChild(TreeNode<T> *pointer); 
    void setSibling(TreeNode<T> *pointer); 
    void InsertFirst(TreeNode<T> *node); // 以第一个左孩子身份插入结点
    void InsertNext(TreeNode<T> *node);// 以右兄弟的身份插入结点
};

template<class T> 
class Tree { 
public:
    Tree();
    virtual ~Tree();
    TreeNode<T>* getRoot();
    void CreateRoot(const T& rootValue);
    bool isEmpty();
    TreeNode<T>* Parent(TreeNode<T> *current);
    TreeNode<T>* PrevSibling(TreeNode<T> *current);   //返回前一个兄弟
    void DeleteSubTree(TreeNode<T> *subroot); 
    void RootFirstTraverse(TreeNode<T> *root); 
    void RootLastTraverse(TreeNode<T> *root); 
    void WidthTraverse(TreeNode<T> *root);
};
 
复制代码

需要注意的是树没有中序遍历,因为树的度大于二,不好判断从哪一个是中间的子节点访问根节点。

先根遍历森林(等价于二叉树前序遍历)

template<classT>
void Tree<T>::RootFirstTraverse(TreeNode<T> * root){
	while(root!=NULL){
		Visit(root);
		RootFirstTraverse(root->LeftMostChild()); //往最左节点下降
		root = root->RightSibling();  //遍历其他树
	}
}
复制代码

后根遍历(等价于二叉树中序遍历)

template<classT>
void Tree<T>::RootLastTraverse(TreeNode<T> * root){
	while(root!=NULL){
		RootFirstTraverse(root->LeftMostChild()); //往最左节点下降
		visit(root);
		root = root->RightSibling();  //遍历其他树
	}
}
复制代码

宽度优先遍历森林(层次遍历)

template<class T>
void Tree<T>::WidthTraverse(TreeNode<T> * root) {
	std::queue<TreeNode<T>*> aQueue;
	TreeNode *pointer = root;
	while(pointer){
		aQueue.push(pointer);
		pointer = pointer->RightSibling();
	}
	while(!aQueue.empty()){
		pointer = aQueue.front();
		aQueue.pop();
		Visit(pointer);
		pointer = pointer->LeftMostChild();
		while(pointer){
			aQueue.push(pointer);
			pointer = pointer->RightSibling();
		}
	}
}
复制代码

树的存储结构

  • 链式存储
    • 子节点表
      • 图的邻接表
      • 索引 值 父结点(索引) 子结点(指针域)
    • 静态左孩子/右兄弟表示法 -实际上是,在数组中存储的“子结点表”
      • 左子节点(指针域) 值 父节点(索引) 右兄弟结点(指针域)
    • 动态表示法
      • 每个结点分配可变的存储空间
        • 子结点数目发生变化,需要重新分配存储空间
    • 动态“左子/右兄”二叉链表示法
      • 左孩子在树中是结点的最左子结点,右子结点是结点原来的右侧兄弟结点
      • 左孩子pchild(指针域) 值info 右兄弟psibling(指针域)
    • 父指针表示法(并查集)
      • 单独介绍
  • 顺序存储

动态二叉链表树的关键实现细节

// 在TreeNode的抽象类中增加以下私有数据成员
private:
    T m_Value; 
    TreeNode<T> *pChild; 
    TreeNode<T> *pSibling;
复制代码

父指针表示法

  • 只需要知道父结点的应用
  • 只需要保存一个指向其父结点的指针域,称为 父指针(parent pointer)表示法
  • 用数组存储树结点,同时在每个结点中附设一个指针指示其父结点的位置

并查集

并查集 是一种特殊的集合,由一些不相 交子集构成,合并查集的基本操作是:

  • Find: 查询结点所在集合
  • Union: 归并两个集合

等价类(equivalence classes)

等价类是指相互等价的元素所组成的最大集合。也就是说在集合上定义了一种等价关系,这个关系把大集合划分为满足与不满足该关系的集合。 所谓最大,就是指不存在类以外的元素,与类内 部的元素等价。

用树来表示等价类的并查

  • 用一棵树代表一个集合
    • 集合用父结点代替
    • 若两个结点在同一棵树中,则它们处于同一个集合
  • 树的实现
    • 存储在静态指针数组中
    • 结点中仅需保存父指针信息

父指针表示法Union/Find算法实现

template<class T>
class ParTreeNode { 
   private:
Tvalue; ParTreeNode<T>* parent; int nCount;
public:
    ParTreeNode();
    virtual ~ParTreeNode(){};
    TgetValue();
    void setValue(const T& val); 
    ParTreeNode<T>* getParent();
    void setParent(ParTreeNode<T>* par); 
    int getCount();
    void setCount(const int count);
};

template<class T> 
class ParTree { 
    public:
        ParTreeNode<T>* array;
        int Size;
        ParTreeNode<T>* Find(ParTreeNode<T>* node) const; // 查找node结点的根结点
        ParTree(const int size);
        virtual ~ParTree();
        void Union(int i,int j); // 把下标为i,j的结点合并成一棵子树
        bool Different(int i,intj);
        //判定下标为i,j的结点是否在一棵树中
};

template <class T>
ParTreeNode<T>* ParTree<T>::Find(ParTreeNode<T>* node) const 
{
    ParTreeNode<T>* pointer=node; 
    while ( pointer->getParent() != NULL )
        pointer=pointer->getParent(); 
    return pointer;
}
复制代码

Union算法

核心是小树往大树上合并。

template<class T>
void ParTree<T>::Union(int i,int j) {
ParTreeNode<T>* pointeri = Find(&array[i]); //找到结点i的根 ParTreeNode<T>* pointerj = Find(&array[j]); //找到结点j的根 if (pointeri != pointerj) {
if(pointeri->getCount() >= pointerj->getCount()) { pointerj->setParent(pointeri); 
pointeri->setCount(pointeri->getCount() +
pointerj->getCount());
}
else {
pointeri->setParent(pointerj); 
pointerj->setCount(pointeri->getCount() +
pointerj->getCount());
  } 
}}
复制代码

路径压缩

  • 查找X
    • 设X最终到达根R
    • 顺着由X到R的路径把每个结点的父指针域均设置 为直接指向R 使用路径压缩规则处理寻找父节点函数Find()
template <class T>
ParTreeNode<T>* ParTree<T>::FindPC(ParTreeNode<T>* node) const {
    if (node->getParent() == NULL) 
        return node;
    node->setParent(FindPC(node->getParent()));
    return node->getParent(); 
}
复制代码
  • 对n个结点进行n次Find操作的开销为O(nα(n)),约 为Θ(nlog*n)
  • Find至多需要一系列n个Find操作的开销非常接 近于Θ(n) – 在实际应用中,α(n)往往小于4

树的顺序存储结构

  • 带右链的先根次序表示
    • 节点按先根次序连续存储
    • Itag(标记有左子节点为0,else 1) info rlink(右指针)
  • 带双标记的先根次序表示 *带右链的先根次序表示”中rlink也有冗余,可以把rlink指针替换为一个标志位rtag,成为“带双标记的 先根次序表示”
  • 带双标记的层次次序表示
  • 带度数的后根次序表示

相关文章:

  • 那个外包工场
  • Node学习5-events模块
  • javascript中 visibility和display的区别
  • npm ERR! Unexpected end of JSON input while parsing near '...inimist:^1.2.0}
  • mysql show命令集合
  • 单例模式的N种写法(Java版)
  • 基于dba_hist_sqlstat查看sql语句的性能历史
  • es6
  • win32之全屏窗口
  • 【ocp新题库】052最新考题收集整理-第7题
  • 蓝桥杯-基础练习12 十六进制转八进制
  • 8 quick ways to clear up drive space in Windows 10
  • 【原创】Hacker学习发展流程图 V1.0
  • 设计模式(八)_门面模式
  • centos 中文乱码解决办法
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • JDK9: 集成 Jshell 和 Maven 项目.
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • log4j2输出到kafka
  • node和express搭建代理服务器(源码)
  • webpack4 一点通
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 反思总结然后整装待发
  • 聊聊sentinel的DegradeSlot
  • 日剧·日综资源集合(建议收藏)
  • 通过几道题目学习二叉搜索树
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • 湖北分布式智能数据采集方法有哪些?
  • # 透过事物看本质的能力怎么培养?
  • #考研#计算机文化知识1(局域网及网络互联)
  • (1)SpringCloud 整合Python
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (分享)自己整理的一些简单awk实用语句
  • (六)软件测试分工
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (原)Matlab的svmtrain和svmclassify
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • .net CHARTING图表控件下载地址
  • .net core 6 redis操作类
  • .NET Core跨平台微服务学习资源
  • .net 流——流的类型体系简单介绍
  • .net6+aspose.words导出word并转pdf
  • .net分布式压力测试工具(Beetle.DT)
  • .net中我喜欢的两种验证码
  • /etc/motd and /etc/issue
  • [ C++ ] STL priority_queue(优先级队列)使用及其底层模拟实现,容器适配器,deque(双端队列)原理了解
  • []T 还是 []*T, 这是一个问题
  • [AI]文心一言爆火的同时,ChatGPT带来了这么多的开源项目你了解吗
  • [Android学习笔记]ScrollView的使用
  • [BUUCTF]-PWN:[极客大挑战 2019]Not Bad解析
  • [BZOJ 1040] 骑士
  • [C++]:for循环for(int num : nums)
  • [C语言][C++][时间复杂度详解分析]二分查找——杨氏矩阵查找数字详解!!!
  • [Django 0-1] Core.Email 模块
  • [hdu 3746] Cyclic Nacklace [kmp]