面试常问的几种树

B树

B树是一种自平衡的多叉树。每个节点都存储关键字值。其左子节点的关键字值小于该节点关键字值,且右子节点的关键字值大于或等于该节点关键字值。B树每一个节点都包含key和value,查询效率比B+树高。

B+树

B+树也是一种自平衡的多叉树。其基本定义与B树相同,不同点在于数据只出现在叶子节点,所有叶子节点增加了一个链指针,方便进行范围查询。
B+树中间节点不存放数据,所以同样大小的磁盘页上可以容纳更多节点元素,访问叶子节点上关联的数据也具有更好的缓存命中率。并且数据顺序排列并且相连,所以便于区间查找和搜索。

AVL树

AVL树是一种改进版的搜索二叉树,其引入平衡因子(左子树高度与右子树高度之差的绝对值),通过旋转使其尽量保持平衡。
任何一个节点的左子树高度与右子树高度之差的绝对值不超过1.

红黑树

红黑树本身是由2-3树发展而来,红黑树是保持黑平衡的二叉树,其查找会比AVL树慢一点,添加和删除元素会比AVL树快一点。增删改查统计性能上讲,红黑树更优。
红黑树主要特征是在每个节点上增加一个属性表示节点颜色,可以红色或黑色。红黑树和AVL树类似,都是在进行插入和删除时通过旋转保持自身平衡,从而获得较高的查找性能。红黑树保证从根节点到叶尾的最长路径不超过最短路径的2倍,所以最差时间复杂度o(logn)。红黑树通过重新着色和左右旋转,更加高效地完成插入和删除之后的自平衡调整。

为什么数据库不用红黑树用B+树?

红黑树的出度为2,而B树的出度一般都非常大。红黑树的树高很明显比B树大非常多,IO次数很多,导致会比较慢,因此检索的次数也就更多。
B+树相比于B树更适合外存索引,拥有更大的出度,IO次数较少,检索效率会更高。

为什么说B+树比B树更适合实际应用中操作系统的文件索引和数据库索引?

B+树的磁盘读写代价更低,B+树的查询效率更加稳定,数据库索引采用B+树而不是B树的主要原因:B+树只要遍历叶子节点就可以实现整棵树的遍历,而且在数据库中基于范围的查询是非常频繁的,而B树只能中序遍历所有节点,效率太低。
B +树的特点:

  • 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的
  • 不可能在非叶子结点命中
  • 非叶子节点相当于叶子节点的索引(稀疏索引),叶子节点相当于是存储(关键字)数据的数据层

增加B+树的路数可以降低树的高度,那么无限增加树的路数是不是可以有最优的查找效率?

不可以,因为这样会形成一个有序数组,文件系统和数据库的索引都是存在硬盘上的,并且如果数据量大的话,不一定能一次性加载到内存中。有序数组没法一次性加载进内存,这时候B+树的多路存储威力就出来了,可以每次加载B+树的一个节点,然后一步步往下找

为什么MySQL索引要使用B+树,而不是B树或者红黑树?

MySQL中的数据一般是放在磁盘中的,读取数据的时候肯定会有访问磁盘的操作,磁盘中有两个机械运动的部分,盘片旋转和磁臂移动。盘片旋转就是多少转每分钟,磁盘移动则是在盘片旋转到指定位置后,移动磁臂开始进行数据的读写。存在一个定位到磁盘中的块的过程,定位是磁盘存取中花费时间比较大的一块,机械运动花费时间远远大于电子运动的时间。当大规模数据存储到磁盘中的时候,定位是一个非常花费时间的过程,我们可以通过B树进行优化,提高读取时定位的效率。
构造一个多阶的B树,在尽量多的节点上存储相关的信息,保证层数(树的高度)尽量的少,以便更快地找到信息,减少磁盘I/O操作。B树是平衡树,每个结点到叶子节点的高度都是相同,保证每个查询的稳定。

为什么MySQL索引适用B+树而不是hash表和B树?

  • 利用Hash需要把数据全部加载到内存中,如果数据量大,是一件消耗内存的事,而采用B+树,是基于按照节点分段加载,减少内存消耗
  • 和业务场景有关,对于唯一查找(查找一个值),Hash确实更快,但数据库中经常查询多条数据,这时候由于B+数据的有序性,与叶子节点又是链表相连,他的查询效率会比Hash快的多
  • B+树的非叶子节点不保存数据,只保存子树的临界值(最大或最小),所以同样大小的节点,B+树相对于B树能够有更多的分支,使得这棵树更加矮胖,查询时做的I/O操作次数也更少。

既然Hash比B+树更快,为什么MySQL用B+树来存储索引呢?

MySQL中存储索引用的数据结构是B+树,B+树的查询时间跟树的高度有关,是log(n),如果用Hash存储,查询时间是o(1)
采用Hash存储确实要更快,但是采用B+树来存储索引的原因主要有以下两点:

  • 从内存角度看,数据库中的索引一般是在磁盘上,数据量大的时候可能无法一次性装入内存,B+树的设计可以允许数据分批加载
  • 从业务场景看,如果只选择一个数据那确实是hash更快,但是数据库中经常会选中多条,由于B+树索引有序,并且又是链表相连,他的查询比Hash快很多

map、set是怎么实现的,红黑树是怎么能够同时实现这两种容器的?为什么使用红黑树?

  • 它们的底层都是以红黑树的结构实现,因此插入删除等操作都是在o(logn)时间内完成,因此可以完成高效的插入删除
  • 定义一个模板参数,如果他是key,那么他就是set;如果他是key-value,那么他就是map;底层是红黑树,实现map的红黑树的节点数据类型是key+value,实现set的节点数据类型是value
  • 因为map和set要求是自动排序的,红黑树能够实现这一功能,而且时间复杂度比较低。
<think>嗯,用户之前让我介绍嵌入式软件工程师的高频面试题,现在他们又想知道面试常问种常用算法。我需要先回顾一下之前提供的答案,确保这次的内容不重复,同时补充更具体的算法部分。 首先,用户可能正在准备嵌入式相关的面试,所以算法问题可能偏向基础和应用,而不是特别复杂的算法。嵌入式系统通常资源有限,更关注效率、实时性,所以常见的算法可能包括排序、查找、位操作等。 接下来,我需要考虑用户可能的真实需求。他们可能希望了解在嵌入式面试中哪些算法最常被问到,以及如何简洁有效地回答。比如,排序算法中的快速排序和归并排序可能因为效率不同而被提及,但嵌入式环境中可能更注重空间复杂度,所以可能更关注冒泡排序或者插入排序这些虽然时间复杂度高但实现简单的算法。 另外,数据结构方面,链表、队列、栈在嵌入式系统中很常见,尤其是环形缓冲区这样的实现。哈希表可能较少,因为动态内存分配受限,但如果有应用场景的话也需要提到。 还要注意用户提到的“种常用算法”,可能需要分点回答,结构清晰。之前的回答用了分类,比如C语言基础、数据结构等,这次可以按算法类型分类,比如排序、查找、字符串处理等。 用户可能也需要代码示例,尤其是嵌入式环境下常用的实现方式,比如用宏定义进行位操作,或者使用指针操作数据结构。同时,要解释算法的时间复杂度和应用场景,帮助面试者理解为何在特定情况下选择某种算法。 需要确保回答符合系统级指令,比如数学公式用$...$,独立公式用$$...$$,但算法部分可能公式较少,更多的是代码和复杂度分析。要检查LaTeX语法是否正确,比如时间复杂度应写成$O(n)$,用行内公式格式。 另外,用户可能对算法的实际应用感兴趣,比如在中断处理、内存管理中的使用。例如,环形缓冲区用于串口通信,位操作用于寄存器配置,这些实际例子能帮助用户更好地理解。 最后,总结部分要提醒用户结合项目经验,说明算法在嵌入式系统中的实际应用,这样回答更全面,帮助用户展示实际能力。</think>以下是嵌入式软件工程师面试中常见的种算法及其核心解析,按应用场景分类整理: --- ### **一、排序算法** #### 1. **快速排序(Quick Sort)** - **核心思想**:分治法 + 基准值(pivot)分割。 - **时间复杂度**:平均 $O(n \log n)$,最坏 $O(n^2)$(优化后避免)。 - **代码片段**: ```c void quick_sort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); quick_sort(arr, low, pi - 1); quick_sort(arr, pi + 1, high); } } int partition(int arr[], int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return i + 1; } ``` #### 2. **归并排序(Merge Sort)** - **特点**:稳定排序,适合链表结构,空间复杂度 $O(n)$。 - **嵌入式应用**:内存充足时处理大数据排序。 --- ### **二、查找算法** #### 1. **二分查找(Binary Search)** - **前提**:有序数组。 - **时间复杂度**:$O(\log n)$。 - **代码片段**: ```c int binary_search(int arr[], int l, int r, int target) { while (l <= r) { int mid = l + (r - l) / 2; if (arr[mid] == target) return mid; if (arr[mid] < target) l = mid + 1; else r = mid - 1; } return -1; } ``` #### 2. **哈希表(Hash Table)** - **核心思想**:键值映射,冲突解决(链地址法/开放寻址法)。 - **嵌入式限制**:需避免动态内存分配,可预分配固定大小哈希表。 --- ### **三、字符串处理算法** #### 1. **KMP算法** - **用途**:高效字符串匹配(时间复杂度 $O(n+m)$)。 - **关键点**:构建部分匹配表(Partial Match Table)。 #### 2. **反转字符串(In-place)** - **嵌入式优化**:指针操作节省内存。 ```c void reverse(char *str) { char *end = str + strlen(str) - 1; while (str < end) { char tmp = *str; *str++ = *end; *end-- = tmp; } } ``` --- ### **四、位操作算法** #### 1. **计算二进制中1的个数** - **优化方法**:Brian Kernighan算法。 ```c int count_ones(uint32_t n) { int count = 0; while (n) { n &= (n - 1); // 清除最低位的1 count++; } return count; } ``` #### 2. **位掩码(Bitmask)应用** - **场景**:配置硬件寄存器或标志位。 ```c #define ENABLE_FEATURE_A (1 << 0) #define ENABLE_FEATURE_B (1 << 1) uint32_t config_reg = 0; config_reg |= ENABLE_FEATURE_A; // 设置位 config_reg &= ~ENABLE_FEATURE_B; // 清除位 ``` --- ### **五、数据结构相关算法** #### 1. **环形缓冲区(Circular Buffer)** - **用途**:串口通信、生产者-消费者模型。 - **关键操作**: - 写入:`(head + 1) % size` - 读取:`(tail + 1) % size` #### 2. **LRU缓存算法** - **核心**:哈希表 + 双向链表,保证$O(1)$访问和插入。 - **嵌入式简化**:固定容量数组 + 时间戳(牺牲时间复杂度)。 --- ### **六、递归与分治** #### 1. **斐波那契数列(动态规划优化)** - **递归问题**:直接递归有重复计算($O(2^n)$)。 - **优化方法**:记忆化搜索或迭代法($O(n)$)。 ```c int fib(int n) { if (n <= 1) return n; int a = 0, b = 1, c; for (int i = 2; i <= n; i++) { c = a + b; a = b; b = c; } return b; } ``` --- ### **七、实际场景高频考点** 1. **内存敏感算法**:选择空间复杂度低的算法(如冒泡排序 vs 归并排序)。 2. **实时性要求**:避免算法最坏情况(如快速排序需随机化基准值)。 3. **硬件相关操作**:位运算、寄存器访问代码的原子性。 --- ### **总结** 嵌入式面试中,算法问题侧重实用性、空间/时间复杂度平衡及底层优化能力。建议熟练掌握: - 基础排序/查找算法 - 位操作与内存管理技巧 - 环形缓冲区、状态机等嵌入式特有模式 回答时需结合硬件资源限制(如避免动态内存分配),并举例说明实际项目中的应用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值