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++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。

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

相关文章
|
5月前
|
C++
二叉树进阶面试题(精华总结)【C++版本】
二叉树进阶面试题(精华总结)【C++版本】
|
5月前
|
存储 编译器 数据库
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
119 0
|
5月前
|
算法 C++ 开发者
【C/C++ 数据结构 】二叉树基本性质:对于任何一颗二叉树T,若其终端结点为n0 ,那么度数为2的结点数为n2。则n0=n2+1...
【C/C++ 数据结构 】二叉树基本性质:对于任何一颗二叉树T,若其终端结点为n0 ,那么度数为2的结点数为n2。则n0=n2+1...
71 0
|
18天前
|
存储 算法 C++
【搜索算法】 跳马问题(C/C++)
【搜索算法】 跳马问题(C/C++)
|
18天前
|
人工智能 算法 Java
【搜索算法】数字游戏(C/C++)
【搜索算法】数字游戏(C/C++)
|
3月前
|
存储 C++
【C++】二叉树进阶之二叉搜索树(下)
【C++】二叉树进阶之二叉搜索树(下)
25 4
|
3月前
|
Java 编译器 C++
【C++】二叉树进阶之二叉搜索树(上)
【C++】二叉树进阶之二叉搜索树(上)
29 3
|
3月前
|
算法 C++
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
39 2
|
5月前
|
存储 C++
【C++】二叉树进阶 -- 详解
【C++】二叉树进阶 -- 详解
|
5月前
|
存储 算法 数据管理
C++中利用随机策略优化二叉树操作效率的实现方法
C++中利用随机策略优化二叉树操作效率的实现方法
110 1