1.树是一种常用数据结构,它是非线性结构。
2.树中任一普通节点可以有0或者多个子节点,但只能有一个父节点。
根节点没有父节点,
叶子节点没有子节点。
3.二叉树:
1)每个节点最多只能有两个子树的有序树
2)左边的子树称为左子树
3)右边的子树成为右子树
4)一棵深度为k的二叉树,如果它包含了(2^k-1)个节点,就把这棵二叉树称为满二叉树
4.满二叉树的特点:
1)每一层上的节点数都是最大节点数,即各层为1,2,4,8,16 .... (2^k-1)
2)一棵有n个节点的二叉树,按满二叉树的编号方式对它进行编号,若树中所有节点和满二叉树1~n编号完全一致,则称该树为
完全二叉树
当完全二叉树最后一层的所有节点都是满的时候,这棵完全二叉树就变成了
满二叉树
3)
二叉树的性质:
1.二叉树第i层上的节点数目至多为(2^i-1)
2.深度为k的二叉树,至多有(2^k-1)个节点
3.在任何一棵二叉树中,如果其叶子节点的数量为n0,度为2的子节电数量为n2,则n0=n2+1
4.具有n个节点的完全二叉树的深度为(log2n+1)
5.要实现二叉树这种数据结构,有以下3种选择
1).
顺序存储
-采用数组来记录二叉树的所有节点
采用顺序存储二叉树会造成一定的空间浪费
不管是遍历树中节点,还是查找树中的节点,都可以非常高效地完成,唯一缺点就是空间浪费很大
2).
二叉链表存储 -每个节点保留一个left,right域
二叉链表存储的思想是让每个节点都能记住它的左右两个子节点
3).
三叉链表存储
-每个节点保留一个left,right,parent域
为了克服二叉链表存储方式中访问父节点不方便的问题,可以将二叉链表扩展成三叉链表
三叉链表存储的思
想是让每个节点不仅记住它的左右两个子节点,还记住它的父节点
6.遍历二叉树:
对二叉树的遍历过程就是将非线性结构的二叉树中的节点排列在一个线性序列上的过程。
1).如果采用顺序结构来保存二叉树,直接遍历底层数组即可
2).如果采用链表来保存二叉树的节点,则有两类遍历方式:
a)深度优先遍历
这种遍历算法将先访问到树中最深层次的节点
如果L,D,R表示左子树,根,右子树。习惯上总是必须先遍历左子树,然后遍历右子树,根据遍历根节点的顺序不同,算法可以分为:
DLR:
先序遍历
根-左-右
LDR:
中序遍历
左-根-右
LRD:
后序遍历
左-右-根
因为二叉树的定义本身就有"递归性",所以深度优先遍历时能非常方便地利用递归来遍历每个节点:
一棵非空二叉树由树根,左子树,右子树组成,依次遍历这3部分,就可以遍历整个二叉树
b)广度优先遍历
这种遍历算法将逐层访问到每层的节点,先访问到根,然后访问第二层的节点...依此类推
也被称为 按层遍历
可以借助具有FIFO特征的列队来实现
package knowledge; import java.util.ArrayDeque; /** * 三叉链表存储二叉树 * @author [email protected] * @param <E> */ public class ThreeLinkBinTree<E> { public static class TreeNode{ Object data; TreeNode left; TreeNode right; TreeNode parent; public TreeNode(){ } public TreeNode(Object data){ this.data = data; } public TreeNode(Object data, TreeNode left, TreeNode right, TreeNode parent){ this.data = data; this.left = left; this.right = right; this.parent = parent; } } //根节点 private static TreeNode rootNode; //以默认的构造器创建二叉树 public ThreeLinkBinTree(){ this.rootNode = new TreeNode(); } //指定根元素创建二叉树 public ThreeLinkBinTree(E data){ this.rootNode = new TreeNode(data); } /** * 为指定节点添加子节点 * @param parent 需要添加子节点的父节点 * @param data 新的子节点数据 * @param isLeft 是否为左节点 * @return 新增节点 */ public TreeNode addNote(TreeNode parent, E data, boolean isLeft){ if(parent==null){ throw new RuntimeException(parent + "节点为null,无法添加子节点"); } if(isLeft && parent.left!=null){ throw new RuntimeException(parent + "节点已有左子节点,无法添加左子节点"); } if(!isLeft && parent.right!=null){ throw new RuntimeException(parent + "节点已有右子节点,无法添加右子节点"); } TreeNode newNode = new TreeNode(data); if(isLeft){ parent.left = newNode; }else{ parent.right = newNode; } newNode.parent = parent; return newNode; } //是否为空 public boolean isEmpty(){ return rootNode.data==null; } //返回根节点 public TreeNode rootNode(){ if(isEmpty()){ throw new RuntimeException("树为空,无法访问根节点"); } return rootNode; } //返回指定节点(非根节点)的父节点 public E parent(TreeNode node){ if(node==null){ throw new RuntimeException("节点为null,无法访问其父节点"); } return (E) node.parent.data; } //返回指定节点(非叶子)的左子节点 public E leftChild(TreeNode parent){ if(parent==null){ throw new RuntimeException("节点为null, 无法添加子节点"); } return (E) (parent.left==null? null : parent.left.data); } public E rightChild(TreeNode parent){ if(parent==null){ throw new RuntimeException("节点为null, 无法添加子节点"); } return (E) (parent.right==null ? null : parent.right.data); } //返回二叉树的深度 public int deep(){ return deep(rootNode); } //每棵子树的深度为其所有子树的最大深度+1 private int deep(TreeNode node){ if(node==null){ return 0; } if(node.left==null && node.right==null){ return 1; }else{ int leftDeep = deep(node.left); int rightDeep = deep(node.right); int max = leftDeep>rightDeep ? leftDeep : rightDeep; return max+1; } } //深度优先遍历-先序遍历 public List<TreeNode> preIterator(){ return preIterator(rootNode); } private List<TreeNode> preIterator(TreeNode node){ List<TreeNode> list = new ArrayList<TreeNode>(); list.add(node); if(node.left!=null){ list.addAll(preIterator(node.left)); } if(node.right!=null){ list.addAll(preIterator(node.right)); } return list; } //深度优先遍历-中序遍历 public List<TreeNode> inIterator(){ return inIterator(rootNode); } private List<TreeNode> inIterator(TreeNode node) { List<TreeNode> list = new ArrayList<TreeNode>(); if(node.left!=null){ list.addAll(inIterator(node.left)); } list.add(node); if(node.right!=null){ list.addAll(inIterator(node.right)); } return list; } //深度优先遍历-后序遍历 public List<TreeNode> postIterator(){ return postIterator(rootNode); } private List<TreeNode> postIterator(TreeNode node){ List<TreeNode> list = new ArrayList<TreeNode>(); if(node.left!=null){ list.addAll(postIterator(node.left)); } if(node.right!=null){ list.addAll(postIterator(node.right)); } list.add(node); return list; } //广度优先遍历 public List<TreeNode> breadthFirst(){ Queue<TreeNode> queue = new ArrayDeque<TreeNode>(); List<TreeNode> list = new ArrayList<TreeNode>(); //将根元素加入"列队" if(rootNode!=null){ queue.offer(rootNode); } while(!queue.isEmpty()){ //将该列队的队尾元素添加到list中 list.add(queue.peek()); TreeNode p = queue.poll(); if(p.left!=null){ queue.offer(p.left); } if(p.right!=null){ queue.offer(p.right); } } return list; } public static void main(String[] args){ Queue<TreeNode> queue = new ArrayDeque<TreeNode>(); List<TreeNode> list = new ArrayList<TreeNode>(); //将根元素加入"列队" if(rootNode!=null){ queue.offer(rootNode); } while(!queue.isEmpty()){ //将该列队的队尾元素添加到list中 list.add(queue.peek()); TreeNode p = queue.poll(); if(p.left!=null){ queue.offer(p.left); } if(p.right!=null){ queue.offer(p.right); } } System.out.println(list); } }
7.哈夫曼树:
也被称为
最优二叉树
是一类
带权路径最短的二叉树
哈夫曼树是二叉树的一种应用,在信息检索中很常用
1)节点之间的路径长度:
从一个节点到另一个节点之间的分支数量,称为两个节点之间的路径长度
树的
路径长度:
从
根节点到树中
每一个节点的路径长度
之和
节点的
带权路径长度:
从
该节点到
根节点
之间的
路径长度与节点上权的
乘积
树的带权路径长度:
树中所有叶子节点的带权路径长度
之和
2)带权路径最小的二叉树被称为哈夫曼树和最优二叉树
3)对于具有n个叶子节点的哈夫曼树,一共需要(2*n-1)个节点
4)哈夫曼编码:
解决
报文编码问题
假设需要把一个字符串"abcdabcaba"进行编码,将它转换成唯一的二进制码,但要求转换出来的二进制码的长度最小。
package knowledge; import java.util.ArrayDeque; /** * 哈夫曼树 * @author [email protected] */ public class HuffmanTree { public static class Node<E>{ E data; double weight; Node leftChild; Node rightChild; public Node(E data, double weight){ this.data = data; this.weight = weight; } public String toString(){ return "Node[data=" + data + ", weight=" + weight + "]"; } } private static Node createTree(List<Node> nodes) { while(nodes.size()>1){ quickSort(nodes); Node left = nodes.get(nodes.size()-1); Node right = nodes.get(nodes.size()-2); Node parent = new Node(null, left.weight + right.weight); parent.leftChild = left; parent.rightChild = right; nodes.remove(nodes.size()-1); nodes.remove(nodes.size()-1); } return nodes.get(0); } private static void quickSort(List<Node> nodes) { subSort(nodes, 0, nodes.size()-1); } private static void subSort(List<Node> nodes, int start, int end) { if(start<end){ Node base = nodes.get(start); int i = start; int j = end+1; while(true){ while(i<end && nodes.get(+i).weight>=base.weight); while(j>start && nodes.get(--j).weight<=base.weight); if(i<j){ swap(nodes, i, j); }else{ break; } } swap(nodes, start, j); subSort(nodes, start, j-1); subSort(nodes, j+1, end); } } private static void swap(List<Node> nodes, int i, int j) { Node tmp; tmp = nodes.get(i); nodes.set(i, nodes.get(i)); nodes.set(j, tmp); } private static List<Node> breadthFirst(Node root) { Queue<Node> queue = new ArrayDeque<Node>(); List<Node> list = new ArrayList<Node>(); if(root!=null){ queue.offer(root); } while(!queue.isEmpty()){ list.add(queue.peek()); Node p = queue.poll(); if(p.leftChild!=null){ queue.offer(p.leftChild); } if(p.rightChild!=null){ queue.offer(p.rightChild); } } return list; } public static void main(String[] args){ List<Node> nodes = new ArrayList<Node>(); nodes.add(new Node("A", 40.0)); nodes.add(new Node("B", 8.0)); nodes.add(new Node("C", 10.0)); nodes.add(new Node("D", 30.0)); nodes.add(new Node("E", 10.0)); nodes.add(new Node("F", 2.0)); Node root = HuffmanTree.createTree(nodes); System.out.println(breadthFirst(root)); } }
8.排序二叉树
通过它可以非常方便地对树中所有节点进行排序和检索
排序二叉树要么是一棵空二叉树,要么是具有以下性质的二叉树
a)若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值
b)若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值
c)它的左/右子树也分别为排序二叉树
9.红黑树
它是一个更高效的检索二叉树,因此常常用来实现关联数组
JDK提供的集合类TreeMap,本身就是一个红黑树的实现
红黑树在原有的排序二叉树上增加了以下几个要求:
1)每个节点要么是红色,要么是黑色
2)根节点永远是黑色的
3)所有的叶节点都是空节点(null),并且是黑色的,被称为黑哨兵
4)每个红色节点的两个子节点都是黑色
5)从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点
树的黑色高度
从根节点到叶子节点的路径中,包含的黑色节点数
对于给定的黑色高度为N的红黑树,从根到叶子节点的最短路径长度为(N-1),最长路径长度为2*(N-1)
插入操作
a)以排序二叉树的方法插入新节点,并将它设为红色
b)进行颜色调换和树旋转(5种情形)
删除操作
a)以排序二叉树的方法删除指定节点
b)进行颜色调换和树旋转,使之满足红黑树特征
package knowledge; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.Queue; /** * 红黑树 * @author [email protected] */ public class RedBlackTree<T extends Comparable> { private static final boolean RED = false; private static final boolean BLACK = true; static class Node{ Object data; Node parent; Node left; Node right; //默认节点颜色为黑色 boolean color = BLACK; public Node(Object data, Node parent, Node left, Node right){ this.data = data; this.parent = parent; this.left = left; this.right = right; } public String toString(){ return "[data=" + data + ", color=" + color + "]"; } public boolean equals(Object obj){ if(this==obj){ return true; } if(obj.getClass()==Node.class){ Node target = (Node) obj; return data.equals(target.data) && parent==target.parent && left==target.left && right==target.right && color==target.color; } return false; } } private Node rootNode; //两个构造器用于创建排序二叉树 public RedBlackTree(){ rootNode = null; } public RedBlackTree(T element){ rootNode = new Node(element, null, null, null); } //添加节点 public void add(T element){ if(rootNode==null){ rootNode = new Node(element, null, null, null); }else{ Node current = rootNode; Node parent = null; int cmp = 0; do{ parent = current; cmp = element.compareTo(current.data); //如果新节点的值大于当前节点的值 if(cmp>0){ //以右节点作为当前节点 current = current.right; }else{ //如果新节点的值小于当前节点的值,以左节点作为当前节 current = current.left; } }while(current!=null); //创建新节点 Node newNode = new Node(element, parent, null, null); //如果新节点的值大于父节点的值 if(cmp>0){ //新节点作为父节点的右节点 parent.right = newNode; }else{ //如果新节的值小于父节点的值,新节点作为父节点的左节点 parent.left = newNode; } //维护红黑树 fixAfterInsertion(newNode); } } //插入节点后修复红黑树 private void fixAfterInsertion(Node newNode) { newNode.color = RED; //循环直到newNode的父节点不是根,而且newNode的父节点不是红色 while(newNode!=null && newNode!=rootNode && newNode.parent.color!=RED){ //如果newNode的父节点是其父节点的左子节点 if(parentOf(newNode)==leftOf(parentOf(parentOf(newNode)))){ //获取newNode的父节点的兄弟节点 Node bro = rightOf(parentOf(newNode)); //如果newNode的父亲节点的兄弟节点是红色 if(colorOf(bro)==RED){ //将newNode的父节点设为黑色 setColor(parentOf(newNode), BLACK); //将newNode的父节点的兄弟节点设为黑色 setColor(bro, BLACK); //将newNode的父节点的父节点设为红色 setColor(parentOf(parentOf(newNode)), RED); newNode = parentOf(parentOf(newNode)); }else{//如果newNode的父节点的兄弟节点是黑色 //如果newNode是其父节点的右子节点 if(newNode==rightOf(parentOf(newNode))){ newNode = parentOf(newNode); rotateLeft(newNode); } //把newNode的父节点设为黑色 setColor(parentOf(newNode), BLACK); //把newNode的父节点的父节点设为红色 setColor(parentOf(parentOf(newNode)), RED); rotateRight(parentOf(parentOf(newNode))); } }else{//如果newNode的父亲节点是其父节点的右子节点 //获取newNode的父亲节点的父亲节点的兄弟节点 Node bro = leftOf(parentOf(parentOf(newNode))); //如果newNode的父亲节点的兄弟节点是红色 if(colorOf(bro)==RED){ //将newNode的父节点设为黑色 setColor(parentOf(newNode), BLACK); //将newNode的父节点的兄弟节点设为黑色 setColor(bro, BLACK); //将newNode的父节点的父节点设为红色 setColor(parentOf(parentOf(newNode)), RED); //将newNode设为newNode的父节点的节点 newNode = parentOf(parentOf(newNode)); }else{//如果newNode的父节点的兄弟节点是黑色 //如果newNode是其父节点的左子节点 if(newNode==leftOf(parentOf(newNode))){ //将newNode的父节点设为newNode newNode = parentOf(newNode); rotateRight(newNode); } //把newNode的父节点设为黑色 setColor(parentOf(newNode), BLACK); //把newNode的父节点的父节点设为红色 setColor(parentOf(parentOf(newNode)), RED); rotateLeft(parentOf(parentOf(newNode))); } } } //将根节点设为黑色 rootNode.color = BLACK; } private Node parentOf(Node node) { return node==null ? null : node.parent; } private Node leftOf(Node node){ return node==null ? null : node.left; } private Node rightOf(Node node){ return node==null ? null : node.right; } private boolean colorOf(Node node){ return node==null? BLACK : RED; } private void setColor(Node node, boolean bv){ if(node!=null){ node.color = bv; } } /** * 执行如下转换 * node r * r node * q q * @param node */ public void rotateLeft(Node node){ if(node!=null){ Node rn = node.right; Node q = rn.left; node.right = q; if(q!=null){ q.parent = node; } rn.parent = node.parent; if(node.parent==null){ rootNode = rn; }else if(node.parent.left==node){ node.parent.left = rn; }else{ node.parent.right = rn; } rn.left = node; node.parent = rn; } } /** * 执行如下转换 * node l * l node * q q * @param node */ public void rotateRight(Node node){ if(node!=null){ Node ln = node.left; Node q = ln.right; node.left = q; if(q!=null){ q.parent = node; } ln.parent = node.parent; if(node.parent==null){ rootNode = ln; }else if(node.parent.left==node){ node.parent.left = ln; }else{ node.parent.right = ln; } ln.right = node; node.parent = ln; } } //删除节点 public void remove(T element){ //获取要删除的节点 Node target = getNode(element); //如果被删除的节点的左子树/右子树都不为空 if(target.left!=null && target.right!=null){ //找到target节点中序遍历的前一个节点 //s用于保存target节点中的左子树中最大的节点 Node s = target.left; //搜索target节点的左子树中最大的节点 while(s.right!=null){ s = s.right; } //用s节点来代替p节点 target.data = s.data; target = s; } //开始修复它的替换节点,如果该替换节点不为null Node replacement = target.left!=null ? target.left : target.right; if(replacement!=null){ //让replacement的parent指向target的parent replacement.parent = target.parent; //如果target的parent为null,表明target本身就是根节点 if(target.parent==null){ rootNode = replacement; }else if(target==target.parent.left){//如果target是其父节点的左子节点 //让target的父节点left指向replacement target.parent.left = replacement; }else{ //让target的父节点right指向replacement target.parent.right = replacement; } //彻底删除target节点 target.left = target.right = target.parent = null; if(target.color = BLACK){ fixAfterDeletion(replacement); } }else if(target.parent==null){ rootNode = null; }else{ //target没有子节点,把它当成虚的替换节点 //修复红黑树 if(target.color==BLACK){ fixAfterDeletion(target); } if(target.parent!=null){ //如果target是其父节点的左子节点 if(target==target.parent.left){ // target.parent.left = null; }else if(target==target.parent.right){ target.parent.right = null; } target.parent = null; } } } //根据给定的值搜索节点 private Node getNode(T element) { //从根节点开始搜索 Node p = rootNode; while(p!=null){ int cmp = element.compareTo(p.data); //如果搜索的值小于当前p节点的值 if(cmp<0){ //向左子树搜索 p = p.left; }else if(cmp>0){ //向右子树搜索 p = p.right; }else{ return p; } } return null; } //删除节点后修复红黑树 private void fixAfterDeletion(Node node) { //直到node不是根节点,且node的颜色是黑色 while(node!=rootNode && colorOf(node)==BLACK){ //如果node是其父节点的左子节点 if(node==leftOf(parentOf(node))){ //获取node节点父节点的兄弟节点 Node bro = rightOf(parentOf(node)); //如果bro节点是红色 if(colorOf(bro)==RED){ //将bro节点设为黑色 setColor(bro, BLACK); //将bro的父节点设为红色 setColor(parentOf(node), RED); rotateLeft(parentOf(node)); //再次将bro设为node的父节点的右子节点 bro = rightOf(parentOf(node)); } //如果node的两个子节点都是黑色 if(colorOf(leftOf(node))==BLACK && colorOf(rightOf(bro))==BLACK){ setColor(bro, RED); node = parentOf(node); }else{ if(colorOf(rightOf(bro))==BLACK){ setColor(leftOf(bro), BLACK); setColor(bro, RED); rotateRight(bro); bro = rightOf(parentOf(node)); } setColor(bro, colorOf(parentOf(node))); setColor(parentOf(node), BLACK); setColor(rightOf(node), BLACK); rotateLeft(parentOf(node)); node = rootNode; } }else{ Node bro = leftOf(parentOf(node)); if(colorOf(bro)==RED){ setColor(bro, BLACK); setColor(parentOf(node), RED); rotateRight(parentOf(node)); bro = leftOf(parentOf(node)); } if(colorOf(rightOf(node))==BLACK && colorOf(leftOf(node))==BLACK){ setColor(bro, RED); node = parentOf(node); }else{ if(colorOf(leftOf(bro))==BLACK){ setColor(rightOf(bro), BLACK); setColor(bro, RED); rotateLeft(bro); bro = leftOf(parentOf(node)); } setColor(bro, colorOf(parentOf(node))); setColor(parentOf(node), BLACK); setColor(leftOf(bro), BLACK); rotateRight(parentOf(node)); node = rootNode; } } } setColor(node, BLACK); } //实现中序遍历 public List<Node> inIterator(){ return inInterator(rootNode); } private List<Node> inInterator(Node rootNode) { List<Node> list = new ArrayList<Node>(); //递归处理左子树 if(rootNode.left!=null){ list.addAll(inInterator(rootNode.left)); } //处理根节点 list.add(rootNode); //递归处理右子树 if(rootNode.right!=null){ list.addAll(inInterator(rootNode.right)); } return list; } //广度优先遍历 public List<Node> breadthFirst(){ Queue<Node> queue = new ArrayDeque<Node>(); List<Node> list = new ArrayList<Node>(); if(rootNode!=null){ queue.offer(rootNode); } while(!queue.isEmpty()){ list.add(queue.peek()); Node p = queue.poll(); if(p.left!=null){ queue.offer(p.left); } if(p.right!=null){ queue.offer(p.right); } } return list; } }