美文网首页
二叉查找树

二叉查找树

作者: 漫游之光 | 来源:发表于2018-09-12 21:27 被阅读0次

    树是n(n大于等于0)个节点的有限集,且这些节点满足以下关系:

    1. 有且仅有一个结点没有父节点,该结点称为树的根;
    2. 除根外,其余每个节点有且仅有一个父结点;
    3. 树的每个节点都构成以它为根的树。

    树的表示,由于每个节点的儿子数可以变化很大而且事先不知道,所以一般树的表示方法是儿子-兄弟表示法。具体的结构体如下:

    struct TreeNode{
        Type element;
        struct TreeNode* firstChild; //指向最左边的孩子
        struct TreeNode* nexrSibling; //指向右边的兄弟结点
    };
    

    二叉树

    二叉树在满足树的条件的同时,满足以下条件:每个节点最多有两个子树,这两个子树有左右之分,次序不可颠倒。

    二叉树的表示:二叉树可以用以下的结构体保存,用链表方式存储:

    struct TreeNode{
        Type element;
        struct TreeNode* left;
        struct TreeNode* right;
    };
    

    也可以用数组存放,按从上至下、从左到右顺序存储n个结点的二叉树的结点父子关系。若起始结点的编号为1,则编号为i的结点的左孩子在2i,右孩子在2i+1。

    二叉树的深度遍历

    二叉树的深度遍历分为前序、中序和后序。这里的前中后指的是访问根节点的时机,如果一开始就访问根节点,称为前序;在中间访问称为中序;最后访问称为后序。具体的代码如下。

    struct TreeNode{
        int val;
        TreeNode *left;
        TreeNode *right;
        TreeNode(int x):val(x),left(NULL),right(NULL){}
    };
    
    void depthTraversal(TreeNode *node){
        if(node == NULL){
            return;
        }
        printf("%d ",node->val); //前序
        depthTraversal(node->left);
        // 此时访问node为中序
        depthTraversal(node->right);
        // 此时访问node为后序
    }
    

    二叉树的层次遍历

    二叉树的层次遍历,就和图的广度优先搜索基本上是一个框架,所以这里直接给出代码。

    void breadthTraversal(TreeNode *root){
        if(root == NULL){
            return;
        }
        queue<TreeNode *> q;
        q.push(root);
        while(!q.empty()){
            TreeNode *node = q.front();
            q.pop();
            printf("%d ",node->val);
            if(node->left != NULL){
                q.push(node->left);
            }
            if(node->right != NULL){
                q.push(node->right);
            }
        }
    }
    

    二叉树的序列化和反序列化

    个人认为二叉树的序列化还是很有用的,主要的用途是在刷题的时候,遇到二叉树的题目时,能够迅速生成一颗二叉树进行测试。虽然像Letcode这种平台可以查看其测试的代码,但自己写的总还是要熟悉一点。

    这里提供的方式是层次遍历的方式,因为这种方式比较直观,代码这里参考的是左程云《程序员代码面试指南》里面的代码,只不过使用的语言改为了C++。下面看具体的代码。

    string serialize(TreeNode *root){
        if(root == NULL){
            return "# ";
        }
        queue<TreeNode *> q;
        q.push(root);
        string res = to_string(root->val) + " ";
        while(!q.empty()){
            TreeNode *node = q.front();
            q.pop();
            if(node->left != NULL){
                res += to_string(node->left->val) + " ";
                q.push(node->left);
            }else{
                res += "# ";
            }
            if(node->right != NULL){
                res += to_string(node->right->val) + " ";
                q.push(node->right);
            }else{
                res += "# ";
            }
        }
        return res;
    }
    
    TreeNode *getTreeNode(string s){
        if(s == "#"){
            return NULL;
        }
        return new TreeNode(stod(s));
    }
    
    TreeNode *deserialize(string data){
        istringstream iss(data);
        string s;
        vector<string> v;
        while(iss>>s){
            v.push_back(s);
        }
        int index = 0;
        TreeNode *root = getTreeNode(v[index++]);
        if(root == NULL){
            return root;
        }
        queue<TreeNode*> q; 
        q.push(root);
        while(!q.empty()){
            TreeNode *node = q.front();
            q.pop();
            node->left = getTreeNode(v[index++]);
            if(node->left != NULL){
                q.push(node->left);
            }
            node->right = getTreeNode(v[index++]);
            if(node->right != NULL){
                q.push(node->right);
            }
        }
        return root;
    }
    

    二叉查找树

    二叉查找树(Binary Search Tree), 它是一颗具有下列性质的二叉树:

    1. 若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
    2. 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
    3. 左、右子树也分别为二叉排序树。
    4. 等于的情况只能出现在左子树或右子树中的某一侧。

    根据《算法导论》里面的伪代码,自己实现了一下。首先,是定义结构。

    #include <iostream>
    #include <vector>
    #include <cstdlib>
    #include <ctime>
    #include<algorithm>
    using namespace std;
    
    struct TreeNode{
        int val;
        TreeNode *left;
        TreeNode *right;
        TreeNode(int x) : val(x), left(NULL), right(NULL) {}
        ~TreeNode(){
            if(left != NULL){
                delete left;
            }
            if(right != NULL){
                delete right;
            }
        }
    };
    
    void inorder(TreeNode *root){
        if(root == NULL){
            return;
        }
        inorder(root->left);
        cout<<root->val<<" ";
        inorder(root->right);
    }
    

    这里有一个辅助函数,inorder,主要是中序遍历该二叉树,判断是否已经是排好序的。

    查询

    搜索二叉树的查询和二分查找差不多,基本思想是,如果当前结点不为空且不等于查找值,如果查找值小于当前结点,就去左边查找(根据二叉查找树的性质,右边不可能存在要查找的值),否则,就去右边查找。下面是具体的代码。

    TreeNode *treeFind(TreeNode *root, int val){
        while(root != NULL && root->val != val){
            if(val < root->val){
                root = root->left;
            }else{
                root = root->right;
            }
        }
        return root;
    }
    

    插入

    插入的情况其实和查找差不多,就是根据插入值的大小,在左子树和右子树之间跳转,直到跳转到空结点。这里需要注意的是,在跳转的过程中,需要记录一个父节点。

    TreeNode* treeInsert(TreeNode *root, int val){
        TreeNode *insertNode = new TreeNode(val);
        TreeNode *parent = NULL; // 当前节点的父节点
        TreeNode *current = root; //当前节点
        while(current != NULL){
            parent = current;
            if(val < current->val){
                current = current->left;
            }else{
                current = current->right;
            }
        }
        if(parent == NULL){ // 树为空的情况
            root = insertNode;
        }else if(val < parent->val){
            parent->left = insertNode;
        }else{
            parent->right = insertNode;
        }
        return root;   
    }
    

    删除

    删除分为三种情况:

    1. 删除结点左子树为空,使用右子树替代。
    2. 删除结点右子树为空,使用左子树替代。
    3. 左右结点的不为空,使用右子树的最小值结点替代该结点。注意,替代的结点可能有右孩子,需要使用右孩子替代替代结点的位置。
    TreeNode* treeDelete(TreeNode *root, int val){
        if(root == NULL){
            return root;
        }
        TreeNode *current = root;
        TreeNode *parent = NULL;
        while(current != NULL && current->val != val){
            parent = current;
            if(val < current->val){
                current = current->left;
            }else{
                current = current->right;
            }
        }
        if(current == NULL){
            return root; // 没有对应的结点
        }
        if(current->left == NULL){ //只有右子树
            treeRepalce(current,parent, current->right,root);
        }else if(current->right == NULL){ //只有左子树
            treeRepalce(current,parent, current->left,root);
        }else{
            /* 先找到右子树的最小值,也就是中序的下一个节点 */ 
            TreeNode *node = current->right;
            TreeNode *nodeParent = current;
            while(node->left != NULL){
                nodeParent = node;
                node = node->left;
            }
            if(nodeParent != current){ /* 如果找到的不是查找结点的右子树 */
                treeRepalce(node,nodeParent,node->right,root); //该子树的父亲可能有右子树,需要调整
                node->right = current->right;
            }
            treeRepalce(current,parent,node,root);
            node->left = current->left;     
        }
        return root;
    }
    

    测试

    写了一个代码,大致测了一下,感觉没有大的问题。

    int main(int argc, char const *argv[])
    {
        srand(time(0));
        vector<int> v;
        for(int i=0;i<10;i++){
            v.push_back(rand() % 100);
            cout<<v[i]<<" ";
        }
        cout<<endl;
        TreeNode *root = NULL;
        for(int i=0;i<10;i++){
            root = treeInsert(root,v[i]);
        }
        inorder(root);
        cout<<endl<<endl;
    
        TreeNode *node;
        for(int i=0;i<10;i++){
            int val = rand() % 100;
            node = treeFind(root,val);
            if(node != NULL){
                cout<<"find "<<val<<" success"<<endl;
            }else{
                cout<<"find "<<val<<" failed"<<endl;
            }
        }
        cout<<endl;
    
        random_shuffle(v.begin(),v.end());
        for(int i=0;i<10;i++){
            cout<<v[i]<<" ";
        }
        cout<<endl;
    
        for(int i=0;i<10;i++){
            root =  treeDelete(root,v[i]);
            inorder(root);
            cout<<endl;
        }
        cout<<endl;
        delete root;
        return 0;
    }
    

    以前是用C语言写的,并且使用的是递归的方法,感觉不是很好,于是又重新写了一次。

    相关文章

      网友评论

          本文标题:二叉查找树

          本文链接:https://www.haomeiwen.com/subject/ucabgftx.html