7 数据结构二
1 二叉树
本章包括:
- 二叉树的建树、遍历
- 二叉排序树的建树、遍历
- 判断2棵树是否是相同的二叉排序树
例题10.1 二叉树遍历(清华大学复试上机)
题目
本题要求递归建立二叉树,而不是使用队列的层次建树方法,“递归建树”后再输出中序遍历序列
例题10.2 二叉树遍历(华中科技大学复试上机)
题目
本题较复杂,先从前序和中序序列得到原二叉树,再输出原二叉树后序序列
2 二叉排序/搜索树
例题10.4 二叉排序树(华中科技大学复试上机)
题目
此题值得一练,包含了二叉排序树的建树、前中后序遍历
习题10.1 二叉搜索树(浙江大学复试上机)
题目
本题判断2个序列是否为同一个二叉搜索树序列,if(它们的中序和先序都一样),则相同,因为可以唯一确定一颗二叉树
题解
#include <cstdio>
#include <iostream>
#include <string>
using namespace std;
struct TreeNode{
char data;
TreeNode* leftchild;
TreeNode* rightchild;
//这里使用了构造函数,即创建时就进行初始赋值,注意写法
TreeNode(char x):data(x),leftchild(NULL),rightchild(NULL){}
};
//这里类型为节点*,所以形参不需要用&引用,但如果用void,则需要写成node* &root
//另外命名可以用Insert而不用Build,否则可能引起歧义
TreeNode* InsertBST(TreeNode* root,char x){
if(root == NULL){
//注意cpp的new的用法,且使用构造函数完成了赋值
root = new TreeNode(x);
}
//插入新结点到排序树时,默认已经递归到最深处无孩子的"root",所以要赋值给child
//且元素值==root->data时,不插入
else if(x < root->data){
root->leftchild = InsertBST(root->leftchild, x);
}
else if(x > root->data){
root->rightchild = InsertBST(root->rightchild, x);
}
return root;
}
//先序序列的字符串
string Preorder(TreeNode* root){
if(root == NULL){
return ""; //string类型要返回空串
}
//返回当前节点作为根节点时的先序序列,则可以递归地得到整个先序序列的字符串
return root->data + Preorder(root->leftchild) + Preorder(root->rightchild);
}
//中序序列的字符串
string Inorder(TreeNode* root){
if(root == NULL){
return "";
}
return Inorder(root->leftchild) + root->data + Inorder(root->rightchild);
}
int main(){
int n;
string str;
while(scanf("%d",&n)!=EOF){
if(n == 0){
break;
}
//tree1是树一的序列,root1是树一的根
string tree1;
cin>>tree1; //cin遇分隔符停止,并抛弃结尾分隔符
TreeNode* root1 = NULL; //要初始化成空指针
for(int i = 0;i<tree1.size();i++){ //将序列插入树一
root1 = InsertBST(root1, tree1[i]);
}
//保存好先序中序序列,用于一会比较
string preorder1 = Preorder(root1);
string inorder1 = Inorder(root1);
for(int i = 0;i<n;i++){
string tree2;
cin>>tree2;
TreeNode* root2 = NULL; //要初始化成空指针
for(int j = 0;j<tree2.size();j++){
root2 = InsertBST(root2, tree2[j]);
}
string preorder2 = Preorder(root2);
string inorder2 = Inorder(root2);
if(preorder1 == preorder2 && inorder1 == inorder2){
printf("YES\n");
}
else{
printf("NO\n");
}
}
}
}
笔记
- 注释较完整
- 二叉树节点的数据结构、struct内的构造函数
- 二叉排序树的建树、遍历
- main函数中对树的初始化,要初始化为空指针,node* root = NULL;
3 优先队列
- 优先队列的基本使用:定义 priority_queue<int myPriorityQueue; 增删 push()和pop();访问 myPriorityQueue.top()优先级最高的元素
- 按“优先级”排序,默认是大根堆,若想变小根堆 或其他排序方法:可取巧在push前都加个负号,top后再乘-1变回原数;也可以运算符重载,bool operator< (Complex left, Complex right){}
例题10.5 复数集合(北邮复试上机)
题目
主要考察了优先队列、运算符重载来比较结构体,
题解
#include <queue>
#include <cstdio>
#include <iostream>
#include <string>
#include <cmath> //求次幂,是pow,不是power
using namespace std;
struct Complex{
int real;
int unreal;
int mo;
Complex(int a, int b):real(a),unreal(b),mo(pow(a,2)+pow(b,2)){}
};
bool operator < (Complex left, Complex right){
//return left.real*left.real + left.unreal*left.unreal < right.real*right.real + right.unreal*right.unreal;
//这里运算符重载是因为结构体,另外因为是用大根堆,所以仍是<时触发交换,>时是小根堆
return left.mo < right.mo;
}
int main(){
int n;
while(scanf("%d",&n)!=EOF){
priority_queue<Complex> myPriorityQueue;//注意定义的位置
for(int i = 0;i < n;i++){
//这里调试好几次才找到错误,用getline好像不会跳过开头的分隔符,故改为直接用cin
string str;
cin>>str;
if(str == "Pop"){
//操作为pop时
if(myPriorityQueue.empty()){
printf("empty\n");
}
else{
Complex ccur = myPriorityQueue.top();
printf("%d+i%d\n",ccur.real,ccur.unreal);
myPriorityQueue.pop();
printf("SIZE = %d\n",myPriorityQueue.size());
}
}
else{
//操作为insert时,还要继续输入l+i2
int a1,b1;
//这里的输入方法,按部就班用scanf就好,会跳过前面空格
scanf("%d+i%d",&a1,&b1);
Complex ccur(a1,b1); //这里使用了构造函数,所以下面2行不用了
//ccur.real = a1;
//ccur.unreal = b1;
myPriorityQueue.push(ccur);
printf("SIZE = %d\n",myPriorityQueue.size());
}
}
}
}
笔记
- 注意构造函数的写法,与cmath中pow函数不是power
- 运算符重载:注意是大根堆还是小根堆,默认大根堆,即“<为真时”触发交换,,所以若想在优先队列中使用小根堆,先输出优先级小的,需要运算符重载return >为真时触发交换
- 用getline输入str时出错,改用cin后好使,估计是因为cin会跳过开头的分隔符
- 输入insert 1+i2时,空格的“后半部分”使用scanf输入,因为会跳过前面空格,注意格式一致
例题10.6 哈夫曼树(北邮复试上机)
题目
有上一题的基础,这道题很简单,只需要把所有元素值的负数存入优先队列,然后每次top出2个最大的,也就是原叶子结点中2个权值最小的。
top完别忘pop,再把新节点push进优先队列,用wpl累加,最后输入wpl时别忘再乘回来-1。
题解
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int main() {
int n;
while (scanf("%d", &n) != EOF) {
priority_queue<int> myPriorityQueue;
int wpl = 0;
for (int i = 0; i < n; i++) {
int num;
cin >> num;
//乘-1再入优先队列,即可巧妙运用默认的大根堆实现小根堆
myPriorityQueue.push(num * (-1));
}
//wpl不断累加,退出条件是优先队列中只有一个根节点
while (myPriorityQueue.size() > 1) {
int small1 = myPriorityQueue.top();
myPriorityQueue.pop();
int small2 = myPriorityQueue.top();
myPriorityQueue.pop();
int newWeight = small1 + small2;
myPriorityQueue.push(newWeight);//别忘新节点入队
wpl = wpl + small1 + small2;
}
printf("%d\n",wpl*(-1));
}
}
笔记
- 有上一题的基础,稍作改动即可
- wpl的巧妙计算方法:wpl=左子树的wpl+左根权+右子树的wpl+右根权,居然被我歪打正着写对了
习题10.2 查找第k小的数(北邮复试上机)
题目
仍采用优先队列,元素乘-1再入队,取巧地在优先队列默认大根堆的情况下实现了小根堆,最后别忘乘回来-1即可。
注意:第k小,但相同的元素算同1个,也就是只有所有相同元素都从优先队列pop出去后,才i++。
#include <queue>
#include <cstdio>
#include <iostream>
using namespace std;
int main(){
int n;
while(scanf("%d",&n)!=EOF){
priority_queue<int> myPriorityQueue;
for(int i = 0;i<n;i++){
int num;
cin>>num;
myPriorityQueue.push(num*(-1));
}
int k;
cin>>k;
for(int i = 0;i<k-1;){//把第k小之前的都出队
//但是要实现相同大小算一样大,所以要前后比较,分情况讨论
int curnum1 = myPriorityQueue.top();
myPriorityQueue.pop();
int curnum2 = myPriorityQueue.top();
if(curnum1 == curnum2){
//如果当前top出的元素与下一个队首元素=,则i不增加
continue;
}
else{
//只有当当前top出的元素已经是与它相同的最后一个,再i++
i++;
}
}
int answer = myPriorityQueue.top()*(-1);
printf("%d\n",answer);
}
}
笔记
在本节例题的基础上,本题不算难,注意一些细节。
4 散列表(更多map的使用)
例题10.7 查找学生信息(清华大学复试上机)
题目
本题用到了map,关键字为int学号,映射值为string其他信息。
注意map的操作:定义、添加、查找、访问。
题解
#include <map>
#include <cstdio>
#include <string>
#include <iostream>
using namespace std;
int main(){
int n,m;
map<int,string> mymap;//定义要使用的map,学号为关键字,其他信息为映射值
while(scanf("%d",&n)!=EOF){
for(int i = 0;i<n;i++){
//每行先读入学号,遇空格停止,cin忽略前面的空格
int stunum;
//cin不对后面的分隔符做处理,因此我猜后面用getline时会自动忽略前面的分隔符
cin>>stunum;
//scanf("%d",&stunum);
string str;
getline(cin,str);
mymap[stunum] = str; //用“下标”的方式给map添加新元素
}
scanf("%d",&m);
for(int j = 0;j < m;j++){
int searchnum;
scanf("%d",&searchnum);
if(mymap.find(searchnum)!=mymap.end()){//注意在map中查找的写法
if(searchnum<10){
//看示例后原本以为学号<10时要前面加0,但是测试用例中不需要加0,坑人
printf("%d%s\n",searchnum,mymap[searchnum].c_str());
}
else{
printf("%d%s\n",searchnum,mymap[searchnum].c_str());
}
}
else{
printf("No Answer!\n");
}
}
}
}
笔记
- 本题也可以创建一个struct存储学号以外的信息,然后map<int,struct>即可
- 想实现把一行中空格隔开的部分分别输入到不同变量,可以cin和getline连续使用,,也可以直接getline然后再
- 用scanf输出string类型时,加上.c_str()。
- 输出格式可能给错,可以留意一下。
- map的定义、状态、添加、访问。