二叉查找树

2018-09-12  本文已影响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语言写的,并且使用的是递归的方法,感觉不是很好,于是又重新写了一次。

上一篇下一篇

猜你喜欢

热点阅读