算法训练营 查找算法(二叉查找树)

二叉查找树

原理

  • 二叉查找树,又叫作二叉搜索树、二叉排序树,是一种对查找和排序都有用的特殊二叉树
  • 二叉查找树或是空树,或是满足如下性质的二叉树:
    ①若其左子树非空,则左子树上所有节点的值均小于根节点的值;
    ②若其右子树非空,则右子树上所有忽而短板的值均大于根节点的值;
    ③其左右子树本身各是一棵二叉查找树。
  • 二叉查找树的特性:左子树<根<右子树,即二叉查找树的中序遍历是一个递增序列。

二叉查找树的查找

  • 因为二叉查找树的中序遍历有序性,所以查找与二分查找类似,每次都缩小查找范围,查找效率较高
算法设计
  1. 若二叉查找树为空,查找失败,则返回空指针
  2. 若二叉查找树非空,则将待查找关键字x与根节点的关键字T->data进行比较。
  • x == T->data,查找成功,则返回T
  • x < T->data,则递归查找左子树
  • x < T->data,则递归查找右子树

二叉树的插入

  • 因为二叉查找树的中序遍历存在有序性,所以首先要查找待插入关键字的插入位置,当查找不成功时,再将待插入关键字作为新的叶子节点称为最优一个查找节点的左孩子或右孩子
算法设计
  1. 若二叉查找树为空,则创建一个新的节点s,将待插入关键字放入新节点的数据域,将s节点作为根结点,左右子树均为空。
  2. 若二叉查找树非空,则将待查找关键字x与根结点的关键字T->data进行比较。
  • x<T->data,则将x插入左子树中
  • x>T->data,则将x插入右子树中

二叉查找树的创建

  • 二叉查找树的创建可以从空树开始,按照输入关键字的顺序依次进行插入操作,最终得到一颗二叉查找树。
算法设计
  1. 初始化二叉查找树为空树,T = NULL
  2. 输入一个关键字x,将x插入二叉查找树T中;
  3. 重复步骤2,直到关键字输入完毕

二叉查找树的删除

  • 首先要在二叉查找树中找到待删除节点,然后执行删除操作。假设指针p指向待删除节点,指针f指向p的双亲节点。根据待删除节点所在位置的不同,删除操作的处理方法也不同,可以分为下面三种情况。
  1. 被删除节点的左子树为空,如果被删除节点的左子树为空,则令其右子树子承父业代替其位置即可。
  2. 被删除节点的右子树为空。如果被删除节点的右子树为空,则令其左子树子承父业代替其位置即可。
  3. 被删除节点的左右子树均不为空。根据二叉查找树的中序有序性,删除该节点时,可以用其直接前驱(或直接后继)代替其位置,然后删除其直接前驱(或直接后继)即可。
算法设计
  1. 在二叉查找树中查找待删除关键字的位置,p指向待删除节点,f指向p的双亲节点,如果查找失败,则返回。
  2. 如果查找成功,则分三种情况进行删除操作。
  • 如果被删除节点的左子树为空,则令其右子树子承父业代替其位置即可。
  • 如果被删除节点的右子树为空,则令其左子树子承父业代替其位置即可。
  • 如果被删除节点的左右子树均不为空,则令其直接前驱(或者直接后继)代替它,再删除其直接前驱(或直接后继)

算法实现

#include<iostream>
using namespace std;
#define ENDFLAG -1
typedef int ElemType;
typedef struct BSTNode{
    ElemType data;	//结点数据域
    BSTNode *lchild,*rchild;	//左右孩子指针
}BSTNode,*BSTree;
BSTree SearchBST(BSTree T,ElemType key); //二叉排序树的递归查找
void InsertBST(BSTree &T,ElemType e); //二叉排序树的插入
void CreateBST(BSTree &T ); //二叉排序树的创建
void DeleteBST(BSTree &T,char key);
void InOrderTraverse(BSTree &T);//中序遍历
int main()
{
    BSTree T;
    cout<<"请输入一些整型数,-1结束"<<endl;
    CreateBST(T);
    cout<<"当前有序二叉树中序遍历结果为"<<endl;
    InOrderTraverse(T);
    cout<<endl;
    ElemType key;//待查找或待删除内容
    cout<<"请输入待查找关键字"<<endl;
    cin>>key;
    BSTree result=SearchBST(T,key);
    if(result)
        cout<<"找到"<<key<<endl;
    else
        cout<<"未找到"<<key<<endl;
    cout<<"请输入待删除关键字"<<endl;
    cin>>key;
    DeleteBST(T,key);
    cout<<"当前有序二叉树中序遍历结果为"<<endl;
    InOrderTraverse(T);
    return 0;
}

BSTree SearchBST(BSTree T,ElemType key){
    //若查找成功,则返回指向该数据元素结点的指针,否则返回空指针
    if((!T)|| key==T->data)
        return T;
    else if (key<T->data)
        return SearchBST(T->lchild,key);//在左子树中继续查找
    else
        return SearchBST(T->rchild,key); //在右子树中继续查找
}

void InsertBST(BSTree &T,ElemType e){
    //当二叉排序树T中不存在关键字等于e的数据元素时,则插入该元素
    if(!T)
    {
        BSTree S=new BSTNode; //生成新结点
        S->data=e;             //新结点S的数据域置为e
        S->lchild=S->rchild=NULL;//新结点S作为叶子结点
        T=S;            		//把新结点S链接到已找到的插入位置
    }
    else if(e<T->data)
        InsertBST(T->lchild,e );//插入左子树
    else if(e>T->data)
        InsertBST(T->rchild,e);//插入右子树
}

void CreateBST(BSTree &T ){
    //依次读入一个关键字为key的结点,将此结点插入二叉排序树T中
    T=NULL;
    ElemType e;
    cin>>e;
    while(e!=ENDFLAG)//ENDFLAG为自定义常量,作为输入结束标志
    {
        InsertBST(T,e);  //插入二叉排序树T中
        cin>>e;
    }
}

void DeleteBST(BSTree &T,char key){
    //从二叉排序树T中删除关键字等于key的结点
    BSTree p=T;BSTree f=NULL;
    BSTree q;
    BSTree s;
    if(!T) return; //树为空则返回
    while(p)//查找
    {
        if(p->data==key) break;  //找到关键字等于key的结点p,结束循环
        f=p;                //f为p的双亲
        if (p->data>key)
            p=p->lchild; //在p的左子树中继续查找
        else
            p=p->rchild; //在p的右子树中继续查找
    }
    if(!p) return; //找不到被删结点则返回
    //三种情况:p左右子树均不空、无右子树、无左子树
    if((p->lchild)&&(p->rchild))//被删结点p左右子树均不空
    {
        q=p;
        s=p->lchild;
        while(s->rchild)//在p的左子树中继续查找其前驱结点,即最右下结点
        {
            q=s;
            s=s->rchild;
        }
        p->data=s->data;  //s的值赋值给被删结点p,然后删除s结点
        if(q!=p)
            q->rchild=s->lchild; //重接q的右子树
        else
            q->lchild=s->lchild; //重接q的左子树
        delete s;
    }
    else
    {
        if(!p->rchild)//被删结点p无右子树,只需重接其左子树
        {
            q=p;
            p=p->lchild;
        }
        else if(!p->lchild)//被删结点p无左子树,只需重接其右子树
        {
            q=p;
            p=p->rchild;
        }
        /*――――――――――将p所指的子树挂接到其双亲结点f相应的位置――――――――*/
        if(!f)
            T=p;  //被删结点为根结点
        else if(q==f->lchild)
            f->lchild=p; //挂接到f的左子树位置
        else
            f->rchild=p;//挂接到f的右子树位置
        delete q;
    }
}

void InOrderTraverse(BSTree &T){
    if(T)
    {
        InOrderTraverse(T->lchild);
        cout<<T->data<<"\t";
        InOrderTraverse(T->rchild);
    }
}

输入与输出:

请输入一些整型数,-1结束
35 98 65 44 34 87 98 -1
当前有序二叉树中序遍历结果为
34      35      44      65      87      98
请输入待查找关键字
44
找到44
请输入待删除关键字
87
当前有序二叉树中序遍历结果为
34      35      44      65      98

训练1:落叶

题目描述

给定一个字母二叉搜索树的树叶删除序列,输出树的先序遍历。

输入:输入包含多个测试用例。每个测试用例都是一行或多行大写字母序列,每行都给出按上述描述步骤从二叉搜索树中删除的树叶,每行给出的字母都按字母升序排列。在测试用例之间以一行分隔,该行仅包含一个星号“*”。在最后一个测试用例后给出一行,该行仅给出一个符号“$”。在输入中没有空格或空行。

输出:对于每个测试用例,都有唯一的二叉搜索树,单行输出该树的先序遍历。

算法设计

  • 最后一个字母一定为树根,先输入的字母在树的深层,可以逆序建树。
  • 读入字母序列后用字符串存储,然后逆序创建二叉搜索树;
  • 将小的字母插入左子树中,将大的字母插入右子树中。输出该数的先序遍历序列:根、左子树、右子树。

算法实现

#include<iostream>
#include<cstring>
#include<string>
using namespace std;
struct data{ //树结构
    int l,r;
    char c;
}tree[110];
int cnt=1;
void insert(int t,char ch); //在二叉搜索树中插入字符ch
void preorder(int t); //先序遍历
int main(){
    string s1,s;
    while(1){
        s="";
        memset(tree,0,sizeof(tree)); //初始化树
        while(cin>>s1&&s1[0]!='*'&&s1[0]!='$')
			s+=s1;
        for(int i=s.length()-1;i>=0;i--)
        	insert(1,s[i]);
        preorder(1);
        cout<<endl;
        if(s1[0]=='$')
			break;
    }
}
void insert(int t,char ch){
    if(!tree[t].c){
        tree[t].c=ch;
        return;
    }
    if(ch<tree[t].c){
        if(!tree[t].l){
            tree[++cnt].c=ch;
            tree[t].l=cnt;
        }
        else
            insert(tree[t].l,ch);
    }
    if(ch>tree[t].c){
        if(!tree[t].r){
            tree[++cnt].c=ch;
            tree[t].r=cnt;
        }
        else
            insert(tree[t].r,ch);
    }
}

void preorder(int t){
    if(!tree[t].c)
        return;
    cout<<tree[t].c;
    preorder(tree[t].l);
    preorder(tree[t].r);
}

输入:

BDHPY
CM
GQ
K
*
AC
B
$

输出:

KGCBDHQMPY

训练2:完全二叉搜索树

题目描述

有一颗无限的完全二叉树,节点中的数字是1,2,3,…如下图所示。在根节点为X的子树中,可以从左侧节点向下,直到最后一级获得该子树中的最小数,也可以从右侧节点向下找到该子树中的最大数。求解X的子树中的最小数和最大数是多少。

输入:第1行包含一个整数 N N N,表示查询的数量。在接下来的 N N N行中,每行都包含一个数字,表示根号位 X X X的子树( 1 ≤ X ≤ 2 31 − 1 1 \leq X \leq 2^{31}-1 1X2311

输出:共 N N N行,其中第 i i i行包含第 i i i个查询的答案

算法设计

  • 有规律可循,若 n n n为奇数,那么必然是叶子节点,最大数和最小数都是它自己,否则求 n n n所在的层数(倒数的层数,底层为0层),它的层数就是 n n n的二进制表示中从低位开始第1个1所处的位置i(最后一个非0位)。
  • 例如, 6 = ( 110 ) 2 6 = (110)_{2} 6=(110)2,“110”从低位开始第1个“1”的位置为1,因此6在第1层: 12 = ( 1100 ) 2 12 = (1100)_{2} 12=(1100)2,“1100”从低位开始第1个“1”的位置为2,因此12在第2层
  1. 求解lowbit(n) = n & (-n)
  2. k = lowbit(n)-1,输出最小数n-k,最大数n+k

算法实现

#include<iostream>
using namespace std;

int lowbit(int n){
	return n&(-n);
}

int main(){
	int T,n,k;
	cin>>T;
	while(T--){
		cin>>n;
		k=lowbit(n)-1;
		cout<<n-k<<" "<<n+k<<endl;
	}
	return 0;
}

输入:

2
8
10

输出:

1 15
9 11

训练3:硬木种类

题目描述

某国有数百种硬木树种,该国自然资源部利用卫星成像技术编译了一份特定日期每棵树的物种清单。计算每个物种占所有种群的百分比

输入:输入包括没课树的物种清单,每行一棵树。物种名称不超过30个字符,卜曹刚1000000棵树。

输出:按字母顺序输出植物种群中代表的每个物种的名称,然后是占所有种群的百分比,保留小数点后4位。

算法设计

  1. 使用二叉搜索树,先将每个单词都存入二叉树中,没出现一次就修改该单词所在节点cnt++;最后通过中序遍历输出结果
  2. 排序后统计,并输出结果

算法实现

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
int sum=0;
typedef struct node{
    string word;
    struct node *l,*r;
    int cnt;
}*nodeptr;
nodeptr rt;
string w;
void insertBST(nodeptr &root,string s); //将字符串s插入到二叉搜索树root中
void midprint(nodeptr root); //中序遍历
int main(){
    rt=NULL;//一定要初始化
    while(getline(cin,w)){//输入完回车,按ctrl+z,回车
        insertBST(rt,w);
        sum++;
    }
    midprint(rt);
    return 0;
}
void insertBST(nodeptr &root,string s){//将字符串s插入到二叉搜索树root中
    if(root==NULL){
        nodeptr p=new node;
        p->l=p->r=NULL;
        p->cnt=1;
        p->word=s;
        root=p;
    }
    else if(s==root->word)
        root->cnt++;
    else if(s<root->word)
        insertBST(root->l,s);
    else
        insertBST(root->r,s);
}

void midprint(nodeptr root){//中序遍历
    if(root!=NULL){
        midprint(root->l);
        cout<<root->word;
        printf(" %.4lf\n",((double)root->cnt/(double)sum)*100);
        midprint(root->r);
    }
}

输入:

Red Alder
Ash
Aspen
Basswood
Ash
Beech
Yellow Birch
Ash
Cherry
Cottonwood
Ash
Cypress
Red Elm
Gum
Hackberry
White Oak
Hickory
Pecan
Hard Maple
White Oak
Soft Maple
Red Oak
Red Oak
White Oak
Poplan
Sassafras
Sycamore
Black Walnut
Willow
#

输出:

Ash 13.7931
Aspen 3.4483
Basswood 3.4483
Beech 3.4483
Black Walnut 3.4483
Cherry 3.4483
Cottonwood 3.4483
Cypress 3.4483
Gum 3.4483
Hackberry 3.4483
Hard Maple 3.4483
Hickory 3.4483
Pecan 3.4483
Poplan 3.4483
Red Alder 3.4483
Red Elm 3.4483
Red Oak 6.8966
Sassafras 3.4483
Soft Maple 3.4483
Sycamore 3.4483
White Oak 10.3448
Willow 3.4483
Yellow Birch 3.4483

训练4:二叉搜索树

题目描述

判断两个序列是否为同一个二叉搜索树序列

输入:第1行包含一个数n(1 \leq n \leq 20),表示有n个序列需要判断,在n = 0时输入结束。接下来的一行是一个序列,序列长度小于10,包含0~9的数字,没有重复的数字,根据这个序列可以构造出一颗二叉搜索树。再接下来的n行有n个序列,每个序列的格式都跟第1个序列一样,请判断这两个序列能否组成同一颗二叉树。

输出:如果序列相同,则输出“YES”,否则输出“NO”

算法设计

  • 可以先通过判断二叉搜索树的先序遍历序列是否相同来输出结果。
  • 因为根据二叉搜索树的中序有序性,先序遍历序列相同,中序遍历序列也一定相同。
  • 通过先序遍历序列和中序遍历序列可以唯一确定一颗二叉树
  1. 使用二叉搜索树,先将每个数字都存进二叉搜索树中,得到先序遍历;
  2. 将后面每一行的每个数字都存进二叉搜索树中,得到先序遍历,比较其是否相等,如果相等,则输出“YES”,否则输出“NO”。

算法实现

#include<cstdio>
#include<cstring>
using namespace std;
int cnt;
char a[15],b[15],c[15];
typedef struct node{
    int num;
    node *lc,*rc;
}*nodeptr;
nodeptr root;
void Insert(nodeptr &t,int x); //将x插入到二叉搜索树t中
void preorder(nodeptr t,char b[]); //中序遍历
int main(){
    int n;
    while(scanf("%d",&n),n){
        cnt=0;
        root=NULL;
        scanf("%s",a);
        for(int i=0;a[i]!='\0';i++)
            Insert(root,a[i]-'0');
        preorder(root,b);
        b[cnt]='\0';
        while(n--){
            cnt=0;
            root=NULL;
            scanf("%s",a);
            for(int i=0;a[i]!='\0';i++)
                Insert(root,a[i]-'0');
            preorder(root,c);
            c[cnt]='\0';
            if(strcmp(b,c)==0)//c++字符串可以用==比较 
                printf("YES\n");
            else
                printf("NO\n");
        }
    }
    return 0;
}
void Insert(nodeptr &t,int x){//将x插入到二叉搜索树t中 
    nodeptr p;
    if(t==NULL){
        p=new node;
        p->lc=NULL;
        p->rc=NULL;
        p->num=x;
        t=p;
    }
    else{
        if(x<=t->num)
            Insert(t->lc,x);//插入到左子树 
        else
            Insert(t->rc,x);//插入到右子树 
    }
}

void preorder(nodeptr t,char b[]){//中序遍历 
    if(t){
        b[cnt++]=t->num+'0';
        preorder(t->lc,b);
        preorder(t->rc,b);
    }
}

输入:

2
567432
543267
576342
0

输出:

YES
NO
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羽星_s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值