二叉排序树实现(C++封装)

设计思路

设计一个类,根结点只可读取,具备构造二叉树、插入结点、删除结点、查找、 查找最大值、查找最小值、查找指定结点的前驱和后继等功能接口。

二叉排序树概念

它或者是一棵空树;或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)左、右子树也分别为二叉排序树。

二叉排序树的各种操作

插入新节点
这是一个递归操作,递归设计时要找到最源头,才能得到最简设计。一种设计是判断叶子节点,把新节点作为叶子节点的孩子插入;一种是永远当作根进行插入,插入节点永远是当前子树的根!看代码:

//root为二级指针的原因是,如果树为空,需要将根修改反馈回来
bool BinaryTree::InsertNode(pNode * cuRoot, int data, pNode self)
{   //递归设计时找到最源头,才能得到最简设计
    if (*cuRoot == nullptr){
        pNode node = new Node;
        if (node == nullptr)
            return false;
        node->data = data;
        node->lChild = node->rChild = node->parent = nullptr;
        (*cuRoot) = node;
        node->parent = self;
        return true;
    }
    if (data > (*cuRoot)->data)
        InsertNode(&(*cuRoot)->rChild, data, *cuRoot);
    else
        InsertNode(&(*cuRoot)->lChild, data, *cuRoot);
    return true;
}

构造函数
一共两个重载函数:一个无参,一个接受数组利用插入函数直接构造二叉排序树。

BinaryTree::BinaryTree(int * datum, int len)
{
    root = nullptr;
    for (int i = 0; i < len; i++)
        InsertNode(&root, datum[i], root);
}

BinaryTree::BinaryTree()
{
    root = nullptr;
}

查找函数
这也是一个递归操作,为了对外隐藏root(根节点),因此编写了一个私有函数,进行真正的查找操作。

//真正的查找函数
BinaryTree::pNode BinaryTree::_searchKey(pNode root, int key){
    if (root == nullptr)
        return nullptr;
    if (root->data == key)    //找到了
        return root;
    else if (root->data > key)//值偏小,到左子树找
        return _searchKey(root->lChild, key);
    else                      //值偏大,到右子树找
        return _searchKey(root->rChild, key);
}
//对外接口
BinaryTree::pNode BinaryTree::SearchKey(int key){
    return _searchKey(root, key);
}

找前驱节点
要么为左子树中最大者,要么一直追溯其父节点链,第一个是其父节点的右孩子的父节点,即为所求。

BinaryTree::pNode BinaryTree::SearchPredecessor(pNode node){
    if (node == nullptr)
        return nullptr;
    else if (node->lChild != nullptr)
        return SearchMaxNode(node->lChild);
    else
    {
        if (node->parent == nullptr)
            return nullptr;
        while (node)
        {
            if (node->parent->rChild == node)
                break;
            node = node->parent;
        }
        return node->parent;
    }
}

找后继节点
与找前驱节点基本相似。 要么为右子树中最小者,要么一直追溯其父节点链,第一个是其父节点的左孩子的父节点,即为所求。

BinaryTree::pNode BinaryTree::SearchSuccessor(pNode node){
    if (node == nullptr)
        return nullptr;
    else if (node->rChild != nullptr)
        return SearchMinNode(node->rChild);
    else
    {
        if (node->parent == nullptr)
            return nullptr;
        while (node)
        {
            if (node->parent->lChild == node)
                break;
            node = node->parent;
        }
        return node->parent;
    }
}

找最小值

BinaryTree::pNode BinaryTree::SearchMinNode(pNode curNode){
    if (curNode == nullptr)
        return nullptr;
    //一直找到左子树为空的节点,即为最小值
    while (curNode->lChild != nullptr)
        curNode = curNode->lChild;
    return curNode;
}

找最大值

BinaryTree::pNode BinaryTree::SearchMaxNode(pNode curNode){
    if (curNode == nullptr)
        return nullptr;
    //一直找到右子树为空的节点,即为最大值
    while (curNode->rChild != nullptr)
        curNode = curNode->rChild;
    return curNode;
}

中序遍历

void BinaryTree::_visitMiddle(pNode root){
    if (root != nullptr){
        _visitMiddle(root->lChild);
        printf("%d;", root->data);
        _visitMiddle(root->rChild);
    }
}

void BinaryTree::VisitMiddle(){
    _visitMiddle(root);
}

删除节点
这个是最麻烦的操作,分四种情况分别处理,最麻烦的是被删节点左右子树都存在的情况,这时将被删节点内容换成其后继内容,删除其后继(递归)。

bool BinaryTree::DeleteNode(int key){
    //return _deleteNode(root, key);
    pNode node = SearchKey(key);
    if (!node)
        return false;
    //被删节点为叶子结点
    if (node->lChild == nullptr && node->rChild == nullptr){
        if (node->parent == nullptr){
            root = nullptr;
        }
        else
        {
            if (node->parent->lChild == node)
                node->parent->lChild = nullptr;
            else
                node->parent->rChild = nullptr;
        }
        delete node;
    }
    //被删节点只有左子树
    else if (node->lChild != nullptr && node->rChild == nullptr){
        //将左孩子的父亲指向被删节点的父亲
        node->lChild->parent = node->parent;
        //被删节点为根,修改根节点
        if (node->parent == nullptr)
            root = node->lChild;
        else if(node->parent->lChild == node)
            node->parent->lChild = node->lChild;
        else
            node->parent->rChild = node->lChild;
        delete node;
    }
    //被删节点只有右子树
    else if (node->lChild == nullptr && node->rChild != nullptr){
        //将右孩子的父亲指向被删节点的父亲
        node->rChild->parent = node->parent;
        //被删节点为根,修改根节点
        if (node->parent == nullptr)
            root = node->rChild;
        else if (node->parent->lChild == node)
            node->parent->lChild = node->rChild;
        else
            node->parent->rChild = node->rChild;
        delete node;
    }
    //被删节点左、右子树都有
    else {  //用后继节点取代删除节点,并删除后继
        pNode successor = SearchSuccessor(node);
        int temp = successor->data;
        DeleteNode(temp);
        node->data = temp;
    }
}

柝构函数
函数超出作用域范围时,清理占用内存。

BinaryTree::~BinaryTree()
{
    _delAllNode(root);
}
void BinaryTree::_delAllNode(pNode root){
    if (root != nullptr && root!=NULL){
        _delAllNode(root->lChild);
        _delAllNode(root->rChild);
        DeleteNode(root->data);
    }
}

类的定义(头文件)

#pragma once

#include<stdio.h>
#include<stdlib.h> 

class BinaryTree
{
private:
    typedef struct Node{
        struct Node * parent;
        struct Node * lChild;
        struct Node * rChild;
        int data;
    }*pNode;
    pNode root;
    void _visitMiddle(pNode root);
    pNode _searchKey(pNode root, int key);
    void _delAllNode(pNode root);
public:
    BinaryTree();
    BinaryTree(int * datum, int len);
    pNode SearchMaxNode(pNode node);
    pNode SearchMinNode(pNode node);
    pNode GetRoot();
    pNode SearchKey(int key);
    bool DeleteNode(int key);
    pNode SearchPredecessor(pNode node);
    pNode SearchSuccessor(pNode node);
    void VisitMiddle();
    bool InsertNode(pNode * cuRoot, int data, pNode self);
    ~BinaryTree();
};

调用示例

#include <conio.h>
#include "BinaryTree.h"

int main()
{
    int arrs[] = { 23, 65, 12, 3, 8, 76,  90, 21, 75, 34,345, 61 };
    int len = sizeof(arrs) / sizeof(arrs[0]);
    BinaryTree bTree(arrs,len);
    bTree.DeleteNode(90);
    bTree.VisitMiddle();
    getch();
    return 0;
}
时间: 2024-10-06 21:08:13

二叉排序树实现(C++封装)的相关文章

STL源码笔记(17)—二叉排序树BST(C++封装)

二叉排序树BST STL中还有一类非常重要的容器,就是关联容器,比如map啊set啊等等,这些容器说实话,在应用层上还不能完全得心应手(比如几种容器效率的考虑等等),更别说源码了,因此这一部分打算稳扎稳打,好好做做笔记研究一番. 说到关联容器,我们想到了什么AVL树,红黑树等等,但大多时候我们仅仅局限于知道其名字,或者知道其概念,俗话说"talk is cheap,show me the code",因此,我打算从他们的祖爷爷二叉排序树开始下手.(其实,侯老师的书上也是这么安排的哈)

POJ 2418 各种二叉排序树

题意很明确,统计各个字符串所占总串数的百分比,暴力的话肯定超时,看了书上的题解后发现这题主要是用二叉排序树来做,下面附上n种树的代码. 简单的二叉排序树,不作任何优化(C语言版的): 1 #include<stdio.h> 2 #include<string.h> 3 #include<stdlib.h> 4 5 typedef struct node{ 6 char name[52]; 7 struct node *lchild, *rchild; 8 int cou

STL源码笔记(18)—平衡二叉树AVL(C++封装+模板)

AVLTree平衡二叉树 在几年前刚学数据结构时,AVL-Tree只是一个仅仅需要掌握其概念的东西,今非昔比,借看STL源码剖析的契机希望从代码层面将其拿下. 1.简介 二叉查找树给我们带来了很多方便,但是由于其在有序序列插入时就会退化成单链表(时间复杂度退化成 O(n)),AVL-tree就克服了上述困难.AVL-tree是一个"加上了平衡条件的"二叉搜索树,平衡条件确保整棵树的深度为O(log n). AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差

python学习 面向对象封装

from collectoins import namedtuplePoint=namedtuple('point',['x','y'])t1=Point(1,2)print(t1.x)print(t1.y)没有方法的类属性不会发生变化    定义简单 不能改变面向对象封装私有属性!!!私有方法!!!用装饰器描述的几个方法@property !!!@classmethod!!@staticmethod! 封装:class Teacher:     def __init__(self,name,p

python—面向对象的封装

封装 私有属性 class Teacher: __identifier = 'Teacher' #私有静态属性 def __init__(self,name,pwd) self.name = name self.__pwd = pwd #私有属性 内部使用,外部不能使用 def pwd(self): print(self.__pwd) alex = Teacher('alex','3714') alex.pwd() class Teacher: __identifier = 'Teacher'

dbutils封装对象,单列,一行一列(用)

基本用法:查找并封装对象与对象集合 public User findUserByNamePassword(String name,String password){ QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource()); String sql = "select * from user where name='"+name+"' and password = '"+password

python基础--接口与归一化设计、封装、异常、网络编程

1 接口与归一化设计 1.1 归一化概念: 归一化的好处: 1.归一化让使用者无需关心对象的类是什么,只需要知道这些对象都具备某些功能就可以了,这极大降低了使用者的使用难度. 2.归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合 继承的两种用途 一:继承基类的方法,并且做出自己改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的.因为它使得子类与基类出现强耦合. 二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中

自动化测试框架 selenium api的封装

接上一篇 http://tianxietaotao.blog.51cto.com/12002420/1951701 这篇我大概介绍下我这个工具的简单介绍 先上图: 制作背景: Web自动化测试在现在测试领域已经越来越普遍,但是写代码对于好多测试猿代码基础较弱,搭建系统也比较麻烦.所以我抽闲暇时间做了这个简单的工具:下面简单介绍下功能 工具简单介绍: 1.工具栏:Resume:调试阶段执行到下一个断点 next:单步执行 debug/run 模式切换 执行.停止 2.用例树:用例采用execl或者

lambda表达式封装对数据库的查询

前言: 1.为什么要封装lambda表达式数据库查询,原因有一下几点: 1.1.在以往的开发中进行数据库表查询时,其实所需要的字段就是其中几个,但是在开发中,开发者往往习惯select * 进行查询,当数据多和用户量多时,查询的效率会降低. 1.2.在写查询where条件的时候,总是用string.format去拼接字符串,开发效率低. 1.3.代码不够优雅,代码中嵌套和多sql语句,如果是表字段发生改变时编译器检查不出来,代码出错的概率大. 1.4.本着 write less  do more