C语言数据结构之平衡二叉树(AVL树)实现方法示例

本文实例讲述了C语言数据结构之平衡二叉树(AVL树)实现方法。分享给大家供大家参考,具体如下:

AVL树是每个结点的左子树和右子树的高度最多差1的二叉查找树。

要维持这个树,必须在插入和删除的时候都检测是否出现破坏树结构的情况。然后立刻进行调整。

看了好久,网上各种各种的AVL树,千奇百怪。

关键是要理解插入的时候旋转的概念。

//
// AvlTree.h
// HelloWorld
// Created by feiyin001 on 17/1/9.
// Copyright (c) 2017年 FableGame. All rights reserved.
//
#ifndef __HelloWorld__AvlTree__
#define __HelloWorld__AvlTree__
#include <iostream>
namespace Fable
{
  int max(int a, int b)
  {
    return a > b? a:b;
  }
  //二叉查找树,对于Comparable,必须实现了><=的比较
  template<typename Comparable>
  class AvlTree
  {
  public:
    //构造函数
    AvlTree(){}
    //复制构造函数
    AvlTree(const AvlTree& rhs)
    {
      root = clone(rhs.root);
    }
    //析构函数
    ~AvlTree()
    {
      makeEmpty(root);
    }
    //复制赋值运算符
    const AvlTree& operator=(const AvlTree& rhs)
    {
      if (this != &rhs)
      {
        makeEmpty(root);//先清除
        root = clone(rhs.root);//再复制
      }
      return *this;
    }
    //查找最小的对象
    const Comparable& findMin()const
    {
      findMin(root);
    }
    //查找最大的对象
    const Comparable& findMax()const
    {
      findMax(root);
    }
    //是否包含了某个对象
    bool contains(const Comparable& x)const
    {
      return contains(x, root);
    }
    //树为空
    bool isEmpty()const
    {
      return root == nullptr;
    }
    //打印整棵树
    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);
    }
  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){}
    };
    typedef AvlNode* AvlNodePtr;
    AvlNodePtr root;//根结点
    //顺时针旋转
    void clockwiseRotate(AvlNodePtr& a)
    {
      AvlNodePtr b = a->left;//左叶子
      a->left = b->right;//a的左叶子变为b的右叶子,b本来的子结点都比a小的。
      b->right = a;//b的右结点指向a,b的高度上升了。
      a->height = max(height(a->left), height(a->right)) + 1;//重新计算a的高度
      b->height = max(height(b->left), a->height) + 1;//重新计算b的高度
      a = b;//a的位置现在是b,当前的根结点
    }
    //逆时针旋转
    void antiClockWiseRotate(AvlNodePtr& a)
    {
      AvlNodePtr b = a->right;//右结点
      a->right = b->left;//a接收b的左结点
      b->left = a;//自己成为b的左结点
      a->height = max(height(a->left), height(a->right)) + 1;//计算高度
      b->height = max(b->height, height(a->right)) + 1;//计算高度
      a = b;//新的根结点
    }
    //对左边结点的双旋转
    void doubleWithLeftChild(AvlNodePtr& k3)
    {
      antiClockWiseRotate(k3->left);//逆时针旋转左结点
      clockwiseRotate(k3);//顺时针旋转自身
    }
    //对右边结点的双旋转
    void doubleWithRightChild(AvlNodePtr& k3)
    {
      clockwiseRotate(k3->right);//顺时针旋转有节点
      antiClockWiseRotate(k3);//逆时针旋转自身
    }
    //插入对象,这里使用了引用
    void insert(const Comparable& x, AvlNodePtr& t)
    {
      if (!t)
      {
        t = new AvlNode(x, nullptr, nullptr);
      }
      else if (x < t->element)
      {
        insert(x, t->left);//比根结点小,插入左边
        if (height(t->left) - height(t->right) == 2)//高度差达到2了
        {
          if (x < t->left->element)//插入左边
          {
            clockwiseRotate(t);//顺时针旋转
          }
          else
          {
            doubleWithLeftChild(t);//双旋转
          }
        }
      }
      else if (x > t->element)
      {
        insert(x, t->right);//比根结点大,插入右边
        if (height(t->right) - height(t->left) == 2)//高度差达到2
        {
          if (t->right->element < x)//插入右边
          {
            antiClockWiseRotate(t);//旋转
          }
          else
          {
            doubleWithRightChild(t);//双旋转
          }
        }
      }
      else
      {
        //相同的
      }
      t->height = max(height(t->left), height(t->right)) + 1;//计算结点的高度
    }
    void removeMin(AvlNodePtr& x, AvlNodePtr& t)const
    {
      if (!t)
      {
        return;//找不到
      }
      if (t->left)
      {
        removeMin(t->left);//使用了递归的方式
      }
      else
      {
        //找到最小的结点了
        x->element = t->element;
        AvlNodePtr oldNode = t;
        t = t->right;
        delete oldNode;//删除原来要删除的结点
      }
      if (t)
      {
        t->height = max(height(t->left), height(t->right)) + 1;//计算结点的高度
        if(height(t->left) - height(t->right) == 2)
        { //如果左儿子高度大于右儿子高度
          if(height(t->left->left) >= height(t->left->right))//并且左儿子的左子树高度大于左儿子的右子树高度
          {
            clockwiseRotate(t); //顺时针旋转
          }
          else
          {
            doubleWithLeftChild(t);//双旋转左子树
          }
        }
        else
        {
          if(height(t->right->right) - height(t->right->left) == 2) //如果右子树大于左子树
          {
            antiClockWiseRotate(t);//逆时针旋转
          }
          else
          {
            doubleWithRright(t);//双旋转右子树
          }
        }
      }
    }
    //删除某个对象,这里必须要引用
    void remove(const Comparable& x, AvlNodePtr& t)const
    {
      if (!t)
      {
        return;//树为空
      }
      else if (x < t->element)
      {
        remove(x, t->left);//比根结点小,去左边查找
      }
      else if (x > t->element)
      {
        remove(x, t->right);//比根结点大,去右边查找
      }
      else if (!t->left && !t->right)//找到结点了,有两个叶子
      {
        removeMin(t, t->right);//这里选择的方法是删除右子树的最小的结点
      }
      else
      {
        AvlNodePtr oldNode = t;
        t = (t->left) ? t->left : t->right;//走到这里,t最多只有一个叶子,将t指向这个叶子
        delete oldNode;//删除原来要删除的结点
      }
      if (t)
      {
        t->height = max(height(t->left), height(t->right)) + 1;//计算结点的高度
        if(height(t->left) - height(t->right) == 2)
        { //如果左儿子高度大于右儿子高度
          if(height(t->left->left) >= height(t->left->right))//并且左儿子的左子树高度大于左儿子的右子树高度
          {
            clockwiseRotate(t); //顺时针旋转
          }
          else
          {
            doubleWithLeftChild(t);//双旋转左子树
          }
        }
        else
        {
          if(height(t->right->right) - height(t->right->left) == 2) //如果右子树大于左子树
          {
            antiClockWiseRotate(t);//逆时针旋转
          }
          else
          {
            doubleWithRright(t);//双旋转右子树
          }
        }
      }
    }
    //左边子树的结点肯定比当前根小的,所以一直往左边寻找
    AvlNodePtr findMin(AvlNodePtr t)const
    {
      if (!t)
      {
        return nullptr;//找不到
      }
      if (!t->left)
      {
        return t;
      }
      return findMin(t->left);//使用了递归的方式
    }
    //右边子树的结点肯定比当前根大,所以一直往右边找
    AvlNodePtr findMax(AvlNodePtr t)const
    {
      if (t)
      {
        while (t->right)//使用了循环的方式
        {
          t = t->right;
        }
      }
      return t;
    }
    //判断是否包含某个对象,因为要使用递归,所以还有一个public版本的
    bool contains(const Comparable& x, AvlNodePtr t)const
    {
      if (!t)
      {
        return false;//空结点了
      }
      else if (x < t->element)
      {
        //根据二叉树的定义,比某个结点小的对象,肯定只能存在与这个结点的左边的子树
        return contains(x, t->left);
      }
      else if (x > t->element)
      {
        //根据二叉树的定义,比某个结点大的对象,肯定只能存在与这个结点的右边的子树
        return contains(x, t->right);
      }
      else
      {
        //相等,就是找到啦。
        return true;
      }
    }
    //清空子树
    void makeEmpty(AvlNodePtr& t)
    {
      if (t)
      {
        makeEmpty(t->left);//清空左边
        makeEmpty(t->right);//清空右边
        delete t;//释放自身
      }
      t = nullptr;//置为空
    }
    //打印子树,这里没有使用复杂的排位,纯属打印
    void printTree(AvlNodePtr t)const
    {
      if (!t)
      {
        return;
      }
      std::cout << t->element << std::endl;//输出自身的对象
      printTree(t->left);//打印左子树
      printTree(t->right);//打印右子树
    }
    AvlNodePtr clone(AvlNodePtr t)const
    {
      if (!t)
      {
        return nullptr;
      }
      return new AvlNode(t->element, clone(t->left), clone(t->right));
    }
    int height(AvlNodePtr t)const
    {
      return t == nullptr ? -1 : t->height;
    }
  };
}
#endif

简单测试一下。

//
// AvlTree.cpp
// HelloWorld
// Created by feiyin001 on 17/1/9.
// Copyright (c) 2017年 FableGame. All rights reserved.
//
#include "AvlTree.h"
using namespace Fable;
int main(int argc, char* argv[])
{
  AvlTree<int> a;
  for(int i = 0; i < 100; ++i)
  {
    a.insert(i);
  }
  return 0;
}

这个删除的方法完全是自己写的,可能不是很高效。

希望本文所述对大家C语言程序设计有所帮助。

您可能感兴趣的文章:

  • 使用C语言构建基本的二叉树数据结构
  • 举例讲解C语言程序中对二叉树数据结构的各种遍历方式
  • C语言 数据结构平衡二叉树实例详解
  • C语言 数据结构之中序二叉树实例详解
  • C语言数据结构之线索二叉树及其遍历
  • C语言数据结构二叉树简单应用
  • C语言数据结构之二叉树的非递归后序遍历算法
  • C语言实现二叉树遍历的迭代算法
  • C语言 二叉树的链式存储实例
  • C语言二叉树的非递归遍历实例分析
  • C语言实现找出二叉树中某个值的所有路径的方法
(0)

相关推荐

  • C语言 二叉树的链式存储实例

    二叉树的链式存储 实现二叉树的基本操作:建立.遍历.计算深度.结点数.叶子数等. 输入C,先序创建二叉树,#表示空节点: 输入H:计算二叉树的高度: 输入L:计算二叉树的叶子个数: 输入N:计算二叉树节点总个数: 输入1:先序遍历二叉树: 输入2:中序遍历二叉树: 输入3:后续遍历二叉树: 输入F:查找值=x的节点的个数: 输入P:以缩格文本形式输出所有节点. 很简单就不需要多解释了,代码贴上 #include <stdio.h> #include <stdlib.h> #incl

  • C语言数据结构二叉树简单应用

     C语言数据结构二叉树简单应用 在计算机科学中,二叉树是每个节点最多有两个子树的树结构.通常子树被称作"左子树"(left subtree)和"右子树"(right subtree),接下来我就在这里给大家介绍一下二叉树在算法中的简单使用: 我们要完成总共有 (1)二叉树的创建 (2)二叉树的先中后序递归遍历 (3)统计叶子结点的总数 (4)求树的高度 (5)反转二叉树 (6)输出每个叶子结点到根节点的路径 (7)输出根结点到每个叶子结点的路径. 定义二叉树结点类型

  • C语言 数据结构之中序二叉树实例详解

    C语言数据结构 中序二叉树 前言: 线索二叉树主要是为了解决查找结点的线性前驱与后继不方便的难题.它只增加了两个标志性域,就可以充分利用没有左或右孩子的结点的左右孩子的存储空间来存放该结点的线性前驱结点与线性后继结点.两个标志性域所占用的空间是极少的,所有充分利用了二叉链表中空闲存的储空间. 要实现线索二叉树,就必须定义二叉链表结点数据结构如下(定义请看代码): left leftTag data rightTag right 说明: 1.       leftTag=false时,表示left

  • C语言 数据结构平衡二叉树实例详解

    数据结构平衡二叉树 参考代码如下: /* 名称:平衡二叉树 语言:数据结构C语言版 编译环境:VC++ 6.0 日期: 2014-3-26 */ #include <stdio.h> #include <malloc.h> #include <windows.h> #define LH +1 // 左高 #define EH 0 // 等高 #define RH -1 // 右高 #define N 5 // 数据元素个数 typedef char KeyType; /

  • 举例讲解C语言程序中对二叉树数据结构的各种遍历方式

    二叉树遍历的基本思想 二叉树的遍历本质上其实就是入栈出栈的问题,递归算法简单且容易理解,但是效率始终是个问题.非递归算法可以清楚的知道每步实现的细节,但是乍一看不想递归算法那么好理解,各有各的好处吧.接下来根据下图讲讲树的遍历. 1.先序遍历:先序遍历是先输出根节点,再输出左子树,最后输出右子树.上图的先序遍历结果就是:ABCDEF 2.中序遍历:中序遍历是先输出左子树,再输出根节点,最后输出右子树.上图的中序遍历结果就是:CBDAEF 3.后序遍历:后序遍历是先输出左子树,再输出右子树,最后输

  • C语言数据结构之二叉树的非递归后序遍历算法

    C语言数据结构之二叉树的非递归后序遍历算法 前言: 前序.中序.后序的非递归遍历中,要数后序最为麻烦,如果只在栈中保留指向结点的指针,那是不够的,必须有一些额外的信息存放在栈中. 方法有很多,这里只举一种,先定义栈结点的数据结构 typedef struct{Node * p; int rvisited;}SNode //Node 是二叉树的结点结构,rvisited==1代表p所指向的结点的右结点已被访问过. lastOrderTraverse(BiTree bt){ //首先,从根节点开始,

  • C语言二叉树的非递归遍历实例分析

    本文以实例形式讲述了C语言实现二叉树的非递归遍历方法.是数据结构与算法设计中常用的技巧.分享给大家供大家参考.具体方法如下: 先序遍历: void preOrder(Node *p) //非递归 { if(!p) return; stack<Node*> s; Node *t; s.push(p); while(!s.empty()) { t=s.top(); printf("%d\n",t->data); s.pop(); if(t->right) s.pus

  • C语言实现找出二叉树中某个值的所有路径的方法

    本文实例讲述了C语言实现找出二叉树中某个值的所有路径的方法,是非常常用的一个实用算法技巧.分享给大家供大家参考. 具体实现方法如下: #include <iostream> #include <vector> #include <iterator> #include <algorithm> using namespace std; vector<int> result; struct Node { Node(int i = 0, Node *pl

  • 使用C语言构建基本的二叉树数据结构

    二叉树结构常用的一些初始化代码 #include #include typedef struct Node{ int data; Node *leftchild; Node *rightchild; }Node; /* 初始化一棵二叉树排序树. */ void InitBinaryTree(Node**root,int elem) { *root=(Node*)malloc(sizeof(Node)); if(!(*root)) { printf("Memory allocation for r

  • C语言数据结构之线索二叉树及其遍历

    C语言数据结构之线索二叉树及其遍历 遍历二叉树就是以一定的规则将二叉树中的节点排列成一个线性序列,从而得到二叉树节点的各种遍历序列,其实质是:对一个非线性的结构进行线性化.使得在这个访问序列中每一个节点都有一个直接前驱和直接后继.传统的链式结构只能体现一种父子关系,¥不能直接得到节点在遍历中的前驱和后继¥,而我们知道二叉链表表示的二叉树中有大量的空指针,当使用这些空的指针存放指向节点的前驱和后继的指针时,则可以更加方便的运用二叉树的某些操作.引入线索二叉树的目的是: 为了加快查找节点的前驱和后继

  • C语言实现二叉树遍历的迭代算法

    本文实例讲述了C语言实现二叉树遍历的迭代算法,是数据结构算法中非常经典的一类算法.分享给大家供大家参考. 具体实现方法如下: 二叉树中序遍历的迭代算法: #include <iostream> #include <stack> using namespace std; struct Node { Node(int i, Node* l = NULL, Node* r = NULL) : item(i), left(l), right(r) {} int item; Node* le

随机推荐