【王道机试指南学习笔记】第十章 数据结构二

前言与提示

数据结构一中介绍的都是线性数据结构,这次介绍一些非线性的——二叉树、二叉排序树、优先队列和散列表。

10.1 二叉树Binary Tree

重点提醒

建立二叉树,则按照二叉树的定义进行递归建树,每个结点定义如下:

struct TreeNode{
    int data;//数据 ElementType即可为int char等
    TreeNode *leftChild;//左子树
    TreeNode *rightChild;//右子树
};

主要的遍历方法:①②③区别就在访问 根结点 的顺序不同
①前序遍历 NLR

void PreOrder(TreeNode* root){
    if(root == NULL) return ;
    Visit(root.data);
    PreOrder(root.leftChild);
    PreOrder(root.rightChild);
    return ;
}

②中序遍历 LNR

void InOrder(TreeNode* root){
    if(root == NULL) return ;
    InOrder(root.leftChild);
    Visit(root.data);
    InOrder(root.rightChild);
    return ;
}

③后序遍历 LRN

void PostOrder(TreeNode* root){
    if(root == NULL) return ;
    PostOrder(root.leftChild);
    PostOrder(root.rightChild);
    Visit(root.data);
    return ;
}

④层次遍历:按照高度一层层遍历(队列)

void LevelOrder(TreeNode* root){
    queue<TreeNode*> myQueue;
    if(root !== NULL){
        myQueue.push(root);
    }
    while(!myQueue.empty()){
        TreeNode* current = myQueue.front();
        myQueue.pop();
        visit(current.data);
        if(current.leftChild != NULL)
            myQueue.push(current.leftChild);
        if(current.rightChild != NULL)
            myQueue.push(current.rightChild);
    }
    return ;
}

题目练习

例题10.1 二叉树遍历(清华复试)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/4b91205483694f449f94c179883c1fef
关于引用:
C++中 引 入了 一种新的类型——引用
引用(reference)不是新定义一个变量, 而是给已存在的对象取了 一个别名 ,引用类型,引用另外一种类型。
编译器不会为引用对象新开辟内存空间, 它和它引用的对象共用同一块内存空间 。
在此代码中相当于使得position变成一个全局变量。

#include <iostream>
using namespace std;
struct TreeNode{
    char data;//数据
    TreeNode *leftChild;//左子树
    TreeNode *rightChild;//右子树
    TreeNode(char c):data(c),leftChild(NULL),rightChild(NULL){}
};
//先序遍历建立二叉树
TreeNode* Build(int& position,string str){//这里&不是取地址符号,而是引用符号
    char c = str[position++];//当前字符
    if(c == '#') return NULL;//返回空树
    TreeNode* root = new TreeNode(c);//创建新节点
    root->leftChild = Build(position,str);
    root->rightChild = Build(position,str);
    return root;
}

void InOrder(TreeNode* root){
    if(root == NULL) return ;
    InOrder(root->leftChild);
    cout<<root->data<<" ";
    InOrder(root->rightChild);
    return ;
}
int main(){
    string str;
    while(cin>>str){
        int position = 0;//标记字符串处理位置
        TreeNode* root = Build(position,str);
        InOrder(root);
        cout<<endl;
    }
    return 0;
}

不用引用 改用了全局变量的版本…只放了有变化的函数

#include <iostream>
using namespace std;
//全局变量
int pos = 0;//标记字符串处理位置
TreeNode* Build(int position,string str){
    char c = str[pos++];//当前字符
    if(c == '#') return NULL;//返回空树
    TreeNode* root = new TreeNode(c);//创建新节点
    root->leftChild = Build(pos,str);//如果不用& 则position就不能一直++
    root->rightChild = Build(pos,str);
    return root;
}
int main(){
    string str;
    while(cin>>str){
        TreeNode* root = Build(pos,str);
        InOrder(root);
        cout<<endl;
    }
    return 0;
}

例题10.2 二叉树遍历(华科复试)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/6e732a9632bc4d12b442469aed7fe9ce
中序遍历和先序/后序2个中的任1搭配,都可唯一地确定一棵二叉树。

#include <iostream>
#include <cstring>
using namespace std;

struct TreeNode{
    char data;//数据
    TreeNode *leftChild;//左子树
    TreeNode *rightChild;//右子树
    TreeNode(char c):data(c),leftChild(NULL),rightChild(NULL){}
};
TreeNode* Build(string str1,string str2){
    if(str1.size()==0) return NULL;//返回空树
    char c = str1[0];//当前字符
    TreeNode* root = new TreeNode(c);//创建新节点
    int position = str2.find(c);//在中序中寻找切分点
    //重点!!!
    root->leftChild = Build(str1.substr(1,position),str2.substr(0,position));
    root->rightChild = Build(str1.substr(position+1),str2.substr(position+1));
    return root;
}

void PostOrder(TreeNode* root){
    if(root == NULL) return ;
    PostOrder(root->leftChild);
    PostOrder(root->rightChild);
    cout<<root->data;
    return ;
}
int main(){
    string str1,str2;
    while(cin>>str1>>str2){
        TreeNode* root = Build(str1,str2);
        PostOrder(root);
        cout<<endl;
    }
    return 0;
}

10.2 二叉排序树(二叉搜索树)

重点提醒

特殊的二叉树,一棵非空的二叉排序树具有如下特征:
(1)若左子树非空,则左子树上所有结点关键字值 < 根结点
(2)若右子树非空,则右子树上所有结点关键字值 > 根结点
(3)左右子树也是一棵二叉排序树
【左子树结点值<根结点值<右子树结点值】,若中序遍历,定是个升序序列。

题目练习

例题10.3 二叉排序树(华科复试)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/30a0153649304645935c949df7599602

#include <iostream>
#include <cstring>
using namespace std;

struct TreeNode{
    int data;//数据
    TreeNode *leftChild;//左子树
    TreeNode *rightChild;//右子树
    TreeNode(int x):data(x),leftChild(NULL),rightChild(NULL){}
};
TreeNode* Insert(TreeNode* root,int x,int father){
    if(root == NULL){
        root = new TreeNode(x);
        cout<<father<<endl;
    }
    else if(x<root->data)
        root->leftChild = Insert(root->leftChild,x,root->data);
    else
        root->rightChild = Insert(root->rightChild,x,root->data);
    return root;
}

int main(){
    int n;
    while(cin>>n){
        TreeNode *root = NULL;//建立空树
        for(int i = 0;i<n;i++){
            int x;
            cin>>x;
            root = Insert(root,x,-1);//逐个插入
        }
    }
    return 0;
}

例题10.4 二叉排序树(华科复试)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/b42cfd38923c4b72bde19b795e78bcb3
这题的代码都很标准,可以做范本。


#include <iostream>
#include <cstring>
using namespace std;

struct TreeNode{
    int data;//数据
    TreeNode *leftChild;//左子树
    TreeNode *rightChild;//右子树
    TreeNode(int x):data(x),leftChild(NULL),rightChild(NULL){}
};
TreeNode* Insert(TreeNode* root,int x){
    if(root == NULL){
        root = new TreeNode(x);
    }
    else if(x<root->data)
        root->leftChild = Insert(root->leftChild,x);
    else if(x>root->data) //不用else 而在用一次else if 规避相等
        root->rightChild = Insert(root->rightChild,x);
    return root;
}
void InOrder(TreeNode* root){
    if(root == NULL) return ;
    InOrder(root->leftChild);
    cout<<root->data<<" ";
    InOrder(root->rightChild);
    return ;
}
void PreOrder(TreeNode* root){
    if(root == NULL) return ;
    cout<<root->data<<" ";
    PreOrder(root->leftChild);
    PreOrder(root->rightChild);
    return ;
}

void PostOrder(TreeNode* root){
    if(root == NULL) return ;
    PostOrder(root->leftChild);
    PostOrder(root->rightChild);
    cout<<root->data<<" ";
    return ;
}
int main(){
    int n;
    while(cin>>n){
        TreeNode *root = NULL;//建立空树
        for(int i = 0;i<n;i++){//逐个插入
            int x;
            cin>>x;
            root = Insert(root,x);
        }
        PreOrder(root);
        cout<<endl;
        InOrder(root);
        cout<<endl;
        PostOrder(root);
        cout<<endl;
    }
    return 0;
}

习题10.1 二叉搜索树(浙大复试)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/3d6dd9a58d5246f29f71683346bb8f1b
代码写法是逐个递归比较。
另一种思路:对二叉排序树而言,相同元素的二叉排序树中序遍历一定相同,而不同二叉排序树使用前序遍历就可以发现不相同。
故 puts(strcmp(a,b)==0?“YES”:“NO”);

#include <iostream>
#include <cstring>
using namespace std;

struct TreeNode{
    int data;//数据
    TreeNode *leftChild;//左子树
    TreeNode *rightChild;//右子树
    TreeNode(int x):data(x),leftChild(NULL),rightChild(NULL){}
};
TreeNode* Insert(TreeNode* root,int x){
    if(root == NULL){
        root = new TreeNode(x);
    }
    else if(x< root->data){
        root->leftChild = Insert(root->leftChild,x);
    }
    else{
        root->rightChild = Insert(root->rightChild,x);
    }
    return root;
}

bool JudgeSame(TreeNode* r1,TreeNode* r2){//判断两树是否相等
    if(!r1&&!r2) return true;
    if(r1&&r2&&r1->data==r2->data){//有根节点且相等,继续比左右
        return JudgeSame(r1->leftChild,r2->leftChild)&&JudgeSame(r1->rightChild,r2->rightChild);
    }else return false;
}

int main(){
    int n;
    string str;
    while(cin>>n){
        if(n==0) break;
        cin>>str;
        TreeNode *root = NULL;//建立空树
        for(int i = 0;i<str.size();i++){
            root = Insert(root,str[i]);
        }
        while(n--){
            string str1;
            cin>>str1;
            TreeNode *root1 = NULL;
            for(int i = 0;i<str1.size();i++){
                root1 = Insert(root1,str1[i]);
            }
        if(JudgeSame(root,root1))
            cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
        }
    }
    return 0;
}

10.4 散列表

重点提醒

散列表是一种根据关键字(key)直接访问的数据结构,建立关键字和存储位置的直接映射关系(map),便可利用关键码直接访问元素,加快查找速度。
平均时间复杂度:O(1)
相对的线性结构和树形结构的查找都需要进行比较,查找效率取决于比较次数,最优做到O(logn)

STL-map

映射模板map是一个将关键字(key)和映射值(value)形成一对映射后进行绑定存储的容器。
map的性能能满足绝大多数题目的要求,性能要求太高时,只需将map改成unordered_map。

基本操作

#include,使用map标准模板用。
在这里插入图片描述

映射应用

输入信息,进行查询

例题10.7 查找学生信息(清华复试)

题目OJ网址(牛客网):单向映射~
https://www.nowcoder.com/questionTerminal/fe8bff0750c8448081759f3ee0d86bb4

关于cin!
cin从输入缓冲区读取数据时,会跳过首个有效字符之前的[空格][Tab][换行]这些分隔符。
cin读取字符成功后该字符之后的分隔符会残留在输入缓冲区.
getline(cin,str)用于捕获[换行]符,并将换行符之前的内容全都保存在str中,并且将’\n’直接从输入缓冲区中删除掉
很好的一段讲解:在这里插入图片描述

#include <iostream>
#include <cstdio>
#include <map>

using namespace std;
map<string,string> student;//定义一个散列表

int main(){
    int n;
    cin>>n;
    //scanf("%d",&n);
    getchar();//吃掉回车
    for (int i = 0; i < n; ++i){
        string str,str2,str3;
        //cin>>str 不可 因为会只读入分隔符之前的作为str
        getline(cin,str);
        int pos = str.find(" ");
        string key = str.substr(0,pos);//str学号作关键字
        student[key] = str;//信息作为映射值
    }
    int m;
    cin>>m;
    for (int i = 0; i < m; ++i){
        string research;
        cin>>research;
        string answer = student[research];
        if(answer == "") answer = "No Answer!";
        cout<<answer<<endl;
    }
    return 0;
}

例题10.8 魔咒词典(浙大复试)

题目OJ网址(牛客网):双向映射~
https://www.nowcoder.com/questionTerminal/c6ca566fa3984fae916e6d7beae8ea7f
双向映射,且“魔咒”和“功能”不会重复出现,故可以将双向映射放在同一个映射内
当然也可以建立两个映射,分别作为关键字。

substr:string.substr(start,length);
substr() 方法可在字符串中抽取从 start 下标开始的指定数目的字符

#include <iostream>
#include <cstdio>
#include <map>

using namespace std;
map<string,string> dictionary;//定义一个散列表

int main(){
    string str;
    while(getline(cin,str)){
        if(str == "@END@") break;
        int pos = str.find("]");
        string key = str.substr(0,pos+1);//魔咒 后半表示长度
        string value = str.substr(pos+2);//功能
        dictionary[key] = value;
        dictionary[value] = key;
    }
    int n;
    cin>>n;
    getchar();//吃掉空格 先读数字后读字符都要来个这个
    for (int i = 0; i < n; ++i){
        string str2;
        getline(cin,str2);
        string answer = dictionary[str2];
        if(answer=="") answer = "what?";
        else if(answer[0] == '[')
            answer = answer.substr(1,answer.size()-2);
        cout<<answer<<endl;
    }
    return 0;
}

例题10.9 子串计算(北大复试)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/bcad754c91a54994be31a239996e7c11

//10.9
#include <iostream>
#include <cstdio>
#include <map>
using namespace std;

map<string,int> number;//定义一个散列表

int main(){
    string str;
    while(cin>>str){
        for (int i = 0; i <= str.size(); ++i){
            for(int j = 0;j<i;j++){
                string key = str.substr(j,i-j);
                number[key]++;
            }
        }
        //迭代器
        //map<string,int>::iterator it;
        for(auto it=number.begin();it !=number.end();it++){
            if(1<it->second){
                cout<<it->first<<" "<<it->second<<endl;
            }
        }
    }
    return 0;
}

习题10.4 统计同成绩学生人数(浙大复试)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/bcad754c91a54994be31a239996e7c11

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;

map<int,int> number;//定义一个散列表

int main(){
    int n;
    while(cin>>n){
        if(n==0) break;
        else{
            int score,research;
            for(int i = 0;i<n;i++){
                cin>>score;
                number[score]++;
            }
            cin>>research;
            //写法一
            int answer = number[research];
            cout<<answer<<endl;
            //写法二:迭代器
            //map<int,int>::iterator it=number.find(research);
            //cout<<it->second<<endl;
        }
    }
    return 0;
}

习题10.5 开门人和关门人(浙大复试)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/a4b37b53a44d454ab0834e1517983215
使用map的字典序和映射特性

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;

map<string,string> open;
map<string,string> close;

int main(){
    int n;
    while(cin>>n){
        for(int i = 0;i<n;i++){
            string str1,str2,str3;
            cin>>str1>>str2>>str3;
            open.insert(pair<string,string>(str2,str1));
            close.insert(pair<string,string>(str3,str1));
        }
        cout<<open.begin()->second<<" "<<close.rbegin()->second<<endl;
        //后半个用close.end()->second是不对的
    }
    return 0;
}

习题10.6 谁是你的潜在朋友(北大复试)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/0177394fb25b42b48657bc2b1c6f9fcc

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;

const int MAX = 200;
map<int,int> num;
int book[MAX];//存放读者i喜欢的书的编号

int main(){
    int n,m;
    while(cin>>n>>m){
        for(int i = 0;i<n;i++){
            int choice;
            cin>>book[i];
            choice = book[i];
            num[choice]++;
        }
        for(int i = 0;i<n;i++){
            if(num[book[i]]==1) cout<<"BeiJu"<<endl;
            else{
                int answer = num[book[i]]-1;
                cout<<answer<<endl;
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值