简介

自平衡二叉树(AVL)属于二叉平衡树的一类,此类树主要完成一个从键到值的查找过程,即字典(或映射),它维护树高度的方式与其他数据结构不同。

自平衡规则:

  1. AVL树的左、右子树都是AVL树
  2. 左、右子树的高度差不超过1

在数据结构中,最常见的数据间关系的抽象就是集合(Collection字典(Dictionary

集合就是线性表(元素允许重复),而字典是一种非多键映射关系(键不允许重复)。

对集合而言,一个班中的所有学生构成一个集合,可以是有序的(有序集合)也可以是无序的(无序集合),查找的时间复杂度一般是O(n),很低。集合的典型就是C#中的List,STL中的vector。集合主要用于存储数据,很少用于查找。如要用于查找,那么可以选择基于有序表的二分查找,相应时间复杂度是O(logn),而要想从无序表转变为有序表,就是各种排序算法的使命了。

而对字典而言,如同根据一个学生的学号找到他这次考试的成绩一样,主要是用于查找的。这是从键到值的单值映射,键不能重复,值可以重复。根据键找到值是字典的工作。如果是平衡查找树,就为O(logn),实现一般以红黑树为主(比AVL简单),如Java的TreeMap、STL的map;如果是哈希表,就为O(1),如Java的HashMap。

源码

由于网上的算法通常是以递归形式出现的,且结点有父指针,更加自由。但是在这里,没有递归没有父指针,这就意味着,算法的实现难度要更高,算法的维护与调试用了两天时间。

解决递归问题靠栈,解决无父指针问题靠栈,殊途同归。

avltree.h(AVL Tree) AVL树模版

baltree.h(Balance Tree) 平衡树基类

btree.cpp(Binary Tree and Console) main函数

树的建立

在这里,只讲元素的插入,而对于删除操作,由于其复杂性,暂且不能实现,参考网上关于树的旋转等资料,实现了删除操作,但没有进行完整测试。

结点的数据结构为:

template<class T>
struct AVLNode
{
T data;
AVLNode *lchild, *rchild;
int BF; // 平衡因子
};

前面与二叉树一样,BF用于累计高度差,因为查询高度是一个枯燥的递归操作。

这里用到了数学归纳的知识,一开始,树的BF是满足要求的,那么,我们对树的每个操作若可以保证BF满足条件要OK,由于后面的操作建立在前面的操作之上只要保证了每一步操作的可靠性,那么BF的正确性就是可以保障的。这就要求对于每一个插入和删除操作,要保证类似于数据库那样的ACID特性中的一致性,类比数据库,只要一步操作出错,就算后面的操作是正确的,数据库也没法正常使用了。

创建结点:

template<class T, class N>
N* AVLTree<T, N>::New()
{
N* newp = BiTree<T, N>::New();
newp->BF = 0;
return newp;
}

按数组插入:

template<class T, class N>
AVLTree<T, N>::AVLTree(const vector<T>& s)
{
if (s.empty()) throw "数组不能为空";
root = new N;
root->data = s[0];
root->BF = 0;
root->lchild = NULL;
root->rchild = NULL;
for (int i = 1; i < s.Length(); i++) Insert(s[i]);
}

树的旋转

1)LL

template<class T, class N>
inline TStatus AVLTree<T, N>::RotateLL(N *&p, N *&parent, N *ancestor)
{
// 前
// A(parent,root),BF(2)
// |
// B(p),BF(1)__________|_____AR
// |
// BL(X)_____|_____BR // 后
// B(p,root),BF(0)
// |
// BL(X)_____|___________________A(parent),BF(0)
// |
// BR_____|_____AR bool descent = !parent->rchild;
this->RotateRight(p, parent, ancestor); parent->BF = p->BF == 1 ? 0 : 1;
p->BF--;
N *swap; swap = p; p = parent; parent = swap;
return (ancestor && descent) ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
}

2)RR

template<class T, class N>
inline TStatus AVLTree<T, N>::RotateRR(N *&p, N *&parent, N *ancestor)
{
// 前
// A(parent,root),BF(-2)
// |
// AL_____|___________________|
// B(p),BF(-1)
// BL_____|_____BR(X) // 后
// B(p,root),BF(0)
// |
// A(parent),BF(0)_____|_____BR(X)
// |
// AL_____|_____BL bool descent = !parent->lchild;
this->RotateLeft(p, parent, ancestor); parent->BF = p->BF == -1 ? 0 : -1;
p->BF++;
N *swap; swap = p; p = parent; parent = swap;
return (ancestor && descent) ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
}

3)LR

template<class T, class N>
inline TStatus AVLTree<T, N>::RotateLR(N *&p, N *&parent, N *ancestor)
{
////////////////////////////////////////////////////////////////////////// // >>>> 情况一 // 前
// A(parent,root),BF(2)
// |
// B(p),BF(-1)_________|_____AR
// |
// BL_____|_____C(pc),BF(1)
// |
// CL(X)______|______CR // 后(减少一层)
// C(pc,root),BF(0)
// |
// B(p),BF(0)____|_____A(parent),BF(-1)
// | |
// BL_____|_____CL(x) CR_____|_____AR ////////////////////////////////////////////////////////////////////////// // >>>> 情况二 // 前
// A(parent,root),BF(2)
// |
// B(p),BF(-1)_________|_____AR
// |
// BL_____|_____C(pc),BF(-1)
// |
// CL______|______CR(X) // 后(减少一层)
// C(pc,root),BF(0)
// |
// B(p),BF(1)____|_____A(parent),BF(0)
// | |
// BL_____|_____CL CR(x)_____|_____AR N *pc = p->rchild;
this->RotateLeft(pc, p, parent);
this->RotateRight(pc, parent, ancestor); if (pc->BF == 1)
{
p->BF = 0;
parent->BF = -1;
pc->BF = 0;
}
else if (pc->BF == -1)
{
p->BF = 1;
parent->BF = 0;
pc->BF = 0;
}
else
{
parent->BF = 0;
p->BF = 0;
}
parent = pc;
return ancestor ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
}

4)RL

template<class T, class N>
inline TStatus AVLTree<T, N>::RotateRL(N *&p, N *&parent, N *ancestor)
{
////////////////////////////////////////////////////////////////////////// // >>>> 情况一 // 前
// A(parent,root),BF(-2)
// |
// AL_____|___________________B(p),BF(1)
// |
// C(pc),BF(-1)__|______BR
// |
// CL_____|_____CR(X) // 后(减少一层)
// C(pc,root),BF(0)
// |
// A(parent),BF(1)|_____B(p),BF(0)
// | |
// AL_____|_____CL CR(x)_____|_____BR ////////////////////////////////////////////////////////////////////////// // >>>> 情况二 // 前
// A(parent,root),BF(-2)
// |
// AL_____|___________________B(p),BF(1)
// |
// C(pc),BF(1)___|______BR
// |
// CL(X)_____|_____CR // 后(减少一层)
// C(pc,root),BF(0)
// |
// A(parent),BF(0)|_____B(p),BF(-1)
// | |
// AL_____|_____CL(x) CR_____|_____BR N *pc = p->lchild;
this->RotateRight(pc, p, parent);
this->RotateLeft(pc, parent, ancestor); if (pc->BF == -1)
{
parent->BF = 1;
p->BF = 0;
pc->BF = 0;
}
else if (pc->BF == 1)
{
parent->BF = 0;
p->BF = -1;
pc->BF = 0;
}
else
{
parent->BF = 0;
p->BF = 0;
}
parent = pc;
return ancestor ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
}

5)左旋

template<class T, class N>
void BALTree<T, N>::RotateLeft(N *p, N *parent, N *ancestor)
{
// 前
// A(parent,root)
// |
// AL_____|___________________|
// B(p)
// BL_____|_____BR(X) // 后
// B(p,root)
// |
// A(parent)___________|_____BR(X)
// |
// AL_____|_____BL parent->rchild = p->lchild;
p->lchild = parent; if (ancestor != NULL)
{
if (ancestor->lchild == parent) // 修改p的双亲为ancestor
ancestor->lchild = p;
else
ancestor->rchild = p;
}
else
{
this->root = p;
}
}

6)右旋

template<class T, class N>
void BALTree<T, N>::RotateRight(N *p, N *parent, N *ancestor)
{
// 前
// A(parent,root)
// |
// B(p)________________|_____AR
// |
// BL(X)_____|_____BR // 后
// B(p,root)
// |
// BL(X)_____|___________________A(parent)
// |
// BR_____|_____AR parent->lchild = p->rchild;
p->rchild = parent; if (ancestor != NULL)
{
if (ancestor->lchild == parent) // 修改p的双亲为ancestor
ancestor->lchild = p;
else
ancestor->rchild = p;
}
else
{
this->root = p;
}
}

结点的插入

对于AVL,结点的插入是一项复杂的工作。由于插入操作,树原本的平衡被破坏,需要重新调整一次。

基本步骤是:

  1. 二分查找,找到相应的空位置,并插入(若已存在则忽略),同时存储查找路径

  2. 从路径逆着向上找到第一个最小不平衡子树的根结点的父结点,接下来就是对这个子树进行调整
  3. 调整子树,有LL、LR、RL、RR四种情况

1)添加结点

template<class T, class N>
bool BALTree<T, N>::Insert(T data)
{
N* newp; if (this->root == NULL)
{
newp = this->New();
newp->data = data;
this->root = newp;
this->HandleRoot();
return true;
} stack<N*> path; // 存储插入前的查找路径(便于回溯) //////////////////////////////////////////////////////////////////////////
// 插入操作
N *p = this->root;
while (true)
{
path.push(p);
if (data < p->data) // 插入值小于根结点,入左子树
{
if (p->lchild != NULL)
{
p = p->lchild; // 值小于LL,则递归入L
}
else
{
newp = this->New();
newp->data = data;
path.push(newp);
p->lchild = newp;
break; // 根结点无左孩子,正好插入
}
}
else if (data > p->data) // 插入值大于根结点,入右子树
{
if (p->rchild != NULL)
{
p = p->rchild; // 值大于RR,则递归入R
}
else
{
newp = this->New();
newp->data = data;
path.push(newp);
p->rchild = newp;
break; // 根结点无右孩子,正好插入
}
}
else // 插入值等于根结点,返回
{
return false;
}
} // 插入完毕 ////////////////////////////////////////////////////////////////////////// // 调整插入路径上的结点
BalanceAfterInsert(path);
return true;
}

2)插入后的平衡

template<class T, class N>
void BALTree<T, N>::BalanceAfterInsert(stack<N*>& path)
{
N *child = NULL; // *child作为*p的孩子结点
N *p = path.top();
path.pop();
N *parent = path.top(); // *parent是*p的父结点
path.pop();
N *ancestor;
while (true)
{
ancestor = path.empty() ? NULL : path.top();
TStatus status = CheckPathAfterInsert(child, p, parent, ancestor);
switch (status)
{
case T_OK:
return;
case T_BREAK:
BalanceInternalAfterInsert(child, p, parent, ancestor);
return;
case T_CONTINUE:
break;
} if (path.empty())
return; child = p;
p = parent;
parent = path.top(); // 由path向上回溯
path.pop();
}
}

3)检查平衡

template<class T, class N>
TStatus AVLTree<T, N>::CheckPathAfterInsert(N *child, N *p, N *parent, N *ancestor)
{
if (parent->lchild == p) // p是parent的左孩子,那么parent的BF++
parent->BF++; // BF的变化:-1->0->1->2
else // p是parent的右孩子,那么parent的BF--
parent->BF--; // BF的变化:1->0->-1->-2 if (parent->BF == 2 || parent->BF == -2) // *parent是失衡结点(第一个|BF|>=2)
return T_BREAK; // 找到最小不平衡子树的根结点 if (parent->BF == 0) // 在插入新结点后,*parent的左右子树高度相等(BF为0),说明以*parent为根的子树高度未增加
return T_OK; // 所以路径中的其余祖先结点无需调整BF return T_CONTINUE;
}

4)调整平衡

template<class T, class N>
TStatus AVLTree<T, N>::BalanceInternalAfterInsert(N *child, N *p, N *parent, N *ancestor)
{
////////////////////////////////////////////////////////////////////////// if (parent->BF == 2 && p->lchild == child) // LL
{
RotateLL(p, parent, ancestor);
} ////////////////////////////////////////////////////////////////////////// else if (parent->BF == -2 && p->rchild == child) // RR
{
RotateRR(p, parent, ancestor);
} ////////////////////////////////////////////////////////////////////////// else if (parent->BF == 2 && p->rchild == child) // LR
{
RotateLR(p, parent, ancestor);
} ////////////////////////////////////////////////////////////////////////// else if (parent->BF == -2 && p->lchild == child) // RL
{
RotateRL(p, parent, ancestor);
} ////////////////////////////////////////////////////////////////////////// return T_OK;
}

调整过程全部是旋转操作

结点的删除

结点的删除比插入要复杂得多。删除某一结点后,可能需要多次调整。

基本步骤是:

  1. 二分查找,找到要删除的结点,并用最近的结点替换掉它,然后删除它,调整BF

  2. 从路径逆着向上找到第一个最小不平衡子树的根结点的父结点,接下来就是对这个子树进行调整
  3. 调整子树,有LL、LR、RL、RR四种情况,如果调整后子树高度下降(即父结点BF改变),那么跳到步骤2

1)定位结点

template<class T, class N>
bool BALTree<T, N>::Delete(T data)
{
if (this->root == NULL)
{
return false;
} stack<N*> path; // 存储删除前的查找路径(便于回溯) //////////////////////////////////////////////////////////////////////////
// 查找操作
N *p = this->root;
while (true)
{
path.push(p);
if (data < p->data) // 插入值小于根结点,入左子树
{
if (p->lchild != NULL)
{
p = p->lchild; // 值小于LL,则递归入L
}
else
{
return false; // 没找到
}
}
else if (data > p->data) // 插入值大于根结点,入右子树
{
if (p->rchild != NULL)
{
p = p->rchild; // 值大于RR,则递归入R
}
else
{
return false; // 没找到
}
}
else // 找到
{
break;
}
} // 定位成功 ////////////////////////////////////////////////////////////////////////// // 调整删除路径上的结点
BalanceAfterDelete(path);
return true;
}

2)删除结点

template<class T, class N>
void BALTree<T, N>::BalanceAfterDelete(stack<N*>& path)
{
// 替代结点
Replace(path); if (path.empty())
return; // 删除结点并平衡 N *p = NULL;
N *child = NULL; // *child作为*p的孩子结点
N *parent = path.top(); // *parent是*p的父结点
path.pop();
N *ancestor;
TStatus status = T_OK;
while (true)
{
ancestor = path.empty() ? NULL : path.top();
if (status == T_CONTINUE_STATUS)
status = ancestor ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
status = CheckPathAfterDelete(child, p, parent, ancestor, status);
switch (status)
{
case T_OK:
return;
case T_BREAK:
status = BalanceInternalAfterDelete(child, p, parent, ancestor);
if (status == T_OK)
return;
break;
case T_CONTINUE:
status = T_OK;
break;
case T_CONTINUE_STATUS:
break;
} if (path.empty())
return; child = p;
p = parent;
parent = path.top(); // 由path向上回溯
path.pop();
}
}

3)替换结点

删除结点前,先将结点与其他结点进行替换,使得对于结点的删除转变为对于叶子结点的删除。

template<class T, class N>
void AVLTree<T, N>::Replace(stack<N*>& path)
{
N *p = path.top();
path.pop();
N *parent = path.empty() ? NULL : path.top();
if (!p->lchild && !p->rchild) // p为叶子结点,直接删除
{
if (!parent) // 根结点
{
this->root = NULL;
}
else if (parent->lchild == p)
{
parent->lchild = NULL;
parent->BF--;
}
else
{
parent->rchild = NULL;
parent->BF++;
}
}
else if (p->lchild && p->rchild) // 双子树,转化为单子树
{
// 注意:替换时,BF也要替换
if (p->lchild->rchild) // 替换左子树最右结点
{
vector<N*> new_path;
new_path.push_back(p->lchild);
N *lr = p->lchild->rchild;
while (lr)
{
new_path.push_back(lr);
lr = lr->rchild;
}
path.push(new_path.back());
lr = path.top();
new_path.pop_back();
for (vector<N*>::iterator it = new_path.begin(); it != new_path.end(); it++)
{
path.push(*it);
}
lr->BF = p->BF;
if (!parent) // 根结点
this->root = lr;
else if (parent->lchild == p)
parent->lchild = lr;
else
parent->rchild = lr;
path.top()->rchild = lr->lchild;
path.top()->BF++;
lr->lchild = p->lchild;
lr->rchild = p->rchild;
}
else if (p->rchild->lchild) // 替换右子树最左结点
{
vector<N*> new_path;
new_path.push_back(p->rchild);
N *rl = p->rchild->lchild;
while (rl)
{
new_path.push_back(rl);
rl = rl->lchild;
}
path.push(new_path.back());
rl = path.top();
new_path.pop_back();
for (vector<N*>::iterator it = new_path.begin(); it != new_path.end(); it++)
{
path.push(*it);
}
rl->BF = p->BF;
if (!parent) // 根结点
this->root = rl;
else if (parent->lchild == p)
parent->lchild = rl;
else
parent->rchild = rl;
path.top()->lchild = rl->rchild;
path.top()->BF--;
rl->lchild = p->lchild;
rl->rchild = p->rchild;
}
else // 替代孩子
{
if (!parent) // 根结点
{
if (this->root->BF != -1)
{
this->root = p->lchild;
this->root->rchild = p->rchild;
this->root->BF = p->BF - 1;
}
else
{
this->root = p->rchild;
this->root->lchild = p->lchild;
this->root->BF = p->BF + 1;
}
}
else if (parent->lchild == p)
{
if (parent->BF != -1)
{
parent->lchild = p->lchild;
parent->lchild->rchild = p->rchild;
parent->lchild->BF = p->BF - 1;
}
else
{
parent->lchild = p->rchild;
parent->lchild->lchild = p->lchild;
parent->lchild->BF = p->BF + 1;
}
path.push(parent->lchild);
}
else
{
if (parent->BF != -1)
{
parent->rchild = p->lchild;
parent->rchild->rchild = p->rchild;
parent->rchild->BF = p->BF - 1;
}
else
{
parent->rchild = p->rchild;
parent->rchild->lchild = p->lchild;
parent->rchild->BF = p->BF + 1;
}
path.push(parent->rchild);
}
}
}
else if (p->lchild) // 左单子树
{
if (!parent) // 根结点
{
this->root = p->lchild;
}
else
{
if (parent->lchild == p)
{
parent->lchild = p->lchild;
parent->BF--;
}
else
{
parent->rchild = p->lchild;
parent->BF++;
}
}
}
else // 右单子树
{
if (!parent) // 根结点
{
this->root = p->rchild;
}
else
{
if (parent->lchild == p)
{
parent->lchild = p->rchild;
parent->BF--;
}
else
{
parent->rchild = p->rchild;
parent->BF++;
}
}
}
delete p;
}

4)检查平衡

template<class T, class N>
TStatus AVLTree<T, N>::CheckPathAfterDelete(N *child, N *p, N *parent, N *ancestor, TStatus status)
{
if (status == T_OK)
{
if (p && p->BF == 0)
{
if (parent->lchild == p) // p是parent的左孩子,p的BF为0,说明p层数减少一,故parent的BF--
parent->BF--;
else // p是parent的右孩子,p的BF为0,说明p层数减少一,故parent的BF++
parent->BF++;
}
}
else
{
if (status == T_DECL) //平衡后,子树高度减少,那么要向上检查平衡性
{
parent->BF--;
return parent->BF == 0 ? T_CONTINUE_STATUS : T_OK;
}
else if (status == T_DECR) //平衡后,子树高度减少,那么要向上检查平衡性
{
parent->BF++;
return parent->BF == 0 ? T_CONTINUE_STATUS : T_OK;
}
} if (parent->BF == 2 || parent->BF == -2) // *parent是失衡结点(第一个|BF|>=2)
return T_BREAK; // 找到最小不平衡子树的根结点 if (parent->BF != 0) // 在删除结点后,*parent的左右子树高度差绝对值为1(|BF|为1),说明以*parent为根的子树高度未改变
return T_OK; // 所以路径中的其余祖先结点无需调整BF return T_CONTINUE;
}

5)调整平衡

template<class T, class N>
TStatus AVLTree<T, N>::BalanceInternalAfterDelete(N *child, N *p, N *parent, N *ancestor)
{
////////////////////////////////////////////////////////////////////////// if (parent->BF == 2 && (!p || parent->rchild == p)) // L
{
p = parent->lchild; if (p->BF == -1) // LR
{
return RotateLR(p, parent, ancestor);
}
else // LL
{
return RotateLL(p, parent, ancestor);
}
} ////////////////////////////////////////////////////////////////////////// else if (parent->BF == -2 && (!p || parent->lchild == p)) // R
{
p = parent->rchild; if (p->BF == 1) // RL
{
return RotateRL(p, parent, ancestor);
}
else // RR
{
return RotateRR(p, parent, ancestor);
}
} //////////////////////////////////////////////////////////////////////////
return T_OK;
}

总结

虽然算法的实现思路很简单,但实际要考虑很多因素,稍微一个小错误都可能导致结果不一致的现象(BF未调整)。然而,在理解AVL上的基础上,理解红黑树就不那么困难了。

相比较AVL的递归算法而言,可以看出,递归算法是具有简洁美的,这是由二叉树的递归性质决定的。

由于AVL操作的复杂性,因而不常用AVL,现在主要使用红黑树(RBT),它们的区别是:

  • AVL讲究整体平衡,因而要求严格、操作复杂 ,子树高度差不大于1

  • RBT追求局部平衡,因而实现较AVL简单,最长路径比上最短路径小于2

树(三)——自平衡二叉树(AVL)的更多相关文章

  1. 二叉查找树(BST)、平衡二叉树(AVL树)(只有插入说明)

    二叉查找树(BST).平衡二叉树(AVL树)(只有插入说明) 二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点, ...

  2. AVL树(三)之 Java的实现

    概要 前面分别介绍了AVL树"C语言版本"和"C++版本",本章介绍AVL树的Java实现版本,它的算法与C语言和C++版本一样.内容包括:1. AVL树的介绍 ...

  3. 二叉查找树、平衡二叉树(AVL)、B+树、联合索引

    1. [定义] 二叉排序树(二拆查找树)中,左子树都比节点小,右子树都比节点大,递归定义. [性能] 二叉排序树的性能取决于二叉树的层数 最好的情况是 O(logn),存在于完全二叉排序树情况下,其访 ...

  4. Java 树结构实际应用 四(平衡二叉树/AVL树)

    平衡二叉树(AVL 树) 1 看一个案例(说明二叉排序树可能的问题) 给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.  左边 BST 存在的问题分析: ...

  5. 数据结构与算法--从平衡二叉树(AVL)到红黑树

    数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树.算法的性能取决于树的形状,而树的形状取决于插入键的顺序.在最好的情况下,n个结点的树是完全平衡的,如下图"最好情况&q ...

  6. 平衡二叉树AVL - 插入节点后旋转方法分析

    平衡二叉树 AVL( 发明者为Adel'son-Vel'skii 和 Landis)是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1. 首先我们知道,当插入一个节点,从此插入点到树根 ...

  7. 伸展树(三)之 Java的实现

    概要 前面分别通过C和C++实现了伸展树,本章给出伸展树的Java版本.基本算法和原理都与前两章一样.1. 伸展树的介绍2. 伸展树的Java实现(完整源码)3. 伸展树的Java测试程序 转载请注明 ...

  8. 树:BST、AVL、红黑树、B树、B+树

    我们这个专题介绍的动态查找树主要有: 二叉查找树(BST),平衡二叉查找树(AVL),红黑树(RBT),B~/B+树(B-tree).这四种树都具备下面几个优势: (1) 都是动态结构.在删除,插入操 ...

  9. 树-二叉搜索树-AVL树

    树-二叉搜索树-AVL树 树 树的基本概念 节点的度:节点的儿子数 树的度:Max{节点的度} 节点的高度:节点到各叶节点的最大路径长度 树的高度:根节点的高度 节点的深度(层数):根节点到该节点的路 ...

  10. 树-二叉平衡树AVL

    基本概念 AVL树:树中任何节点的两个子树的高度最大差别为1. AVL树的查找.插入和删除在平均和最坏情况下都是O(logn). AVL实现 AVL树的节点包括的几个组成对象: (01) key -- ...

随机推荐

  1. DataTable插件指定某列不可排序

    datatable是一个jQuery扩展的表格插件.其提供了强大的表格功能. 官方地址:http://www.datatables.NET/ DataTable提供的表格样式里面,第一行都是会有排序功 ...

  2. javascript的sort()方法

    定义和用法: sort() 方法用于对数组的元素进行排序. 语法: 1 arrayObject.sort(sortby) 描述: sortby    可选.必须是函数.规定排序顺序  . 返回值: 对 ...

  3. 位图切割器&位图裁剪器

    位图切割器: 虽然网上有类似的工具,PhotoShop 也有类似功能,但前者似乎不支持超大位图切割(以 G 计大小),而后者的切割块数量好像有比较小的限定范围,于是自己动手写了这个工具. 至于为什么是 ...

  4. Android 通过JNI实现守护进程,使得Service服务不被杀死

    来自: http://finalshares.com/read-7306 转载请注明出处: http://blog.csdn.net/yyh352091626/article/details/5054 ...

  5. Java 动态生成复杂 Word

    Java 动态生成复杂 Word 阅读目录 1. 制作 Word 模版,将你需要动态生成的字段用${}替换.2. 将 Word文档保存为 xml .3. 引入项目. 项目中需要用 java 程序生成d ...

  6. oracle 开窗分析函数和树形结构

    1.row_number() over(partition by ... order by ...)2.rank() over(partition by ... order by ...)3.dens ...

  7. mysql 数据库 表字段添加表情兼容

    项目中的几个需要支持Emoji表情符号,手机自带的表情,其实添加也很简单: 1 修改数据库 配置my.cnf  init-connect='SET NAMES utf8mb4'             ...

  8. 【网摘】CURL常用命令

    原文地址: http://www.thegeekstuff.com/2012/04/curl-examples/ 下载单个文件,默认将输出打印到标准输出中(STDOUT)中 curl http://w ...

  9. C#引用C++开发的DLL

    C#语言使用方便,入门门槛较代,上手容易,并且语法与C,java有很类似的地方,IDE做的也好,通用性好,是MS下一代开发的主要力量.但是其开源代码较少,类库不是十分完美,在架构方面还有一些需要做的工 ...

  10. yii2 renderPartial

    在 views/news/_copyright.php 中插入以下代码: <div> This is text about copyright data for news items &l ...