之前说到了二叉树的遍历,下面继续:
满二叉树: 二叉树每一层的节点都达到了最大,其实就是这棵树是满着的。第n层有2的(n-1)次方个节点,前n层共2的n次方-1个节点。
完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
网上找了2个图片:(左:满二叉树 右:完全二叉树)
最优二叉树:这个需要权重,指带权路径最小的生成树,也成为哈夫曼树。
//将一个数组转化为一颗哈夫曼树
public static TreeNode build(int[] nums){
ArrayList<TreeNode> deque= new ArrayList<>();
for (int i:nums) {
deque.add(new TreeNode(i));
}
while(deque.size()>1){
Collections.sort(deque);
TreeNode node = deque.get(0);
TreeNode node2 = deque.get(1);
//选择2个最小的元素构造
TreeNode<Integer> par = new TreeNode<>((Integer) node.getData()+(Integer)node2.getData());
par.lChild=node;
par.rChild=node2;
deque.remove(node);
deque.remove(node2);
deque.add(par);
}
return deque.get(0);
}
二叉排序树:一棵有顺序的树,他的规则是左节点于中间节点,右节点大于中间节点。这种树结构查找起来非常快(除极端情况)时间复杂度是log2n;极端情况就会变成一个链表。
//排序二叉树的构造
public static TreeNode build(int[] nums) {
TreeNode<Integer> root = new TreeNode(nums[0]);
for (int i = 1; i < nums.length; i++) {
TreeNode<Integer> node = new TreeNode(nums[i]);
getlocal(root,node);
}
return root;
}
//递归实现
public static void getlocal(TreeNode<Integer> target, TreeNode<Integer> temp) {
if (target.getData() > temp.getData()) {
if (target.lChild != null) {
getlocal(target.lChild, temp);
} else {
target.lChild = temp;
}
} else {
if (target.rChild != null) {
getlocal(target.rChild, temp);
} else {
target.rChild = temp;
}
}
}
平衡二叉树:也是一个有序的树,不过这个是为了防止二叉排序树有极端情况而出现的。它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,他要求他的子树都是一个平衡二叉树。这个构建代码就有点多了。
红黑树:与平衡二叉树类似,它满足下面几个性质:
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质4. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
B树:它属于多叉树,一棵M阶多叉树,每个节点之多有m课子树,除根节点以后的所有非终端节点至少[m/2]颗子树。
这是一棵5阶B树。
B+树:是B树的一种变形树。
(1)B+跟B树不同B+树的非叶子节点不保存关键字记录的指针,只进行数据索引,这样使得B+树每个非叶子节点所能保存的关键字大大增加;
(2)B+树叶子节点保存了父节点的所有关键字记录的指针,所有数据地址必须要到叶子节点才能获取到。所以每次数据查询的次数都一样;
(3)B+树叶子节点的关键字从小到大有序排列,左边结尾数据都会保存右边节点开始数据的指针。
(4)非叶子节点的子节点数=关键字数(来源百度百科)(根据各种资料 这里有两种算法的实现方式,另一种为非叶节点的关键字数=子节点数-1(来源维基百科),虽然他们数据排列结构不一样,但其原理还是一样的Mysql 的B+树是用第一种方式实现);
B*树:
B*树是B+树的变种,相对于B+树他们的不同之处如下:
(1)首先是关键字个数限制问题,B+树初始化的关键字初始化个数是cei(m/2),b*树的初始化个数为(cei(2/3*m))
(2)B+树节点满时就会分裂,而B*树节点满时会检查兄弟节点是否满(因为每个节点都有指向兄弟的指针),如果兄弟节点未满则向兄弟节点转移关键字,如果兄弟节点已满,则从当前节点和兄弟节点各拿出1/3的数据创建一个新的节点出来;
来源:https://zhuanlan.zhihu.com/p/27700617
森林和二叉树相互转化:
将一个森林转换为二叉树:
具体方法是:1.将森林中的每棵树变为二叉树;2.因为转换所得的二叉树的根结点的右子树均为空,故可将各二叉树的根结点视为兄弟从左至右连在一起,就形成了一棵二叉树。
二叉树转换为树:
是树转换为二叉树的逆过程。
1.加线。若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点…,都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。
2.去线。删除原二叉树中所有结点与其右孩子结点的连线。
散列表
散列表也是叫哈希表,基本思想是确定一个函数,求得每个关键码相应的函数并以此作为存储地址,直接将该元素存入到相应的地址空间中。
常用散列函数:
1.直接定址法:Hash(key) = a*key+b; (a,b是常数)
2.除留余数法:Hash(key)=key%p;p一般选取为小于m的最大质数,也可以是不包含小于20的质因子的合数。
3.乘余取整法:Hash(key)=[B*(A*key%1)] 一般A取: A = 1/2(根号5 -1);
4.数字分析法:数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相 同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会 明显降低。
5.平方取中法:取关键字平方后的中间几位作为散列地址。
6.随机数法:选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。
处理冲突方法:
1.开放定址法:发送冲突以后,从该地址顺序向下一个地址探测,知道找到一个空的单元。
2.二测探测法:H1= (Hash(key)+di)%m,di为增量序列 1的平方 负1的平方 2的平放,负二的平方....。
3.双散列函数探测法:先用一个函数对关键码计算散列地址,一旦产生冲突,再用第二个Hash函数确定移动的步长因子,最后通过步长音泽序列由探测函数再寻找空的散列地址。
4.拉链法:将同一个地址冲突的位置拉成一个拉链,再将单链表的头指针根据散列地址顺序组织起来。
5。公共溢出区法:散列表函数产生的散列地址集为[0,m-1],则分配两个表。基本表:每个单位只存放一个元素;溢出表:只有关键码对应的散列地址在产生冲突的元素一律存入该表中。
图结构
图的种类
有向图:顶点的连线是有方向的。
无向图:顶点的连线时没有方向的。
存储方式
邻接矩阵:对于一个n个顶点采用顺序存储,任意两个顶点之间是否有邻接关系,是否有边,用一个n*n的矩阵来表示。
邻接表:每一条边存储一个结点,把某个顶点Vi所有邻接边链接成一个单链表。
图的遍历和那些算法面试中应该问的很少,就不费劲把时间花在这里了。