数据结构与算法
- 数据结构与算法
二叉树、链表、K-d树、
1、二叉树?
2、说一下红黑树和AVL树的定义,特点,以及二者区别
答:
平衡二叉树(AVL树):
平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。一句话表述为:以树中所有结点为根的树的左右子树高度之差的绝对值不超过1。将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上的所有结点的平衡因子只可能是-1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。
红黑树:
红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑)。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,通常使用红黑树。
性质:
1. 每个节点非红即黑
2. 根节点是黑的;
3. 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的;
4. 如果一个节点是红色的,则它的子节点必须是黑色的。
5. 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点;
区别:
AVL 树是高度平衡的,频繁的插入和删除,会引起频繁的rebalance,导致效率下降;红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。
3、说一下哈夫曼编码
答:
哈夫曼编码是哈夫曼树的一种应用,广泛用于数据文件压缩。哈夫曼编码算法用字符在文件中出现的频率来建立使用0,1表示个字符的最优表示方式,其具体算法如下:
(1)哈夫曼算法以自底向上的方式构造表示最优前缀码的二叉树T。
(2)算法以|C|个叶结点开始,执行|C|-1次的“合并”运算后产生最终所要求的树T。
(3)假设编码字符集中每一字符c的频率是f(c)。以f为键值的优先队列Q用在贪心选择时有效地确定算法当前要合并的2棵具有最小频率的树。一旦2棵具有最小频率的树合并后,产生一棵新的树,其频率为合并的2棵树的频率之和,并将新树插入优先队列Q。经过n-1次的合并后,优先队列中只剩下一棵树,即所要求的树T。
4、说一说你理解的stack overflow,并举个简单例子导致栈溢出
答:
栈溢出概念:栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致栈中与其相邻的变量的值被改变。
栈溢出的原因:
1. 局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。局部变量是存储在栈中的,因此这个很好理解。解决这类问题的办法有两个,一是增大栈空间,二是改用动态分配,使用堆(heap)而不是栈(stack)。
2. 递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。
3. 指针或数组越界。这种情况最常见,例如进行字符串拷贝,或处理用户输入等等。
5、说一下栈和堆的区别,以及为什么栈要快。
答:
堆和栈的区别:
(1)堆是由低地址向高地址扩展;栈是由高地址向低地址扩展。
(2)堆中的内存需要手动申请和手动释放;栈中内存是由OS自动申请和自动释放,存放着参数、局部变量等内存。
(3)堆中频繁调用malloc和free,会产生内存碎片,降低程序效率;而栈由于其先进后出的特性,不会产生内存碎片。
(4)堆的分配效率较低,而栈的分配效率较高。
栈的效率高的原因:
栈是操作系统提供的数据结构,计算机底层对栈提供了一系列支持:分配专门的寄存器存储栈的地址,压栈和入栈有专门的指令执行;而堆是由C/C++函数库提供的,机制复杂,需要一些列分配内存、合并内存和释放内存的算法,因此效率较低。
6、说一下栈和堆的区别。
答:
(1)申请方式:
栈由系统自动分配和管理,堆由程序员手动分配和管理。
效率:
栈由系统分配,速度快,不会有内存碎片。
堆由程序员分配,速度较慢,可能由于操作不当产生内存碎片。
- 扩展方向
栈从高地址向低地址进行扩展,堆由低地址向高地址进行扩展。
- 程序局部变量是使用的栈空间,new/malloc动态申请的内存是堆空间,函数调用时会进行形参和返回值的压栈出栈,也是用的栈空间。
7、说一下小根堆特点
答:
堆是一棵完全二叉树(如果一共有h层,那么1~h-1层均满,在h层可能会连续缺失若干个右叶子)。
(1)小根堆
若根节点存在左子女则根节点的值小于左子女的值;若根节点存在右子女则根节点的值小于右子女的值。
(2)大根堆
若根节点存在左子女则根节点的值大于左子女的值;若根节点存在右子女则根节点的值大于右子女的值。
8、说一下Array&List,数组和链表的区别。
答:
数组的特点:
数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。数组的插入数据和删除数据效率低,插入数据时,这个位置后面的数据在内存中都要向后移。删除数据时,这个数据后面的数据都要往前移动。但数组的随机读取效率很高。因为数组是连续的,知道每一个数据的内存地址,可以直接找到给地址的数据。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。数组需要预留空间,在使用前要先申请占内存的大小,可能会浪费内存空间。并且数组不利于扩展,数组定义的空间不够时要重新定义数组。
链表的特点:
链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。不指定大小,扩展方便。链表大小不用定义,数据随意增删。
各自的优缺点
数组的优点:1. 随机访问性强;2. 查找速度快。
数组的缺点:1. 插入和删除效率低;2. 可能浪费内存;3. 内存空间要求高,必须有足够的连续内存空间;4. 数组大小固定,不能动态拓展。
链表的优点:1. 插入删除速度快;2. 内存利用率高,不会浪费内存;3. 大小没有固定,拓展很灵活。
链表的缺点:不能随机查找,必须从第一个开始遍历,查找效率低。
9、介绍一下各种排序算法及时间复杂度。
参考回答:
(1)插入排序:对于一个带排序数组来说,其初始有序数组元素个数为1,然后从第二个元素,插入到有序数组中。对于每一次插入操作,从后往前遍历当前有序数组,如果当前元素大于要插入的元素,则后移一位;如果当前元素小于或等于要插入的元素,则将要插入的元素插入到当前元素的下一位中。
(2)希尔排序:先将整个待排序记录分割成若干子序列,然后分别进行直接插入排序,待整个序列中的记录基本有序时,在对全体记录进行一次直接插入排序。其子序列的构成不是简单的逐段分割,而是将每隔某个增量的记录组成一个子序列。希尔排序时间复杂度与增量序列的选取有关,其最后一个值必须为1。
(3)归并排序:该算法采用分治法;对于包含m个元素的待排序序列,将其看成m个长度为1的子序列。然后两两合归并,得到n/2个长度为2或者1的有序子序列;然后再两两归并,直到得到1个长度为m的有序序列。
(4)冒泡排序:对于包含n个元素的带排序数组,重复遍历数组,首先比较第一个和第二个元素,若为逆序,则交换元素位置;然后比较第二个和第三个元素,重复上述过程。每次遍历会把当前前n-i个元素中的最大的元素移到n-i位置。遍历n次,完成排序。
(5)快速排序:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
(6)选择排序:每次循环,选择当前无序数组中最小的那个元素,然后将其与无序数组的第一个元素交换位置,从而使有序数组元素加1,无序数组元素减1.初始时无序数组为空。
(7)堆排序:堆排序是一种选择排序,利用堆这种数据结构来完成选择。其算法思想是将带排序数据构造一个最大堆(升序)/最小堆(降序),然后将堆顶元素与待排序数组的最后一个元素交换位置,此时末尾元素就是最大/最小的值。然后将剩余n-1个元素重新构造成最大堆/最小堆。
各个排序的时间复杂度、空间复杂度及稳定性如下:
10、说一说你知道的排序算法及其复杂度。
参考回答:
1、冒泡排序:
从数组中第一个数开始,依次遍历数组中的每一个数,通过相邻比较交换,每一轮循环下来找出剩余未排序数的中的最大数并“冒泡”至数列的顶端。
稳定性:稳定
平均时间复杂度:O(n ^ 2)
2、插入排序:
从待排序的n个记录中的第二个记录开始,依次与前面的记录比较并寻找插入的位置,每次外循环结束后,将当前的数插入到合适的位置。
稳定性:稳定
平均时间复杂度:O(n ^ 2)
3、希尔排序(缩小增量排序):
希尔排序法是对相邻指定距离(称为增量)的元素进行比较,并不断把增量缩小至1,完成排序。
希尔排序开始时增量较大,分组较多,每组的记录数目较少,故在各组内采用直接插入排序较快,后来增量di逐渐缩小,分组数减少,各组的记录数增多,但由于已经按di−1分组排序,文件叫接近于有序状态,所以新的一趟排序过程较快。因此希尔 排序在效率上比直接插入排序有较大的改进。
在直接插入排序的基础上,将直接插入排序中的1全部改变成增量d即可,因为希尔排序最后一轮的增量d就为1。
稳定性:不稳定
平均时间复杂度:希尔排序算法的时间复杂度分析比较复杂,实际所需的时间取决于各次排序时增量的个数和增量的取值。时间复杂度在O(n ^ 1.3)到O(n ^ 2)之间。
4、选择排序:
从所有记录中选出最小的一个数据元素与第一个位置的记录交换;然后在剩下的记录当中再找最小的与第二个位置的记录交换,循环到只剩下最后一个数据元素为止。
稳定性:不稳定
平均时间复杂度:O(n ^ 2)
5、快速排序
1)从待排序的n个记录中任意选取一个记录(通常选取第一个记录)为分区标准;
2)把所有小于该排序列的记录移动到左边,把所有大于该排序码的记录移动到右边,中间放所选记录,称之为第一趟排序;
3)然后对前后两个子序列分别重复上述过程,直到所有记录都排好序。
稳定性:不稳定
平均时间复杂度:O(nlogn)
6、堆排序:
堆:
1、完全二叉树或者是近似完全二叉树。
2、大顶堆:父节点不小于子节点键值,小顶堆:父节点不大于子节点键值。左右孩子没有大小的顺序。
堆排序在选择排序的基础上提出的,步骤:
1、建立堆
2、删除堆顶元素,同时交换堆顶元素和最后一个元素,再重新调整堆结构,直至全部删除堆中元素。
稳定性:不稳定
平均时间复杂度:O(nlogn)
7、归并排序:
采用分治思想,现将序列分为一个个子序列,对子序列进行排序合并,直至整个序列有序。
稳定性:稳定
平均时间复杂度:O(nlogn)
8、计数排序:
思想:如果比元素x小的元素个数有n个,则元素x排序后位置为n+1。
步骤:
1)找出待排序的数组中最大的元素;
2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
稳定性:稳定
时间复杂度:O(n+k),k是待排序数的范围。
9、桶排序:
步骤:
1)设置一个定量的数组当作空桶子; 常见的排序算法及其复杂度:
2)寻访序列,并且把记录一个一个放到对应的桶子去;
3)对每个不是空的桶子进行排序。
4)从不是空的桶子里把项目再放回原来的序列中。
稳定性:稳定
时间复杂度:O(n+C) ,C为桶内排序时间。
11、什么是稳定排序?稳定排序哪几种?
参考回答:
什么是稳定排序?
答:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
稳定排序有:计数排序、冒泡排序、直接插入排序、折半插入排序、归并排序。
12、介绍一下各种排序算法及时间复杂度。