灵活&&高效的符号表--二叉查找树

一丶定义

一颗二叉查找树是一颗二叉树,其中每个结点的键都大于其任左子树任意结点的键而小于右子树任意结点的键。如标题所述,它有着链表插入的灵活性和有序数组查找的高效性。

二丶基本实现

注:本文采用的是C++语言实现 但算法与数据结构是一种思想 与语言无关。

1.查找

查找的思路比较简单,需要查找的键存在当前树中就直接返回相应的值,如果查找的键小于根结点就在左子树中继续递归查找,大于就在右子树中查找,最后没查找到就直接返回一个空链接。
相关代码如下

if ( node == NULL) {
     return NULL;
}
if (key == node->key) {
   return &(node->value);
} else if (key < node->key) {
    return search(node->left,key);
} else {
    return search(node->right,key);
}

2.插入

插入的思路与查找差不多,插入时树如果为空,就返回含有一个该键值对的新结点。如果被查找的键小于根结点的键就继续在左子树中插入该键,大于则往右子树插入。代码如下

if (node == NULL) {
      count++;
      return new Node(key,value);
 }
if (key == node->key) {
   node->value = value;
} else if(key < node->key) {
   node->left = insert(node->left,key,value);
} else if (key > node->key) {
   node->right = insert(node->right,key,value);
}

return node;

3.删除操作

首先简单介绍下删除最大与最小结点。直接分别递归在左丶右子树中找到最小与最大的结点,删除后返回新的二叉树所对应的根结点。
如下图所示


这里重点介绍下删除任意结点的操作。因为我们在进行删除操作时必须还有保持二叉查找树的有序性。所以删除当前结点后还要进行适当的操作,有2种选择: 第一种方法是取当前结点(也就是被删除的结点)的右子树中的最小结点替换被删除结点的位置,第二种方法是取当前结点的左子树中的最大结点替换被删除结点的位置。注意在这个过程我们需要用到查找最小与最大结点的函数 其实看了前面的查找插入后这个实现是很简单的 后面会给出代码。然后所取结点的左端与右端分别连接到被删除结点的左右端,同理删除之前先要判断结点是否为空,以及不为空时的位置。如下图(暂时只有第一种情况 不过思路一样 后面会给出)



看看上面的删除最大或者最小结点时 返回的是当前结点。所以这里的successor的右结点就直接指向这个方法的返回值。每次删除与替换时要注意递减与递增操作。

4.遍历

二叉树叉遍历分为前序丶后序丶中序遍历以及层序遍历。
前序遍历:先遍历当前结点→然后遍历左子树→最后遍历右子树 。
中序遍历:先遍历左子树→然后遍历当前结点→最后遍历右子树。
后序遍历:先遍历左子树→然后遍历右子树→最后遍历当前结点。
层序遍历:也称广度优先遍历,从根结点开始在每层从左至右进行遍历。
具体就是直接按顺序递归



上面三种遍历的实现没什么区别,这里重点介绍下层序遍历。这里可以创建一个队列,然后把根结点push进去,再用一个循环,得到一个队首的元素,pop出队列,并判断是当前结点是否有左右孩子,有就push进队列,然后继续循环直到队列为空。如下所示


5.向上与向下取整操作

向上取整(floor):查找小于当前结点的最大值。如果给定的键与根结点的键相等,那么根结点本身就是floor结点。如果给定的键小于根结点的键,那么floor结点一定位于根结点的左子树中;如果给定的键大于根结点的键,那么根结点可能就是floor结点,也可能不是,所以要继续在根结点的右子树中递归查找。代码如下图



向下取整(ceil):查找大于当前结点的最小值。同样,如果给定的键与根结点相等,那么根结点本身就是ceil结点。如果给定的键大于根结点的键,那么ceil结点一定位于根结点的右子树中;如果给定的键小于根结点的键,那么根结点可能就是ceil结点,也可能不是,所以要继续在根结点的左子树中递归查找。代码如下图


6.选择与排名

选择(select):现在我们要查找排名为k的键,如果左子树中的结点数count大于k,就继续递归的在左子树中查找排名为k的键;如果k等于count,就直接返回当前结点;如果count小于k,就继续递归的在右子树中查找排名为k-count-1的键。
代码如下图



排名(rank):rank()是select()的逆过程,它返回给定键的排名。如果给定的键和根结点的键相等,则返回根结点的左子树中结点的总数。如果给定的键小于根结点,就递归的计算该结点在左子树的排名;如果给定的键大于根结点,就返回根点的数量加上它在右子树中的排名(递归计算)。
代码如下图


这里写图片描述

三丶总结

总的来说,二叉树的实现并不困难,且当树的构造和随机模型近似时在各种实际应用场景它都能进行快速的查找和插入,它高效的解决了很多在之前看似不可能的任务,本文也只是介绍了一些常用的,更多实现可以自己去搜索。不过二叉查找树也有自己的缺点,它在最坏情况下(也就是树的所有操作都沿着一条或两条路径进行)所需的时间和树的高度成正比。于是为了解决问题就出现了后来的平衡二叉树和红黑树,这里就不多说了。最后附上二叉查找树的全部代码,代码虽然有点长,但是每一部分理解了整体理解就不难了。

template <typename Key,typename Value>
class BST {

private:
    struct Node {
        Key key;
        Value value;
        Node *left;
        Node *right;
        int count;

        Node(Key key,Value value,int count) {
            this->key = key;
            this->value = value;
            this->left = this->right = NULL;
            this->count = count = 0;
        }

        Node(Node *node) {
            this->key = key;
            this->value = value;
            this->left = node->left;
            this->right = node->right;
        }
    };

    Node *root;
    int *count;

public:
    BST() {
        root = NULL;
        count = 0;
    }
    ~BST() {
        destroy(root);
    }

    int size() {
        return *count;
    }

    bool isEmpty() {
        return count == 0;
    }

    void insert(Key key,Value value) {

        root = insert(root,key,value);
    }

    bool contain(Key key) {

        return contain(root,key);
    }

    Value* search(Node* node,Key key) {

        if ( node == NULL) {
            return NULL;
        }

        if (key == node->key) {
            return &(node->value);
        } else if (key < node->key) {
            return search(node->left,key);
        } else {
            return search(node->right,key);
        }

    }

    //前序遍历
    void preOrder() {
        preOrder(root);
    }

    //中序遍历
    void inOrder() {
        inOrder(root);
    }

    //后序遍历
    void postOrder() {
        postOrder(root);
    }

    //层序遍历
    void levelOrder() {

        queue<Node*> q;
        q.push(root);
        while (!q.empty()) {

            Node* node = q.front();
            q.pop();

            cout << node->key << endl;

            if (node->left) {
                q.push(node->left);
            }
            if (node->right) {
                q.push(node->right);
            }
        }
    }

    // 寻找最小的键值
    Key mininum() {

        assert( count!= 0);
        Node* minNode = mininum(root);

        return minNode->key;
    }

    // 寻找最大的键值
    Key maxnum() {

        assert( count != 0);
        Node* maxNode = maxnum(root);
        return maxNode->key;
    }

    // 从二叉树中删除最小值所在节点
    void removeMin() {

        if (root) {
            root = removeMin(root);
        }
    }

    // 从二叉搜索树中删除最小值所在节点
    void removeMax() {

        if (root) {
            root = removeMax(root);
        }
    }

    // 从二叉搜索树中删除键值为key的节点
    void remove(Key key) {

        root = remove(root,key);
    }

    // 获取小于当前结点的最大结点
    Key* floor(Key key) {

        if ( count == 0 || key < mininum()) {
            return NULL;
        }

        Node *floorNode = floor(root,key);
        return &(floorNode->key);
    }

    // 获取大于当前结点的最小结点
    Key* ceiling(Key key) {

        if ( count == 0 || key > maxnum())
            return NULL;

        Node *ceilNode = ceil(root,key);
        return &(ceilNode->key);

    }

    // select操作
    Key* select(int k) {
        return select(root,k)->key;
    }

    // rank操作
    Key* rank(Key key) {
        return rank(key,root);
    }

private:
    // 向以node为根的二叉搜索树中 插入节点(key,value)
    // 返回插入新节点后的二叉树的根
    Node* insert(Node *node,Key key,Value value) {

        if (node == NULL) {
            count++;
            return new Node(key,value,count);
        }

        if (key == node->key) {
            node->value = value;
        } else if(key < node->key) {
            node->left = insert(node->left,key,value);
        } else if (key > node->key) {
            node->right = insert(node->right,key,value);
        }

        return node;
    }

    // 查看以node为根的二叉搜索树中是否包含键值为key的节点
    bool contain(Node* node,Key key) {

        if ( node == NULL) {
            return false;
        }

        if (key == node->key) {
            return true;
        } else if(key < node->key){
            return contain(node->left,key);
        } else {
            return contain(node->right,key);
        }
    }


    //对以node为根的二叉树进行前序遍历
    void preOrder(Node* node) {

        if (node != NULL) {
            cout << node->key << endl;
            preOrder(node->left);
            preOrder(node->right);
        }
    }

    //对以node为根的二叉树进行中序遍历
    void inOrder(Node* node) {

        if (node != NULL) {
            inOrder(node->left);
            cout << node->key << endl;
            inOrder(node->right);
        }
    }

    //对以node为根的二叉树进行后序遍历
    void postOrder(Node* node) {

        if (node != NULL) {
            postOrder(node->left);
            postOrder(node->right);
            cout << node->key <<endl;
        }
    }

    void destroy(Node* node) {

        if (node != NULL) {
            destroy(node->left);
            destroy(node->right);

            delete node;
            count--;
        }
    }

    // 寻找最小的键值对应的节点
    Node* mininum(Node* node) {

        if (node->left == NULL) {
            return node;
        }
        return mininum(node->left);
    }

    // 寻找最大的键值对应的节点
    Node* maxnum(Node* node) {

        if (node->right == NULL) {
            return node;
        }
        return maxnum(node->right);
    }

    // 从二叉搜索树中删除最小值所在节点
    Node* removeMin(Node* node) {

        if ( node->left == NULL) {

            Node* rightNode = node->right;
            delete node;
            count--;
            return rightNode;
        }

        node->left = removeMin(node->left);
        return node;
    }


    // 从二叉搜索树中删除最大值所在节点
    // 返回删除节点后新的二分搜索树的根
    Node* removeMax(Node* node) {

       if (node->right == NULL) {

           Node* leftNode = node->left;
           delete node;
           count--;
           return leftNode;
       }

        node->right = removeMax(node->right);
        return node;
    }

    // 删除以node为根的二分搜索树中键值为key的节点
    // 返回删除节点后新的二分搜索树的根
    Node* remove(Node* node,Key key) {

        if (node == NULL) {
            return NULL;
        }

        if (key < node->key) {
            node->left = remove(node->left,key);
            return node;
        } else if (key > node->key) {
            node->right = remove(node->right,key);

            return node;
        } else {

            if (node->left == NULL) {
                Node *rightNode = node->right;
                delete node;
                count--;
                return rightNode;
            }
            if (node->right == NULL) {
                Node *leftNode = node->left;
                delete node;
                count--;
                return leftNode;
            }
            //以要删除节点的右子树中最小的节点替换被删除节点的位置
            Node *successor = new Node(mininum(node->right));
            count++;

            successor->right = removeMin(node->right);
            successor->left = node->left;

            delete node;
            count--;
            return successor;
        }
    }

    // the second remove
    Node* remove2(Node* node,Key key) {

        if (node == NULL) {
            return NULL;
        }

        if (key < node->key) {
            node->left = remove(node->left,key);
        } else if (key > node->key) {
            node->right = remove(node->right,key);
        } else {
            if (node->left == NULL) {
                Node *rightNode = node->right;
                delete node;
                count--;
                return rightNode;
            }
            if (node->right == NULL) {
                Node *leftNode = node->left;
                delete node;
                count--;
                return leftNode;
            }

            //以要删除的节点的左子树中最大的节点替换被删除节点的位置
            Node* successor = new Node(maxnum(node->left));
            count++;

            successor->left = remove2(node->left,key);
            successor->right = node->right;

            delete node;
            count--;

            return successor;
        }


    }

    // 向上取整
    Node* floor(Node* node,Key key) {
        if (node ==NULL) {
             return NULL;
        }
        if (node->key == key) {
            return node;
        }
        if (node->key > key) {
            return floor(node->left,key);
        } else {
            Node* t = floor(node->right,key);
            if (t != NULL)
                return t;
            return node;
        }
    }

    // 向下取整
    Node* ceil(Node* node,Key key) {
        if (node == NULL) {
            return NULL;
        }
        if (node->key == key) {
            return node;
        }
        if (node->key < key) {
            return ceil(node->right,key);
        } else {
            Node* t = floor(node->left,key);
            if (t != NULL)
                return t;
            return node;
        }
    }

    // 查找排名为k的键
    Node* select(Node* node,int k) {
        if ( node == NULL)
            return NULL;
        int n = node->count; //当前的结点总数
        if (n > k)
            return select(node->left,k);
        else if (n < k)
            return select(node->right,k-n-1);
        else
            return node;
    }

    // 计算给定键的排名
    int rank(Key key,Node* node) {
        if ( node == NULL)
            return NULL;
        if (key < node->key) {

            return rank(key,node->left);
        } else if (key > node->key) {
            return 1 + node->count + rank(key,node->right);
        } else {
            return node->count;
        }
    }
};

参考《c++算法与数据结构》和 《算法第4版》。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容