🍦🍦🍦今天我们继续来学习数据结构的剩下部分:二叉排序树、hash 算法、前缀树。大家一起来学习计算机考研机试中所涉及到的数据结构问题呀~
目录
[🧊🧊🧊5.4 二叉排序树](#🧊🧊🧊5.4 二叉排序树)
[🥥例题:DreamJudge 1411](#🥥例题:DreamJudge 1411)
[DreamJudge 1317 二叉搜索树 🍰](#DreamJudge 1317 二叉搜索树 🍰)
[DreamJudge 1396 二叉排序树 - 华科](#DreamJudge 1396 二叉排序树 - 华科)
[🧊🧊🧊5.5 hash算法](#🧊🧊🧊5.5 hash算法)
[DreamJudge 1329 统计同成绩的学生人数](#DreamJudge 1329 统计同成绩的学生人数)
[DeamJudge 1225 谁是你潜在朋友](#DeamJudge 1225 谁是你潜在朋友)
[DreamJudge 1175 剩下的树](#DreamJudge 1175 剩下的树)
[DreamJudge 1209 刷出一道墙](#DreamJudge 1209 刷出一道墙)
[🧊🧊🧊5.6 前缀树](#🧊🧊🧊5.6 前缀树)
[🥥例题:DreamJudge 1098](#🥥例题:DreamJudge 1098)
[DreamJudge 1492 三叉树 🍰](#DreamJudge 1492 三叉树 🍰)
[DreamJudge 1654 二叉树 🍰](#DreamJudge 1654 二叉树 🍰)
[DreamJudge 1827 有向树形态](#DreamJudge 1827 有向树形态)
[DreamJudge 1841 树的高度](#DreamJudge 1841 树的高度)
🧊🧊🧊5.4 二叉排序树
🥥题型总结:
定义(左<根<右)
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树,是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
(4)没有键值相等的结点。
考法
1、考察定义的理解,根据定义建立二叉排序树,然后输出其先序、中序、后序。
2、考察二叉排序树的应用,例如多次查找,建立二叉排序树之后将查找的复杂度从线性降低到 log 级别。对于可以使用 C++的同学而言,直接用前面讲过的 map 即可,对于只能使用 C 语言的同学而言,掌握二叉排序可以解决大量的查找类问题。
🥥例题:DreamJudge 1411


根据二叉排序的定义将所有元素插入到二叉排序树中,然后分别输出这颗二叉排序树的先序遍历、中序遍历、后序遍历。
cpp
#include <bits/stdc++.h>
using namespace std;
typedef struct node{
int data;
struct node *lchild,*rchild;
}*BitTree;
//将元素插入二叉排序树中
void InsertBitTree(BitTree &T, int x) {
if (T == NULL) {
T = new node;
T->data = x;
T->lchild = NULL;
T->rchild = NULL;
return;
}
if (x == T->data) return;
else if (x < T->data) InsertBitTree(T->lchild, x);
else InsertBitTree(T->rchild, x);
}
//将二叉树按照先序输出
void PreOrderTraverse(BitTree T) {
if (T != NULL) {
cout << T->data << ' ';
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
//将二叉树按照中序输出
void InOrderTraverse(BitTree T) {
if (T != NULL) {
InOrderTraverse(T->lchild);
cout << T->data << ' ';
InOrderTraverse(T->rchild);
}
}
//将二叉树按照后序输出
void PostOrderTraverse(BitTree T) {
if (T != NULL) {
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
cout << T->data << ' ';
}
}
int main(){
int n, x;
while (cin >> n) {
BitTree T = NULL;
for (int i = 1; i <= n; i++) {
cin >> x;
InsertBitTree(T, x);
}
PreOrderTraverse(T); cout << endl;
InOrderTraverse(T); cout << endl;
PostOrderTraverse(T); cout << endl;
}
return 0;
}
🥥练习题目:
DreamJudge 1317 二叉搜索树 🍰
cpp
//摘自N诺用户:为欢几何
#include<bits/stdc++.h>
using namespace std;
struct Tree {
int data;
struct Tree* lchild;
struct Tree* rchild;
};
void _insert(struct Tree* &root, int t) {
if(root == NULL) {
root = new Tree;
root->data = t;
root->lchild = NULL;
root->rchild = NULL;
return;
} else if(t < root->data) {
_insert(root->lchild, t);
} else if(t > root->data) {
_insert(root->rchild, t);
} else if(t == root->data)
return;
}
bool cmp(struct Tree* r1, struct Tree* r2) {
if(r1 == NULL && r2 == NULL)
return true;
if(r1 == NULL || r2 == NULL)
return false;
if(r1->data != r2->data)
return false;
return cmp(r1->lchild, r2->lchild) && cmp(r1->rchild, r2->rchild);
}
int main() {
int n;
while(cin >> n) {
if(n==0) return 0;
struct Tree* root = NULL;
char s1[15];
cin>>s1;
int l1 = strlen(s1);
for(int i = 0; i < l1; i++) {
_insert(root, s1[i]-'0');
}
while(n--) {
struct Tree* r = NULL;
char s2[15];
cin>>s2;
int l2 = strlen(s2);
for(int i = 0; i < l2; i++) {
_insert(r, s2[i]-'0');
}
if(cmp(root, r))
cout << "YES" << endl;
else if(!cmp(root, r))
cout << "NO" << endl;
}
}
return 0;
}
DreamJudge 1396 二叉排序树 - 华科
cpp
#include <bits/stdc++.h>
using namespace std;
typedef struct node{
int data;
struct node *lchild,*rchild;
}*Tree;
int tmp=-1;
void Insert(Tree &root,int cur)
{
if(root==NULL){
root=new node;
root->data=cur;
root->lchild=NULL;
root->rchild=NULL;
cout<<tmp<<endl;
tmp=-1;
return;
}
else if(root->data==cur) return;
else if(root->data>cur)//这个数比当前根结点小,去左子树找
{
tmp=root->data;
Insert(root->lchild,cur);
}
else //这个数比当前根节点大,去右子树找
{
tmp=root->data;
Insert(root->rchild,cur);
}
}
int main(){
int n;
int cur;
while(cin>>n)
{
Tree root=NULL;
for(int i=0;i<n;i++)
{
cin>>cur;
Insert(root,cur);
}
}
return 0;
}
🧊🧊🧊5.5 hash算法
哈希算法在考研机试中的题目几乎都可以用 map 来解决。
🥥题型总结:
- **输入 N 个数,统计某个数出现的次数:**使用辅助数组进行标记
- **输入 N 个数,进行排序或求前 K 小的数,其中数值的区间范围小:**使用辅助数组进行标记,如果值为负数或很大,将区间进行平移即可
- **有多段区间进行覆盖,问其中某个点被覆盖到的次数:**使用辅助数组进行标记,直接遍历每一段区间累加上去。
上面这几种勉强可列为哈希算法的范围,其实都是对数组的一种应用技巧,使用数组下标对应存储的值,数组存的值是出现的次数。
- **给一个等式 a+b+c+d=0,其中 a,b,c,d 的范围是[-50,50],求 a,b,c,d 使得等式成立的值:**如果直接暴力枚举,那么复杂度是 O(n^4),我们可以将等式移项变成 a+b=-(c+d),我们分别枚举 a+b 的值存起来,-(c+d)的值存起来,复杂度是 O(n^2),那么问题就转化成了,比较两个数组中相同的值的个数。可以用二分查找的方法,也可以用 map,还可以自己构造哈希函数,最终的时间复杂度是 O(n^2log(n^2))。
- **输入 n 个字符串,问相同的字符串的个数:**很典型的字符串哈希,建议用 map,基本都能解决。
🥥练习题目:
DreamJudge 1329 统计同成绩的学生人数
cpp
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,cur,check;
while(cin>>n)
{
if(n==0) break;
map<int,int> mp;
for(int i=0;i<n;i++)
{
cin>>cur;
if(mp.find(cur)==mp.end()) mp.insert({cur,1});
else mp[cur]++;
}
cin>>check;
if(mp.find(check)==mp.end()) cout<<0<<endl;
else cout<<mp[check]<<endl;
}
return 0;
}
DeamJudge 1225 谁是你潜在朋友
cpp
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,m;
while(cin>>n>>m)
{
vector<int> a(n+5),b(m+5);
for(int i=1;i<=n;i++)
{
cin>>a[i];
b[a[i]]++;
}
for(int i=1;i<=n;i++)
{
if(b[a[i]]-1) cout<<b[a[i]]-1<<endl;
else cout<<"BeiJu"<<endl;
}
}
return 0;
}
DreamJudge 1175 剩下的树
cpp
#include<bits/stdc++.h>
using namespace std;
int main()
{
int l,m,a[10010]={0};
while(cin>>l>>m)
{
for(int i=0;i<=l;i++) a[i]=1;
int start,end;
for(int k=0;k<m;k++)
{
cin>>start>>end;
for(int i=start;i<=end;i++)
{
if(a[i]==1) a[i]=0;
}
}
int cnt=0;
for(int i=0;i<=l;i++)
{
if(a[i]==1) cnt++;
}
cout<<cnt<<endl;
}
return 0;
}
DreamJudge 1209 刷出一道墙
cpp
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
vector<int> a(200010,0);
int b,e;
while(cin>>b>>e)
{
if(b==0&&e==0) break;
a[b]++; // 标记刷墙起点
a[e+1]--; // 标记刷墙终点
}
// 计算前缀和
for(int i=1;i< a.size();i++) a[i]+=a[i-1];
int l,r;
while(cin>>l>>r)
{
if(l==0&&r==0) break;
for(int i=l;i<=r;i++) printf("%d\n",a[i]);//用cout会超时
}
return 0;
}
🧊🧊🧊5.6 前缀树
🥥题型总结:
定义
前缀树,又称单词查找树,Trie 树,是一种树形结构,哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
考点分析
- **n 个字符串中找到某个字符串是否存在或出现了几次:**数据小直接顺序查找比较,数据大的时候可以直接用 map 来查找,如果只能用 C 语言,那么就需要用前缀树来解决。
- **n 个字符串中查找包含某个前缀的的字符串的个数:**这是非常典型的应用方法,建立前缀树即可知道每个前缀的单词个数。
🥥例题:DreamJudge 1098


这道题的数据很小,所以各种办法都可以解决,如果题目的数据大一些,比如有10W 个字符串的时候,这个时候就需要用前缀树来解决了,其实这个题就是求前缀树的叶子结点的个数,因为只有是叶子结点才不会是其他字符串的前缀。
cpp
#include <bits/stdc++.h>
using namespace std;
typedef struct node{ //注意 typedef 不能省略
char data;
struct node *lchild,*rchild;
}*BitTree;
//先序遍历的方式创建二叉树
void CreatBitTree(BitTree &T) {
char c;
cin >> c;
if (c == '0') T = NULL;
else {
T = new node;
T->data = c;
CreatBitTree(T->lchild);
CreatBitTree(T->rchild);
}
}
//将二叉树按照先序输出:根左右
void PreOrderTraverse(BitTree T) {
if (T != NULL) {
cout << T->data << ' ';
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
//将二叉树按照中序输出:左根右
void InOrderTraverse(BitTree T) {
if (T != NULL) {
InOrderTraverse(T->lchild);
cout << T->data << ' ';
InOrderTraverse(T->rchild);
}
}
//将二叉树按照后序输出:左右根
void PostOrderTraverse(BitTree T) {
if (T != NULL) {
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
cout << T->data << ' ';
}
}
//二叉树的叶子节点个数
int Leaf(BitTree T) {
if (T == NULL) return 0;
if (T->lchild == NULL && T->rchild == NULL) return 1;
return Leaf(T->lchild) + Leaf(T->rchild);
}
//二叉树的深度
int Deepth(BitTree T) {
if (T == NULL) return 0;
int x = Deepth(T->lchild);
int y = Deepth(T->rchild);
return max(x,y) + 1;
}
int main(){
BitTree T;
CreatBitTree(T);
PreOrderTraverse(T); cout << endl;
InOrderTraverse(T); cout << endl;
PostOrderTraverse(T); cout << endl;
cout << Leaf(T) << endl;
return 0;
}
下面给出两个通用模板,大家做题的时候可以直接套上去使用,一个是链式存储的方法,另一个是静态数组的方法:
链式存储:
cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 26;
typedef struct TrieNode {
int nCount;
struct TrieNode *next[maxn];
}Trie;
Trie root;
void InitTrie() {
for (int i = 0; i < maxn; i++)
root.next[i] = NULL;
}
void CreateTrie(char *str) {
int len = strlen(str);
Trie *p = &root, *q;
for(int i = 0; i < len; i++) {
int k = str[i] - 'a';
if (p->next[k] == NULL) {
q = (Trie *)malloc(sizeof(root));
q->nCount = 1;
for (int j = 0; j < maxn; j++)
q->next[j] = NULL;
p->next[k] = q;
p = p->next[k];
}
else {
p->next[k]->nCount++;
p = p->next[k];
}
}
}
int FindTrie(char *str) {
int len = strlen(str);
Trie *p = &root;
for (int i = 0; i < len; i++) {
int k = str[i] - 'a';
if (p->next[k] == NULL) return 0;
p = p->next[k];
}
return p->nCount;
}
int main() {
char str[15];
InitTrie();
while (gets(str) && str[0]) CreateTrie(str);
while (gets(str)) printf("%d\n", FindTrie(str));
return 0;
}
静态数组:
cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000000 + 10;
int trie[maxn][26] = {0}; // 存储下一个字符的位置
int num[maxn] = {0};
int pos = 1;
void InsertTrie(char word[]) {
int c = 0;
for (int i = 0; word[i]; i++) {
int k = word[i] - 'a';
if (trie[c][k] == 0) trie[c][k] = pos++;
c = trie[c][k];
num[c]++;
}
}
int FindTrie(char word[]) {
int c = 0;
for (int i = 0; word[i]; i++) {
int k = word[i] - 'a';
if (trie[c][k] == 0) return 0;
c = trie[c][k];
}
return num[c];
}
int main() {
char str[15] = {0};
while (gets(str) && str[0]) InsertTrie(str);
while (gets(str)) printf("%d\n", FindTrie(str));
return 0;
}
🥥练习题目:
DreamJudge 1492 三叉树 🍰
输出样例:
100 101 102 5
102 101 100 108 103 8
103 108 105 16
105 108 104 106 14
106 104 108 100 107 109 19
109 107 100
cpp
//摘自N诺用户:致敬大佬
#include <bits/stdc++.h>
using namespace std;
const int N = 1000; // 假设节点数最大为1000
int tr[N][3]; // 存储树结构
map<int, vector<int>> paths; // 存储从根到叶子节点的路径
// DFS记录路径
void dfs(int node, vector<int> &path) {
path.push_back(node);
bool isLeaf = true;
for (int i = 0; i < 3; i++) {
if (tr[node][i] != -1) {
isLeaf = false;
dfs(tr[node][i], path);
}
}
if (isLeaf) {
paths[node] = path;
}
path.pop_back();
}
// 找到两个路径的最后一个公共节点
int findLastCommon(const vector<int> &path1, const vector<int> &path2) {
int minLength = min(path1.size(), path2.size());
int lastCommon = -1; // 用于标记最后一个公共节点的位置
for (int i = 0; i < minLength; i++) {
if (path1[i] != path2[i]) break;
lastCommon = i;
}
return lastCommon;
}
int main() {
memset(tr, -1, sizeof tr); // 初始化所有子节点为 -1
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int t;
cin >> t;
for (int j = 0; j < 3; j++) {
cin >> tr[t][j];
}
}
vector<int> path;
dfs(100, path); // 假设100是根节点
int m;
cin >> m;
vector<pair<int, int>> leaves(m);
for (int i = 0; i < m; i++) {
cin >> leaves[i].first >> leaves[i].second;
}
sort(leaves.begin(), leaves.end(), [](pair<int, int> &a, pair<int, int> &b) {
return a.second < b.second; // 按优先级排序
});
int current = 100; // 从根节点开始
for (auto leaf : leaves) {
int nextLeaf = leaf.first;
vector<int> &path1 = paths[current];
vector<int> &path2 = paths[nextLeaf];
int lastCommon = findLastCommon(path1, path2);
// 输出从当前节点到nextLeaf的路径
for (int i = path1.size() - 1; i > lastCommon; i--) {
cout << path1[i - 1] << " ";
}
for (size_t i = lastCommon + 1; i < path2.size(); i++) {
cout << path2[i] << " ";
}
cout << endl;
current = nextLeaf;
}
// 最后从最后一个叶子节点回到根节点
vector<int> &pathToRoot = paths[current];
for (int i = pathToRoot.size() - 1; i > 0; i--) {
cout << pathToRoot[i - 1] << " ";
}
return 0;
}
DreamJudge 1654 二叉树 🍰
cpp
//摘自N诺用户:kas
#include<bits/stdc++.h>
using namespace std;
vector<int> FindLevel(int parent[],int start) {
vector<int> vec;
while (parent[start]) {
vec.push_back(start);
start = parent[start];
}
vec.push_back(start);
return vec;
}
int main()
{
int t, n, m, lchild, rchild, end1, end2;
vector<int> vec_end1, vec_end2;
cin >> t;
while (t--) {
cin >> n >> m;
//int* parent = new int[n + 1];
int parent[100];
//根节点的父节点设为0
parent[1] = 0;
for (int i = 1; i <= n; ++i) {
cin >> lchild >> rchild;
if (lchild != -1)
parent[lchild] = i;
if (rchild != -1)
parent[rchild] = i;
}
for (int i = 1; i <= m; ++i) {
cin >> end1 >> end2;
vec_end1 = FindLevel(parent, end1);
reverse(vec_end1.begin(), vec_end1.end());
vec_end2 = FindLevel(parent, end2);
reverse(vec_end2.begin(), vec_end2.end());
int size1 = vec_end1.size(), size2 = vec_end2.size(), dist = 0;
while (size1 > size2) {
vec_end1.pop_back();
size1--;
dist++;
}
while (size1 < size2) {
vec_end2.pop_back();
size2--;
dist++;
}
while (!vec_end1.empty() && !vec_end2.empty()) {
if (vec_end1.back() != vec_end2.back()) {
dist += 2;
vec_end1.pop_back();
vec_end2.pop_back();
}
else
break;
}
cout << dist << endl;
}
}
}
DreamJudge 1827 有向树形态
DreamJudge 1841 树的高度
这一部分比较难,大家可以收藏起来慢慢学慢慢练!
创作不易,点个赞吧~点赞收藏不迷路,感兴趣的宝子们欢迎关注该专栏~
今天有点事,练习题没做完,我后边会尽快补齐的,做完我就更新啦,大家可以先自己练习着~
勤奋努力的宝子们,学习辛苦了!🌷🌷🌷休息下,我们下部分再见👋( •̀ ω •́ )✧~