• 【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直到剩下一颗二叉树即为所求的哈夫曼树
  • 相关阅读:
    java 基础7
    java 基础5
    java 基础6
    java 基础4
    java 基础2
    java 基础3
    java 基础1
    使用HTML的基本结构创建网页
    jsp Servlet 文件上传
    Filter过滤器 不登陆无法访问其他页面
  • 原文地址:https://www.cnblogs.com/leotan0321/p/6081368.html
Copyright © 2020-2023  润新知