以下是笔者总结的Java笔试面试题中数据结构与算法部分,答案为笔者自己总结得出,未必完全正确,仅供参考
数据结构和算法这一块主要靠编程去熟悉。这里选了一些题大概口述了一下,理解思想,实践为主
快速排序思想是分治思想
每一趟排序
① 选第一个数为基准值(t),定义两个指针分别表示从左向右(i)和从右向左遍历(j)
② 先从右向左遍历,如果j位置的数比t小,那么把这个数放到i位置上
③ 接着从左向右遍历,如果i位置的数比t大,那么把这个数放到j位置上
④ 直到i和j相等时,这个位置就是t的最终位置
一轮下来,至少有一个数到它最终的位置上,同时保证左边的数都比基准值小,右边的都比基准值大
归并排序思想是分治思想。先把数组一直折半分(一分为二),直到每个子数组的长度为1或2,然后对这些子数组进行两两合并,直到合并完成,排序也就完成了
(1) 选择排序是依次把右边最小的值拿到放在左边
(2) 插入排序是假定左边的序列是有序的,把右边的数一个个拿过来插入左边的序列中
(1) 数组中的存储结构是连续的,并且有下标。链表存储结构是不连续的
(2) 数组中只有一个域存储数据。链表需要多定义一个或两个指针用于表示前驱或后继结点
(3) 数组适用于查询多修改少的情况,因为数组有下标更方便访问。链表适用于查询少修改多的情况,链表在增加或删除元素后,只需要修改前驱或后继结点的指向即可
红黑树本质上是一种二叉查找树,主要是在二叉查找树的基础上增加一些颜色标记,是一种平衡二叉树,插入、删除和查找最坏情况下时间复杂度为O(log(n))
红黑树必须满足5个特性:
(1) 所有结点要么是红的,要么是黑的
(2) 根结点是黑色的
(3) 所有叶子结点也是黑色的
(4) 每个红色结点的两个子结点都是黑色的
(5) 从任一节点到其每个叶子的所有路径都包含相同数目的黑色结点
(1) 树中每个结点最多m棵子树
(2) 所有叶子结点在同一层
(3) 根节点至少两棵子树
(4) 非根非叶结点至少m/2棵子树
(5) 有k个关键字的非叶结点有k+1棵子树
(1) 关键字的数量不同。B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用。B树虽然也有m个子结点,但是其只有m-1个关键字
(2) 存储的位置不同。B+树中的数据都存储在叶子结点上。B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上
(3) 分支结点的构造不同。B+树的分支结点仅仅存储着关键字信息和儿子的指针,也就是说内部结点仅仅包含着索引信息。B树则可以存物理数据
(4) 查询不同。B+树在查找时要从跟结点走到叶子结点。B树不一定,找到数据就返回
定义两个指针,一个叫快指针,一个叫慢指针,快指针每次向前移动2步,慢指针向前移动1步,两个指针同时向前移动,每向前移动一次两个指针都进行一次比较。如果发现两个指针有相等的情况,则说明有环,若快指针到达了链表尾部(为null),则说明没有环
定义两个指针,一个先行指针,一个后来指针,让先行指针先走k步,然后两个指针同时走,当先行指针指向null时,后来指针指的位置就是倒数第k个元素
(1) 开放定址法。所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到
(2) 二次哈希法。当发生hash冲突时,使用其它的hash算法重新计算,直到不发生冲突为止
(3) 链地址法。每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个位置上的多个节点可以用这个单向链表连接起来
(4) 建立公共溢出区。将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
(1) 线性探测
Hi = (Hash(key)+di) mod m
发生hash冲突时,将hash值由1递增并找到一个空位
(2) 平方探测
① 根据哈希函数算出Hashval,i 初始化为零,判断HashTable[Hashval]是否被占用,如果没被占用,则Hashval就是根据哈希函数算出的值,跳出平方探测;如果被占用则向右探测(执行②)
② 判断HashTable[Hashval + i * i]是否被占用,如果没被占用,则Hashval = Hashval + i * i,跳出平方探测;如果被占用则向左探测(执行③)
③ 判断HashTable[Hashval - i * i]是否被占用,如果没被占用,则Hashval = Hashval - i * i,跳出平方探测;如果被占用则i++继续向右探测(执行②)
栈是后进先出。的队列是先进先出的
(1) 堆是一种完全二叉树,分为最大堆和最小堆。最大堆的父节点均大于两个子节点,最小堆的父节点均小于两个子节点
(2) 对于给定的n个记录,初始时把这些记录看作是一颗顺序存储的完全二叉树,然后将其调整为一个大顶堆,然后将堆的最后一个元素与堆顶元素进行交换后,堆的最后一个元素即为最大记录。接着将前n-1个元素重新调整为一个大顶堆,再将堆顶元素与当前堆的最后一个元素进行交换得到次大的记录,重复该过程直到调整的堆中只剩一个元素为止
(1) 二叉树每个结点最多有两棵子树,即左子树和右子树
(2) 二叉树是一种递归的结构
(3) 二叉树的第i层最多有2的i-1次方个结点
(4) 深度为k的二叉树最多有2的k次方减1个结点
(5) 二叉树中度为0的结点总是比度为2的结点多1个
(1) 先序遍历(深搜遍历)。先遍历根结点,然后遍历根结点的左子树,最后遍历根结点的右子树
(2) 中序遍历。先遍历根结点的左子树,然后遍历根结点,最后遍历根结点的右子树
(3) 后序遍历。先遍历根结点的左子树,然后遍历根结点的右子树,最后遍历根结点
(4) 层次遍历(广搜遍历)。从跟结点开始,从上到下,从左到右遍历
(1) 广度遍历。从一个结点出发,先访问这个结点的邻接结点,然后再依次遍历邻接结点的邻接结点,这是个递归过程,直到遍历完所有结点为止
(2) 深度遍历。从一个结点A出发,访问第一个邻接结点B,然后再访问B的第一个邻接结点C,直到没有邻接结点遍历为止。然后又回来遍历A的第二个结点,重复上述过程,这是个递归的过程,直到遍历完所有结点为止
(1) max = (a + b + |a-b|) / 2
(2) min = (a + b - |a-b|) / 2
分析:这个n达到亿级,由于机器内存有限,不建议直接把这n个数排序。假设n为1亿,定义int数组,每个int占4个字节,那么占用内存约400MB,如果10亿,就将近4G了
可以用以下方法解决
(1) 定长数组法。先定义一个长度为k的数组,把前k个数从文件读入到这个数组并从大到小排好序,然后把后面的数一个个读出来放到数组上。如果比最后一个数小了,那么继续读取下一个,否则就找到位置“插队”,并舍弃最后一个
(2) 分治法。把这n个数组分成m(m一般大于100)份,然后对这m份的数分别找出前k大的数,然后对这m*k个数归并,找出前k个即可