C++之搜索二叉树

简介: C++之搜索二叉树

前言

本文介绍了二叉搜索树的相关概念,主要介绍了如何使用和实现搜索二叉树以及搜索二叉树具体的例题练习。


一、二叉搜索树概念

二叉搜索树也称为二叉排序树

二叉树有以下性质

1.空树是二叉搜索树;

2.二叉搜索树的非空左子树的所有节点小于根节点的值;

3.二叉搜索树的非空右子树的所有节点大于根节点的值;

4.二叉搜索树的左右子树也是二叉搜索树。

二、二叉搜索树操作

1.增

  1. 树为空,直接新增节点,赋值给root指针;
  2. 树不为空,根据二叉树的性质查找插入位置,插入新节点。

2.删

  1. 查找该节点是否在树中,如果不在则不进行操作;
  2. 如果在树中,则要删除该节点,分以下四种情况:
    a.无孩子,则直接删除该节点;
    b.要删除的节点无左孩子,则删除该节点,同时让该节点的父节点指向该节点的右孩子;
    c.要删除的节点无右孩子,则删除该节点,同时让该节点的父节点指向该节点的左孩子;
    d.左右孩子都有,则让该节点的右孩子的最左节点(右孩子的最小孩子)赋值给该节点,然后在右子树中删除最左节点即可。(替换删除法

3.查

  1. 从根节点出发,比根节点的值大的去它的右子树找,比根节点小的去它的左子树找;
  2. 最多查找高度次(二叉树的高度),如果走到空还没找到,说明该树没有这个值。

搜索二叉树不支持修改。

三、二叉搜索树实现

1.查

pnode Find(K k)//查
    {
      pnode cur = _pnode;
      while (cur)
      {
        if (cur->_k > k)
        {
          cur = cur->left;
        }
        else if (cur->_k < k)
        {
          cur = cur->right;
        }
        else
        {
          return cur;
        }
      }
      return cur;
    }

2.增

bool insert(K k)//增
    {
      pnode newnode = new node(k);
      //如果该树没有节点就直接插入新节点为根节点
      if (_pnode == nullptr)
      {
        _pnode = newnode;
        return true;
      }
      pnode cur = _pnode;
      pnode parent = cur;//记录当前节点的父节点,因为如果要插入就要插入在当前节点的父节点的左右孩子处
      while (cur)
      {
        parent = cur;
        if (cur->_k > k)
        {
          cur = cur->_left;
        }
        else if (cur->_k < k)
        {
          cur = cur->_right;
        }
        else//如果该节点已经在树中存在就插入失败
        {
          return false;
        }
      }
      //插入元素
      if (parent->_k > k) parent->_left = newnode;
      else parent->_right = newnode;
      return true;
    }

3.删

bool erase(K k)//删
    {
      //树为空删除失败
      if (_pnode == nullptr) return false;
      pnode cur = _pnode;
      pnode parent = cur;
      while (cur)
      {
        if (cur->_k > k)
        {
          parent = cur;
          cur = cur->_left;
        }
        else if (cur->_k < k)
        {
          parent = cur;
          cur = cur->_right;
        }
        else//删除元素(cur->_k == k)
        {
          //无孩子
          if (cur->_left == nullptr && cur->_right == nullptr)
          {
            if (cur == parent->_left) parent->_left = nullptr;
            else parent->_right = nullptr;
            delete cur;
            cur = nullptr;
          }
          //有左孩子,没有右孩子
          else if (cur->_left && cur->_right == nullptr)
          {
            if (cur == parent->_left) parent->_left = cur ->_left;
            else parent->_right = cur ->_left;
            delete cur;
            cur = nullptr;
          }
          //有右孩子,没有左孩子
          else if (cur->_left == nullptr && cur->_right)
          {
            if (cur == parent->_left) parent->_left = cur->_right;
            else parent->_right = cur->_right;
            delete cur;
            cur = nullptr;
          }
          //两个孩子都有(替换删除法)
          else if (cur->_left&&cur->_right)
          {
            pnode pR = cur->_right;//cur的右子树的根节点
            pnode pRL = cur->_right;//cur的右子树的最左节点
            while (pRL->_left)
            {
              pRL = cur->_left;
            }
            K temp = pRL->_k;
            erase(temp);
            cur->_k = temp;
          }
          return true;
        }
      }
      //树中没有该元素删除失败
      return false;
    }

模拟实现搜索二叉树的完整代码在文末。

四、二叉搜索树应用

1.K模型

只有K作为关键码,结构中只需要存储K值即可,关键码即为需要搜索到的值

例题,判断一个单词拼写是否正确?

  1. 将词库中所有单词集合中每一个单词作为K值,构建一棵二叉树;
  2. 在这个二叉树中检索是否存在这个单词,如果存在说明拼写正确,如果不存在则说明拼写错误。

2.KV模型

每一个关键码K都有一个对应的V值,即<K,V>键值对。

例如,英汉词典,通过英文可以快速找到中文,英文与中文可以构成一个键值对<English,Chinese>。

例如,计算单词出现的次数,用单词可以找到它出现的次数,单词与它出现的次数可以构成一个键值对<word,count>。

英汉词典的例子:

namespace kv
{
  template<class K,class V>
  struct BSTnode
  {
    BSTnode(const K& k = K(),const V& v = V())
    :_k(k)
    , _v(v)
    , _left(nullptr)
    , _right(nullptr)
    {}
    K _k;
    V _v;
    BSTnode<K, V>* _left;
    BSTnode<K, V>* _right;
  };
  template<class K, class V>
  class BSTree
  {
    typedef BSTnode<K,V> node;
    typedef node* pnode;
  public:
    BSTree()//构造
      :_pnode(nullptr)
    {}
    BSTree(const BSTree<K,V>& t)//拷贝构造
    {
      _pnode = Copy(t._pnode);
    }
    BSTree<K,V>& operator=(const BSTree<K,V> t)//赋值运算符重载
    {
      swap(_pnode, t._pnode);
      return *this;
    }
    ~BSTree()//析构
    {
      destory(_pnode);
      _pnode = nullptr;
    }
    pnode Find(const K& k)//查
    {
      pnode cur = _pnode;
      while (cur)
      {
        if (cur->_k > k)
        {
          cur = cur->_left;
        }
        else if (cur->_k < k)
        {
          cur = cur->_right;
        }
        else
        {
          return cur;
        }
      }
      return cur;
    }
    bool insert(const K& k, const V& v)//增
    {
      pnode newnode = new node(k,v);
      //如果该树没有节点就直接插入新节点为根节点
      if (_pnode == nullptr)
      {
        _pnode = newnode;
        return true;
      }
      pnode cur = _pnode;
      pnode parent = cur;//记录当前节点的父节点,因为如果要插入就要插入在当前节点的父节点的左右孩子处
      while (cur)
      {
        parent = cur;
        if (cur->_k > k)
        {
          cur = cur->_left;
        }
        else if (cur->_k < k)
        {
          cur = cur->_right;
        }
        else//如果该节点已经在树中存在就插入失败
        {
          return false;
        }
      }
      //插入元素
      if (parent->_k > k) parent->_left = newnode;
      else parent->_right = newnode;
      return true;
    }
    bool erase(const K& k)//删
    {
      //树为空删除失败
      if (_pnode == nullptr) return false;
      pnode cur = _pnode;
      pnode parent = cur;
      while (cur)
      {
        if (cur->_k > k)
        {
          parent = cur;
          cur = cur->_left;
        }
        else if (cur->_k < k)
        {
          parent = cur;
          cur = cur->_right;
        }
        else//删除元素(cur->_k == k)
        {
          //无孩子
          if (cur->_left == nullptr && cur->_right == nullptr)
          {
            if (cur == parent->_left) parent->_left = nullptr;
            else parent->_right = nullptr;
            delete cur;
            cur = nullptr;
          }
          //有左孩子,没有右孩子
          else if (cur->_left && cur->_right == nullptr)
          {
            if (cur == parent->_left) parent->_left = cur->_left;
            else parent->_right = cur->_left;
            delete cur;
            cur = nullptr;
          }
          //有右孩子,没有左孩子
          else if (cur->_left == nullptr && cur->_right)
          {
            if (cur == parent->_left) parent->_left = cur->_right;
            else parent->_right = cur->_right;
            delete cur;
            cur = nullptr;
          }
          //两个孩子都有(替换删除法)
          else if (cur->_left&&cur->_right)
          {
            pnode pR = cur->_right;//cur的右子树的根节点
            pnode pRL = cur->_right;//cur的右子树的最左节点
            while (pRL->_left)
            {
              pRL = cur->_left;
            }
            K temp = pRL->_k;
            erase(temp);
            cur->_k = temp;
          }
          return true;
        }
      }
      //树中没有该元素删除失败
      return false;
    }
    //中序遍历
    void inorder()
    {
      _inorder(_pnode);
      cout << endl;
    }
  private:
    void destory(pnode p)
    {
      if (p == nullptr) return;
      destory(p->_left);
      destory(p->_right);
      delete p;
    }
    pnode Copy(pnode root)
    {
      if (root == nullptr) return nullptr;
      pnode newnode = new node(root->_k);
      newnode->_left = Copy(root->_left);
      newnode->_right = Copy(root->_right);
      return newnode;
    }
    void _inorder(pnode root)
    {
      if (root == nullptr) return;
      _inorder(root->_left);
      cout << '<'<<root->_k<<','<<root->_v<<'>' << "  ";
      _inorder(root->_right);
    }
    pnode _pnode;
  };
  void test()
  {
    kv::BSTree<string, string> dict;
    dict.insert("sort", "排序");
    dict.insert("string", "字符串");
    dict.insert("left", "左边");
    dict.insert("right", "右边");
    string str;
    while (cin >> str)
    {
      kv::BSTnode<string, string>* ret = dict.Find(str);
      if (ret)
      {
        cout << ret->_v << endl;
      }
      else
      {
        cout << "无此单词" << endl;
      }
    }
  }
}

测试运行结果:

五、二叉搜索树性能

二叉树的插入和删除都需要先进行查找,因此查找的效率代表着二叉树的性能

对有n个节点的二叉树,若每个元素的查找概率相同,那么二叉搜索树平均查找次数是二叉树的高度次,即二叉树越高比较次数越多。

但对于同一个关键码的集合,如果各个关键码的插入次序不同,所得到的二叉树的结构可能不同。

二叉搜索树性能最好是它的结构为完全二叉树(或接近完全二叉树),它的平均比较次数为l o g 2 N log_2 Nlog2N

二叉搜索树性能最差是他的结构退化成单支,如图中右边的二叉树,它的平均比较次数为N 2 \frac{N}{2}2N

如果搜索二叉树退化成单支,那么它的性能就丢失了,如何对其进行改进呢?这就涉及我们之后介绍的AVL树和红黑树。

六、模拟实现搜索二叉树

namespace Jinger
{
  template<class K>
  struct BSTnode
  {
    BSTnode(const K& k = K())
    :_k(k)
    ,_left(nullptr)
    , _right(nullptr)
    {}
    K _k;
    BSTnode<K>* _left;
    BSTnode<K>* _right;
  };
  template<class K>
  class BSTree
  {
    typedef BSTnode<K> node;
    typedef node* pnode;
  public:
    BSTree()//构造
      :_pnode(nullptr)
    {}
    BSTree(const BSTree<K>& t)//拷贝构造
    {
      _pnode = Copy(t._pnode);
    }
    BSTree<K>& operator=(const BSTree<K> t)//赋值运算符重载
    {
      swap(_pnode,t._pnode);
      return *this;
    }
    ~BSTree()//析构
    {
      destory(_pnode);
      _pnode = nullptr;
    }
    pnode Find(const K& k)//查
    {
      pnode cur = _pnode;
      while (cur)
      {
        if (cur->_k > k)
        {
          cur = cur->left;
        }
        else if (cur->_k < k)
        {
          cur = cur->right;
        }
        else
        {
          return cur;
        }
      }
      return cur;
    }
    bool insert(const K& k)//增
    {
      pnode newnode = new node(k);
      //如果该树没有节点就直接插入新节点为根节点
      if (_pnode == nullptr)
      {
        _pnode = newnode;
        return true;
      }
      pnode cur = _pnode;
      pnode parent = cur;//记录当前节点的父节点,因为如果要插入就要插入在当前节点的父节点的左右孩子处
      while (cur)
      {
        parent = cur;
        if (cur->_k > k)
        {
          cur = cur->_left;
        }
        else if (cur->_k < k)
        {
          cur = cur->_right;
        }
        else//如果该节点已经在树中存在就插入失败
        {
          return false;
        }
      }
      //插入元素
      if (parent->_k > k) parent->_left = newnode;
      else parent->_right = newnode;
      return true;
    }
    bool erase(const K& k)//删
    {
      //树为空删除失败
      if (_pnode == nullptr) return false;
      pnode cur = _pnode;
      pnode parent = cur;
      while (cur)
      {
        if (cur->_k > k)
        {
          parent = cur;
          cur = cur->_left;
        }
        else if (cur->_k < k)
        {
          parent = cur;
          cur = cur->_right;
        }
        else//删除元素(cur->_k == k)
        {
          //无孩子
          if (cur->_left == nullptr && cur->_right == nullptr)
          {
            if (cur == parent->_left) parent->_left = nullptr;
            else parent->_right = nullptr;
            delete cur;
            cur = nullptr;
          }
          //有左孩子,没有右孩子
          else if (cur->_left && cur->_right == nullptr)
          {
            if (cur == parent->_left) parent->_left = cur ->_left;
            else parent->_right = cur ->_left;
            delete cur;
            cur = nullptr;
          }
          //有右孩子,没有左孩子
          else if (cur->_left == nullptr && cur->_right)
          {
            if (cur == parent->_left) parent->_left = cur->_right;
            else parent->_right = cur->_right;
            delete cur;
            cur = nullptr;
          }
          //两个孩子都有(替换删除法)
          else if (cur->_left&&cur->_right)
          {
            pnode pR = cur->_right;//cur的右子树的根节点
            pnode pRL = cur->_right;//cur的右子树的最左节点
            while (pRL->_left)
            {
              pRL = cur->_left;
            }
            K temp = pRL->_k;
            erase(temp);
            cur->_k = temp;
          }
          return true;
        }
      }
      //树中没有该元素删除失败
      return false;
    }
    //中序遍历
    void inorder()
    {
      _inorder(_pnode);
      cout<<endl;
    }
  private:
    void destory(pnode p)
    {
      if (p == nullptr) return;
      destory(p->_left);
      destory(p->_right);
      delete p;
    }
    pnode Copy(pnode root)
    {
      if (root == nullptr) return nullptr;
      pnode newnode = new node(root->_k);
      newnode->_left = Copy(root->_left);
      newnode->_right = Copy(root->_right);
      return newnode;
    }
    void _inorder(pnode root)
    {
      if (root == nullptr) return;
      _inorder(root->_left);
      cout << root->_k << "  ";
      _inorder(root->_right);
    }
    pnode _pnode;
  };
  //测试插入和删除
  void test1()
  {
    int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
    BSTree<int> t;
    for (auto e : a)
    {
      t.insert(e);
    }
    t.inorder();
    t.erase(3);
    t.inorder();
    t.erase(13);
    t.inorder();
  }
}

总结

以上就是今天要讲的内容,本文介绍了二叉搜索树的相关概念。本文作者目前也是正在学习C++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。

最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

目录
打赏
0
0
0
0
0
分享
相关文章
二叉树进阶面试题(精华总结)【C++版本】
二叉树进阶面试题(精华总结)【C++版本】
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
298 0
【C/C++ 数据结构 】二叉树基本性质:对于任何一颗二叉树T,若其终端结点为n0 ,那么度数为2的结点数为n2。则n0=n2+1...
【C/C++ 数据结构 】二叉树基本性质:对于任何一颗二叉树T,若其终端结点为n0 ,那么度数为2的结点数为n2。则n0=n2+1...
237 0
|
6月前
|
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
154 12
|
6月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
140 10
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
203 2
|
12月前
|
【C++】二叉树进阶之二叉搜索树(下)
【C++】二叉树进阶之二叉搜索树(下)
68 4
|
12月前
|
【C++】二叉树进阶之二叉搜索树(上)
【C++】二叉树进阶之二叉搜索树(上)
63 3
AI助理
登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问

你好,我是AI助理

可以解答问题、推荐解决方案等