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

🍨🍨🍨这一章我们开始更新一些常见的数据结构题型,包括栈的应用、哈夫曼树、二叉树、二叉排序树、hash 算法、前缀树等内容。希望能帮助大家更好地掌握计算机考研机试中所涉及到的数据结构问题。

目录

🧊🧊🧊5.1 栈的应用

🥥题型总结:

🥥例题:DreamJudge 1501

🥥练习题目:

DreamJudge 1067 括号的匹配 🍰

DreamJudge 1296 括号匹配问题

DreamJudge 1838 括号匹配

🧊🧊🧊5.2 哈夫曼树

🥥题型总结:

🥥例题:DreamJudge 1544

🥥哈夫曼编码:

🥥练习题目:

DreamJudge 1382 哈夫曼树

DreamJudge 1562 哈夫曼编码

🧊🧊🧊5.3 二叉树

🥥满二叉树

🥥完全二叉树

🥥二叉树遍历

🍈前序遍历

🍈中序遍历

🍈后序遍历

🍈层次遍历

🥥题型总结:

🥥例题:DreamJudge 1109

🥥练习题目:

DreamJudge 1161 二叉树遍历 🍰

DreamJudge 1233 二叉树 🍰

DreamJudge 1264 二叉树 2 🍰

DreamJudge 1401 二叉树遍历 🍰

DreamJudge 1551 判断二叉树是否对称 🍰

DreamJudge 1561 二叉树(北京邮电大学)


🧊🧊🧊5.1 栈的应用

🥥题型总结:

栈是一种只能在一端进行插入和删除操作的数据结构,满足先进后出的特性。

我们通过 stack<int> S 定义一个全局栈来储存整数的空stack。stack 可以存任何类型的数据,比如 stack<string> S 等等。

#include <iostream>
#include <stack>
using namespace std;
stack<int> S;
int main() {
    S.push(1);
    S.push(10);
    S.push(7);
    while (!S.empty()) {
        cout << S.top() << endl;
        S.pop();
    }
    return 0;
}

只能用C语言的同学用数组模拟栈:

//栈和队列都可以用数组模拟实现
#include <stdio.h>
int st[1005];//定义一个栈
int main() {
    int top = 0;//栈顶下标
    st[++top] = 1;//将 1 入栈
    st[++top] = 10;//将 10 入栈
    st[++top] = 7;//将 7 入栈
    while (top > 0) {//当栈不为空
        printf("%d\n", st[top]);//输出栈顶元素
        top--;//将栈顶元素出栈
    }
    return 0;
}

常见应用方式:

  1. 计算表达式的值
  2. 带优先级的括号匹配问题

当查询次数很多或数据量特别大的时候就不适合用顺序查找了。这类查找类题目,推荐使用map

🥥例题:DreamJudge 1501

用栈模拟即可,和栈顶元素匹配说明配对成功,将栈顶元素出栈,否则配对不成功,将当前元素入栈。最后查看栈是否为空,若为空则是 YES,否则就是 NO。

#include <bits/stdc++.h>
using namespace std;
int main() {
    char s[300];
    scanf("%s", &s);
    int len = strlen(s);
    stack<char> st;
    for (int i = 0; i < len; i++) {
        if (!st.empty()) {
            char now = st.top();
            if (s[i]==')'&&now=='('||s[i]==']'&&now=='[')
                st.pop();
            else st.push(s[i]);
        }
        else st.push(s[i]);
    }
    if (!st.empty()) printf("NO\n");
    else printf("YES\n");
    return 0;
}

🥥练习题目:

DreamJudge 1067 括号的匹配 🍰

涉及到优先级问题的,使用map进行优先级记录,然后判断

#include<bits/stdc++.h>
using namespace std;
//设置括弧的优先级
map<char,int> priority={ {'<',0},{'(',1},{'[',2},{'{',3} };
//设置括弧的匹配
map<char,char> match={ {'>','<'},{')','('},{']','['},{'}','{'} };

bool start(string s){
    int len=s.size();
    stack<char> st;
    for(int i=0;i<len;i++)
	{
            if(s[i]=='<'||s[i]=='('||s[i]=='['||s[i]=='{')
			{//左括号入栈处理
               if(!st.empty()&&priority[s[i]]>priority[st.top()]) return false;//判断括号的优先级顺序是否正确
               st.push(s[i]);
            }
			else
			{//右括号出栈匹配处理
                if(st.empty()) return false;
                else if(st.top()!=match[s[i]]) return false;
                else st.pop();
            }
    }
    if(st.empty()) return true;
    else return false;
}

int main(){
    int n;
    cin>>n;
    string s;
    for(int i=0;i<n;i++)
	{
        cin>>s;
        if(start(s)) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

DreamJudge 1296 括号匹配问题

算法思想:

括号匹配是典型的栈的应用,因此可以采用链栈,思路如下:

(1)第一次遍历输入字符串,

        1.是左括号,则入栈,同时标记数组的相应位置置空格

        2.是右括号,则检测栈是否为空,不为空,则判断有对应的左括号,同时出栈;为空,则没有对应的左括号,标记数组置‘?’

(2)第二次遍历输入字符串,
        1.是右括号,则入栈。

        2.是左括号,则检测栈是否为空,不为空,则判断有对应的右括号,同时出栈;为空,则没有对应的左括号,标记数组置‘$’。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	string s;
	char ans[110]={' '};
	while(cin>>s)
	{
		stack<pair<char,int> > st;
		int len=s.size();
		memset(ans,' ',len);
		for(int i=0;i<len;i++)
		{
			if(s[i]=='(') st.push({s[i],i});
			else if(s[i]==')'&&st.empty()) ans[i]='?';
			else if(s[i]==')'&&!st.empty()) st.pop();
			else continue;
		}
		while(!st.empty())
		{
			pair<char,int> cur=st.top();
			st.pop();
			ans[cur.second]='$';
		}
		cout<<s<<endl;
		for(int i=0;i<len;i++) cout<<ans[i];
		cout<<endl;
	}
	return 0;
}

DreamJudge 1838 括号匹配

#include<bits/stdc++.h>
using namespace std;
int main()
{
	string s;
	while(cin>>s)
	{
		stack<char> st;
		int len=s.size(),flag=0;
		for(int i=0;i<len;i++)
		{
			if(s[i]=='<'||s[i]=='('||s[i]=='{'||s[i]=='[') st.push(s[i]);
			else if(s[i]=='>'&&st.top()!='<') 
			{
				cout<<"no"<<endl;
				flag=1;
				break;
			}
			else if(s[i]==')'&&st.top()!='(') 
			{
				cout<<"no"<<endl;
				flag=1;
				break;
			}
			else if(s[i]==']'&&st.top()!='[') 
			{
				cout<<"no"<<endl;
				flag=1;
				break;
			}
			else if(s[i]=='}'&&st.top()!='{') 
			{
				cout<<"no"<<endl;
				flag=1;
				break;
			}
			else continue;
		}
		if(flag==0) cout<<"yes"<<endl;
	}
	return 0;
}

🧊🧊🧊5.2 哈夫曼树

给定 N 个权值作为 N 个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

哈夫曼树的生成原理:每次取权值最小的两个节点(可以是叶子节点&叶子节点,也可以是叶子节点&中间节点,也可以是中间节点&中间节点)合并起来并生成一个以两节点权重和为权重的根节点,重复上述步骤直至生成一棵完整的树。

🥥题型总结:

  1. 直接要求构造哈夫曼树,输出 WPL,即带权路径长度。
  2. 考察概念的理解,如下面的例题,合并果子
  3. 考察哈夫曼编码

🥥例题:DreamJudge 1544

想要求得最小值,每次合并最小两个元素即可。乍一看输入样例,好像只要从小到大排个序就行,然后从前往后依次合并。实际上这个方法是错误的,比如5,6,7,8 这四个数,如果先合并 5 和 6,然后再和 7 合并,然后再和 8 合并这样得到的结果就会偏大,正确的方法是先合并 5 和 6,然后再合并 7 和 8,最后 11 和 15 合并在一起,这样才是最优解。为什么呢?因为在合并的过程中产生的新的数不一定是最小的,所以在每一次合并的过程中我们都需要重新排序找出当前最小的两个数。但是如果每一次合并就要重新全部排序,复杂度太高,不能接受。这道题我们使用优先队列来维护单调性,问题就解决了。

#include <bits/stdc++.h>
using namespace std;
struct node {
    int x;
    node(int a) {x = a;}//构造函数方便赋值
};
//定义优先队列的比较关系 和 sort 的 cmp 类似
bool operator < (const node& a, const node& b ){
    return a.x > b.x;
}
int main()
{
    priority_queue<node> q;
    int n, x;
    cin >> n;
    for(int i = 0;i < n;i++) {
        cin >> x;
        q.push(x);
    }
    int ans = 0;
    while (q.size() > 1) {
        node num1 = q.top();
        q.pop();
        node num2 = q.top();
        q.pop();
        ans += (num1.x + num2.x);
        q.push(node{num1.x+num2.x});
    }
    cout << ans;
    return 0;
}

如果是机试真能用纯 C 语言的同学,在数据量不大的情况下,可以用合并之后重新排序的暴力算法,如果数据量大,建议用后一节的二叉排序树来实现,如果二叉排序树平衡,新增一个点和删除一个点都是 log 级别的复杂度,可以满足数据量大的需求。

🥥哈夫曼编码:

哈夫曼树的应用很广,哈夫曼编码就是其在电讯通信中的应用之一,是一种广泛地用于数据文件压缩的有效编码方法。其压缩率通常在 20%~90%之间。在电讯通信业务中,通常用二进制编码来表示字母或其他字符,并用这样的编码来表示字符序列。

例:如果需传送的电文为 ‘ABACCDA’,它只用到四种字符,用两位二进制编码便可分辨。假设 A, B, C, D 的编码分别为 00, 01,10, 11,则上述电文便为 ‘00010010101100’(共 14 位),译码员按两位进行分组译码,便可恢复原来的电文。

能否使编码总长度更短呢?

实际应用中各字符的出现频度不相同,用短(长)编码表示频率大(小)的字符,使得编码序列的总长度最小,使所需总空间量最少

数据的最小冗余编码问题

在上例中,若假设 A, B, C, D 的编码分别为 0,00,1,01,则电文 ‘ABACCDA’ 便为‘000011010’(共 9 位),但此编码存在多义性:可译为: ‘BBCCDA’、‘ABACCDA’、‘AAAACCACA’ 等。

译码的惟一性问题

要求任一字符的编码都不能是另一字符编码的前缀,这种编码称为前缀编码(其实是非前缀码)。 在编码过程要考虑两个问题,数据的最小冗余编码问题,译码的惟一性问题,利用最优二叉树可以很好地解决上述两个问题。

用二叉树设计二进制前缀编码

以电文中的字符作为叶子结点构造二叉树。然后将二叉树中结点引向其左孩子的分支标‘0’,引向其右孩子的分支标 ‘1’; 每个字符的编码即为从根到每个叶子的路径上得到的0, 1 序列。如此得到的即为二进制前缀编码。

编码: A:0, C:10,B:110,D:111

任意一个叶子结点都不可能在其它叶子结点的路径中。

用哈夫曼树设计总长最短的二进制前缀编码

假设各个字符在电文中出现的次数(或频率)为 wi ,其编码长度为 li,电文中只有 n 种字符,则电文编码总长为:

设计电文总长最短的编码,设计哈夫曼树(以 n 种字符出现的频率作权)。

由哈夫曼树得到的二进制前缀编码称为哈夫曼编码

例:如果需传送的电文为 ‘ABACCDA’,即:A, B, C, D 的频率(即权值)分别为 0.43, 0.14,0.29, 0.14,试构造哈夫曼编码。

编码: A:0, C:10, B:110, D:111 。电文 ‘ABACCDA’ 便为 ‘0110010101110’(共 13 位)。

例:如果需传送的电文为 ‘ABCACCDAEAE’,即:A, B, C, D, E 的频率(即权值)分别为0.36, 0.1, 0.27, 0.1, 0.18,试构造哈夫曼编码。

编码: A:11,C:10,E:00,B:010,D:011 ,则电文 ‘ABCACCDAEAE’ 便为‘110101011101001111001100’(共 24 位,比 33 位短)。

译码

从哈夫曼树根开始,对待译码电文逐位取码。若编码是“0”,则向左走;若编码是“1”,则向右走,一旦到达叶子结点,则译出一个字符;再重新从根出发,直到电文结束。

电文为 “1101000” ,译文只能是“CAT”。

🥥练习题目:

DreamJudge 1382 哈夫曼树

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    while(cin>>n)
    {
    	priority_queue<int,vector<int>,greater<int> > q;//默认大根堆,这是小根堆
    	int res=0;
    	//入队
    	while(n--)
    	{
    		int a;
    		cin>>a;
    		q.push(a);
    	}
    	while(q.size()>1)
    	{
			//取出两个权值最小的结点合并
    		int cur=q.top();
    		q.pop();
    		cur+=q.top();
    		q.pop();
			//记录结果并放回小根堆
    		res+=cur;
    		q.push(cur);
    	}
    	cout<<res<<endl;
    }
	return 0;
}

DreamJudge 1562 哈夫曼编码

//摘自N诺评论区用户:Cookie‘s AE86
#include<bits/stdc++.h>
using namespace std;

void func(string s){
    int len = s.size();
    set<char> myset;
    for(int i = 0; i < len ;i++)
        myset.insert(s[i]);
    //获得每种字符使用频率的优先队列
    priority_queue<int, vector<int>, greater<int>> pq;
    for(auto i = myset.begin(); i != myset.end(); ++i){
        int tmp = count(s.begin(), s.end(), *i);
        pq.push(tmp);
    }
    //计算WPL
    int wpl = 0;
    if(pq.size() == 1)
        wpl = len;
    while(pq.size() > 1){
        int tmp = pq.top();
        pq.pop();
        tmp += pq.top();
        pq.pop();
        pq.push(tmp);
        wpl += tmp;
    }
    printf("%d %d %.1f\n", len*8, wpl, len*8.0/wpl);
}

int main() {
    string s;
    while(cin >> s){
        if (s == "END")
            break;
        func(s);
    }
    return 0;
}

🧊🧊🧊5.3 二叉树

二叉树是 n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。

下图展示了一棵普通二叉树:

二叉树特点

由二叉树定义以及图示分析得出二叉树有以下特点:

1)每个结点最多有两颗子树,所以二叉树中不存在度大于 2 的结点。

2)左子树和右子树是有顺序的,次序不能任意颠倒。

3)即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

二叉树性质

1)在二叉树的第 i 层上最多有 2^(i-1) 个节点 。(i>=1)

2)二叉树中如果深度为 k,那么最多有 2^(k) -1 个节点。(k>=1)

3)n0=n2+1,n0表示度数为 0 的节点数,n2表示度数为 2 的节点数。

4)在完全二叉树中,具有 n 个节点的完全二叉树的深度为[log2(n)]+1,其中[log2(n)]是向下取整。

5)若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性:

(1) 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;

(2) 若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;

(3) 若 2i+1>n,则该结点无右孩子结点, 否则,编号为 2i+1 的结点为其右孩子结点。

🥥满二叉树

满二叉树:在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。

满二叉树的特点有:

1)叶子只能出现在最下一层。出现在其它层就不可能达成平衡。

2)非叶子结点的度一定是 2。

3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。

🥥完全二叉树

完全二叉树:对一颗具有 n 个结点的二叉树按层编号,如果编号为 i(1<=i<=n)的结点与同样深

度的满二叉树中编号为 i 的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。

下图展示一棵完全二叉树:

特点:

1)叶子结点只能出现在最下层和次下层。

2)最下层的叶子结点集中在树的左部。

3)倒数第二层若存在叶子结点,一定在右部连续位置。

4)如果结点度为 1,则该结点只有左孩子,即没有右子树。

5)同样结点数目的二叉树,完全二叉树深度最小。

注:满二叉树一定是完全二叉树,但反过来不一定成立。

🥥二叉树遍历

二叉树的遍历是指从二叉树的根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次,且仅被访问一次。

二叉树的访问次序可以分为四种:前序遍历、中序遍历、后序遍历、层序遍历

🍈前序遍历

根左右

前序遍历通俗的说就是从二叉树的根结点出发,当第一次到达结点时就输出结点数据,按照先向左在向右的方向访问。

上图所示二叉树访问如下:

  1. 从根结点出发,则第一次到达结点 A,故输出 A;
  2. 继续向左访问,第一次访问结点 B,故输出 B;
  3. 按照同样规则,输出 D,输出 H;
  4. 当到达叶子结点 H,返回到 D,此时已经是第二次到达 D,故不在输出 D,进而向 D 右子树访问,D 右子树不为空,则访问至 I,第一次到达 I,则输出 I;
  5. I 为叶子结点,则返回到 D,D 左右子树已经访问完毕,则返回到 B,进而到 B 右子树,第一次到达 E,故输出 E;
  6. 向 E 左子树,故输出 J;
  7. 按照同样的访问规则,继续输出 C、F、G;

则上图所示二叉树的前序遍历输出为:

ABDHIEJCFG

🍈中序遍历

左根右

中序遍历就是从二叉树的根结点出发,当第二次到达结点时就输出结点数据,按照先向左在向右的方向访问。

上图所示二叉树中序访问如下:

  1. 从根结点出发,则第一次到达结点 A,不输出 A,继续向左访问,第一次访问结点 B,不输出B;
  2. 继续到达 D,H;
  3. 到达 H,H 左子树为空,则返回到 H,此时第二次访问 H,故输出 H;
  4. H 右子树为空,则返回至 D,此时第二次到达 D,故输出 D;
  5. 由 D 返回至 B,第二次到达 B,故输出 B;
  6. 按照同样规则继续访问,输出 J、E、A、F、C、G;

则上图所示二叉树的中序遍历输出为:

HDIBJEAFCG

🍈后序遍历

左右根

后序遍历就是从二叉树的根结点出发,当第三次到达结点时就输出结点数据,按照先向左在向右的方向访问。

上图所示二叉树后序访问如下:

  1. 从根结点出发,则第一次到达结点 A,不输出 A,继续向左访问,第一次访问结点 B,不输出
  2. B;
  3. 继续到达 D,H;
  4. 到达 H,H 左子树为空,则返回到 H,此时第二次访问 H,不输出 H;
  5. H 右子树为空,则返回至 H,此时第三次到达 H,故输出 H;
  6. 由 H 返回至 D,第二次到达 D,不输出 D;
  7. 继续访问至 I,I 左右子树均为空,故第三次访问 I 时,输出 I;
  8. 返回至 D,此时第三次到达 D,故输出 D;
  9. 按照同样规则继续访问,输出 J、E、B、F、G、C,A;

则上图所示二叉树的后序遍历输出为:

HIDJEBFGCA

🍈层次遍历

每一层从左到右遍历所有节点。

针对上图所示二叉树的层次遍历结果为:

ABCDEFGHIJ

🥥题型总结:

遍历常考考点

1)已知前序遍历序列和中序遍历序列,确定一棵二叉树。

例题:若一棵二叉树的前序遍历为 ABCDEF,中序遍历为 CBAEDF,请画出这棵二叉树。

分析:前序遍历第一个输出结点为根结点,故 A 为根结点。早中序遍历中根结点处于左右子树结点中间,故结点 A 的左子树中结点有 CB,右子树中结点有 EDF。

如图所示:

按照同样的分析方法,对 A 的左右子树进行划分,最后得出二叉树的形态如图所示:

2)已知后序遍历序列和中序遍历序列,确定一棵二叉树。

后序遍历中最后访问的为根结点,因此可以按照上述同样的方法,找到根结点后分成两棵子树,进而继续找到子树的根结点,一步步确定二叉树的形态。

注:已知前序遍历序列和后序遍历序列,不可以唯一确定一棵二叉树。

🥥例题:DreamJudge 1109

这是一道综合性的题目,考察学生对二叉树的几种遍历方式的掌握情况。

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

🥥练习题目:

DreamJudge 1161 二叉树遍历 🍰

//摘自N诺用户:huanghu
#include<bits/stdc++.h>
using namespace std;

string s;
int len;
typedef struct BitNode{
    int data;
    struct BitNode *lchild,*rchild;
}BitNode,*BiTree;

//输入需要按照先序序列的遍历序列将值依次输入建树,若某个孩子值为空就输入99来代替
//输入的数据一定要符合一颗二叉树的定义,不然一直递归
BiTree create(BiTree &T){
    if(len>=s.length()) return NULL;
    char data = s[len++];
    if(data!='#'){
        T = (BiTree) malloc(sizeof(BitNode));
        T->data = data;
        T->lchild = NULL;//将左右两个指针置空防止脏地址
        T->rchild = NULL;
        T->lchild = create(T->lchild);
        T->rchild = create(T->rchild);
    }
    return T;
}

//中序遍历
void InOrder(BiTree &T){
    if(T!=NULL){
        InOrder(T->lchild);
        printf("%c ",T->data);
        InOrder(T->rchild);
    }
}

int main(){
    while(cin>>s){
        len = 0;
        BiTree T;
        BiTree tree = create(T);
        InOrder(tree);
        cout<<endl;
    }
    return 0;
}

DreamJudge 1233 二叉树 🍰

//摘自N诺用户:Cookie‘s AE86 
#include<bits/stdc++.h>
using namespace std;
int main(){
    int x,y;
    while(cin>>x>>y)
	{
        while(x!=y)
		{
            if(x>y)
                x/=2;
            else
                y/=2;
        }
        cout<<x<<endl;
    }
    return 0;
}

DreamJudge 1264 二叉树 2 🍰

//摘自N诺用户:钟馨雨 
#include <bits/stdc++.h>
using namespace std;
int main(){
    int m,n;
    while(cin>>m>>n)
	{
        int sum=1;
        int x=m;
        int y=m;
        int a=1;
        while(n>=y){
            x*=2;//最小子节点 
            y=y*2+1;//最大子节点 
            if(n>=x&&n<=y){
                sum=sum+n-x+1; 
                break;
            }
            else if(n<x)break;
            else{
                a++;
                sum=pow(2,a)-1;
            }
        }
        cout<<sum<<endl;
    }
}

DreamJudge 1401 二叉树遍历 🍰

//摘自N诺用户:1935569240 
#include<bits/stdc++.h>
using namespace std;
struct node {
    char ch;
    struct node* left, * right;
};

string forStr,cenStr;
int len,cnt=0;

struct node* createTree(int low, int high, struct node* T) {

    if (low > high) return NULL;//结束
    //在中序遍历中查找对应根结点
    for (int i = low; i <= high; i++) {
        if (cenStr[i] == forStr[cnt]) {
            cnt++;
            //记录下标
            //创建结点
            T = (struct node*)malloc(sizeof(struct node));
            T->ch = cenStr[i];
            T->left = createTree(low, i - 1, T->left);
            T->right = createTree(i + 1, high, T->right);
            break;
        }
    }
    return T;
}

//后续遍历
void laterPrint(struct node* T) {
    if (T == NULL) {
        return;
    }
    laterPrint(T->left);
    laterPrint(T->right);
    cout << T->ch;
}
int main()
{
    while(cin>>forStr>>cenStr){
        struct node* T = NULL;
        len = forStr.size();
        T = createTree(0, len - 1, T);
        laterPrint(T);
        cnt = 0;
        cout << endl;
    }
    return 0;
}

DreamJudge 1551 判断二叉树是否对称 🍰

//摘自N诺用户:我与代码的故事 
#include<bits/stdc++.h>
using namespace std;  
      
string data;

// 定义二叉树节点  
typedef struct Tree {  
    char val;  
    Tree *left, *right;  
    Tree(char x) : val(x), left(nullptr), right(nullptr) {}  
}Tree;  
      
// 层次遍历建树  
Tree* build(const string& data) {  
    if (data.empty() || data[0] == '#') return NULL;  
      
    queue<Tree*> q;  
    Tree* root = new Tree(data[0]);  
    q.push(root);  
      
    int i = 1;  
    while (!q.empty() && i < data.size()) {  
        Tree* node = q.front();  
        q.pop();  
      
        char leftChar = data[i++];  
        char rightChar = (i < data.size()) ? data[i++] : '#';  
      
        if (leftChar != '#') {  
            node->left = new Tree(leftChar);  
            q.push(node->left);  
        }  
      
        if (rightChar != '#') {  
            node->right = new Tree(rightChar);  
            q.push(node->right);  
        }  
    }  
      
    return root;  
}  
      
// 检查二叉树是否镜像对称  
bool dfs(Tree* p, Tree* q) 
{  
	if (!p || !q) return !p && !q;
    	
    return p->val == q->val && dfs(p->left, q->right) && dfs(p->right, q->left);
}  
      
// 主函数  
int main() 
{  
    cin >> data;
	
    for(int i = 0; i < data.size(); i ++)
    	if(data[i] != '#') data[i] = 'A';
    		 
    Tree* root = build(data);  
      
    if (dfs(root -> left, root -> right)) cout << "YES" << endl;  
    else cout << "NO" << endl;  
         
    return 0;  
}

DreamJudge 1561 二叉树(北京邮电大学)

//这道题目用之前”摘自N诺用户:1935569240 “的那个代码就可以解决,都是先序+中序→后序
#include<bits/stdc++.h>
using namespace std;
struct node {
    char ch;
    struct node* left, * right;
};

string forStr,cenStr;
int len,cnt=0;

struct node* createTree(int low, int high, struct node* T) {

    if (low > high) return NULL;//结束
    //在中序遍历中查找对应根结点
    for (int i = low; i <= high; i++) {
        if (cenStr[i] == forStr[cnt]) {
            cnt++;
            //记录下标
            //创建结点
            T = (struct node*)malloc(sizeof(struct node));
            T->ch = cenStr[i];
            T->left = createTree(low, i - 1, T->left);
            T->right = createTree(i + 1, high, T->right);
            break;
        }
    }
    return T;
}

//后续遍历
void laterPrint(struct node* T) {
    if (T == NULL) {
        return;
    }
    laterPrint(T->left);
    laterPrint(T->right);
    cout << T->ch;
}
int main()
{
    while(cin>>forStr>>cenStr){
        struct node* T = NULL;
        len = forStr.size();
        T = createTree(0, len - 1, T);
        laterPrint(T);
        cnt = 0;
        cout << endl;
    }
    return 0;
}

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值