计算机考研复试之数据结构

博主本人整理资料不易,如果文章对大家有用的话,恳请大家能够动动小手帮忙点个赞,如果能点个关注的话那就更好了…

请你说一下红黑树和AVL树的定义、特点、以及二者的区别

  • 平衡二叉树(AVL)树:
    平衡二叉树又称AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。
    一句话表述为:以树中所有节点为根的树的左右子树高度之差的绝对值不超过1。将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上的所有结点的平衡因子只可能是-1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。
  • 红黑树:
    红黑树是一种二叉查找树,但在每个结点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑)。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,通常使用红黑树。
    性质:
    (1)每个结点非红即黑
    (2)根节点是黑的
    (3)每个叶结点(叶结点即树尾端NULL指针或NULL结点)都是黑的
    (4)如果一个节点是红色的,则它的子节点必须是黑色的
    (5)对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点
  • 区别:
    AVL树是高度平衡的,频繁的插入和删除,会引起频繁的rebalance,导致效率下降;
    红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。

请你说一下哈夫曼编码

哈夫曼编码是哈夫曼树的一种应用,广泛用于数据文件压缩。哈夫曼编码算法用字符在文件中出现的频率来建立使用0,1表示每个字符的最优表示方式,其具体算法如下:
(1)哈夫曼算法以自底向上的方式构造表示最优前缀码的二叉树T。
(2)算法以n个叶节点开始,执行n-1次的合并运算后产生最终所要求的树T。
(3)假设编码字符集中每一个字符c的频率是f(c)。以f为键值的优先队列Q用在贪心选择时有效地确定算法当前要求合并的2棵具有最小频率的树。一旦2棵具有最小频率的树合并后,产生一棵新的树,其频率为合并的2棵树的频率之和,并将新树插入优先队列Q。经过n-1次的合并后,优先队列中只剩下一棵树,即所要求的树T。

请你介绍一下B+树

B+树是一种多路搜索树,主要为磁盘或其他直接存取辅助设备而设计的一种平衡查找树,在B+树中,每个结点都可以有多个孩子,并且按照关键字大小有序排列。所有记录结点都是按照键值的大小顺序存放在同一层的叶节点中。相比B树,其具有以下几个特点:
(1)每个结点上的指针上限为2d而不是2d+1(d为节点的出度)
(2)内节点不存储data,只存储key
(3)叶子节点不存储指针

请说一说你理解的栈溢出,并举个简单例子导致栈溢出

  • 栈溢出的概念:
    栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致栈中与其相邻的变量的值被改变。
  • 栈溢出的原因:
    (1)局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。局部变量是存储在栈中的,因此这个很好理解。解决这类问题的办法有两个,一个是增大栈空间,二是改用动态分配,使用堆(heap)而不是栈(stack)。
    (2)递归调用的层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。
    (3)指针或数组越界。这种情况最常见,例如进行字符串拷贝,或处理用户输入等等。

请你回答一下堆和栈的区别,以及为什么栈要快

  • 堆和栈的区别:
    (1)扩展方向:堆是由低地址向高地址扩展;栈是由高地址向低地址扩展。
    (2)申请方式:堆由程序员手动分配和管理;栈由系统自动分配和管理;
    (3)效率:堆由程序员分配,速度较慢,分配效率较低,可能由于操作不当产生内存碎片;而栈由系统分配,速度快,分配效率较高,不会有内存碎片。
    (4)程序局部变量使用的是栈空间,new/malloc动态申请的内存是堆空间,函数调用时会进行形参和返回值的压栈出栈,也是用的栈空间。
  • 栈的效率高的原因:
    栈是操作系统提供的数据结构,计算机底层对栈提供了一系列支持:分配专门的存储器存储栈的地址,压栈和入栈有专门的指令执行;
    而堆是由C/C++函数库提供的,机制复杂,需要一系列分配内存、合并内存和释放内存的算法,因此效率较低。

请说说小根堆特点

堆是一棵完全二叉树(如果一共有h层,那么1~h-1层均满,在h层可能会连续缺少若干个右叶子)。

  • 小根堆
    若根节点存在左子女则根节点的值小于左子女的值;若根节点存在右子女则根节点的值小于右子女的值。
  • 大根堆
    若根节点存在左子女则根节点的值大于左子女的值;若根节点存在右子女则根节点的值大于右子女的值。

请回答一下Array和List,数组和链表的区别

  • 数组的特点:
    数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。数组的插入数据和删除数据效率低,插入数据时,这个位置后面的数据在内存中都要向后移。删除数据时,这个数据后面的数据都要往前移动。但数组的随机读取效率很高。因为数组是连续的,知道每一个数据的内存地址,可以直接找到给定地址的数据。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。数组需要预留空间,在使用前要先申请占内存的大小,可能会浪费内存空间。并且数组不利于扩展,数组定义的空间不够时要重新定义数组。
  • 链表的特点:
    链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。不指定大小,扩展方便。链表大小不用定义,数据随意增删。
  • 各自的优缺点
    数组的优点:
    (1)随机访问性强
    (2)查找速度快
    数组的缺点:
    (1)插入和删除效率低
    (2)可能浪费内存
    (3)内存空间要求高,必须有足够的连续内存空间
    (4)数组大小固定,不能动态拓展
    链表的优点:
    (1)插入删除速度快
    (2)内存利用率高,不会浪费内存
    (3)大小没有固定,拓展很灵活
    链表的缺点:
    不能随机查找,必须从第一个开始遍历,查找效率低

请问求第k大的数的方法及各自的复杂度是怎样的,另外当有相同元素时,还可以使用什么不同的方法求第k大的元素

  • 首先使用快速排序算法将数组按照从大到小排序,然后取第k个,其时间复杂度最快为O(nlogn)
  • 使用堆排序,建立最大堆,然后调整堆,直到获得第k个元素,其时间复杂度为O(n+klogn)
  • 首先利用哈希表统计数组中每个元素出现的次数,然后利用计数排序思想,线性从大到小扫描过程中,前面有k-1个数则为第k大的数
  • 利用快速排序思想,从数组中随机选择一个数i,然后将数组分成两部分Dl,Dr,Dl的元素都小于i,Dr的元素都大于i。然后统计Dr元素个数,如果Dr元素个数等于k-1,那么第k大的数即为k,如果Dr元素个数小于k,那么继续求Dl中第k-Dr大的元素;如果Dr元素个数大于k,那么继续求Dr中第k大的元素。
  • 当有相同元素的时候,首先利用哈希表统计数组中每个元素出现的次数,然后利用计数排序思想,线性从大到小扫描过程中,前面有k-1个数则为第k大的数,平均情况下时间复杂度为O(n)

请你来介绍一下各种排序算法及时间复杂度

  • 插入排序
    对于一个待排序数组来说,其初始有序数组个数为1,然后从第二个元素,插入到有序数组中。对于每一次插入操作,从后往前遍历有序数组,如果当前元素大于要插入元素,则后移一位;如果当前元素小于或等于要插入的元素,则将要插入的元素插入到当前元素的下一位中。
  • 希尔排序
    先将整个待排序记录分割成若干子序列,然后分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。其子序列的构成不是简单的逐段分割,而是将每隔某个增量的记录组成一个子序列。希尔排序时间复杂度与增量序列的选取有关,其最后一个值必须为1。
  • 归并排序
    该算法采用分治法;对于包含m个元素的待排序序列,将其看成m个长度为1的子序列。然后两两合归并,得到n/2个长度为2或者1的有序子序列;然后再两两归并,直到得到1个长度为m的有序序列。
  • 冒泡排序
    对于包含n个元素的待排序数组,重复遍历数组,首先比较第一个和第二个元素,若为逆序,则交换元素位置;然后比较第二个和第三个元素,重复上述过程。每次遍历会把当前n-i个元素中的最大的元素移到第n-i位置。遍历n次,完成排序。
  • 快速排序
    通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
  • 选择排序
    每次循环,选择当前无序数组中最小的那个元素,然后将其与无序数组的第一个元素交换位置,从而使有序数组元素加1,无序数组元素减1,初始时无序数组为空。
  • 堆排序
    堆排序是一种选择排序,利用堆这种数据结构来完成。其算法思想是将待排序的数据构造成一个最大堆(升序)/最小堆(降序),然后将堆顶元素与待排序数组的最后一个元素交换位置,此时末尾元素就是最大/最小的值。然后将剩余n-1个元素重新构造成最大堆/最小堆。
  • 各个排序的时间复杂度、空间复杂度及稳定性如下:
    在这里插入图片描述

请你介绍一下快速排序算法;以及什么是稳定性排序,快速排序是稳定的吗;

  • 快速排序算法
    根据哨兵元素,用两个指针指向待排序数组的首尾,首指针从前往后移动找到比哨兵元素大的,尾指针从后往前移动到比哨兵元素小的,交换两个元素,直到两个指针相遇,这是一趟排序,经过这趟排序后,比哨兵元素大的在右边,小的在左边。经过多趟排序后,整个数组有序。
    稳定性:不稳定
    平均时间复杂度:O(nlogn)
  • 稳定排序
    假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则为不稳定的。
    快速排序算法是不稳定的排序算法。例如:
    待排序数组:int a[ ]={1,2,2,3,4,5,6};
    若选择a[2](即数组中的第二个2)为枢轴,而把大于等于比较字的数均放置在大数数组中,则a[1](即数组中的第一个2)会到pivot的右边,那么数组中的两个2非原序。
    若选择a[1]为比较字,而把小于等于比较字的数均放置在小数数组中,则数组中的两个2顺序也非原序。

请你说一说哈希冲突的解决方法

  • 1、开放定址法
    开放定址法有个非常关键的特征,就是所有输入的元素全部放在哈希表里,也就是说,位桶的实现是不需要任何的链表来实现的,换句话说,也就是说哈希表的装载因子不会超过1。它的实现是在插入一个元素的时候,先通过哈希函数进行判断,若是发生哈希冲突,就以当前地址为基准,根据再寻址的方法(探查序列),去寻找下一个地址,若发生冲突再去寻找,直至找到一个为空的地址为止。所以这种方法又称再散列法。
    有几种常见的探查序列的方法:
    • (1)线性探测
      di=1,2,3,4,5…,m-1;这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
    • (2)二次探查
      di=1²,-1²,2²,-2²,…,k²,-k²(k<=m/2);这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。
    • (3)伪随机探测
      di=伪随机数序列;具体实现时,应建立一个伪随机数发生器,如(i=(i+p)%m),生成一个随机序列,并给定一个随机数做起点,每次去加上这个伪随机数++就可以了。
  • 2、链地址法
    每个位桶实现的时候,采用链表或者树的数据结构来存取发生哈希冲突的输入域的关键字,也就是被哈希函数映射到同一个位桶上的关键字。
    在这里插入图片描述
    紫色部分即代表哈希表,也称哈希数组,数组中的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中,即链接在桶后。
  • 3、公共溢出区
    建立一个公共溢出区域,把hash冲突的元素都放在该溢出区里。查找时,如果发现hash表中对应桶里存在其他元素,还需要在公共溢出区里再次进行查找。
  • 4、再哈希法
    再散列法其实很简单,就是再使用哈希函数去散列一个输入的时候,输出是同一个位置就再次散列,直至不发生冲突位置。
    缺点:每次冲突都要重新散列,计算时间增加。

判断一个链表是否为回文链表,说出你的思路并手写代码

  • 思路
    使用栈存储链表前半部分,然后一个个出栈,与后半部分元素比较,如果链表长度未知,可以使用快慢指针的方法,将慢指针指向的元素入栈,然后如果快指针指向了链表尾部,此时慢指针指向了链表中间
原创文章 60 获赞 56 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_40605573/article/details/105586166