zoukankan      html  css  js  c++  java
  • 【NOIp复习】数据结构之栈、队列和二叉树

    1、STL中的stack头文件自带函数

    • empty()堆栈是否为空
    • push()压入元素
    • pop()弹出元素(并不会返回顶部元素,pop之前先判断!empty())
    • size()(返回栈的元素个数)
    • top()(返回栈顶元素)
    • 声明:stack<元素类型> 堆栈名
    • 复制:stack c1(c2) 代表将c2复制到c1

    2、数制转换

    输入格式

    输入一个十进制数N与需要转换的进制d

    输出格式

    输出转换后的d进制数

    思路分析

    转换进制其实就是用短除法不停地除以d求余数,直到剩下的N比d小为止,倒着把余数从左到右写一遍的过程。因为这个“倒着取余数”的操作,让我们想到可以将计算过程在栈中顺序进行,然后弹出顺序就是倒序。

    代码实现

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <stack>
    using namespace std;
    
    char ch[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
    int n,d;
    stack<int> s;
    
    int main(){
        scanf("%d%d",&n,&d);
        while(n){
            s.push(n%d);
            n=n/d;
        }
        while(!s.empty()) {
            printf("%c",ch[s.top()]); s.pop();
        }
        return 0;
    } 

    回顾反思

    top并不会弹出栈顶元素,pop并不会返回栈顶元素,所以经常top过后就pop,并且要pop或者top之前务必确认!empty()

    3、后序表达式

    输入格式

    一个带括号的四则运算表达式

    输出格式

    表达式的计算结果

    思路分析

    建两个栈,一个数字栈,一个符号栈。
    从左到右读,遇到符号时先判断与栈顶符号的优先级顺序,如果入栈元素比栈顶元素优先级低,就弹出数字栈栈顶元素与符号栈栈顶符号计算,直到入栈元素优先级不低于栈顶元素为止。
    原理就是保证符号栈中优先级始终从高到低,优先级高的永远比优先级低的先算。
    处理括号:左括号优先级最低,右括号优先级最高,遇到右括号就一直弹出计算直到弹出第一个左括号为止。

    队列

    1、STL中的queue

    • queue q
    • front()返回队首元素
    • back()返回队尾元素
    • push()插入队尾
    • pop()队首出队
    • empty()队列是否为空
    • size()返回队列元素个数

    2、(NOIP2015)机器翻译

    题目描述

    小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。
    这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换。对于每个英文单词,软件会先在内存中查找这个单词的中文含义,如果内存中有,软件就会用它进行翻译;如果内存中没有,软件就会在外存中的词典内查找,查出单词的中文含义然后翻译,并将这个单词和译义放入内存,以备后续的查找和翻译。
    假设内存中有M个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过M-1,软件会将新单词存入一个未使用的内存单元;若内存中已存入M个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。
    假设一篇英语文章的长度为N个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。

    输入格式

    输入文件共2行。每行中两个数之间用一个空格隔开。
    第一行为两个正整数M和N,代表内存容量和文章的长度。
    第二行为N个非负整数,按照文章的顺序,每个数(大小不超过1000)代表一个英文单词。文章中两个单词是同一个单词,当且仅当它们对应的非负整数相同。

    输出格式

    包含一个整数,为软件需要查词典的次数。

    二叉树

    二叉树数组表示法

    对于一颗满二叉树(除了最底层节点以外,每个节点都有左右儿子),容易得到左儿子编号是父亲编号的2倍,右儿子为2n+1,所以把任意二叉树补成一颗满二叉树(没有的点就赋初值)即可。

    二叉树链表表示法

    struct tree{
        struct tree *left;//递归定义左右子树
        int data;//节点内存放的数据
        struct tree *right;
    };
    typedef struct tree treenode;//typedef A B将A类型的名字定义为B
    typedef struct tree *b_tree;
    
    b_tree insert(b_tree root, int node){//插入新节点
        b_tree newnode;//待插入节点
        b_tree currentnode;
        b_tree parentnode;
    
        newnode=(b_tree)malloc(sizeof(treenode));//分配空间
        newnode->data=node;
        newnode->left=NULL;
        newnode->right=NULL;
    
        if(root==NULL)//建立第一个节点
        return newnode;
        else{
            currentnode=root;//当前结点=当前根节点
            while(currentnode!=NULL)//找到有空儿子的节点为止,作为待插入节点的父节点
            {
                parentnode=currentnode;//父节点=当前结点
                if(currentnode->data>node)
                    currentnode=currentnode->left;//如果父节点的数据比待插入节点大,当前结点=父节点的左节点
                else
                    currentnode=currentnode->right;//如果父节点的数据比待插入节点小,当前结点=父节点的右节点
            }
            if(parentnode->data>node)//如果父节点比子节点大,子节点就到左子树,否则到右子树
                parentnode->left=newnode;
            else
                parentnode->right=newnode;
        }
        return root;
    }
    
    b_tree create(int *data, int len){//建立二叉树,data数组为需要建树的数据,len为数组长度,返回该二叉树的根节点地址;
        b_tree root=NULL;//根节点初始化为空
        for(int i=1;i<=len;i++)
            root=insert(root,data[i]);
        return root;
    }
    
    void print(b_tree root){//前序遍历输出二叉树
        if(root!=NULL){
            printf("%d ",root->data);
            print(root->left);
            print(root->right);
        }
    }

    二叉树的序遍历

    前序遍历:根子树——左节点——右子树
    中序遍历:左节点——根子树——右子树
    后序遍历:左子树——右子树——根节点
    下面是三种知二求一的算法…

    求后序

    前序遍历中第一个字母就是根节点,在中序遍历中找到这个字母,左边就是左子树的中序遍历,右边就是右子树的中序遍历;前序遍历紧接着的是左子树的前序遍历,然后是右子树的前序遍历,所以又变成了左右子树分别已知前序中序遍历求后序遍历的问题——递归。

    举个栗子:已知
    前序遍历为ABDEFGCH
    中序遍历为DFEGBAHC
    A把中序遍历分为了
    左子树中序:DFEGB
    右子树中序:HC
    左子树前序:BDEFG
    右子树前序:CH

    求前序

    和求后序的思路一毛一样

    #include <cstdio>
    #include <cstring>
    #define RES(a,b) memset(a,b,sizeof(a))
    using namespace std;
    
    char in[20],post[20];
    int len;
    
    void print_pre(int inl, int inr, int pol, int por){
        if(inl>inr||pol>por) return;
        printf("%c",post[por]);
        int pos;
        for(int i=inl;i<=inr;i++) if(in[i]==post[por]) {pos=i; break;}
        print_pre(inl,pos-1,pol,pol+pos-inl-1);
        print_pre(pos+1,inr,pol+pos-inl,por-1);
        return;
    }
    
    int main(){
        RES(in,0); RES(post,0);
        scanf("%s",in); scanf("%s",post);
        len=strlen(in)-1;
        print_pre(0,len,0,len);
        return 0;
    }

    求中序(重点)

    已知:
    前序ABDEFGCH
    后序FGEDBHCA

    数组存储转链表存储

    struct tree{
        struct tree *left;
        int data;
        struct tree *right;
    };
    typedef struct tree treenode;
    typedef struct tree *b_tree;
    
    //和之前一样定义二叉树节点的结构体 
    int N;//N为数组结构的长度 
    
    b_tree create(int *node, int position){
        b_tree newnode;
        if(node[position]==0||position>N)//根节点和不存在的节点都是NULL 
            return NULL;
        else{
            newnode=(b_tree)malloc(sizeof(treenode));
            newnode->data=node[postion];
            newnode->left=create(node,2*position);
            newnode->right=create(node,2*position+1);
            return newnode;//否则递归建树 
        }
    } 
    

    二叉查找树

    最开始用链表建树的时候你发现了它已经是一颗二叉查找树了吗?二叉查找树满足如下三点性质:

    • 左子树中的节点小于根节点
    • 右子树中的节点大于根节点
    • 左右子树仍是二叉平衡树

    要实现以下三种操作

    • 查找
    • 插入
    • 删除

    Treap——随机平衡二叉查找树

    因为二叉查找树可能会因为数据的原因退化成一条链,查找效率变低,所以Treap的原理就是给每一个节点分配一个随机的关键词,对于关键词Treap要满足堆的性质,对于权值要满足二叉搜索树的性质——构造了一个随机平衡的二叉查找树。

    节点定义

    六个参数:左儿子、右儿子、权值key、用来平衡的随机数fix、该节点重复的数据cnt、以该节点为根节点的子树大小size

    struct Node{
        Node *left, *right;//左右儿子递归定义
        int key,fix,cnt,size;
        Node (int i) : key(i),fix(rand()),size(1),cnt(1) {}//初始化函数
    }*root,*null//根节点和空节点

    堆的性质

    • 大根堆:根节点大于儿子节点
    • 小根堆:根节点小于儿子节点
    • 左右子树仍然是一个堆

    插入

    先做二叉查找树的插入,然后再调整使其满足堆的性质

    • 左旋:右节点到根节点,根节点到左节点(当前结点是根的右儿子)即右节点提升
    • 左旋操作:要旋转的子树根节点编号为a
    void left_rotate(node* &a){
        node *b=a->right;
        a->right=b->left;
        b->left=a;
        a=b;
    }
    • 右旋:左节点到根节点,根节点到右节点(当前结点是根的左儿子)即左节点提升
    • 右旋操作:要旋转的子树根节点编号为a
    void right_rotate(node* &a){
        node* b=a->left;
        a->left=b->right;
        b->right=a;
        a=b;
    }

    插入操作O(logn),旋转操作最坏O(h)

    删除

    把待删除节点旋转到只有一个子节点的地方删除即可。正如上一小节提到的一样,左旋可以使右节点提升而右旋可以使左节点提升,引出如下两种操作。
    - 当当前结点的左节点小于右节点时,右旋
    - 当当前结点的右节点小于左节点时,左旋
    - 直到当前结点子节点个数小于等于一时,直接删除即可

    查找

    就当二叉搜索树查找就好了

    • 如果当前结点大于查找值,在左子树中查找
    • 如果当前结点小于查找值,在右子树中查找

    应用

    求前驱和后继
    • 前驱:该元素在平衡树中不大于该元素的最大元素
    • 后继:该元素在平衡树中不小于该元素的最小元素

    方法(以前驱为例):

    1. 以根节点为当前结点,最优值设为空
    2. 如果当前结点不大于该元素,更新最优值(如果该节点更接近该元素就更新,否则不做变动),访问当前结点的右子树
    3. 如果当前结点大于该元素,访问当前结点的左子树
    4. 直到当前结点是空节点为止

    下面都需要维护子树的大小
    对于旋转、插入、删除三种操作

    • 旋转:
    • 插入:
    • 删除:
    查找第k小的元素
    询问某个元素从小到大的排名

    有时间了搞这道题

    哈夫曼树(最优二叉树)

    定义

    带权路径长度:从根结点到叶子结点的长度与叶子结点数据的乘积
    对一一些给定权值的节点,具有最小带权路径长度的二叉树叫做哈夫曼树

    构造

    权值越大的叶子结点越靠近根节点

    1. 生成树的集合(裸的只有一个节点的树)
    2. 在集合中选取根节点最小和次小的两棵二叉树分别作为左右子树,根节点权值为左右子树根节点权值之和
    3. 在二叉树集合中删除左右两子树,将新生成的二叉树加入集合
    4. 重复2、3直到剩下一颗二叉树即为所求的哈夫曼树
  • 相关阅读:
    HDU 5818 Joint Stacks
    HDU 5816 Hearthstone
    HDU 5812 Distance
    HDU 5807 Keep In Touch
    HDU 5798 Stabilization
    HDU 5543 Pick The Sticks
    Light OJ 1393 Crazy Calendar (尼姆博弈)
    NEFU 2016省赛演练一 I题 (模拟题)
    NEFU 2016省赛演练一 F题 (高精度加法)
    NEFU 2016省赛演练一 B题(递推)
  • 原文地址:https://www.cnblogs.com/leotan0321/p/6081368.html
Copyright © 2011-2022 走看看