C++一步一步实现二叉搜索树

来源:互联网 发布:小鑫优化 编辑:程序博客网 时间:2024/06/08 04:00

学习《STL源码剖析》关联容器的章节,map,set等关联容器的底层实现都是红黑树,本着循序渐进的思想,先实现二叉搜索树。读懂了二叉搜索树的定义后,实现了二叉搜索树的基本功能。文章属原创,代码全手打,欢迎指正讨论。首先给出类的定义,如下:

#include<iostream>#include<string>#include<cstring>#include<sstream>//#include<>using namespace std;class BST{private:    //嵌套类,仅在BST中有效    class Node    {    public:        int val;        Node *left;        Node *right;        Node *parent; //增加父节点,在删除节点时父节点很有用        Node()        {            val = 0;            left = right = parent = NULL;        }    };    Node *m_root;      //存储根节点    Node *m_cur;       //存储查找的结果      int   m_size;      //存储树的大小    void SetNode(Node* cur,Node* newNode);           //设置节点的父子关系    bool CompareTree(const Node *r1,Node* r2) const;public:    bool InsertNode(const int val);                  //插入节点    bool FindNode(const int val);                    //找到返回true,val所在的位置就是pos    bool DelNode(const int val);                     //删除节点    void Inorder(Node *p, string &str) const;        //中序遍历输出参数可自定义    void DestroyTree(Node *p);                       //销毁整棵二叉树,模仿后序遍历      bool operator==(BST &t) const;                   //用于判断两个BST是否相等    void CreateBST(const string &str);    Node* GetRootNode() const    {        return m_root;    }    BST() :m_root(NULL), m_cur(NULL),m_size(0){}    ~BST()    {        DestroyTree(m_root);    }};

一、实现节点插入功能,此处是构建二叉搜索树的基本操作。插入节点时需满足的条件有二:
1、左子树小于父节点。
2、右子树不小于父节点。
以下是详细实现代码,以下代码虽不是最简短的写法,但是但是能想到的思路最清晰的写法,适合用于学习理解二叉搜索树。

bool BST::InsertNode(const int val){    Node *p = new Node();    p->val = val;    m_size++;    if (m_root == NULL)    {        m_root = p;        return true;    }    Node * cur = m_root;    Node * last = m_root;    bool lflag = false;  //为true说明是左子树    while (cur != NULL)    {        last = cur;        if (val >= cur->val)        {            cur = cur->right;            lflag = false;        }        else        {            cur = cur->left;            lflag = true;        }    }    //建立父节点与子树的关系    cur = p;    cur->parent = last;    lflag == true ? last->left = cur : last->right = cur;    return true;}

二、建立二叉搜索树。此处只需要循环调用上面实现的InsertNode方法即可。

void BST::CreateBST(const string &str){    stringstream ss;    int val;    for (int i = 0; i < str.size(); i++)    {        ss << str[i];        ss >> val;        ss.clear();//多次转换需要清空        InsertNode(val);    }}

三、中序遍历二叉搜索树。二叉搜索树中序遍历的结果可得到递增的有序序列,从此处可以理解为BST查找的平均复杂度是O(logn),因为其查找过程类似于二分查找。为了方便输出查看,将输出结果放在string中。中序遍历实现方法有两种,其一为采用递归的方式;其二为使用栈来辅助实现。此处给出递归版本,今后再补充给出非递归版本。

void BST::Inorder(Node *p, string &st) const{    if (p == NULL)    {        return;    }    Inorder(p->left,st);    stringstream ss;    string t;    ss << p->val;    ss >> t;    st += t;    Inorder(p->right, st);}

四、在二叉搜索树中查找节点。二叉排序树的查找操作,平均复杂度为O(logn),最差为O(n)。而平衡二叉搜索树能很好的处理最坏情况。常见的平衡二叉搜索树:红黑树等

bool BST::FindNode(const int val){    if (m_root == NULL)    {        return false;    }    bool ret = false;    Node * cur = m_root;    while (cur != NULL)    {        if (cur->val == val)        {            m_cur = cur;            ret = true;            break;        }        else if (val > cur->val)        {            cur = cur->right;        }        else        {            cur = cur->left;        }    }    return ret;}

五、二叉搜索树删除节点。二叉搜索树不能只是简单的删除某个节点,应该保证BST删除元素后仍然符合二叉搜索树的定义。删除分为三种情况:
1、左子树和右子树均不为空,那么就从右子树中找到一个最小值来替换。
2、仅有一个单个子树,那就将当前元素删除即可
3、没有子树
在编码时一定要注意根节点是特殊情况,根节点没有父节点。
此处代码分为两个步骤,首先找到要替代节点(倘若有),然后替换掉原来的节点。

bool BST::DelNode(const int val){    if (m_root == NULL)    {        return false;    }    m_size--;    if (!FindNode(val))    {        return false;    }    //难点:找到叶子节点后如何处理父节点,防止父节点中出现野指针    //为方便操作,而返回去增加定义父指针    if (m_cur->left != NULL && m_cur->right != NULL)//有两个子树    {        Node * min = m_cur->right;          while (min->left != NULL)        {            min = min->left;        }        m_cur->right == min ? min->parent->right = NULL : min->parent->left = NULL;        SetNode(m_cur,min);        delete m_cur;        m_cur = NULL;    }    else if (m_cur->left != NULL || m_cur->right != NULL)//仅有一个子树    {        if (m_cur->left != NULL)        {            SetNode(m_cur, m_cur->left);            delete m_cur;            m_cur = NULL;        }        else        {            SetNode(m_cur, m_cur->right);            delete m_cur;            m_cur = NULL;        }    }    else //叶子节点、无子树的根节点直接删除    {        SetNode(m_cur, NULL);        delete m_cur;        m_cur = NULL;    }    return false;}

DelNode方法中调用的SetNode代码实现如下,主要作用是重新设定原来节点周围的父子关系。
总共要设定五条关系
1、当前节点与父节点之间的关系(单边1条)
2、与左子树之间的关系(双边2条)
3、与右子树之间的关系(双边2条)

void BST::SetNode(Node* cur, Node* newNode){    //与父节点    if (cur->parent != NULL)    {        if (cur->parent->left == cur)        {            cur->parent->left = newNode;        }        else        {            cur->parent->right = newNode;        }    }    else    {        m_root = newNode;  //父节点为空,说明为根节点    }    //与左子树    if (cur->left != NULL)    {        //子->父        cur->left->parent = newNode;        //父->子        if (newNode != cur->left)        {            newNode->left = cur->left;        }        else        {            //不操作        }    }    //右子树    if (cur->right != NULL)    {        //子->父        cur->right->parent = newNode;        if (newNode != cur->right)        {            newNode->right = cur->right;        }        else        {            //不操作        }    }}
六、二叉树拓扑结构比较。为了比较两个二叉树是否相同,可采用递归的方式实现,代码如下:
bool BST::CompareTree(const Node *r1,Node* r2) const{    if (r1 == NULL && r2 == NULL)    {        return true;    }    if (r1 == NULL || r2 == NULL)    {        return false;    }    if (r1->val == r2->val)    {        if (CompareTree(r1->left, r2->left) && CompareTree(r1->right, r2->right))        {            return true;        }    }    return false;}bool BST::operator==(BST &t) const{    return CompareTree(GetRootNode(),t.GetRootNode());}

七、二叉搜索树的销毁。所有节点均使用的new获取的堆空间,因此必须手动释放,否则会造成内存泄漏。释放的方法模仿后序遍历实现。

void BST::DestroyTree(Node *p){    if (p == NULL)    {        return;    }    DestroyTree(p->left);    DestroyTree(p->right);    delete p;}

现在二叉搜索树已经实现,可以通过一道题目来练习。
题目描述
判断两序列是否为同一二叉搜索树序列
输入描述:
开始一个数n,(1<=n<=20) 表示有n个需要判断,n= 0 的时候输入结束。
接下去一行是一个序列,序列长度小于10,包含(0~9)的数字,没有重复数字,根据这个序列可以构造出一颗二叉搜索树。
接下去的n行有n个序列,每个序列格式跟第一个序列一样,请判断这两个序列是否能组成同一颗二叉搜索树。

输出描述:
如果序列相同则输出YES,否则输出NO

输入例子:
2
567432
543267
576342
0

输出例子:
YES
NO

整个工程的全部代码如下:

#include<iostream>#include<stack>#include<vector>#include<queue>#include<map>#include<algorithm>#include<cctype>#include<string>#include<cstring>#include<sstream>//#include<>using namespace std;class BST{private:    //嵌套类,仅在BST中有效    class Node    {    public:        int val;        Node *left;        Node *right;        Node *parent; //增加父节点,在删除节点时父节点很有用        Node()        {            val = 0;            left = right = parent = NULL;        }    };    Node *m_root; //存储根节点    Node *m_cur;    //存储查找的结果      int m_size;   //存储树的大小    void SetNode(Node* cur,Node* newNode); //设置节点的父子关系    bool CompareTree(const Node *r1,Node* r2) const;public:    bool InsertNode(const int val);                  //插入节点    bool FindNode(const int val);   //找到返回true,val所在的位置就是pos    bool DelNode(const int val);                     //删除节点    void Inorder(Node *p, string &str) const;        //中序遍历输出参数可自定义    void DestroyTree(Node *p);                       //销毁整棵二叉树,模仿后序遍历      bool operator==(BST &t) const;                    //用于判断两个BST是否相等    void CreateBST(const string &str);    Node* GetRootNode() const    {        return m_root;    }    BST() :m_root(NULL), m_cur(NULL),m_size(0){}    ~BST()    {        DestroyTree(m_root);    }};//二叉排序树的查找操作,平均复杂度为O(logn),最差为O(n)。而平衡二叉搜索树能很好的//处理最坏情况。常见的平衡二叉搜索树:红黑树等bool BST::FindNode(const int val){    if (m_root == NULL)    {        return false;    }    bool ret = false;    Node * cur = m_root;    while (cur != NULL)    {        if (cur->val == val)        {            m_cur = cur;            ret = true;            break;        }        else if (val > cur->val)        {            cur = cur->right;        }        else        {            cur = cur->left;        }    }    return ret;}bool BST::InsertNode(const int val){    Node *p = new Node();    p->val = val;    m_size++;    if (m_root == NULL)    {        m_root = p;        return true;    }    Node * cur = m_root;    Node * last = m_root;    bool lflag = false;    while (cur != NULL)    {        last = cur;        if (val >= cur->val)        {            cur = cur->right;            lflag = false;        }        else        {            cur = cur->left;            lflag = true;        }    }    cur = p;    cur->parent = last;    lflag == true ? last->left = cur : last->right = cur;    return true;}void BST::CreateBST(const string &str){    stringstream ss;    int val;    for (int i = 0; i < str.size(); i++)    {        ss << str[i];        ss >> val;        ss.clear();//多次转换需要清空        InsertNode(val);    }}/*总共要设定五条关系1、当前节点与父节点之间的关系(单边1条)2、与左子树之间的关系(双边2条)3、与右子树之间的关系(双边2条)*/void BST::SetNode(Node* cur, Node* newNode){    //与父节点    if (cur->parent != NULL)    {        if (cur->parent->left == cur)        {            cur->parent->left = newNode;        }        else        {            cur->parent->right = newNode;        }    }    else    {        m_root = newNode;  //父节点为空,说明为根节点    }    //与左子树    if (cur->left != NULL)    {        //子->父        cur->left->parent = newNode;        //父->子        if (newNode != cur->left)        {            newNode->left = cur->left;        }        else        {            //不操作        }    }    if (cur->right != NULL)    {        //子->父        cur->right->parent = newNode;        if (newNode != cur->right)        {            newNode->right = cur->right;        }        else        {            //不操作        }    }}//二叉搜索树不能只是简单的删除某个节点,应该保证删除元素后//仍然符合二叉搜索树的定义/*1、左子树和右子树均不为空,那么就从右子树中找到一个最小值来替换。2、仅有一个单个子树或者没有子树,那就将当前元素删除即可*/bool BST::DelNode(const int val){    if (m_root == NULL)    {        return false;    }    m_size--;    if (!FindNode(val))    {        return false;    }    //难点:找到叶子节点后如何处理父节点,防止父节点中出现野指针    //为方便操作,而返回去增加定义父指针    if (m_cur->left != NULL && m_cur->right != NULL)    {        Node * min = m_cur->right;          while (min->left != NULL)        {            min = min->left;        }        m_cur->right == min ? min->parent->right = NULL : min->parent->left = NULL;        SetNode(m_cur,min);        delete m_cur;        m_cur = NULL;    }    else if (m_cur->left != NULL || m_cur->right != NULL)    {        if (m_cur->left != NULL)        {            SetNode(m_cur, m_cur->left);            delete m_cur;            m_cur = NULL;        }        else        {            SetNode(m_cur, m_cur->right);            delete m_cur;            m_cur = NULL;        }    }    else //叶子节点、无子树的根节点直接删除    {        SetNode(m_cur, NULL);        delete m_cur;        m_cur = NULL;    }    return false;}bool BST::CompareTree(const Node *r1,Node* r2) const{    if (r1 == NULL && r2 == NULL)    {        return true;    }    if (r1 == NULL || r2 == NULL)    {        return false;    }    if (r1->val == r2->val)    {        if (CompareTree(r1->left, r2->left) && CompareTree(r1->right, r2->right))        {            return true;        }    }    return false;}bool BST::operator==(BST &t) const{    return CompareTree(GetRootNode(),t.GetRootNode());}void BST::Inorder(Node *p, string &st) const{    if (p == NULL)    {        return;    }    Inorder(p->left,st);    stringstream ss;    string t;    ss << p->val;    ss >> t;    st += t;    Inorder(p->right, st);}void BST::DestroyTree(Node *p){    if (p == NULL)    {        return;    }    DestroyTree(p->left);    DestroyTree(p->right);    delete p;}int main(){    //注释掉的为原始测试代码    //BST tree;    //BST tree1,tree2;    //string str = "567432";// "567043289";    //string str1 = "543267";    //string str2 = "576342";    //tree.CreateBST(str);    //tree1.CreateBST(str1);    //tree2.CreateBST(str2);    //if (tree2 == tree)    //{    //  cout << "same tree!!" << endl;    //}    //str.clear();    //tree.Inorder(tree.GetRootNode(),str);    //cout << str << endl;    //if(tree.FindNode(4))    //{    //  cout << "I find it !!" << endl;    //}    //else    //{    //  cout << "I find nothing!" << endl;    //}    //tree.DelNode(4);    //if (tree.FindNode(4))    //{    //  cout << "I find it !!" << endl;    //}    //else    //{    //  cout << "I find nothing!" << endl;    //}    //str.clear();    //tree.Inorder(tree.GetRootNode(), str);    //cout << str << endl;    int n = 0;    string str;    while (cin >> n >> str)    {        BST tree;        tree.CreateBST(str);        while (n--)        {            string match;            cin >> match;            BST treeMatch;            treeMatch.CreateBST(match);            if (tree == treeMatch)            {                cout << "YES" << endl;            }            else            {                cout << "NO"<< endl;            }        }    }    return 0;}

写在最后:完整的实现了二叉搜索树后回过来看,在实现删除功能时可以不增加父指针,增加的指针带来了替换操作的复杂度上升。直接导致这部分代码需要更多的时间调试。

0 0
原创粉丝点击