二叉树

什么是二叉树?

父节点至多只有两个子树的树形结构成为二叉树。如下图所示,图1不是二叉树,图2是一棵二叉树。

          图1 普通的树                                                                                   图2 二叉树

如果一棵树所有的非叶子节点都有两个子节点,则称该树为完全二叉树,图2就是一棵完全二叉树。

二叉查找树(ADT

二叉树一个重要的应用是二差查找树,顾名思义,二叉查找树是二叉树在查找方面的应用。根据以往的知识,如果给你一个数组或者是链表,可能需要遍历一整个数组或者是量表才能找到需要查找的目标。使用二叉树作为查找的数据结构,能够大大缩小查找的深度(若链表和数组的长度为N,一棵二叉树的深度为logN),提高查找的效率。

如何定义一棵查找树?

设每一个节点对应一个键值,而该节点的左子节点对用的数值小于该节点的值,该节点的右子节点的数值大于该节点的值。如下图所示:

              图3 二差查找树                                                             图4 非二差查找树

图3是二叉查找树,因为他总数满足上述的条件,而图4不是二叉查找树,图中红色框住的部分不满足二叉查找树的条件(父节点6<左子节点7)。

代码:

struct Tree{

double value;

Tree *left;

Tree *right;

};

TreeNode表示二叉树节点。其中value表示树对应的值,left表示该节点的左节点指针,right表示该节点的右节点指针。

二叉查找树的重要操作

1.查找

1.1 查找固定的数值

给定一个数值,查找二叉树中是否有对应的数值,如果树中包含该数值,则返回数值对应的节点,否则,返回NULL。查找的过程:

首先定位到跟节点,如果查找的数值跟节点的数值相等,则根节点为所求;否则,如果数值小于根节点的数值,则查找根节点的左节点,相反,则查找跟节点的右节点。这样一直遍历下去,知道找到对应的或者或者已经不能继续往下查找为止(即已经到达子节点)。

代码:

 Tree* Find(double value,Tree *t){
if(t==NULL)
return NULL;
if(value==t->value)
return t;
else if(value<t->value)
return Find(value,t->left);
else
return Find(value,t->right);
}

1.2查找最大值或最小值

二叉查找树一个很大的特点是左子节点的数值<父节点的数值,而右子节点的数值>父节点的数值,因此查找最大值或者最小值就相当的方便,只需要从跟节点开始,不断遍历左子结点,直到到达叶子节点,就可以得到最小值;而查找最大值则从根节点开始遍历右子结点,直到到达叶子节点,对用的数值即为最大值。

代码:

超找最小元素的节点

 Tree* FindMin(Tree* t){
if(t==NULL)
return NULL;
if(t->left==NULL)
return t;
else{
return FindMin(t->left);
}
}

查找最大元素的节点

 Tree* FindMax(Tree *t){
if(t==NULL)
return NULL;
if(t->right==NULL)
return t->right;
else
return FindMax(t->right);
}

2. 插入

插入操作是将一个数值插入到二叉查找树中的过程,插入后的树依然满足二叉查找树的条件。一棵二叉查找树的构建过程,就是一个不断将元素插入到二叉树的过程。

插入的操作最关键的是一个查找的过程,如果在二叉树中找到要插入的数值,则什么都不用做,如果找不到,只需要在最后我们查找的叶子节点上新增加一个子节点即可。举一个例子:

上图是一棵二叉树,如果我们要插入11,则按以下步骤进行:

再比如要找17,按以下步骤查找:

代码:

 void Insert(double value,Tree* t){
if(t==NULL){
t = new Tree();
if(t==NULL)
return;
else{
t->value=value;
t->left=NULL;
t->right=NULL;
}
}
if(t->value>value)
Insert(value,t->left);
else
Insert(value,t->right);
}

3. 删除

删除操作就是删除键值与数值相同的节点。删除操作相比查找和插入来说是一个较为复杂的过程。如果要删除的节点就是叶子节点,直接将该节点删除即可,但是,如果要删除非叶子节点,就需要通过调整其他节点的位置来构建新的二叉树。一般来说,直接用该节点的右子树的最小值替换该节点的值,然后再删除右子树的最小节点即可。

例子:

AVL

虽说二叉查找树是一种优秀的数据结构,能够大大降低数据查询的复杂度。但是,并不是说有情况下二叉树都能够达到快速查找的目的。

我们发现,如果按照[7,10,11,12,14,15,18]这样的顺序一个个元素进行插入的话就会出现右图所示的二叉树,这样的二叉树跟一个链表几乎是没有区别的,查找的效率一样,没有体现出二叉树的优势。出现这种原因是构建二叉树的过程中没有平衡节点的左右子树的高度。根节点7的右子树有很高的深度,但是左子树是空的。我们需要的是一棵左右节点平衡的二叉树,而其中一种传统的平衡二叉树是AVL树。

定义:AVL树是二叉树,其各个节点的左右子树的高度相差不超过1。

定义:数的高度可以看做是节点与最低的叶子节点的距离。跟节点的高度最大,而叶子节点的高度为0,一个不存在的节点的高度定义为-1。

例如:左图中节点12的高度为2,节点18的高度为0,而右图中节点12的高度为3,节点18的高度为1。

左图是一棵AVL树,右图不是一棵AVL树,因为右图节点15的左右子树的高度相差2(左子树的高度为-1,右子树的盖度为1)。

AVL树的构建

AVL树的构建同样是不断将元素插入的过程,但是与二叉查找树不同,AVL树在插入的过程中需要满足AVL树的条件,如果发现插入新的元素后不能满足AVL条件,需要通过调整元素的位置直到满足条件。

元素的插入无非就只有以下四种情况:1.左子树插入一个左节点;2.左子树插入一个右节点;3.右子树插入一个左节点;4.右子树插入一个右节点。

其中,1和4、2和3是对称的操作,因此下面只讨论1和2两种情况。

1. 左子树插入一个左节点

如上图所述,左边是原始的二叉树,该二叉树满足AVL树的条件,在插入元素5后变成了右边的二叉树,而此时不满足AVL树的条件,因为节点10的左右两棵子树的高度相差2(左子树的高度为1,右子树的高度为-1)。

此时需要通过对子树进行调整才能让二叉树再次满足AVL的条件。

如上图所示,调整后的二叉树重新变成一棵AVL树,则种调整的方法称为“左旋转”,通过旋转调整节点的位置,使二叉树满足AVL树的条件。同理,如果新增的元素为右子树的右子树,而且新增后子树的左右子树高度相差为2,此时进行“右旋转”即可调整为AVL树。

2. 左子树插入一个右节点

如上图所述,左边是原始的二叉树,该二叉树满足AVL树的条件,在插入元素8后变成了右边的二叉树,而此时不满足AVL树的条件,因为节点10的左右两棵子树的高度相差2(左子树的高度为1,右子树的高度为-1)。

与情况1(左子树新增左节点)不一样,此时不能通过一个简单的“坐旋转”来调整左右子树的高度。怎么办呢?需要通过两次“旋转”,显示通过对元素7进行右旋转,然后再对10进行左旋转。如下图所示:

      同理,对于右子树插入一个左节点的情况,如果此时不符合AVL树的条件,需要先进性“左旋转”,再进行“右旋转”即可。

代码:

 AVLTree* AVLInsert(double value,AVLTree * tree){
if(tree==NULL){
tree = new AVLTree();
tree->value=value;
tree->left=NULL;
tree->right=NULL;
tree->height=;
return;
}
if(value>tree->value){
tree->right=AVLInsert(value,tree->right);
if(Height(tree->right)-Height(tree->left)==){
if(value>tree->right->value)
tree=SingleRotateWithRight(tree);
else
tree=DoubleRouteWithRight(tree);
}
}
else{
tree->left=AVLInsert(value,tree->left);
if(Height(tree->left)-Height(tree->right)==){
if(value<tree->left->value)
tree=SingleRotateWithLeft(tree);
else
tree=DoubleRouteWithLeft(tree);
}
}
tree->height = Height(tree->left)>Height(tree->right)?Height(tree->left)+:Height(tree->right)+;
return tree;
}
//左旋转
AVLTree* SingleRotateWithLeft(AVLTree * tree){
AVLTree * left = tree->left;
tree->left=left->right;
left->right=tree;
tree->height=Height(tree->left)>Height(tree->right)?Height(tree->left)+:Height(tree->right)+;
left->height=Height(left->left)>Height(left->right)?Height(left->left)+:Height(left->right)+;
return left;
}
//右旋转
AVLTree* SingleRotateWithRight(AVLTree * tree){
AVLTree * right = tree->right;
tree->right=right->left;
right->left=tree;
tree->height=Height(tree->left)>Height(tree->right)?Height(tree->left)+:Height(tree->right)+;
right->height=Height(right->left)>Height(right->right)?Height(right->left)+:Height(right->right)+;
return right;
}
//右—左双旋转
AVLTree* DoubleRouteWithLeft(AVLTree* tree){
tree->left = SingleRotateWithRight(tree->left);
return SingleRotateWithLeft(tree);
}
//右—左双旋转
AVLTree* DoubleRouteWithRight(AVLTree* tree){
tree->right = SingleRotateWithLeft(tree->right);
return SingleRotateWithRight(tree);
}

二叉树与AVL树的更多相关文章

  1. 二叉树,AVL树和红黑树

    为了接下来能更好的学习TreeMap和TreeSet,讲解一下二叉树,AVL树和红黑树. 1. 二叉查找树 2. AVL树 2.1. 树旋转 2.1.1. 左旋和右旋 2.1.2. 左左,右右,左右, ...

  2. 二叉树-二叉查找树-AVL树-遍历

    一.二叉树 定义:每个节点都不能有多于两个的儿子的树. 二叉树节点声明: struct treeNode { elementType element; treeNode * left; treeNod ...

  3. python常用算法(5)——树,二叉树与AVL树

    1,树 树是一种非常重要的非线性数据结构,直观的看,它是数据元素(在树中称为节点)按分支关系组织起来的结构,很像自然界中树那样.树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形 ...

  4. 5分钟了解二叉树之AVL树

    转载请注明出处:https://www.cnblogs.com/morningli/p/16033733.html AVL树是带有平衡条件的二叉查找树,其每个节点的左子树和右子树的高度最多相差1.为了 ...

  5. 二叉树之AVL树的平衡实现(递归与非递归)

    这篇文章用来复习AVL的平衡操作,分别会介绍其旋转操作的递归与非递归实现,但是最终带有插入示例的版本会以递归呈现. 下面这张图绘制了需要旋转操作的8种情况.(我要给做这张图的兄弟一个赞)后面会给出这八 ...

  6. 二叉树之AVL树

    高度为 h 的 AVL 树,节点数 N 最多2^h − 1: 最少N(h)=N(h− 1) +N(h− 2) + 1. 最少节点数n 如以斐波那契数列可以用数学归纳法证明: 即: N(0) = 0 ( ...

  7. 04-树4. Root of AVL Tree-平衡查找树AVL树的实现

    对于一棵普通的二叉查找树而言,在进行多次的插入或删除后,容易让树失去平衡,导致树的深度不是O(logN),而接近O(N),这样将大大减少对树的查找效率.一种解决办法就是要有一个称为平衡的附加的结构条件 ...

  8. AVL树和伸展树 -数据结构(C语言实现)

    读数据结构与算法分析 AVL树 带有平衡条件的二叉树,通常要求每颗树的左右子树深度差<=1 可以将破坏平衡的插入操作分为四种,最后通过旋转恢复平衡 破坏平衡的插入方式 描述 恢复平衡旋转方式 L ...

  9. 【数据结构】什么是AVL树

    目录 什么是AVL树 1. 什么是AVL树 2. 节点的实现 3. AVL树的调整 3.1 LL旋转 3.2 RR旋转 3.3 RL旋转 3.4 LR旋转 什么是AVL树 二叉查找树的一个局限性就是有 ...

随机推荐

  1. BZOJ 3997: [TJOI2015]组合数学 [偏序关系 DP]

    3997: [TJOI2015]组合数学 题意:\(n*m:\ n \le 1000\)网格图,每个格子有权值.每次从左上角出发,只能向下或右走.经过一个格子权值-1.至少从左上角出发几次所有权值为0 ...

  2. BZOJ 3620: 似乎在梦中见过的样子 [KMP 暴力]

    和我签订契约,成为魔法少女吧 题意:求所有形似于A+B+A 的子串的数量 , 且len(A)>=k,len(B)>=1 位置不同其他性质相同的子串算不同子串,位置相同但拆分不同的子串算同一 ...

  3. python+opencv2相机位姿估计

    最近在做基于图像的室内定位方面的研究,于是使用到了百度最新的室内数据库Image-based Localization (IBL) .由于该数据库给出的数据是每幅图像和其对应相机的内外参数和光心投影方 ...

  4. Spring Mybatis-分页插件使用

    Mybatis分页切入点 Mybatis内部有个plugins(插件)概念,本质上属于拦截器的思想.具体的解析可见外文MyBatis拦截器原理探究.本文将在此基础上直接展示实现代码和其他的相关解析 分 ...

  5. 损失函数 hinge loss vs softmax loss

    1. 损失函数 损失函数(Loss function)是用来估量你模型的预测值 f(x) 与真实值 Y 的不一致程度,它是一个非负实值函数,通常用 L(Y,f(x)) 来表示. 损失函数越小,模型的鲁 ...

  6. CentOS 7 搭建基于携程Apollo(阿波罗)配置中心单机模式

    Apollo(阿波罗)是携程框架部门研发的配置管理平台,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性.服务端基于Spring Boot ...

  7. Project support for both iOS 6 and iOS 7

    原文:https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TransitionGuide/S ...

  8. 日程管理 FullCalendar

    日程管理,采用著名组件FullCalendar日历插件实现FullCalendar提供了丰富的属性设置和方法调用,开发者可以根据FullCalendar提供的API快速完成一个日历日程的开发1.实现按 ...

  9. Redis基础及入门

    一. 什么是 Redis            Redis 是一个可基于内存,有着完备的持久化机制并以 Key-Value 形式存储的非关系型数据库.也称为数据结构服务器.    二. Redis 的 ...

  10. 高性能JavaScript读书笔记

    零.组织结构 根据引言,作者将全书划分为四个部分: 一.页面加载js的最佳方式(开发前准备) 二.改善js代码的编程技巧(开发中) 三.构建与部署(发布) 四.发布后性能检测与问题追踪(线上问题优化) ...