【Nan's 王道机试指南学习笔记】第十章 数据结构二
前言与提示
数据结构一中介绍的都是线性数据结构,这次介绍一些非线性的——二叉树、二叉排序树、优先队列和散列表。
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
映射应用
输入信息,进行查询
例题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;
}