高度:节点n到叶子节点的最长路径为节点n的高度,叶子结点的高度为零。树的高度为根节点到叶子结点的最长简单路径。
深度:根节点到节点n的路径为n的深度,根节点的深度为0。
高度深度也有说是从1开始算的,两种说法都有。
度:节点的度是节点拥有的子树个数,二叉树的度小于等于二,因为它的最多只能有两个节点,也就是两个子数。
满二叉树:高度为h的树的结点个数为2h-1(h>=1)。
完全二叉树:只有最下面两层的节点度数可以小于2,满二叉树也属于完全二叉树。
二叉树的性质:(这里的高度深度都从1开始算)
- 性质1:二叉树第i层上的结点数目最多为 2i-1 (i≥1)。
- 性质2:深度为k的二叉树至多有2i-1个结点(k≥1)。
- 性质3:包含n个结点的二叉树的高度至少为log2 (n+1)。
- 性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1。
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(int key) {
this.val=key;
this.left=null;
this.right=null;
}
}
二叉查找树
二叉查找树中每个节点的左子节点都不大于它本身的值,每个节点的右子节点的值都不小于它本身的值。
- 查询二叉查找树
递归版本
public TreeNode search(TreeNode root ,int target)
{
if(root==null||root.key==target)
return root;
else if(root.key<target)
return search(root.right,target);
else
return search(root.left, target);
}
迭代版本
public TreeNode iterativeSearch(TreeNode root ,int target)
{
while(root!=null&&root.key!=target)
{
if(target<root.key)
root=root.left;
else
root=root.right;
}
return root;
}
- 查找最大值
由二叉查找树得性质可得,树中最大的值一定是最右叶子节点的值
public TreeNode findMax(TreeNode root){
while(root.right!=null)
root=root.right;
return root;
}
- 查找最小值
由二叉查找树得性质可得,树中最小的值一定是最左叶子节点的值
public TreeNode findMin(TreeNode root){
while(root.left!=null)
root=root.left;
return root;
}
- 前序遍历树
//迭代
public List<Integer> iterativePreorderTraversal(TreeNode root){
List<Integer> res=new ArrayList<Integer>();
Stack<TreeNode> stack=new Stack<TreeNode>();
stack.add(root);
while(!stack.isEmpty())
{
TreeNode tmp=stack.pop();
res.add(tmp.val);
if(tmp.right!=null)
stack.add(tmp.right);
if(tmp.left!=null)
stack.add(tmp.left);
}
return res;
}
//递归
public List<Integer> preorderTraversal(TreeNode root){
List<Integer> res=new ArrayList<Integer>();
preorderCore(root,res);
return res;
}
private void preorderCore(TreeNode root, List<Integer> res) {
// TODO Auto-generated method stub
res.add(root.val);
if(root.left!=null){
preorderCore(root.left, res);
}
if(root.right!=null)
preorderCore(root.right, res);
}
- 中序遍历树
//迭代中序遍历
public List<Integer> iterativeInOrderTraversal(TreeNode root){
Stack<TreeNode> stack=new Stack<TreeNode>();
List<Integer> res=new ArrayList<Integer>();
TreeNode tmp=root;
while (tmp != null || !stack.isEmpty()) {
while (tmp != null) {
stack.add(tmp);
tmp = tmp.left;
}
tmp = stack.pop();
res.add(tmp.val);
tmp = tmp.right;
}
return res;
}
//递归中序遍历
public List<Integer> inOrderTraversal(TreeNode root){
List<Integer> res=new ArrayList<Integer>();
inOrderCore(root,res);
return res;
}
private void inOrderCore(TreeNode root, List<Integer> res) {
// TODO Auto-generated method stub
if(root.left!=null){
inOrderCore(root.left, res);
}
res.add(root.val);
if(root.right!=null)
inOrderCore(root.right, res);
}
- 后序遍历树
//迭代
public List<Integer> iterativePostorderTraversal(TreeNode root){
List<Integer> res=new ArrayList<Integer>();
Stack<TreeNode> stack1=new Stack<TreeNode>();
Stack<TreeNode> stack2=new Stack<TreeNode>();
stack1.add(root);
while(!stack1.isEmpty())
{
TreeNode tmp=stack1.pop();
stack2.push(tmp);
if(tmp.left!=null)
stack1.add(tmp.left);
if(tmp.right!=null)
stack1.add(tmp.right);
}
while(!stack2.isEmpty()){
res.add(stack2.pop().val);
}
return res;
}
//递归
public List<Integer> postorderTraversal(TreeNode root){
List<Integer> res=new ArrayList<Integer>();
postorderCore(root,res);
return res;
}
private void postorderCore(TreeNode root, List<Integer> res) {
// TODO Auto-generated method stub
if(root.left!=null){
postorderCore(root.left, res);
}
if(root.right!=null)
postorderCore(root.right, res);
res.add(root.val);
}
- 插入
//迭代
public void iterativeinsert(TreeNode root, int target) {
TreeNode key = new TreeNode(target);
TreeNode parent = null;
if (root == null)
root = key;
TreeNode tmp = root;
while (tmp != null) {
parent = tmp;
//小于当前节点的值则向左子节点中寻找,大于则向右子节点中寻找
if (target < tmp.val) {
tmp = tmp.left;
} else {
tmp = tmp.right;
}
}
if (parent.val > target)
parent.left = key;
else
parent.right = key;
}
//递归
public void insert(TreeNode root, int target) {
if (root == null)
{
root = new TreeNode(target);
return ;
}
if(root.val>target)
insert(root.left, target);
else
insert(root.right,target);
}
- 删除
public void iterativedelete(TreeNode root,int target){
if(root==null)
return;
while(root!=null)
{
if(root.val==target)
{
if(root.left==null&&root.right==null)
root=null;
else if(root.left==null)
root=root.right;
else if(root.right==null)
root=root.left;
else{
TreeNode parent=null;
TreeNode succ=root.right;
while(succ.left!=null)
{
parent=succ;
succ=succ.left;
}
parent.left=succ.right;
succ.left=root.left;
root=succ;
}
}
else if(root.val>target)
root=root.left;
else
root=root.right;
}
}
public void delete(TreeNode root, int target) {
if (root == null)
return;
if (root.val > target)
delete(root.left, target);
else if (root.val < target)
delete(root.right, target);
else {
if (root.left == null && root.right == null) {
root = null;
} else if (root.left == null)
root = root.right;
else if (root.right == null)
root = root.left;
else {
TreeNode parent = null;
TreeNode succ = root.right;
while (succ.left != null) {
parent = succ;
succ = succ.left;
}
parent.left = succ.right;
succ.left = root.left;
root = succ;
}
}
}
红黑树
红黑树的性质
- 每个节点是红色的或者黑色的;
- 根节点是黑色的;
- 每个叶子节点是黑色的;
- 如果一个节点是红色的,则它的两个子节点都是黑色的;
- 对于每个节点,从该节点到其所有后代叶子节点的简单路径上均包含相同数目的黑色节点。
黑高:从某个结点出发到其叶子节点的任意一条简单路径上黑色节点的个数。红黑树的黑高即为其根节点的黑高
有n个节点的红黑二叉树高度最多为2log2(n+1)(此处的高度为树高,不是黑高)
因为在二叉树中搜索,找最大值和最小值这些操作的时间复杂度都与树的高度有关,而红黑树的高度最多为2log2(n+1),则它做这些操作的时间复杂度为O(logN)
对红黑树进行插入和删除操作可能使红黑树不满足上面的性质,因此要修改树的某些节点的颜色和结构,来使红黑树保持它的性质。
源码如下:
public class RedBlackTree {
private Node root;
private static boolean RED = true;
private static boolean BLACK = false;
private class Node {
int val;
Node left;
Node right;
boolean color;
int count;
public Node(int val, boolean color) {
this.val = val;
this.color = color;
this.count = 1;
}
}
public boolean isRed(Node x) {
return x.color;
}
public int size() {
return size(root);
}
public boolean isEmpty() {
return size(root) == 0;
}
private int size(Node root) {
if (root == null)
return 0;
else
return root.count;
}
Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color;
h.color = RED;
x.count = h.count;
h.count = 1 + size(h.left) + size(h.right);
return x;
}
Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
x.count = h.count;
h.count = 1 + size(h.left) + size(h.right);
return x;
}
void flipColors(Node h) {
h.color = RED;
h.left.color = BLACK;
h.right.color = BLACK;
}
private Node balance(Node h) {
if (isRed(h.right) && !isRed(h.left))
h = rotateLeft(h);
if (isRed(h.left) && isRed(h.left.left))
h = rotateRight(h);
if (isRed(h.left) && isRed(h.right))
flipColors(h);
h.count = 1 + size(h.left) + size(h.right);
return h;
}
public void Insert(int target) {
root = Insert(root, target);
root.color = BLACK;
}
private Node Insert(Node h, int target) {
if (h == null)
return new Node(target, RED);
if (h.val > target)
h.left = Insert(h.left, target);
else
h.right = Insert(h.right, target);
return balance(h);
}
void moveflipColors(Node h) {
// 这是用于删除节点的flipColor方法,该方法用于节点的合并,将父节点中的部分给与子节点
h.color = BLACK;
h.left.color = RED;
h.right.color = RED;
}
private Node moveRedLeft(Node h) {
// 将当前节点和左子节点与右子节点成为一个4-节点
moveflipColors(h);
if (isRed(h.right.left)) {
h.right = rotateRight(h.right);
h = rotateLeft(h);
flipColors(h);
}
return h;
}
public void deleteMin() {
if (!isRed(root.left) && !isRed(root.right))
root.color = RED;
root = deleteMin(root);
if (!isEmpty())
root.color = BLACK;
}
private Node deleteMin(Node h) {
// 找到最小的值了,则将它置为空
if (h.left == null)
return null;
// 如果当前节点不是3-节点,并且他的左孩子不是一个3-节点,则需要调整
if (!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
h.left = deleteMin(h.left);
return balance(h);
}
private Node moveRedRight(Node h) {
// 将当前节点和左子节点与右子节点成为一个4-节点
moveflipColors(h);
if (isRed(h.left.left)) {
h = rotateRight(h);
flipColors(h);
}
return h;
}
public void deleteMax() {
if (!isRed(root.left) && isRed(root.right))
root.color = RED;
root = deleteMax(root);
root.color = BLACK;
}
private Node deleteMax(Node h) {
if (isRed(h.left))
h = rotateRight(h);
if (h.right == null)
return null;
if (!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
h.right = deleteMax(h.right);
return balance(h);
}
public void delete(int target) {
if (!isRed(root.right) && !isRed(root.left))
root.color = RED;
root = delete(root, target);
if (!isEmpty())
root.color = RED;
}
public Node delete(Node h, int target) {
if (h.val > target) {
if (!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
h.left = delete(h.left, target);
} else {
if (isRed(h.left))
h = rotateRight(h);
if (target == h.val && h.right == null)
return null;
if (!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
if (target == h.val) {
Node x = min(h.right);
h.val = x.val;
h.right = deleteMin(h.right);
} else {
h.right = delete(h.right, target);
}
}
return balance(h);
}
Node min(Node h) {
Node min = h.right;
while (min.left != null) {
min = min.left;
}
return min;
}
}
AVL树:平衡二叉树,是严格平衡的二叉树,左右子树的高度差相差不超过1,理想状况下平衡二叉树查找数据的时间复杂度为O(logN)。为了保持平衡,在进行删除和插入的时候会根据平衡因子进行大量旋转操作,这会增大时间复杂度,因此平衡二叉树适用于查询操作较多,而插入删除操作较少的情况。windows对进程地址空间的管理用到了AVL树。
红黑树:平衡二叉树,通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,确保没有一条路径会比其他路径长2倍每个节点到不同叶子节点的路径中黑色节点的数量是相等的,因而是近似平衡的。所以相对于严格要求平衡的AVL树来说,它的旋转保持平衡次数较少。用于搜索时,插入删除次数多的情况下我们就用红黑树来取代AVL。
红黑树的应用:
- Java的TreeSet和TreeMap以及jdk8之后的HashMap中。
- 著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块。
B树和B+树
B树的概念
B-树中所有结点中孩子结点个数的最大值成为B-树的阶,通常用m表示,从查找效率考虑,一般要求m>=3。一棵m阶B-树或者是一棵空树,或者是满足以下条件的m叉树。
1.根结点至少有两个子女。
2.每个中间节点都包含k-1个元素和k个孩子,其中 ceil(m/2) ≤ k ≤ m(ceil是向上取整)
3.每一个叶子节点都包含k-1个元素,其中 ceil(m/2) ≤ k ≤ m
4.所有的叶子结点都位于同一层。
5.每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域划分
6.每个结点的结构为:(n,A0,K1,A1,K2,A2,… ,Kn,An)
其中,Ki(1≤i≤n)为关键字,且Ki<Ki+1(1≤i≤n-1)。
Ai(0≤i≤n)为指向子树根结点的指针。且Ai所指子树所有结点中的关键字均小于Ki+1。
n为结点中关键字的个数,满足ceil(m/2)-1≤n≤m-1。
B+树:
1.有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据
都保存在叶子节点。
2.所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小
自小而大顺序链接。
3.所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
B+树相比B树的优势:
1.单一节点存储更多的元素,使得查询的IO次数更少;
2.所有查询都要查找到叶子节点,查询性能稳定;
3.所有叶子节点形成有序链表,便于范围查询。
B树一般用于数据库中做索引,因为它们分支多层数少,而磁盘IO是非常耗时的,所以我们要有效的减少磁盘IO次数避免磁盘频繁的查找。
B+树是B树的变种树,有n棵子树的节点中含有n个关键字,每个关键字不保存数据,只用来索引,数据都保存在叶子节点。是为文件系统而生的。
B+树相对B树磁盘读写代价更低:因为B+树非叶子结点只存储键值,单个节点占空间小,索引块能够存储更多的节点,从磁盘读索引时所需的索引块更少,所以索引查找时I/O次数较B树索引少,效率更高。而且B+树在叶子节点存放的记录以链表的形式链接,范围查找或遍历效率更高。Mysql InnoDB用的就是B+Tree索引。