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

🍦🍦🍦今天我们继续来学习数据结构的剩下部分:二叉排序树、hash 算法、前缀树。大家一起来学习计算机考研机试中所涉及到的数据结构问题呀~

目录

🧊🧊🧊5.4 二叉排序树

🥥题型总结:

定义(左<根<右)

考法

🥥例题:DreamJudge 1411

🥥练习题目:

DreamJudge 1317 二叉搜索树 🍰

DreamJudge 1396 二叉排序树 - 华科

🧊🧊🧊5.5 hash算法

🥥题型总结:

🥥练习题目:

DreamJudge 1329 统计同成绩的学生人数

DeamJudge 1225 谁是你潜在朋友

DreamJudge 1175 剩下的树

DreamJudge 1209 刷出一道墙

🧊🧊🧊5.6 前缀树

🥥题型总结:

定义

考点分析

🥥例题:DreamJudge 1098

🥥练习题目:

DreamJudge 1492 三叉树 🍰

DreamJudge 1654 二叉树 🍰

DreamJudge 1827 有向树形态

DreamJudge 1841 树的高度


🧊🧊🧊5.4 二叉排序树

🥥题型总结:

定义(左<根<右)

二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树,是一棵空树,或者是具有下列性质的二叉树:

(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;

(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;

(3)左、右子树也分别为二叉排序树;

(4)没有键值相等的结点

考法

1、考察定义的理解,根据定义建立二叉排序树,然后输出其先序、中序、后序

2、考察二叉排序树的应用,例如多次查找,建立二叉排序树之后将查找的复杂度从线性降低到 log 级别。对于可以使用 C++的同学而言,直接用前面讲过的 map 即可,对于只能使用 C 语言的同学而言,掌握二叉排序可以解决大量的查找类问题。

🥥例题:DreamJudge 1411

根据二叉排序的定义将所有元素插入到二叉排序树中,然后分别输出这颗二叉排序树的先序遍历、中序遍历、后序遍历。

#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 二叉搜索树 🍰

//摘自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 二叉排序树 - 华科

#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 统计同成绩的学生人数

#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 谁是你潜在朋友

#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 剩下的树

#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 刷出一道墙

#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 个字符串的时候,这个时候就需要用前缀树来解决了,其实这个题就是求前缀树的叶子结点的个数,因为只有是叶子结点才不会是其他字符串的前缀

#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;
}

下面给出两个通用模板,大家做题的时候可以直接套上去使用,一个是链式存储的方法,另一个是静态数组的方法:

链式存储:

#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;
}

 静态数组:

#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
//摘自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 二叉树 🍰

//摘自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 树的高度

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

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

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

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

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值