1.二叉树
是一种树形结构,每个结点至多有那个子树,并且二叉树的子树有左右之分,次序不能颠倒。
满二叉树:
即每个节点都有俩个子节点。一个深度为k且有2的k次方-1个节点的二叉树。
完全二叉树:
深度为k的,有n个节点的二叉树,当且仅当每一个节点都与深度为k的满二叉树从1-n 一一对应时,就是完全二叉树。是满二叉树的子集。
储存结构:
顺序存储结构:即使用数组进行存储。第一个元素为根节点,左子树的索引为层数*2-1,左子树的索引为层数*2,用0来表示空元素。但会造成大量空间的浪费,因此适用于完全二叉树。
链式存储结构:每个节点都有指向左子树和右子树的指针。这里我们用Java实现。为了插入和删除方便,我们设置一个头结点,指向根节点。
public class BinaryTree<T> {
public class Node {
Node lchild;
Node rchild;
T data;
}
private Node head;
}
遍历方法:
先序遍历、中序遍历、后序遍历。其中先中后指的是根节点的遍历顺序。且 左子树 先于右子树遍历
先序遍历(即深度遍历):根 -> 左 -> 右 A->B->D->G->C->E->F
代码:
public void frontOrder(Node node){
if(node == null){
return ;
}
System.out.println(node.data);
frontOrder(node.lchild);
frontOrder(node.rchild);
}
//使用栈来完成
public void frontOrder2(Node node){
if(node == null){
return ;
}
Stack<Node> stack = new Stack();
stack.push(node);
while (! stack.isEmpty()){
Node parent = stack.pop();
System.out.println(parent.data);
if(parent.rchild != null){
stack.push(parent.rchild);
}
if(parent.lchild != null){
stack.push(parent.lchild);
}
}
}
中序遍历:左 -> 根 -> 右 D->G->B->A->E->C->F
代码:
public void middleOrder(Node node){
if(node == null){
return ;
}
middleOrder(node.lchild);
System.out.println(node.data);
middleOrder(node.rchild);
}
后序遍历:左 -> 右 -> 中 G->D->B->E->F->C->A
代码
public void afterOrder(Node node){
if(node == null){
return ;
}
afterOrder(node.lchild);
afterOrder(node.rchild);
System.out.println(node.data);
}
按层遍历即广度遍历:
可以使用队列这种形式,先进先出
public void cengOrder(Node node ){
//LinkedList 支持队列操作
LinkedList<Node> nodes = new LinkedList<>();
nodes.push(node);
while (! nodes.isEmpty()){
Node parent = nodes.poll();
System.out.println(parent.data);
//放入左节点
if(parent.lchild != null){
nodes.addLast(parent.lchild);
}
//放入右节点
if(parent.rchild != null){
nodes.addLast(parent.rchild);
}
}
}
线索二叉树:
以二叉链表为存储结构时,只能找到结点的的左右孩子结点的信息,而不能获得它的前驱和后继的信息。为了获取前驱和后继,最简单的方法就是添加前驱和后继的指针,但是这样会造成空间的浪费。为了避免空间浪费,我们依旧使用左右结点来作为前驱和后继,并加上标志位。
定义如下:若结点有左子树,则令lchild指向左孩子,否则指向前驱;若结点有右孩子,则令rchild指向右孩子,否则指向后继。
public class BinaryTree<T> {
public class Node {
Node lchild;
Node rchild;
T data;
int lTag; //为0指向左孩子,为1指向前驱
int RTag;
}
private Node head;
}
森林与二叉树的转换:
1.连线:将兄弟结点连线
2.抹线:抹去除左子树的其他子树的线
3.旋转
哈夫曼树(最优二叉树):
带权路径最小的树为最优二叉树。
构建:假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
哈夫曼编码:即将左0右1,即可获得编码
堆:
左子树和右子树上的元素大于父节点的元素,因此父节点的元素为最小的。或者是左子树和右子树的元素小于父节点的元素,父节点的元素为最大。
二叉排序树(二叉查找树)
1.若它的左子树不空,则左子树上的左右结点的值均小于它的根节点的值
2.若它的右子树不空,则右子树的所有结点的值均大于它的根节点的值
3.它的左右树也分别为二叉排序树。
用于查找数据。
比如想查找7的话,先要比较8,在比较3,在比较6,得到7;可见比较次数是与二叉树的深度有关。因此最坏时间复杂度是O(logn)
平衡二叉树:
1.左右树的深度只差不超过1,
2.左子树、右子树均为平衡二叉树。
我们希望二叉排序树都是平衡二叉树,显得较低一些,比较次数少。
平衡二叉树的添加与删除调整:旋转,
红黑树
根节点是黑色的;
每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据;
任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的;
每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;
B+树:
每个节点中子节点的个数不能超过 m,也不能小于 m/2;
根节点的子节点个数可以不超过 m/2,这是一个例外;
m 叉树只存储索引,并不真正存储数据,这个有点儿类似跳表;
通过链表将叶子节点串联在一起,这样可以方便按区间查找
一般情况,根节点会被存储在内存中,其他节点存储在磁盘中
B树:
B+ 树中的节点不存储数据,只是索引,而 B 树中的节点存储数据;B 树中的叶子节点并不需要链表来串联。