树 - 二叉搜索树

二叉排序树 (Binary Sort Tree)

定义: 二叉排序树又称“二叉查找树”、“二叉搜索树”。

二叉排序树:或者是一棵空树,或者是具有下列性质的二叉树:

  1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

  2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

  3. 它的左、右子树也分别为二叉排序树。

数据结构: 二叉树一般采用二叉链表实现。

1. 数据结构

//define data type
typedef int keyType;

//define the data structure
typedef struct BinaryTreeNode
{
    keyType key;
    BinaryTreeNode * lchild;
    BinaryTreeNode * rchild;
} BinaryNode,*BinaryTree;


2. 插入

/** insert a Node into BinaryTree
 *  @params tree: The BinaryTree
 *  @params key: the Key of the inserting Node.
 *  @return 1: succeed, 2: fail
 **/
int insert(BinaryTree &tree, keyType key)
{
    //当不存在树,直接插入这个新结点
    if(tree == NULL) {
        tree = (BinaryNode *) malloc(sizeof(BinaryNode));
        tree->key = key; 
        return 1;
    }
    int found = 0;
    BinaryNode* curr = tree;
    BinaryNode* prev = NULL;
    //从根开始,与key比较,大往右子树,小往左子树,找到的话说明存在结点,不需要插入
    while (curr != NULL) {
        if (key == curr->key) {
            found = 1;
            break;
        }
        prev = curr;
        curr = (key > curr->key)? curr->rchild: curr->lchild;
    }
    //找不到的时候在最后查找停下来的位置插入新结点
    if(!found && curr == NULL) {
        curr = (BinaryNode*) malloc(sizeof(BinaryNode));
        curr->key = key;
        ((key > prev->key) ? prev->rchild:prev->lchild) = curr;
        return 1;
    }
    return 0;
}


3. 查找

//查找,成功返回node, 失败返回NULL
BinaryNode* search(BinaryTree tree, keyType key) 
{
    BinaryNode* node = tree;
    int found = 0;
    while(node != NULL) {
        if(key == node->key) {
            found = 1;
            break;
        }
        node = (key > node->key) ? node->rchild : node->lchild;
    }
    return found ? node : NULL;
}


3. 前序,中序,后序遍历

前序,中序,后序可以理解成是将root作为前序输出,中序输出,还是后序输出。

//前序遍历输出, 前序是指 根 最先输出
void pre_print(BinaryTree tree) 
{
    if(tree!=NULL) {
        cout << tree->key << " ";
        pre_print(tree->lchild);
        pre_print(tree->rchild);
    }
}

//后序遍历输出, 后序是指 根 最后输出
void after_print(BinaryTree tree) 
{
    if(tree!=NULL) {
        after_print(tree->lchild);
        after_print(tree->rchild);
        cout << tree->key << " ";
    }
}

//中序遍历输出, 中序是指 根 在中间输出
void inorder_print(BinaryTree tree) 
{
    if(tree!=NULL) {
        inorder_print(tree->lchild);
        cout << tree->key << " ";
        inorder_print(tree->rchild);
    }
}


4. 删除

删除是整个二叉搜索树编码难度最高的地方,需要考虑到各种情况。这里借用算法导论中的图,这张图非常清晰的列出所有可能性。
这里写图片描述
下面代码中3,4 采用要删除结点的左子树结点

int delete_key(BinaryTree tree, keyType key) {
    //设定一个临时的树根,他的key假设为无穷大,防止出现root被删除的情况。
    BinaryTree head = (BinaryNode *)malloc(sizeof(BinaryNode));
    head->key = INT_MAX;
    head->lchild = tree;

    //定位要删除的点
    BinaryNode *curr = tree, *prev = head;
    BinaryNode *t1 = NULL, *t2 = NULL;
    int found = 0;
    while(curr != NULL) {
        if(key == curr->key) {
            found = 1;
            break;
        }
        prev = curr;
        curr = ((key > curr->key) ? curr->rchild : curr->lchild); 
    }

    if(found) {

        //前两种情况只需要把要删除的点的左或者右结点给prev的结点接管就行。
        if(curr->lchild == NULL) {  //左子树为空,包括左右子树都为空的情况
            ((curr == prev->lchild) ? prev->lchild : prev->rchild) =  curr -> rchild;
            free(curr);
        } else if (curr->rchild == NULL)  //右子树为空
        {
            ((curr == prev->lchild) ? prev->lchild : prev->rchild) =  curr -> lchild;
            free(curr);
        } else { //左右子树都不为空
            t1 = curr -> lchild;
            while(t1 -> rchild != NULL) {  //循环找到左子树的右子树的右子树。。。一直下去,找到那个数来替换curr
                t2 = t1;
                t1 = t1 -> rchild;
            }
            curr -> key = t1 -> key;
            ((t2 == NULL) ? curr -> lchild : t2 -> rchild) = t1 -> lchild;//之前定义t2为NULL,如果t2为NULL,代表curr的左子树的右子树是空的。
            free(t1);
        }
    }

    //还原
    tree = head->lchild;
    free(head);

    return found;
}

先序遍历;中序遍历;后续遍历;层次遍历。事实上,知道任意两种方式,并不能唯一地确定树的结构,但是,只要知道中序遍历和另外任意一种遍历方式,就一定可以唯一地确定一棵树。

5. 用非递归的方法实现前序,中序,后序遍历

前序

前序的实现是最容易的,采用一个栈,先压入根,然后进入循环。先输出,再压入右子树,再压人左子树。

//非递归方法 前序
/**
 *  按先输出根节点,再压人右子树,再压入左子树
 **/ 
void PreOrder(BinaryTree tree)
{
    stack<BinaryTreeNode*> s;
    s.push(tree);
    BinaryTreeNode *curr = tree;
    while(!s.empty()) {
        curr = s.top();  
        s.pop();  //因为c++ 的stack.pop()不返回值,只是删除栈顶元素。所以使用top(先返回值)
        cout << curr->key << " ";
        if(curr->rchild != NULL) {
            s.push(curr->rchild);
        }
        if(curr->lchild != NULL) {
            s.push(curr->lchild);
        }       
    }
}
中序

中序的思路也容易一些,往左子树压到底,然后开始弹出打印,并试图进入右子树,没有就继续弹,有就继续深入右儿子的左子树。

void InOrder(BinaryTree tree) {
    stack<BinaryTree> s;
    BinaryTreeNode * curr = tree;
    while(!s.empty() || curr!=NULL) {
        if(curr != NULL) {
            s.push(curr);
            curr = curr->lchild;
        }else{
            curr = s.top();
            s.pop();
            cout << curr->key << " ";
            curr = curr->rchild;
        }
    }
}
后序

后序的实现相对困难,先看两个栈的实现方法
用s2来保存最后的顺序,循环,每次把s1栈顶的元素压入s2,再压入s1不为空的左子树和右子树。

void PosOrder(BinaryTree tree)
{
    stack<BinaryTreeNode*> s1;
    stack<BinaryTreeNode*> s2;
    s1.push(tree);
    BinaryTreeNode *curr;
    while(!s1.empty()) {
        curr = s1.top();
        s1.pop();
        s2.push(curr);
        if(curr->rchild != NULL) {
            s1.push(curr->rchild);
        }
        if(curr->lchild != NULL) {
            s1.push(curr->lchild);
        }
    }
    while(!s2.empty()) {
        cout << s2.top()->key << " ";
        s2.pop();
    }
}

用一个栈也可以实现
用h来表示上一个打印的结点。循环分3种情况
1. 左儿子不为空,并且左儿子不是上一个打印的结点,右儿子也不是上一个打印的结点。这时候就继续压入左儿子。
2. 如果左儿子是上一个被打印的结点,然后右儿子不为空,那么说明左子树已经打印完,那么深入右子树
3. 如果右儿子是上一个被打印的结点,或者该树左右儿子都是空,那么直接打印这个结点,然后赋值h

void PosOrderOneStack(BinaryTree tree)
{
    stack<BinaryTreeNode*> s;
    s.push(tree);
    BinaryTreeNode* h = tree;
    BinaryTreeNode* c;
    while(!s.empty()) {
        c = s.top();
        if(c->lchild != NULL && h != c->lchild &&  h != c->rchild) {
            s.push(c->lchild);
        }else if(c->rchild != NULL && h!= c->rchild) {
            s.push(c->rchild);
        }else{
            cout << s.top()->key << " ";
            s.pop();
            h = c;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值