排序二叉树对于我们寻找无序序列中的元素的效率有了大大的提高。查找的最差情况是树的高度。这里就有问题了,将无序数列转化为

二叉排序树的时候,树的结构是非常依赖无序序列的顺序,这样会出现极端的情况。

【如图1】:

  这样的一颗二叉排序树就是一颗比较极端的情况。我们在查找时候,效率依赖树的高度,所以不希望这样极端情况出现,而是希望元素比较均匀

的分布在根节点两端。

技术参考:fun4257.com/


问题提出:

  能不能有一种方法,使得我们的二叉排序树不依赖无序序列的顺序,也能使得我们得到的二叉排序树是比较均匀的分布。

引入:

  平衡二叉树(Self-Balancing Binary Search Tree 或 Height-Balanced Binary Search Tree),是一种特殊的二叉排序树,其中每一个结点的

左子树和右子树的高度差至多等于1.

  这里的平衡从名字中可以看出,Height-Balanced是高度平衡。

  它或者是一颗空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1.

  若将二叉树上的结点的平衡因子BF(Balance Factor)定义为该节点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只

可能是-1、0、1。否则就不是平衡二叉树。

  上图图1中,就不是平衡二叉树。

  以图1来看看各个结点的平衡因子。

【如下图2】:


 技术参考:fun1404.com

如何构成平衡二叉树?

从转化为平衡二叉树的过程中可以提炼出转化的几个基本情况:

下图是在维基百科上摘录的:

可以看出调整的操作分两大类,前两个是一组,后两个是一组,每组之间是对称的。

前两个是对应上图1 2 中情况,

后两个是对应上图5 6 中情况。

分别以其中一种旋转为例,另一种对应的旋转对称。

单次左旋:对应上图1(左左)中情况

简单左右旋转代码:(只有一次)

 void rotateL(pBinTree *p)//左旋转
{
pBinTree r;
r = (*p)->rchd; //r 为新的根
(*p)->rchd = r->lchd;
r->lchd = (*p);
(*p) = r;
} void rotateR(pBinTree *p)//右旋转
{
pBinTree r;
r = (*p)->lchd; //r 为新的根 (*p)->lchd = r->rchd; //新根节点的右孩子附到旧的根结点的左孩子
r->rchd = (*p);
(*p) = r;
}

两次旋转 对应图中3(左右)情况

需要旋转两次简单的左右旋转。基于上面代码就可以实现。

为了方便,AVL引入了BF(平衡因子)来调整树。只要出现非平衡树就调整,把不平衡消除最小的情况。

下面就是通过判断BF来实现调整

  void BlanceLeft(pBinTree *p)//从最小非平衡树开始调整
{
pBinTree nR,nRchd;
nR = (*p)->lchd;
switch (nR->bf)
{
case LH: //新插入的结点在左子树
{
(*p)->bf = EH;
nR->bf = EH;
rotateR(p);
break;
}
case RH: //新插入的结点在右子树
{
nRchd = nR->rchd;
switch(nRchd->bf)//增加结点是nR的左孩子还是右孩子?
{
case LH://
{
(*p)->bf = RH;
nR->bf = EH;
break;
}
case EH://
{
(*p)->bf = EH;
nR->bf = EH;
break;
}
case RH:
{
(*p)->bf = EH;
nR->bf = LH;
break;
}
}
nRchd->bf = EH;
rotateL(&((*p)->lchd));
rotateR(p);
}
}
}
void BlanceRight(pBinTree *p)//从最小非平衡树开始调整
{
pBinTree nR,nRchd;
nR = (*p)->rchd; switch (nR->bf){
case RH: //新插入的结点在左子树
{
(*p)->bf = EH;
nR->bf = EH;
rotateL(p);
break;
}
case LH: //新插入的结点在右子树
{
nRchd = nR->lchd;
switch(nRchd->bf)//增加结点是nR的左孩子还是右孩子?
{
case LH://
{
(*p)->bf = EH;
nR->bf = RH;
break;
}
case EH://
{
(*p)->bf = EH;
nR->bf = EH;
break;
}
case RH:
{
(*p)->bf = LH;
nR->bf = EH;
break;
}
}
nRchd->bf = EH;
rotateR(&((*p)->rchd));
rotateL(p);
}
}
}

然后再就是插入算法,这里采用递归的方式插入。

bool InsertAVL(pBinTree *T,int key,bool *taller)
{
if (!*T)
{
*T = (pBinTree)malloc(sizeof(BinTree));
(*T)->data = key;
(*T)->bf = EH;
(*T)->lchd = NULL;
(*T)->rchd = NULL;
*taller = true;
}
else {
if (key == (*T)->data)
{
*taller = false;
return false;
}
if (key < (*T)->data)
{
if (!InsertAVL(&((*T)->lchd),key,taller))
{
return false;
}
if (*taller)
{
switch ((*T)->bf)
{
case LH:
{
BlanceLeft(T);
*taller = false;
break;
}
case EH:
{
(*T)->bf = LH;
*taller = true;
break;
}
case RH:
{
(*T)->bf = EH;
*taller = false;
break;
}
}
}
}
else // key > (*T)->data
{
if (!InsertAVL(&((*T)->rchd),key,taller))
{
return false;
}
if (*taller)
{
switch ((*T)->bf)
{
case LH:
{
(*T)->bf = EH;
*taller = false;
break;
}
case EH:
{
(*T)->bf = RH;
*taller = true;
break;
}
case RH:
{
BlanceRight(T);
*taller = false;
break;
}
} }
}
}
return true;
}

以上的代码用switch case 显得非常的繁琐。会导致删除结点的程序判断BF调整非平衡的步骤更多。

以后添加删除部分代码。

完整代码:

 // AVL.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h" #include
#define LH 1
#define EH 0
#define RH -1 typedef int dataType; typedef struct BinTNode {
dataType data;
int bf;
struct BinTNode *lchd,*rchd;
}BinTree,*pBinTree; void rotateL(pBinTree *p)
{
pBinTree r;
r = (*p)->rchd; //r 为新的根 (*p)->rchd = r->lchd;
r->lchd = (*p);
(*p) = r;
} void rotateR(pBinTree *p)
{
pBinTree r;
r = (*p)->lchd; //r 为新的根 (*p)->lchd = r->rchd; //新根节点的右孩子附到旧的根结点的左孩子
r->rchd = (*p);
(*p) = r;
} void BlanceLeft(pBinTree *p)//从最小非平衡树开始调整
{
pBinTree nR,nRchd;
nR = (*p)->lchd;
switch (nR->bf)
{
case LH: //新插入的结点在左子树
{
(*p)->bf = EH;
nR->bf = EH;
rotateR(p);
break;
}
case RH: //新插入的结点在右子树
{
nRchd = nR->rchd;
switch(nRchd->bf)//增加结点是nR的左孩子还是右孩子?
{
case LH://
{
(*p)->bf = RH;
nR->bf = EH;
break;
}
case EH://
{
(*p)->bf = EH;
nR->bf = EH;
break;
}
case RH:
{
(*p)->bf = EH;
nR->bf = LH;
break;
}
}
nRchd->bf = EH;
rotateL(&((*p)->lchd));
rotateR(p);
}
}
}
void BlanceRight(pBinTree *p)//从最小非平衡树开始调整
{
pBinTree nR,nRchd;
nR = (*p)->rchd; switch (nR->bf){
case RH: //新插入的结点在左子树 {
(*p)->bf = EH;
nR->bf = EH;
rotateL(p);
break;
}
case LH: //新插入的结点在右子树
{
nRchd = nR->lchd;
switch(nRchd->bf)//增加结点是nR的左孩子还是右孩子?
{
case LH://
{
(*p)->bf = EH;
nR->bf = RH;
break;
}
case EH://
{
(*p)->bf = EH;
nR->bf = EH;
break;
}
case RH:
{
(*p)->bf = LH;
nR->bf = EH;
break;
}
}
nRchd->bf = EH;
rotateR(&((*p)->rchd));
rotateL(p);
}
}
} bool InsertAVL(pBinTree *T,int key,bool *taller)
{
if (!*T)
{
*T = (pBinTree)malloc(sizeof(BinTree));
(*T)->data = key;
(*T)->bf = EH;
(*T)->lchd = NULL;
(*T)->rchd = NULL;
*taller = true;
}
else {
if (key == (*T)->data)
{
*taller = false;
return false;
}
if (key < (*T)->data)
{
if (!InsertAVL(&((*T)->lchd),key,taller))
{
return false;
}
if (*taller)
{
switch ((*T)->bf)
{
case LH:
{
BlanceLeft(T);
*taller = false;
break;
}
case EH:
{
(*T)->bf = LH;
*taller = true;
break;
}
case RH:
{
(*T)->bf = EH;
*taller = false;
break;
}
} }
}
else // key > (*T)->data
{
if (!InsertAVL(&((*T)->rchd),key,taller))
{
return false;
}
if (*taller)
{
switch ((*T)->bf)
{
case LH:
{
(*T)->bf = EH;
*taller = false;
break;
}
case EH:
{
(*T)->bf = RH;
*taller = true;
break;
}
case RH:
{
BlanceRight(T);
*taller = false;
break;
}
} }
}
}
return true;
} int _tmain(int argc, _TCHAR* argv[])
{
int a[] = {,,,,,,,,,};
int i;
bool taller;
pBinTree T = NULL; for (i=;i<;i++)
{
InsertAVL(&T,a[i],&taller);
}
getchar();
return ;
}

算法学习记录-查找——平衡二叉树(AVL)的更多相关文章

  1. 算法学习记录-查找——二叉排序树(Binary Sort Tree)

    二叉排序树 也称为 二叉查找数. 它具有以下性质: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值. 它的左.右子树也分别 ...

  2. 算法学习记录-查找——折半查找(Binary Search)

    以前有个游戏,一方写一个数字,另一方猜这个数字.比如0-100内一个数字,看谁猜中用的次数少. 这个里面用折半思想猜会大大减少次数. 步骤:(加入数字为9) 1.因为数字的范围是0-100,所以第一次 ...

  3. Manacher回文串算法学习记录

    FROM:  http://hi.baidu.com/chenwenwen0210/item/482c84396476f0e02f8ec230 #include<stdio.h> #inc ...

  4. 算法学习记录-图——最小生成树之Kruskal算法

    之前的Prim算法是基于顶点查找的算法,而Kruskal则是从边入手. 通俗的讲:就是希望通过 边的权值大小 来寻找最小生成树.(所有的边称为边集合,最小生成树形成的过程中的顶点集合称为W) 选取边集 ...

  5. 算法学习记录-图(DFS BFS)

    图: 目录: 1.概念 2.邻接矩阵(结构,深度/广度优先遍历) 3.邻接表(结构,深度/广度优先遍历) 图的基本概念: 数据元素:顶点 1.有穷非空(必须有顶点) 2.顶点之间为边(可空) 无向图: ...

  6. PID算法学习记录

    最近做项目需要用到PID算法,这个本来是我的专业(控制理论与控制工程),可是我好像是把这个东西全部还给老师了. 没办法,只好抽时间来学习了. 先占个座,后续将持续更新!

  7. 算法学习 - 平衡二叉查找树实现(AVL树)

    平衡二叉查找树 平衡二叉查找树是非常早出现的平衡树,由于全部子树的高度差不超过1,所以操作平均为O(logN). 平衡二叉查找树和BS树非常像,插入和删除操作也基本一样.可是每一个节点多了一个高度的信 ...

  8. 算法学习记录-排序——插入排序(Insertion Sort)

    插入排序: 在<算法导论>中是这样描述的 这是一个对少量元素进行排序的有效算法.插入排序的工作机理与打牌时候,整理手中的牌做法差不多. 在开始摸牌时,我们的左手是空的,牌面朝下放在桌子上. ...

  9. 算法学习记录-排序——冒泡排序(Bubble Sort)

    冒泡排序应该是最常用的排序方法,我接触的第一个排序算法就是冒泡,老师也经常那这个做例子. 冒泡排序是一种交换排序, 基本思想: 通过两两比较相邻的记录,若反序则交换,知道没有反序的记录为止. 例子: ...

随机推荐

  1. ES6学习笔记(五)-函数扩展

  2. 基于jQuery的数字键盘插件

    有时,我们需要在网页上使用软键盘.今天,就给大家带来一个基于jQuery的数字键盘插件,除了jQuery,不需要依赖任何文件资源.纯数字键盘,有退格,有清除,不支持输入小数(需要的可以自己改一下,主要 ...

  3. 洛谷P3939 数颜色(二分 vector)

    题意 题目链接 Sol 直接拿vector维护每种颜色的出现位置,然后二分一下. #include<bits/stdc++.h> using namespace std; const in ...

  4. jQuery处理JSONP

    http://www.g7blogs.com/?p=821 作为一枚前端,提起jsonp大家都不会陌生.特别是在我们组内的业务中,和服务器端交互的数据几乎都是采用这种形式.但假如要让你用原生的JS写出 ...

  5. AngularJS+RequireJs实现动态加载JS和页面的方案研究【下】

    about.js: [html] view plain copy 在CODE上查看代码片派生到我的代码片 define(['app'], function(app) { app.controller( ...

  6. Android 退出app,后台推送的服务也停止了,怎么可以做到不停止后台服务呢?

    service粘性等的那4种方式试了,三星的可以,小米老款手机可以,新款不行,华为新款也不行,还有魅族什么的,都不行,新款的手机上都有一个安全中心,只有在安全中心里面添加上允许app自启动才可以 怎么 ...

  7. js 正则常用函数

    正则表达式中,需要转义的字符: * . ? + $ ^ [ ] ( ) { } | \ / let reg = /\d+/g let str = 'ad/23/dfww/454/6' 1. reg.t ...

  8. 使用JSONP彻底解决Ajax跨域访问Cookie Session的方案

    最近做开发时要把图片文件放到另外一台服务器上(另外一个域名),因为这样分布式存放,网站打开速度会快很多.而我采用AJAX获取图片服务器上某用户的图片时遇到了问题,按照通常的方式无法获取信息,得到的Co ...

  9. 大数据实时计算工程师/Hadoop工程师/数据分析师职业路线图

    http://edu.51cto.com/roadmap/view/id-29.html http://my.oschina.net/infiniteSpace/blog/308401 大数据实时计算 ...

  10. Jmeter测试普通java类说明

    概述 Apache JMeter是Apache组织开发的基于Java的压力测试工具.本文档主要描述用Jmeter工具对基于Dubbo.Zookeeper框架的Cassandra接口.区块链接口进行压力 ...