中软国际java面试题,PAT甲级备战-树(一)

本文介绍了二叉树的深度遍历方法(前序、中序、后续),邻接表在图和树中的应用,以及如何使用静态数组表示单链表。此外,还讨论了并查集的原理和实现,以及Java开发者的学习资源,包括面试真题和全套学习资料。
摘要由CSDN通过智能技术生成
  • 二叉树

    • 二叉树三种深度遍历
  • 二叉树的广度优先遍历

  • 二叉搜索树

  • 反转二叉树

刷题技巧

输入int类型的01,默认输入1

for(int i=0;~b;i++) 这里的~b为b!=-1

memset(数组名,赋的初值,sizeof 数组名) 初始化数组

单链表

在解决图和树的数据结构问题时,常常用邻接表来存储。邻接表是由很多条单链表构成的,而单链表的实现方式有很多,如数组,结构体,vector等容器实现,在这些实现方式中,静态数组的速度是最快的。

image.png

  • 传统结构体表示链表

struct Node{

int value;

Node* next;

}

每次创建新的链表节点,都需要new一个新的结构体,在竞赛级别的算法题目里,链表的长度都是十万百万级,如果每次创建一个新节点都new一次,光创建出这个链表就已经超时了。

  • 静态数组表示

这里的静态和动态其实是指是否需要重复开辟空间。提前开辟好空间以后用这些空间去表示称为静态,与之对应的每次都需要开辟空间称为动态。

那,如何表示呢?

image.png

e[N], ne[N], head, idx来表示一个单链表!

**e[N]**存放每一个节点的值,**ne[N]**存放下一个节点的下标,head指向头结点,idx表示已经用到了数组中的哪个节点。

  • 具体操作

// 初始化

void init()

{

head = -1;

idx = 0;

}

// 将x插到头结点

void add_to_head(int x)

{

e[idx] = x, ne[idx] = head, head = idx ++ ;

}

// 将x插到下标是k的点后面

void add(int k, int x)

{

e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;

}

// 将下标是k的点后面的点删掉

void remove(int k)

{

ne[k] = ne[ne[k]];

}

邻接表

用邻接表表示一棵树

用idx,h[N],ne[N],e[N]表示

h[N]用于存放邻接表的表头

image.png

构建树

add(int parent_id,int child_id){ //为值为parent_id的父节点添加一个值为child_id的子节点

e[idx] = child_id,ne[idx]=h[parent_id],h[parent_id] = idx++;

}

  • e[]的索引是idx,值是节点中存放的值

  • ne[]的索引是idx,值是下一个节点的idx

  • h[]的索引是节点中存放的值,值是下一个节点的idx

树的遍历

  1. 前序遍历,中序遍历,后续遍历(DFS)
  • 前序遍历 (preorder) 根左右

  • 中序遍历 (inorder)左跟右

  • 后续遍历 (postorder)左右跟

倒推

image.png

  • 前序的第一个是root,后序的最后一个是root。

  • 先确定跟节点,然后根据中序遍历,在根左边的为左子树,根右边的为右子树

  • 对于每一个子树可以看成一个全新的树,仍然遵循上面的规律。

划重点!!根据上面的推论,只要确定了根节点的位置,根据中序遍历我们就能确定左右子树的位置,递归从而构建出整个二叉树。也就是说,我们知道一颗二叉树前序遍历和中序遍历或者后续遍历和中序遍历,就能构建二叉树,而知道前序遍历和后续遍历则不行。

// 一边建树一边遍历

#include<bits/stdc++.h>

using namespace std;

const int N = 50010;

unordered_map<int,int> l,r,pos;

int in[N],pre[N],n;

vector post;

int build(int il,int ir,int pl,int pr){

int root = pre[pl];

int k = pos[root];

if(il<k) l[root] = build(il,k-1,pl+1,pl+1+k-1-il);

if(k<ir) r[root] = build(k+1,ir,pl+1+k-il,pr);

// 一边建树 一边遍历

post.push_back(root);

return root;

}

int main(){

cin>>n;

for(int i=0;i<n;i++){

cin>>pre[i];

}

for(int i=0;i<n;i++){

cin>>in[i];

pos[in[i]]=i;

}

int root=build(0,n-1,0,n-1);

cout<<post[0];

return 0;

}

  1. 层序遍历(BFS)

//p存放层序遍历的结果,l存放父节点的左儿子,r存放父节点的右儿子

void bfs(int root){

p[0] = root;

int hh=0,kk=0;

while(hh<=kk){

int t = p[hh++];

if(l.count(t)) p[++kk] = l[t];

if(r.count(t)) p[++kk] = r[t];

}

cout<<p[0];

for(int i=1;i<n;i++) cout<<’ '<<p[i];

}

并查集

  • 并查集有思维巧妙,代码简短的特点,是面试热点问题。

简单描述一下并查集

现在有两个集合a和b,从这两个集合中取出任意一个元素,如何判断该元素属于哪个集合?以及如何快速将两个集合合并?

–常见思路–

定义一个belong[]数组存储元素归属

if(belong[x]==‘a’) puts(‘a’);

else puts(‘b’);

定义数组操作,查询元素归属的时间复杂度是O(1);但合并两个操作呢?

只能在定义一个新的数组,然后把每个集合中的元素都存进去,时间复杂度为O(n)!

有没有什么办法实现这两个操作且时间复杂度低?

并查集可以在近乎O(1)的时间复杂度下实现这两个操作!!

  • 将集合中的元素存在树中,定义一颗树的跟节点为这个集合的id,定义一个数组p[]存取每个元素的父节点, x的父节点是p[x],而根节点的父节点就是其本身,即p[x]=x,所以我们只要一直不断向上查询这个元素的根节点,就能知道归属。

  • 对于合并集合,由于这两个集合是以两颗树的形式存在,我们只要让一颗树的根节点的父节点为另一颗树的根节点,即可把这两颗树连起来。

image.png

代码实现

  • p[]数组存放每个元素的父节点,即x的父节点是p[x]

  • find(int x):查找某一元素的根节点

  • 判断归属 if( find(a) == find(b) ) 属于同一个集合

  • 合并 p[find(a)] = find(b)

  • 如何实现find()函数

//常规思路,由于每次都要while一遍,所以查找根节点的时间复杂度是个问题

int find(int x){

while(p[x]!=x) x = p[x];

return x;

}

// 采用路径压缩优化,路径压缩就是在查询根节点的过程中,将路径上的每一个节点的父节点都

//重新设置为根节点,避免重复查找,这样时间复杂度可以近乎缩减为O(1);

int find(int x)

{

if (p[x] != x) p[x] = find(p[x]);

return p[x];

}

image.png

#include<bits/stdc++.h>

using namespace std;

const int N = 100010;

int p[N];

int find(int x) // 返回x的祖宗节点 + 路径压缩

{

if (p[x] != x) p[x] = find(p[x]);

return p[x];

}

int main(){

int n,m;

cin>>n>>m;

for(int i=1;i<=n;i++){

p[i] = i;

}

while(m–){

char ins;

int a,b;

cin>>ins>>a>>b;

if(ins==‘M’) p[find(a)] = find(b);

else{

if(find(a)==find(b)) puts(“Yes”);

else puts(“No”);

}

}

return 0;

}

二叉树


二叉树可以用两个数组 l[N] , r[N] 来表示, N 为节点数量,l[i] 记录节点 i 的左儿子,r[i] 记录节点i的右儿子

二叉树三种深度遍历

// k用来记录当前位置,满足pat空格检测输出

void dfs_pre(int root,int &k){

if(root == -1) return;

if(++k == n) cout<<root;

else cout<<root<<’ ';

dfs_pre(l[root],k);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

腾讯T3大牛总结的500页MySQL实战笔记意外爆火,P8看了直呼内行

腾讯T3大牛总结的500页MySQL实战笔记意外爆火,P8看了直呼内行
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
569210256)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

[外链图片转存中…(img-2dZyrkT3-1713569210257)]

[外链图片转存中…(img-Go8hT89P-1713569210258)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值