【2018.2.2-】你真的会二叉搜索树了吗?(普通版本+普通拓展)

BST(Binary Search Tree,二叉搜索树)复习开始了>_<

 ps:所有题目如果未特别说明,时限均为1s。

easy(简单模板):

1.Falling Leaves(二叉搜索树练习)

Description

   

 上图给出一个字母二叉树的图的表示。熟悉二叉树的读者可以跳过字母二叉树、二叉树树叶和字母二叉搜索树的定义,直接看问题描述。

 一棵字母二叉树可以是下述两者之一:

   1,它可以是空树;

   2,它可以有一个根结点,每个结点以一个字母作为数据,并且有指向左子树和右子树的指针,左右子树也是字母二叉树。

   字母二叉树可以用图这样表示:空树忽略不计;每个结点这样标识:结点所包含的字母数据;如果左子树非空,向左下方的线段指向左子树;如果右子树非空,向右下方的线指向右子树。

   二叉树的树叶是一个子树都为空的结点。在上图的实例中,有5个树叶结点,数据为B,D,H,P和Y。

   字母树的前序遍历定义如下:

   如果树为空,则前序遍历也是空的;

   如果树不为空,则前序遍历按下述次序组成:访问根结点的数据;前序遍历根的左子树;前序遍历根的右子树。

   上图中树的前序遍历是KGCBDHQMPY。

   在上图中的树也是字母二叉搜索树。字母二叉搜索树是每个结点满足下述条件的字母二叉树:

   按字母序,根结点的数据在左子树的所有结点的数据之后,在右子树的所有结点的数据之前。

   请考虑在一棵字母二叉搜索树上的下述的操作序列:

   删除树叶,并将被删除的树叶列出;

   重复这一过程直到树为空。

   从下图中左边的树开始,产生树的序列如图所示,最后产生空树。

    

   移除的树叶数据为

   BDHPY

   CM

   GQ

   K

   本题给出这样一个字母二叉搜索树的树叶的行的序列,输出树的前序遍历。

Input

 

 输入给出一个或多个测试用例。每个测试用例是一个一行或多行大写字母的序列。

   每行给出按上述描述的步骤从二叉搜索树中删除的树叶。每行中给出的字母按字母序升序排列。测试用例之间用一行分隔,该行仅包含一个星号 (‘*’)。 

 在最后一个测试用例后,给出一行,仅给出一个美元标志 (‘\$’)。输入中没有空格或空行。

Output

   对于每个输入的测试用例,有唯一的二叉搜索树,产生树叶的序列。输出一行,给出该树的前序遍历,没有空格。

Sample Input

BDHPY
CM
GQ
K
*
AC
B
$

Sample Output

KGCBDHQMPY
BAC

 
明显模板题,敲个BST就好咯。
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 struct bst{
 4     char c;
 5     int lchild,rchild;
 6 }num[30];
 7 int N; //节点计数工具 
 8 char str[30][30];
 9 void add(int t,char c){
10     if(c<num[t].c){ //如果c比当前点字母小,就放在左儿子里
11         if(num[t].lchild==0){ //该点还没有左儿子,将c设为其左儿子 
12             num[N].c=c;
13             num[t].lchild=N++;
14             return ;
15         }
16         else add(num[t].lchild,c);
17     }
18     else { //如果c比当前字母大,就放在右儿子里 
19         if(num[t].rchild==0){ //该点还没有右儿子,将c设为其右儿子 
20             num[N].c=c;
21             num[t].rchild=N++;
22             return ;
23         }
24         else add(num[t].rchild,c);
25     }
26 }
27 void print(int t){
28     printf("%c",num[t].c);
29     if(num[t].lchild) print(num[t].lchild);
30     if(num[t].rchild) print(num[t].rchild);
31 }
32 
33 int main(){
34     int i,j,cont1,len;
35     while(~scanf("%s",str[0])){
36         memset(num,0,sizeof(num));
37         cont1=0;
38         while(isupper(str[cont1][0])) scanf("%s",str[++cont1]);
39         cont1--;
40         
41         N=0;
42         num[N++].c=str[cont1][0];
43         for(i=cont1-1;i>=0;i--){
44             len=strlen(str[i]);
45             for(j=0;j<len;j++)
46                 add(0,str[i][j]); //从根节点开始遍历,将字母加入二叉树 
47         }
48         print(0);
49         putchar('\n');
50         if(str[cont1+1][0]=='$') break;
51     }
52     return 0;
53 }
 

2.笛卡尔树

Description

让我们考虑一种特殊的二叉查找树,叫做笛卡尔树。回想一下,二叉查找树是有根有序的二叉树,这样,对于它的每一个节点x满足以下条件:在它的左子树的每个节点的数值小于x的数值,它的右子树的每个节点的数值大于x的数值。也就是说,如果我们用L(x)表示结点x的左子树,用R(x)表示结点x右子树,用kx表示该结点x的数值,那么对每个结点x我们有

如果y ∈ L(x),那么ky < kx

如果z ∈ R(x),那么kz > kx

若一棵二叉查找树被称为笛卡尔树,那么它的每一个结点x除了主要数值kx外还有一个附加数值ax,且这个数值符合堆的条件,即

如果y是x的父亲,那么ay < ax

因此,一棵笛卡尔树是一棵有根有序的二叉树,这样,它的每个节点拥有两个数值(k , a)和满足上述的三个条件。

给出一系列点,构建出它们的笛卡尔树,或检测构建出它们的笛卡尔树是不可能的。

Input

第一行包括一个整数N(1 <= N <= 50 000),表示你要构建的笛卡尔树的点的对数。

接下来N行包括两个数字,k,a,|k|, |a| <= 30 000,保证每行的k和a是不同的。

输入保证能构建出笛卡尔树的只有一种情况。

Output

如果能构建出笛卡尔树则在第一行输出YES否则输出NO。

如果是YES,则在接下来N行输出这棵树。第i+1行输出第i个结点的父亲,左儿子,右儿子。如果这个结点无父亲或者儿子,则用0代替。

Sample Input

7 
5 4 
2 2 
3 9 
0 5 
1 3 
6 6 
4 11  

Sample Output

YES 
2 3 6 
0 5 1 
1 0 7 
5 0 0 
2 4 0 
1 0 0 
3 0 0  

额,笛卡尔树的定义都告诉你了,还不照着打一遍?
给定一个带两种值的序列,一定能构建出一棵笛卡尔树,并且只能建出唯一一棵满足条件的。构建方法已经写了详细注释,请读者慢慢yy。
#include<bits/stdc++.h>
using namespace std;

const int BufferSize=1; //由于read中是使用Getchar一位一位地读,数据项的个数为1
char buffer[BufferSize],*head,*tail;
int len;
inline char Getchar(){
    if(head==tail){
        len=fread(buffer,1,BufferSize,stdin);
        tail=(head=buffer)+len;
    }
    return *head++;
}
inline int read(){
    int x=0,f=1;char c=Getchar();
    for(;!isdigit(c);c=Getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=Getchar()) x=x*10+c-'0';
    return x*f;
}

struct tree{
    int k,a,id,l,r;
}t[50001];
int n,root,f[50001],l[50001],r[50001];

bool cmp(tree x,tree y){return x.k<y.k;} //按照k排序(bst顺序) 

/*强行脑补一下笛卡尔树的构建 
index(k)为二叉搜索树的值,key(a)为堆的值 
我们按照点的标号index 1-n不断插入节点,因为当前点的index比之前插入的都大,所以根据二叉搜索的性质应该从这棵树的最右边插入。
又要满足堆的性质,所以我们插入一个点A的时候,沿这棵树的最右节点不断向上找,直到找到第一个权值比它小的点B,根据标号,A的左孩子就是B的右孩子,而A就是B的右孩子了(这实际上似乎就是某种旋转)
实际实现的时候我们可以用栈维护这棵树的最右子树(一条从根节点出发全部指向右孩子的链,请自行脑补),不断弹出顶部节点以便找到第一个权值key比它小的点,再更改关系即可。由于每个点都只能进出一次栈,所以复杂度为O(n).
*/
int build(){
    stack<int>st;
    st.push(0); //抽象解释:节点0的右儿子是树根。先插入0是给第38行的while判断的第一条用的,防止队列被弹空。如果队列被弹到这里,说明插入点要放在根节点。 
    for(int i=1;i<=n;i++){
        while(st.top() && t[st.top()].a>t[i].a) st.pop(); //把k和a都比当前点大的点都弹出,最后st.top()就是第一个权值key比当前点小的点了 
        t[i].l=t[st.top()].r; //st.top()的k小于当前k,a小于当前a,因此当前点在st.top()的右子树中。把这个点(B)的右儿子设为当前点(A)的左儿子
        t[st.top()].r=i; //st.top()的右儿子(B)设为当前点(A) 
        st.push(i); //将当前点推入最右子树队列 
    }
    while(st.top()) root=st.top(), st.pop(); //最后栈最底端的元素(st[0])就是根了。因为这个点最早插入树且仍然在右子树链中,那它就是最右子树的根,也就是整棵树的根咯 
}

void dfs(int now){ //查询一下每个点的父亲、左子树、右子树,最后输出用 
    if(t[now].l){ //左子树 
        f[t[t[now].l].id]=t[now].id; //记录左儿子的父亲为自己 
        l[t[now].id]=t[t[now].l].id; //记录自己的左儿子
        dfs(t[now].l);
    }
    if(t[now].r){ //右子树 
        f[t[t[now].r].id]=t[now].id; //记录右儿子的父亲为自己 
        r[t[now].id]=t[t[now].r].id; //记录自己的左儿子
        dfs(t[now].r);
    }
}
 
int main(){
    n=read();
    for(int i=1;i<=n;i++) t[i].k=read(),t[i].a=read(),t[i].id=i; //id用于标记树中的点原来的位置,方便最后按顺序输出 
    sort(t+1,t+1+n,cmp);
    build(); 
    dfs(root);
    printf("YES\n"); //
    for(int i=1;i<=n;i++) printf("%d %d %d\n",f[i],l[i],r[i]);
}

 

3.Binary Search Heap Construction(treap练习,不过跟笛卡尔树有个毛区别)

Description

堆是这样的一种树,每个内结点被赋了一个优先级(一个数值),使得每个内结点的优先级小于其双亲结点的优先级。因此,根结点具有最大的优先级,这也是堆可以用于实现优先级队列和排序的原因。

在一棵二叉树中的每个内结点有标号和优先级,如果相应于标号它是一棵二叉搜索树,相应于优先级它是一个堆,那么它就被称为treap。给出一个标号-优先级对组成的集合,请构造一个包含了这些数据的treap。

Input

输入包含若干测试用例,每个测试用例首先给出整数n,本题设定1<=n<=50000;后面跟着n对的字符串和整数l1/p1, ..., ln/pn,表示每个结点的标号和优先级;字符串是非空的,由小写字母组成;数字是非负的整数。最后的一个测试用例后面以一个0为结束。

Output

对每个测试用例在一行中输出一个treap, treap给出顶点的说明。Treap的形式为 (< left sub-treap >< label >/< priority >< right sub-treap >). 子treaps递归书出,如果是树叶就没有输出。

Sample Input

7 a/7 b/6 c/5 d/4 e/3 f/2 g/1
7 a/1 b/2 c/3 d/4 e/5 f/6 g/7
7 a/3 b/6 c/4 d/7 e/2 f/5 g/1
0

Sample Output

(a/7(b/6(c/5(d/4(e/3(f/2(g/1)))))))
(((((((a/1)b/2)c/3)d/4)e/5)f/6)g/7)
(((a/3)b/6(c/4))d/7((e/2)f/5(g/1)))

这里的treap根笛卡尔树有什么区别,于是再来一发。
#include<iostream>
#include<cstring>
#include<stack>
#include<algorithm>
using namespace std;#define maxn 50010
stack<int> st;
int n;
char str[10];
struct node{
  char name[10];
    int num;
    int l,r;
    void clear(){l=r=0;}
}a[maxn];
bool cmp(node z1,node z2){
    return strcmp(z1.name,z2.name)<0;
}
int build(){
    st.push(0);
    for(int i=1;i<=n;i++){
         while(st.top() && a[st.top()].num<a[i].num)st.pop();
         a[i].l=a[st.top()].r;
         a[st.top()].r=i;
         st.push(i);
    }
    int ans;
    while(st.top()){ans=st.top();st.pop();}
    return ans; //返回树根
}
void dfs(int u){
    if(!u) return;
    printf("(");
    if(a[u].l) dfs(a[u].l);
    printf("%s/%d",a[u].name,a[u].num);
    if(a[u].r) dfs(a[u].r);
    printf(")");
}
int main(){
    while(scanf("%d",&n)){
        if(n==0) break;
        for(int i=0;i<=n;i++) a[i].clear(); 
        for(int i=1;i<=n;i++){
            scanf("%s",str);
            sscanf(str,"%[^/]/%d",a[i].name,&a[i].num);
        }
        sort(a+1,a+n+1,cmp);
        int root=build();
        dfs(root);
        putchar('\n');
    }
    return 0;
}


posted @ 2018-02-02 15:40  大本营  阅读(507)  评论(0编辑  收藏  举报