保研考研机试攻略:第五章——数据结构(2)

🍦🍦🍦今天我们继续来学习数据结构的剩下部分:二叉排序树、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 来解决。

🥥题型总结:

  1. **输入 N 个数,统计某个数出现的次数:**使用辅助数组进行标记
  2. **输入 N 个数,进行排序或求前 K 小的数,其中数值的区间范围小:**使用辅助数组进行标记,如果值为负数或很大,将区间进行平移即可
  3. **有多段区间进行覆盖,问其中某个点被覆盖到的次数:**使用辅助数组进行标记,直接遍历每一段区间累加上去。

上面这几种勉强可列为哈希算法的范围,其实都是对数组的一种应用技巧,使用数组下标对应存储的值,数组存的值是出现的次数

  1. **给一个等式 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))。
  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 树,是一种树形结构,哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

考点分析

  1. **n 个字符串中找到某个字符串是否存在或出现了几次:**数据小直接顺序查找比较,数据大的时候可以直接用 map 来查找,如果只能用 C 语言,那么就需要用前缀树来解决。
  2. **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 树的高度

这一部分比较难,大家可以收藏起来慢慢学慢慢练!

创作不易,点个赞吧~点赞收藏不迷路,感兴趣的宝子们欢迎关注该专栏~

今天有点事,练习题没做完,我后边会尽快补齐的,做完我就更新啦,大家可以先自己练习着~

勤奋努力的宝子们,学习辛苦了!🌷🌷🌷休息下,我们下部分再见👋( •̀ ω •́ )✧~

相关推荐
争不过朝夕,又念着往昔2 分钟前
Redis中Hash(哈希)类型的基本操作
数据库·redis·缓存·哈希算法
繁依Fanyi25 分钟前
使用 Spring Boot + Redis + Vue 实现动态路由加载页面
开发语言·vue.js·pytorch·spring boot·redis·python·算法
aloha_78931 分钟前
B站宋红康JAVA基础视频教程(chapter14数据结构与集合源码)
java·数据结构·spring boot·算法·spring cloud·mybatis
float_com39 分钟前
【STL】 set 与 multiset:基础、操作与应用
c++·stl
临沂堇1 小时前
CCF刷题计划——训练计划(反向拓扑排序)
数据结构·c++·算法·拓扑·ccf
铁匠匠匠1 小时前
【C总集篇】第八章 数组和指针
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
猿饵块1 小时前
cmake--get_filename_component
java·前端·c++
Unicorn建模1 小时前
2024“华为杯”中国研究生数学建模竞赛(E题)深度剖析|数学建模完整过程+详细思路+代码全解析
python·算法·数学建模
森龙安1 小时前
string类,vector<T>,iterator迭代器,C风格字符串,数组
c++
咕咕吖1 小时前
二叉树的层序遍历(c)
数据结构·算法