面试汇总(十):数据结构与算法常见面试总结(三)——哈希、链表、队列、查找、递归

前言

  前两篇文章我们分别介绍了在面试中数据结构与算法中树和堆与栈、数组、排序的一些问题常见的面试题。这篇文章我们继续给大家介绍常见的问题。这篇文章将给大家介绍数据结构与算法中其他相关的问题。

面试题及参考答案

五、哈希

1、请你来说一说hash表的实现,包括STL中的哈希桶长度常数

  • hash表的实现主要包括构造哈希和处理哈希冲突两个方面:
       对于构造哈希来说,主要包括直接地址法、平方取中法、除留余数法等。
       对于处理哈希冲突来说,最常用的处理冲突的方法有开放定址法、再哈希法、链地址法、建立公共溢出区等方法。SGL版本使用链地址法,使用一个链表保持相同散列值的元素。
       虽然链地址法并不要求哈希桶长度必须为质数,但SGI
    STL仍然以质数来设计哈希桶长度,并且将28个质数(逐渐呈现大约两倍的关系)计算好,以备随时访问,同时提供一个函数,用来查询在这28个质数之中,“最接近某数并大于某数”的质数。

2、请你回答一下hash表如何rehash,以及怎么处理其中保存的资源

  C++的hash表中有一个负载因子loadFactor,当loadFactor<=1时,hash表查找的期望复杂度为O(1).因此,每次往hash表中添加元素时,我们必须保证是在loadFactor<1的情况下,才能够添加。因此,当Hash表中loadFactor==1时,Hash就需要进行rehash。rehash过程中,会模仿C++的vector扩容方式,Hash表中每次发现loadFactor> ==1时,就开辟一个原来桶数组的两倍空间,称为新桶数组,然后把原来的桶数组中元素全部重新哈希到新的桶数组中。

3、请你说一下哈希表的桶个数为什么是质数,合数有何不妥?

  哈希表的桶个数使用质数,可以最大程度减少冲突概率,使哈希后的数据分布的更加均匀。如果使用合数,可能会造成很多数据分布会集中在某些点上,从而影响哈希表效率。

4、 请你说一下解决hash冲突的方法

  当哈希表关键字集合很大时,关键字值不同的元素可能会映象到哈希表的同一地址上,这样的现象称为哈希冲突。目前常用的解决哈希冲突的方法如下:
  开放定址法: 当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。有几种常用的探查序列的方法:线性探查、二次探查、伪随机探测
  再哈希法: 当发生哈希冲突时使用另一个哈希函数计算地址值,直到冲突不再发生。这种方法不易产生聚集,但是增加计算时间,同时需要准备许多哈希函数。
  链地址法: 将所有哈希值相同的Key通过链表存储。key按顺序插入到链表中:

面试汇总(十):数据结构与算法常见面试总结(三)——哈希、链表、队列、查找、递归_第1张图片

   这里需要注意的是:紫色部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中,即链接在桶后。
   建立公共溢出区: 采用一个溢出表存储产生冲突的关键字。如果公共溢出区还产生冲突,再采用处理冲突方法处理。

5、请谈一谈,hashCode() 和equals() 方法的重要性体现在什么地方?

  Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。

6、请说一说,什么是HashMap,并说明Java中的HashMap的工作原理是什么?

  HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。并且HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。另外,HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
  HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
  通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
  HashMap类有一个叫做Entry的内部类。这个Entry类包含了key-value作为实例变量。 每当往hashmap里面存放key-value对的时候,都会为它们实例化一个Entry对象,这个Entry对象就会存储在前面提到的Entry数组table中。Entry具体存在table的那个位置是 根据key的hashcode()方法计算出来的hash值(来决定)。

7、如何构造一致性

  先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0,232-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0,232-1]),接着在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。
  这种算法解决了普通余数Hash算法伸缩性差的问题,可以保证在上线、下线服务器的情况下尽量有多的请求命中原来路由到的服务器。
  标准:一致性哈希提出了在动态变化的Cache环境中,哈希算法应该满足的4个适应条件:

  • 1)、平衡性(Balance)
      平衡性也就是说负载均衡,是指客户端hash后的请求应该能够分散到不同的服务器上去。一致性hash可以做到每个服务器都进行处理请求,但是不能保证每个服务器处理的请求的数量大致相同。
  • 2)、单调性(Monotonicity)
      单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应该能够保证已分配的内容可以被映射到新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。
  • 3)、分散性(Spread)
      分布式环境中,客户端请求时候可能不知道所有服务器的存在,可能只知道其中一部分服务器,在客户端看来他看到的部分服务器会形成一个完整的hash环。如果多个客户端都把部分服务器作为一个完整hash环,那么可能会导致,同一个用户的请求被路由到不同的服务器进行处理。这种情况显然是应该避免的,因为它不能保证同一个用户的请求落到同一个服务器。所谓分散性是指上述情况发生的严重程度。
  • 4)、负载(Load)
      负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

8、apriori算法

  • 1)Apriori原理
       如果一个项集是频繁的,则它的所有子集一定也是频繁的;相反,如果项集是非频繁的,则它的所有超集也一定是非频繁的。
  • 2)发现频繁项集
      假定事务总数为N,支持度阈值是minsup,发现频繁项集的过程如下(理论上,存在许多产生候选项集的方法,本例使用支持度阈值来产生):
    ①初始时每个项都被看作候选1-项集。计数对它们的支持度之后,将支持度少于阈值的候选项集丢弃,生成频繁1-项集。
    ②在第二次迭代,依据Apriori原理(即所有非频繁的1-项集的超集都是非频繁的),仅使用频繁1-项集来产生候选2-项集。此时生成的候选2-项集有多个,将支持度少于阈值的候选项集丢弃,生成频繁2-项集。
    ③经过多次迭代,每次用上一次生成的频繁n-项集产生新的候选(n+1)-项集,直至没有发现频繁(n+1)-项集,则得到的频繁n-项集就是最终结果。
  • 3)发现关联规则
      发现关联规则是指找出支持度大于等于minsup并且置信度大于等于minconf的所有规则,其中minsup和minconf是对应的支持度阈值和置信度阈值。

9、KM算法

  匈牙利算法:求最大匹配,那么我们希望每一个在左边的点都尽量找到右边的一个点和它匹配。我们依次枚举左边的点x的所有出边指向的点y,若y之前没有被匹配,那么(x,y)就是一对合法的匹配,我们将匹配数加一,否则我们试图给原来匹配y的x’重新找一个匹配,如果x’匹配成功,那么(x,y)就可以新增为一对合法的匹配。给x’寻找匹配的过程可以递归解决.
  从一边的未饱和点出发,寻找增广路。复杂度:O(VE)
  KM算法:给定一个带权的二分图,求权值最大的完备匹配
  相等子图的完备匹配=原图的最大权匹配
1)初始化可行性顶标
2)对n个点在相等子图中寻找增广路
  (1)初始化访问标记
  (2)寻找增广路
   (3)若增广路不存在,则修改交错路中的顶标,直到对某个点而言找到一条增广路为止
3)求得最大权
   算法时间复杂度O(n3)O(n3)

10、请你说一下哈希表是做什么的?另外哈希表的实现原理也说一下

  Hash表即散列表,其最突出的优点是查找和插入删除具有常数时间的复杂度
  其实现原理是:把Key通过一个固定的算法函数即所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
   而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。
  哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。哈希表又叫做散列表,分为“开散列”和“闭散列”。
  我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数,也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方。
  但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。后面我们将看到一种解决“冲突”的简便做法。
  总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。

六、链表

1、请问什么是单向链表,如何判断两个单向链表是否相交

  • 1、单向链表
      单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始;链表是使用指针进行构造的列表;又称为结点列表,因为链表是由一个个结点组装起来的;其中每个结点都有指针成员变量指向列表中的下一个结点。
      列表是由结点构成,head指针指向第一个成为表头结点,而终止于最后一个指向nuLL的指针。
  • 2、判断两个链表是否相交
  • 1)方法1:
      链表相交之后,后面的部分节点全部共用,可以用2个指针分别从这两个链表头部走到尾部,最后判断尾部指针的地址信息是否一样,若一样则代表链表相交!
  • 2)方法2:
      可以把其中一个链表的所有节点地址信息存到数组中,然后把另一个链表的每一个节点地址信息遍历数组,若相等,则跳出循环,说明链表相交。进一步优化则是进行hash排序,建立hash表。

2、现在有一个单向链表,谈一谈,如何判断链表中是否出现了环

  单链表有环,是指单链表中某个节点的next指针域指向的是链表中在它之前的某一个节点,这样在链表的尾部形成一个环形结构。
  最常用方法:定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链表;如果走得快的指针走到了链表的末尾(next指向 NULL)都没有追上第一个指针,那么链表就不是环形链表。
  通过使用STL库中的map表进行映射。然后从链表的头指针开始往后遍历,每次遇到一个指针p,就判断 m[p] 是否为0。如果为0,则将m[p]赋值为1,表示该节点第一次访问;而如果m[p]的值为1,则说明这个节点已经被访问过一次了,于是就形成了环。

3、 谈一谈,bucket如果用链表存储,它的缺点是什么?

①查找速度慢,因为查找时,需要循环链表访问
②如果进行频繁插入和删除操作,会导致速度很慢。

4、编辑距离

  编辑距离的作用主要是用来比较两个字符串的相似度的。
  编辑距离,又称Levenshtein距离(莱文斯坦距离也叫做Edit Distance),是指两个字串之间,由一个转成另一个所需的最少编辑操作次数,如果它们的距离越大,说明它们越是不同。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。
  在概念中,我们可以看出一些重点那就是,编辑操作只有三种。插入,删除,替换这三种操作,我们有两个字符串,将其中一个字符串经过上面的这三种操作之后,得到两个完全相同的字符串付出的代价是什么就是我们要讨论和计算的。

5、请你说出几种基本的数据结构

  常见的基本的数据结构有链表队列(只列出面试常考的基本数据结构)
  链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列节点组成,这些节点不必在内存中相连。每个节点由数据部分Data和链部分Next,Next指向下一个节点,这样当添加或者删除时,只需要改变相关节点的Next的指向,效率很高。
   栈和队列是比较特殊的线性表 栈是限制插入和删除只能在一个位置上进行的表,后进先出
   队列只允许在front端进行删除操作,在rear端进行插入操作,
  :树型结构是一类非常重要的非线性数据结构,考察主要以二叉树为主,

七、高级算法

1、请问加密方法都有哪些

  • 1、单向加密
      单向加密又称为不可逆加密算法,其密钥是由加密散列函数生成的。单向散列函数一般用于产生消息摘要,密钥加密等,常见的有:
      MD5(Message Digest Algorithm 5):是RSA数据安全公司开发的一种单向散列算法,非可逆,相同的明文产生相同的密文; > >   SHA(Secure Hash Algorithm):可以对任意长度的数据运算生成一个160位的数值。其变种由SHA192,SHA256,SHA384等;
       CRC-32,主要用于提供校验功能;
    算法特征
    1、输入一样,输出必然相同;
    2、雪崩效应,输入的微小改变,将会引起结果的巨大变化;
    3、 定长输出,无论原始数据多大,结果大小都是相同的;
    4、 不可逆,无法根据特征码还原原来的数据;
  • 2、对称加密
      采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
  • 特点:
    1、加密方和解密方使用同一个密钥;
    2、加密解密的速度比较快,适合数据比较长时的使用;
    3、密钥传输的过程不安全,且容易被破解,密钥管理也比较麻烦;
       优点:对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。
      缺点:对称加密算法的缺点是在数据传送前,发送方和接收方必须商定好秘钥,然后使双方都能保存好秘钥。其次如果一方的秘钥被泄露,那么加密信息也就不安全了。另外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的唯一秘钥,这会使得收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担。
  • 3、非对称加密
      非对称密钥加密也称为公钥加密,由一对公钥和私钥组成。公钥是从私钥提取出来的。可以用公钥加密,再用私钥解密,这种情形一般用于公钥加密,当然也可以用私钥加密,用公钥解密。常用于数字签名,因此非对称加密的主要功能就是加密和数字签名。
    特征
    1)秘钥对,公钥(public key)和私钥(secret key)
    2)主要功能:加密和签名
      发送方用对方的公钥加密,可以保证数据的机密性(公钥加密)。
       发送方用自己的私钥加密,可以实现身份验证(数字签名)。
    3)公钥加密算法很少用来加密数据,速度太慢,通常用来实现身份验证。 > + 常用的非对称加密算法
      RSA:由RSA公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的;既可以实现加密,又可以实现签名。
      DSA(Digital Signature Algorithm):数字签名算法,是一种标准的 DSS(数字签名标准)。
      ECC(Elliptic Curves Cryptography):椭圆曲线密码编码。
  • 4、Hash算法
      它是一种单向算法,用户可以通过hash算法对目标信息生成一段特定长度的唯一Hash值,但不能通过这个Hash值重新获得目标信息,常见的有MD2、MD4、MD5。

2、什么是LRU缓存

   LRU(最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高
实现:使用一个链表保存缓存数据,将新数据插入到头部,每当缓存命中时,则将命中的数据移动到链表头部,当链表满的时候,将链表尾部的数据丢弃。

3、请你说一说洗牌算法

  • 1、Fisher-Yates Shuffle算法
      最早提出这个洗牌方法的是 Ronald A. Fisher 和 Frank Yates,即Fisher–Yates Shuffle,其基本思想就是从原始数组中随机取一个之前没取过的数字到新的数组中,具体如下:
    1)初始化原始数组和新数组,原始数组长度为n(已知)。
    2)从还没处理的数组(假如还剩k个)中,随机产生一个[0,k)之间的数字p(假设数组从0开始)。
    3)从剩下的k个数中把第p个数取出。
    4)重复步骤2和3直到数字全部取完。
    5)从步骤3取出的数字序列便是一个打乱了的数列。
    时间复杂度为O(n*n),空间复杂度为O(n)。
  • 2、Knuth-Durstenfeld
      Shuffle Knuth 和 Durstenfeld 在Fisher等人的基础上对算法进行了改进,在原始数组上对数字进行交互,省去了额外O(n)的空间。该算法的基本思想和 Fisher类似,每次从未处理的数据中随机取出一个数字,然后把该数字放在数组的尾部,即数组尾部存放的是已经处理过的数字。
    算法步骤为:
  1. 建立一个数组大小为 n 的数组 arr,分别存放 1 到 n 的数值;
  2. 生成一个从 0 到 n - 1 的随机数 x;
  3. 输出 arr 下标为 x 的数值,即为第一个随机数;
  4. 将 arr 的尾元素和下标为 x 的元素互换;
  5. 同2,生成一个从 0 到 n - 2 的随机数 x;
  6. 输出 arr 下标为 x 的数值,为第二个随机数;
  7. 将 arr 的倒数第二个元素和下标为 x 的元素互换; ……
      如上,直到输出m 个数为止 时间复杂度为O(n),空间复杂度为O(1),缺点必须知道数组长度n。

4、Kruskal算法的基本过程

  Kruskal算法是以边为主导地位,始终选取当前可用的拥有最小权值的边,所选择的边不能构成回路。首先构造一个只有n个顶点没有边的非连通图,给所有的边按值以从小到大的顺序排序,选择一个最小权值边,若该边的两个顶点在不同的连通分量上,加入到有效边中,否则舍去这条边,重新选择权值最小的边,以此重复下去,直到所有顶点在同一连通分量上。

5、BFS和DFS的实现思想

   BFS:
(1)顶点v入队列
(2)当队列为非空时继续执行否则停止
(3)从队列头取顶点v,查找顶点v的所有子节点并依次从队列尾插入
(4)跳到步骤2
  DFS:
(1)访问顶点v并打印节点
(2)遍历v的子节点w,若w存在递归的执行该节点。

6、关联规则具体有哪两种算法,它们之间的区别

   标签:数据结构与算法
   Apriori和FP-growth算法
  Apriori多次扫描交易数据库,每次利用候选频繁集产生频繁集,而FP-growth则利用树形结构,无需产生候选频繁集而直接得到频繁集,减少了扫描交易数据库的次数,提高算法的效率但是Apriori有较好的扩展性可用于并行计算。一般使用Apriori算法进行关联分析,FP-growth算法来高效发现频繁项集。

7、贪婪算法

  贪婪算法(贪心算法)是指在对问题进行求解时,在每一步选择中都采取最好或者最优(即最有利)的选择,从而希望能够导致结果是最好或者最优的算法。贪婪算法所得到的结果往往不是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果。贪婪算法并没有固定的算法解决框架,算法的关键是贪婪策略的选择,根据不同的问题选择不同的策略。
  必须注意的是策略的选择必须具备无后效性,即某个状态的选择不会影响到之前的状态,只与当前状态有关,所以对采用的贪婪的策略一定要仔细分析其是否满足无后效性。
  其基本的解题思路为:
1)建立数学模型来描述问题;
2)把求解的问题分成若干个子问题;
3)对每一子问题求解,得到子问题的局部最优解;
4)把子问题对应的局部最优解合成原来整个问题的一个近似最优解。

8、模拟退火,蚁群对比

  模拟退火算法:退火是一个物理过程,粒子可以从高能状态转换为低能状态,而从低能转换为高能则有一定的随机性,且温度越低越难从低能转换为高能。就是在物体冷却的过程中,物体内部的粒子并不是同时冷却下来的。因为在外围粒子降温的时候,里边的粒子还会将热量传给外围粒子,就可能导致局部粒子温度上升,当然,最终物体的温度还是会下降到环境温度的。
  先给出一种求最优解的方法,在寻找一个最优解的过程中采取分段检索的方式,在可行解中随机找出来一个,然后在这个解附近进行检索,找到更加优化的解,放弃原来的,在新得到的解附近继续检索,当无法继续找到一个更优解的时候就认为算法收敛,最终得到的是一个局部最优解。
  蚁群算法:很显然,就是模仿蚂蚁寻找食物而发明的算法,主要用途就是寻找最佳路径。先讲一下蚂蚁是怎么寻找食物,并且在寻找到了食物后怎么逐渐确定最佳路径的。
  事实上,蚂蚁的目光很短,它只会检索它附近的很小一部分空间,但是它在寻路过程中不会去重复它留下信息素的空间,也就是它不会往回走。当一群蚂蚁遇到障碍物了之后,它们会随机分为两拨,一波往左一波往右,但是因为环境会挥发掉它们的信息素,于是,较短的路留下的信息素多,而较长的路因为挥发较多,也就留下得少了。接下来,蚁群就会趋向于行走信息素较多的路径,于是大部分蚂蚁就走了较短的路了。但是蚁群中又有一些特别的蚂蚁,它们并不会走大家走的路,而是以一定概率寻找新路,如果新路更短,信息素挥发更少,渐渐得蚁群就会走向更短的路径。
  以上就是蚂蚁寻路的具体过程。把这个过程映射到计算机算法上,计算机在单次迭代过程中,会在路径的节点上留下信息素(可以使用数据变量来表示)。每次迭代都做信息素蒸发处理,多次迭代后聚集信息素较多的路径即可认为是较优路径。

八、队列

1、 什么是Java优先级队列(Priority Queue)?

  PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。

九、查找

1、特别大的数据量,实现查找,排序

  • 1)、位图法
      位图法是我在编程珠玑上看到的一种比较新颖的方法,思路比较巧妙效率也很高。
      使用场景举例:对2G的数据量进行排序,这是基本要求。
      数据:1、每个数据不大于8亿;2、数据类型位int;3、每个数据最多重复一次。
      内存:最多用200M的内存进行操作。
      而位图法的基本思想就是利用一位代表一个数字,例如3位上为1,则说明3在数据中出现过,若为0,则说明3在数据中没有出现过。所以当题目中出现每个数据最多重复一次这个条件时,我们可以考虑使用位图法来进行大数据排序。那么假如使用位图法来进行这题的排序,内存占用多少呢。由题目知道每个数据不大于8亿,那么我们就需要8亿位,占用800000000/8388608=95M的空间,满足最多使用200M内存进行操作的条件,这也是这题能够使用位图法来解决的一个基础。
  • 2)、堆排序法
      堆排序是4种平均时间复杂度为nlogn的排序方法之一,其优点在于当求M个数中的前n个最大数,和最小数的时候性能极好。所以当从海量数据中要找出前m个最大值或最小值,而对其他值没有要求时,使用堆排序法效果很好。
      使用场景:从1亿个整数里找出100个最大的数
  • 步骤:
    (1)读取前100个数字,建立最大值堆。(这里采用堆排序将空间复杂度讲得很低,要排序1亿个数,但一次性只需读取100个数字,或者设置其他基数,不需要1次性读完所有数据,降低对内存要求)
    (2)依次读取余下的数,与最大值堆作比较,维持最大值堆。可以每次读取的数量为一个磁盘页面,将每个页面的数据依次进堆比较,这样节省IO时间。
    (3)将堆进行排序,即可得到100个有序最大值。
    堆排序是一种常见的算法,但了解其的使用场景能够帮助我们更好的理解它。
  • 3)、较为通用的分治策略
      分治策略师对常见复杂问题的一种万能的解决方法,虽然很多情况下,分治策略的解法都不是最优解,但是其通用性很强。分治法的核心就是将一个复杂的问题通过分解抽象成若干个简单的问题。
      应用场景:10G的数据,在2G内存的单台机器上排序的算法
    我的想法,这个场景既没有介绍数据是否有重复,也没有给出数据的范围,也不是求最大的个数。而通过分治虽然可能需要的io次数很多,但是对解决这个问题还是具有一定的可行性的。
  • 步骤:
    (1)从大数据中抽取样本,将需要排序的数据切分为多个样本数大致相等的区间,例如:1-100,101-300…
    (2)将大数据文件切分为多个小数据文件,这里要考虑IO次数和硬件资源问题,例如可将小数据文件数设定为1G(要预留内存给执行时的程序使用)
    (3)使用最优的算法对小数据文件的数据进行排序,将排序结果按照步骤1划分的区间进行存储
    (4)对各个数据区间内的排序结果文件进行处理,最终每个区间得到一个排序结果的文件
    (5)将各个区间的排序结果合并。通过分治将大数据变成小数据进行处理,再合并。

十、递归

1、尾递归

   如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。

总结

  由于数据结构与算法中面试的内容较多,因此前面两篇文章和本篇文章对面试中常见的数据结构与算法进行了简单的总结,分别从数据结构中的树、堆与栈、数组和排序以及本篇文章是关于哈希、链表、队列、查找、递归的相关问题。本人之所以花三篇文章的篇幅来介绍数据结构与算法的相关问题,一方面是为了方便自己以后面试的复习,另外也是给大家再次面试相关岗位的时候提供复习方向以及思路解答。这里就需要我们对数据结构与算法有一个较为深层次的理解。于是,我们在准备的时候,首先就应该夯实基础,只有这样才能在众多的面试者中脱颖而出。另外,作为在计算机行业工作的从事者,掌握一些基础的操作系统的知识是很有必要的,也是我们的基本素养。最后希望大家不断进步,都能尽早拿到自己比较满意的offer!!!!继续加油,未来可期!!!!

你可能感兴趣的:(面试汇总(十):数据结构与算法常见面试总结(三)——哈希、链表、队列、查找、递归)