目录
对于大量的输入数据,链表的线性访问时间太长,不宜使用。
树的实现
由于树的每个结点的儿子数可能变化很大并且事先不知道,因此在数据结构中建立到各儿子结点的直接链接是不可行的,因为这会产生太多浪费的空间。最好的办法是使用:第一儿子/下一兄弟表示法。
struct TreeNode
{
Object element;
TreeNode *firstChild;
TreeNode *nextSibling;
}
结点的所有儿子结点都放在 firstChild 链表中,兄弟结点在 nextSibling 链表中。
二叉树
因为一个二叉树结点最多有两个儿子,所以可以直接链接到它们。实现如下
struct BinaryNode
{
Object element; // The data in the node
BinaryNode *left; // Left child
BinaryNode *right; // Right child
};
具有 N 个结点的二叉树,都将需要 N + 1 个 NULL 链。
二叉树分三种遍历:前根、中根、后根遍历。
查找树 ADT -- 二叉查找树
二叉树的一个重要应用是它们在查找中的应用。
二叉查找树的性质是:对于树中的每个结点 X,它的左子树中所有项的值小于 X 中的项,而它的右子树中所有项的值都大于 X 中的项。
下面的代码中 Comparable 指的是具有比较功能的,查找是基于 “<” 操作符的,所以必须在 Comparable 中进行定义该操作符。
#ifndef BINARYSEARCHTREE_H
#define BINARYSEARCHTREE_H
template <typename Comparable>
class BinarySearchTree
{
public:
BinarySearchTree( ):root(nullptr){}
BinarySearchTree( const BinarySearchTree & rhs );
~BinarySearchTree( )
{ makeEmpty(); }
const Comparable & findMin( ) const
{ return findMin(root)->element; }
const Comparable & findMax( ) const
{ return findMax(root)->element; }
bool contains( const Comparable & x ) const
{ return contains(x, root); }
bool isEmpty( ) const
{
if( root == nullptr )
return true;
else
return false;
}
void printTree( ) const
{ printTree(root); }
void makeEmpty( )
{ makeEmpty(root); }
void insert( const Comparable & x )
{ insert(x, root); }
void remove( const Comparable & x )
{ remove(x, root); }
const BinarySearchTree & operator=( const BinarySearchTree & rhs )
{
if( this != &rhs )
{
makeEmpty();
root = clone(rhs.root);
}
return *this;
}
private:
struct BinaryNode
{
Comparable element;
BinaryNode *left;
BinaryNode *right;
BinaryNode( const Comparable & theElement, BinaryNode *lt, BinaryNode *rt )
: element( theElement ), left( lt ), right( rt ) { }
};
BinaryNode *root; // 根节点
void insert( const Comparable & x, BinaryNode * & t )
{
if( t == nullptr ){
// 如果树中不存在该结点,最终会将树的一个空节点指向这个新节点
t = new BinaryNode(x, nullptr, nullptr);
}else if ( x < t->element ) {
insert( x, t->left );
}else if ( t->element < x) {
insert( x, t->right );
}else {
; // 树中已存在这样的结点,do-nothing
}
}
void remove( const Comparable & x, BinaryNode * & t )
{
if( t == nullptr )
return;
if( x < t->element )
remove(x, t->left);
else if ( t->element < x ) {
remove(x, t->right);
}else if ( t->left != nullptr && t->right != nullptr) {
t->element = findMin(t->right)->element;
remove(t->element, t->right);
}else {
BinaryNode *oldNode = t;
t = (t->left != nullptr) ? t->left : t->right;
delete oldNode;
}
}
BinaryNode * findMin( BinaryNode *t ) const
{
if( t == nullptr )
return nullptr;
if( t->left == nullptr )
return t;
return findMin(t->left);
}
BinaryNode * findMax( BinaryNode *t ) const
{
if( t == nullptr)
return nullptr;
if( t->right == nullptr )
return t;
return findMax(t->right);
}
bool contains( const Comparable & x, BinaryNode *t ) const
{
if( t == nullptr )
return false;
else if (x < t->element) {
return contains(x, t->left);
}else if (t->element < x) {
return contains(x, t->right);
}else {
return true; // 匹配
}
}
void makeEmpty( BinaryNode * & t )
{
if( t != nullptr)
{
makeEmpty(t->left);
makeEmpty(t->right);
delete t;
}
t = nullptr;
}
void printTree( AvlNode *t, void *arg ) const // 后根遍历打印
{
if( t == nullptr )
return;
if( t->left )
printTree(t->left, arg);
if( t->right )
printTree(t->right, arg);
printf((char *)arg, t->element);
}
BinaryNode * clone( BinaryNode *t ) const
{
if( t == nullptr )
return nullptr;
return new BinaryNode(t->element, clone(t->left), clone(t->right));
}
};
#endif // BINARYSEARCHTREE_H
上面是 BinarySearchTree 的实现代码。
一棵树的所有结点的深度和称为 内部路径长(internal path length),记为 D(N)。根结点的深度为 0。
可以推得:D(N) = O(NlogN)。所以任意结点预期的深度为 O(logN)。通常是以 2 为底的 log(N) 稍多一些。
上面的代码中,remove 操作始终使用 右侧子树 中的最小点代替被删除的操作,多次的 insert / remove 操作后二叉树明显的不平衡状态。
AVL 树
AVL 树是带有平衡条件的二叉查找树。一颗 AVL 树是每个结点的左子树和右子树的高度最多差 1 的二叉查找树(空树的高度定义为 -1)。
对 AVL 树,插入和删除操作会引起它的不平衡。
引起不平衡的插入操作有以下四种情景:
- (1)对 A 结点的左儿子的左子树进行一次插入
- (2)对 A 结点的左儿子的右子树进行一次插入
- (3)对 A 结点的右儿子的左子树进行一次插入
- (4)对 A 结点的左儿子的右子树进行一次插入
其中(1)和(4)成镜像对称,调整平衡需要 “单旋转” 操作,(2)和(3)成镜像对称,调整平衡需要 “双旋转” 操作。
单旋转:
对应情景(1):对 k2 结点的左儿子的左子树进行一次插入
对应情景(4):对 k1 结点的左儿子的右子树进行一次插入
双旋转:
对于如下的情形,单旋转并不能解决:
因为 Y 过深,所以不管怎么单旋转都无法达到平衡。
对应情景(2):对 k3 结点的左儿子的右子树进行一次插入
对应情景(3):对 k1 结点的右儿子的左子树进行一次插入
下面的代码中 Comparable 指的是具有比较功能的,查找是基于 “<” 操作符的,所以必须在 Comparable 中进行定义该操作符。
#ifndef AVLTREE_H
#define AVLTREE_H
#define max(a, b) ((a)>(b)?(a):(b))
template <typename Comparable>
class AvlTree
{
public:
AvlTree( ):root(nullptr){}
AvlTree( const AvlTree & rhs );
~AvlTree( )
{ makeEmpty(); }
const Comparable & findMin( ) const
{ return findMin(root)->element; }
const Comparable & findMax( ) const
{ return findMax(root)->element; }
bool contains( const Comparable & x ) const
{ return contains(x, root); }
bool isEmpty( ) const
{
if( root == nullptr )
return true;
else
return false;
}
void printTree( void *arg ) const
{ printTree(root, arg); }
void makeEmpty( )
{ makeEmpty(root); }
void insert( const Comparable & x )
{ insert(x, root); }
void remove( const Comparable & x )
{ remove(x, root); }
const AvlTree & operator=( const AvlTree & rhs )
{
if( this != &rhs )
{
makeEmpty();
root = clone(rhs.root);
}
return *this;
}
private:
struct AvlNode
{
Comparable element;
AvlNode *left;
AvlNode *right;
int height;
AvlNode( const Comparable & theElement, AvlNode *lt, AvlNode *rt, int h = 0 )
: element( theElement ), left( lt ), right( rt ), height( h ) { }
};
AvlNode *root; // 根节点
// 获得结点的高度
int height( AvlNode *t ) const
{
return t == nullptr ? -1 : t->height;
}
/*
* k2 k1
* / \ / \
* k1 A --> k3 k2
* / \ | / \
* k3 B O B A
* |
* O
*/
void rotateWithLeftChild(AvlNode * & k2)
{
AvlNode *k1 = k2->left;
k2->left = k1->right;
k1->right = k2;
k2->height = max(height(k2->left), height(k2->right)) + 1;
k1->height = max(height(k1->left), k2->height) + 1;
//此时该部分的根节点变成 k1,由于是引用,直接赋值就可改变
//原来 k2 的父节点指向孩子的指针为 k1
k2 = k1;
}
/*
* k1 k2
* / \ / \
* A k2 --> k1 K3
* / \ / \ |
* B K3 A B O
* |
* O
*/
void rotateWithRightChild(AvlNode * & k1)
{
AvlNode *k2 = k1->right;
k1->right = k2->left;
k2->left = k1;
k1->height = max(height(k1->left), height(k1->right)) + 1;
k2->height = max(height(k2->right), k1->height) + 1;
k1 = k2;
}
/*
* k3 k3 k3 k2 k2
* / \ / \ / \ / \ / \
* k1 B --> k2 B 或 k2 B --> k1 k3 或 k1 k3
* / \ / \ / / / \ / \ \
* A k2 k1 O k1 A O B A O B
* | / / \
* O A A O
*/
void doubleWithLeftChild(AvlNode * & k3)
{
rotateWithRightChild(k3->left);
rotateWithLeftChild(k3);
}
/*
* k3 k3 k3 k2 k2
* / \ / \ / \ / \ / \
* A k1 --> A k2 或 A k2 --> k3 k1 或 k3 k1
* / \ / \ \ / \ \ / / \
* k2 B O k1 k1 A O B A O B
* | \ / \
* O B O B
*/
void doubleWithRightChild(AvlNode * & k3)
{
rotateWithLeftChild(k3->right);
rotateWithRightChild(k3);
}
void rebalance(AvlNode * & t)
{
if(t){
int diff = height(t->left) - height(t->right);
if(diff == 2)
{
if(height(t->left->left) - height(t->right) == 1)
rotateWithLeftChild(t);
else
doubleWithLeftChild(t);
}
if(diff == -2)
{
if(height(t->right->right) - height(t->left) == 1)
rotateWithRightChild(t);
else
doubleWithRightChild(t);
}
}
}
void insert( const Comparable & x, AvlNode * & t )
{
if( t == nullptr ){
// 如果树中不存在该结点,最终会将树的一个空节点指向这个新节点
t = new AvlNode(x, nullptr, nullptr);
}else if ( x < t->element ) {
insert( x, t->left );
rebalance(t);
}else if ( t->element < x) {
insert( x, t->right );
rebalance(t);
}else {
; // 树中已存在这样的结点,do-nothing
}
t->height = max(height(t->left), height(t->right)) + 1;
}
void remove( const Comparable & x, AvlNode * & t )
{
if( t == nullptr )
return;
if( x < t->element ){
remove(x, t->left);
rebalance(t);
}else if ( t->element < x ) {
remove(x, t->right);
rebalance(t);
}else if ( t->left != nullptr && t->right != nullptr) {
t->element = findMin(t->right)->element;
remove(t->element, t->right);
rebalance(t);
}else {
AvlNode *oldNode = t;
t = (t->left != nullptr) ? t->left : t->right;
delete oldNode;
}
if(t != nullptr)
t->height = max(height(t->left), height(t->right)) + 1;
}
AvlNode * findMin( AvlNode *t ) const
{
if( t == nullptr )
return nullptr;
if( t->left == nullptr )
return t;
return findMin(t->left);
}
AvlNode * findMax( AvlNode *t ) const
{
if( t == nullptr)
return nullptr;
if( t->right == nullptr )
return t;
return findMax(t->right);
}
bool contains( const Comparable & x, AvlNode *t ) const
{
if( t == nullptr )
return false;
else if (x < t->element) {
return contains(x, t->left);
}else if (t->element < x) {
return contains(x, t->right);
}else {
return true; // 匹配
}
}
void makeEmpty( AvlNode * & t )
{
if( t != nullptr)
{
makeEmpty(t->left);
makeEmpty(t->right);
delete t;
}
t = nullptr;
}
void printTree( AvlNode *t, void *arg ) const // 后根遍历打印
{
if( t == nullptr )
return;
if( t->left )
printTree(t->left, arg);
if( t->right )
printTree(t->right, arg);
printf((char *)arg, t->element);
}
AvlNode * clone( AvlNode *t ) const
{
if( t == nullptr )
return nullptr;
return new AvlNode(t->element, clone(t->left), clone(t->right), t->height);
}
};
/*
* 只要你碰到*&,就应该想到**。也就是说这个函数修改或可能修改调用者的指针,
* 而调用者像普通变量一样传递这个指针,不使用地址操作符&。
*/
#endif // AVLTREE_H
测试程序:
int main(int argc, char *argv[])
{
AvlTree<int> avlTree;
avlTree.insert(20);
avlTree.insert(30);
avlTree.insert(10);
avlTree.insert(22);
avlTree.insert(40);
avlTree.insert(50);
avlTree.insert(44);
avlTree.insert(55);
avlTree.insert(12);
avlTree.insert(3);
char buf[5] = " %d ";
avlTree.printTree((void*)buf);
avlTree.remove(10);
avlTree.remove(30);
avlTree.printTree((void*)buf);
return 0;
}
输出:3 12 10 22 20 40 55 50 44 30
3 12 22 20 44 55 50 40