树
- 树的实现
实现树的一种方法是在每一个节点除数据外还有一些链,使得该节点的每一个儿子都有一个链指向他,但是该节点儿子的数量是不知道的,可以采用如下的方式:
public class TreeNode{
Object element;
TreeNode firstChild;//该节点的第一个儿子
TreeNode nextSibling;//该节点的兄弟节点
}
节点 ni 的深度为从根到 ni 的唯一路径的长;节点 ni 的高度是节点 ni 到一片树叶的最长路径的长。
二叉树
二叉树的特点是每个节点都不能有多于两个儿子,其平均深度为
O(N1/2)
。
1. 二叉树的实现
二叉树的节点最多包含两个节点,因而可以保存直接链接到他们的链。
public class BinaryNode{
Object element;
BinaryNode left;
BinaryNode right;
}
2.二叉树的例子-表达式树
表达式树的叶子节点是操作数、变量或者常数,而其他节点为操作符。
利用栈将后缀表达式转换为表达式树遵从以下几个原则:
- 如果读入的是操作数,建立单节点树,压入栈中。
- 如果读入的是操作符,那么从栈中弹出两棵树T1和T2并形成新的树,根节点为操作符,左子树为T2,右子树为T1。
二叉查找树
二叉查找树与二叉树的区别在于,树中的每个节点node,其左子树中所有项的值小于node节点的值;其右子树中所有项的值大于node节点的值。
1.二叉查找树的实现
这里实现了二叉查找树类的框架,包括节点的插入,寻找,删除,遍历等功能
public class BinarySearchTree<T extends Comparable<? super T>> {
private static class BinaryNode<T>{
T element;
BinaryNode<T> left;
BinaryNode<T> right;
public BinaryNode(T element) {
this(element, null, null);
// TODO Auto-generated constructor stub
}
public BinaryNode(T element,BinaryNode<T> left,BinaryNode<T> right){
this.element = element;
this.left = left;
this.right = right;
}
}
private BinaryNode<T> root;
public BinarySearchTree(){
root = null;
}
public void insert(T element){
this.insert(root, element);
}
private BinaryNode<T> insert(BinaryNode<T> node, T element){
/*2*/
}
public void makeEmpty(){
root = null;
}
public boolean isEmpty(){
return root == null;
}
public boolean contains(T element){
return this.contains(root, element);
}
private boolean contains(BinaryNode<T> node, T element){
/**/
}
public void remove(T element){
this.remove(root, element);
}
private BinaryNode<T> remove(BinaryNode<T> node,T element){
}
public T findMin(){
if(isEmpty()) throw new NullPointerException();
return findMin(root).element;
}
private BinaryNode<T> findMin(BinaryNode<T> node){
}
public void preOrderI(){
this.preOrderI(this.root);
}
private void preOrderI(BinaryNode<T> root){
}
public void preOrderT(){
this.preOrderT(this.root);
}
private void preOrderT(BinaryNode<T> root){
}
public void inOrderI(){
this.inOrderI(this.root);
}
private void inOrderI(BinaryNode<T> root){
}
public void inOrderT(){
this.inOrderT(this.root);
}
private void inOrderT(BinaryNode<T> root){
}
public void postOrderI(){
this.postOrderI(this.root);
}
private void postOrderI(BinaryNode<T> root){
}
public void postOrderT(){
this.postOrderT(this.root);
}
private void postOrderT(BinaryNode<T> root){
}
}
2.节点的插入
为了将X插入到树中,从root节点开始向下寻找,如果找到X,则更新节点或者不做任何处理;否则,将X插入到遍历路径上的最后一个节点上,代码如下:
private BinaryNode<T> insert(BinaryNode<T> node, T element){
/*2*/
if(node == null)
return new BinaryNode<T>(element,null,null);
int compareResult = element.compareTo(node.element);
if(compareResult < 0)
node.left = insert(node.left, element);
else if(compareResult >0)
node.right = insert(node.right, element);
else
;
return node;
}
3.contains方法
contains方法的实现类似于insert方法,同样是从root节点向下寻找,如果找到了目标节点,则返回TRUE;否则,继续遍历寻找下一节点。下面的实现方式采用了尾递归的方式,也可用while循环替代:
private boolean contains(BinaryNode<T> node, T element){
/*3*/
if(node == null)
return false;
int compareResult = element.compareTo(node.element);
if(compareResult < 0)
return contains(node.left,element);
else if(compareResult > 0)
return contains(node.right, element);
else
return true;
}
3.节点的删除remove方法
节点的删除分三种情况。如果目标节点是一个叶子节点,则可以直接删除;如果目标节点含有一个子节点,则直接删除该节点,并将该节点的父节点引用链指向子节点;如果目标节点a含有左子树b和右子树c,寻找右子树c中最小值节点替换目标节点(删除),然后删除右子树中的最小值节点即可
private BinaryNode<T> findMin(BinaryNode<T> node){
if(node == null)
return null;
if(node.left != null)
return findMin(node.left);
return node;
}
private BinaryNode<T> remove(BinaryNode<T> node,T element){
if(node == null)
return node;//未找到目标节点
int compareResult = element.compareTo(node.element);
if(compareResult < 0)
node.left = remove(node.left, element);
else if(compareResult > 0)
node.right = remove(node.right, element);
else if(node.left != null && node.right != null){
node.element = findMin(node.right).element;
node.right = remove(node.right, node.element);
}else {
node = (node.left != null)?node.left:node.right;
}
return node;
}
4.遍历
前序遍历(中左右):采用递归的方式或者借助栈来实现遍历的方式。
private void preOrderI(BinaryNode<T> root){
if(root == null)
return;
System.out.println(root.element);
preOrderI(root.left);
preOrderI(root.right);
}
private void preOrderT(BinaryNode<T> root){
if(root == null)
return;
Stack<BinaryNode<T>> stack = new Stack<>();
BinaryNode<T> cur = root;
while(cur != null && !stack.empty()){
if(cur != null){
System.out.println(cur.element);
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
cur = cur.right;
}
}
}
中序遍历(左中右):递归方式和循环遍历方式
private void inOrderI(BinaryNode<T> root){
if(root == null)
return;
inOrderI(root.left);
System.out.println(root.element);
inOrderI(root.right);
}
private void inOrderT(BinaryNode<T> root){
if(root == null)
return;
Stack<BinaryNode<T>> stack = new Stack<>();
BinaryNode<T> cur = root;
while(cur != null || !stack.empty()){
if(cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
System.out.println(cur.element);
cur = cur.right;
}
}
}
后序遍历(左右中):递归的实现方式比较简单;遍历的方式借助于两个栈,利用栈1实现中右左的遍历,并将遍历的元素添加到栈2中,然后将栈2中的元素顺序弹出并打印即可实现左右中的遍历。
栈 | 操作1 | 操作2 | 操作3 | 操作4 | 操作5 |
---|---|---|---|---|---|
stack | 1入 | 3入 | 3出 | 1出 | 2入 |
result | 1入 | 3入 | 无 | 无 | 2入 |
这样,在result栈中弹出的元素正好是231,正好满足后续遍历的规则。
private void postOrderI(BinaryNode<T> root){
if(root == null)
return;
postOrderI(root.left);
postOrderI(root.right);
System.out.println(root.element);
}
private void postOrderT(BinaryNode<T> root){
if(root == null)
return;
Stack<BinaryNode<T>> stack = new Stack<>();
Stack<BinaryNode<T>> result = new Stack<>();
BinaryNode<T> cur = root;
while(cur != null || !stack.empty()){
if(cur != null){
stack.push(cur);
result.push(cur);
cur = cur.right;
}else{
cur = stack.pop();
cur = cur.left;
}
}
while(!result.empty()){
System.out.println(result.pop().element);
}
}
AVL树
AVL树是带有平衡条件的二叉查找树,每个节点的左子树和右子树的高度最多差1(空树的高度为-1),树的深度为
O(logN)
。在高度为
h
的AVL树中,最小节点数
1. AVL树节点的表示
public class AVLNode<T extends Comparable<? super T>>{
public T element;
public int height;
public AVLNode leftNode;
public AVLNode rightNode;
}
2.AVL树的旋转
树中插入节点后,从插入点到根节点路径上的节点的平衡是有可能被改变,因此需要找到第一个不平衡的节点(即最深的点)旋转使其平衡。假定插入后需要重新平衡的节点叫做
α
,插入有可能发生在以下几种情况:
-
α
的左儿子的左子树 单旋
-
α
的左儿子的右子树 双旋
-
α
的右儿子的左子树 双旋
-
α
的右儿子的右子树 单旋
单旋转:以右旋为例,插入节点0后,造成节点4位置的左右子树不平衡,因此需要左旋。首先将节点4的左链指向节点3,然后将节点2的右链指向节点4,最后更新各节点的高度即可。
但是,如果是下图的情况,单旋是无法解决平衡问题的。插入节点3后导致节点4的左右子树高度不平衡,执行右旋之后节点2的左右子树依然不平衡。
第二次的插入是在不平衡点(4)的左节点(2)的右子树进行的,此时应该执行的是双旋操作。
双旋:以右左双旋为例,插入节点7之后造成节点4的左右子树高度不平衡,应先对节点4的右子树8执行右旋,如下图的前3副图,然后再对节点4执行左旋,最后更新节点的高度信息。
public class AvlTree<T extends Comparable<? super T>>{
private static class AvlNode<T>{
public AvlNode(T element) {
this(element, null, null);
// TODO Auto-generated constructor stub
}
public AvlNode(T element,AvlNode<T> left,AvlNode<T> right){
this.element = element;
this.left = left;
this.right = right;
this.height = 0;
}
T element;
AvlNode<T> right;
AvlNode<T> left;
int height;
}
private AvlNode<T> root;
private int height(AvlNode<T> node){
return node == null ? -1 : node.height;
}
public void insert(T element){
this.insert(root, element);
}
private AvlNode<T> insert(AvlNode<T> node,T element){
if(node == null)
return new AvlNode<T>(element);
int compareResult = element.compareTo(node.element);
if(compareResult < 0){
node.left = insert(node.left, element);
if(height(node.left) - height(node.right) == 2){
if(compare(element, node.left.element) < 0){//左儿子的左子树
node = roateWithLeftChild(node);
}else//左儿子的右子树
node = doubleWithLeftChild(node);
}
}else if(compareResult > 0){
node.right = insert(node.right, element);
if(height(node.right) - height(node.left) == 2){
if(compare(element, node.right.element) > 0){//右儿子的右子树
node = roateWithRightChild(node);
}else{//右儿子的左子树
node = doubleWithRightChild(node);
}
}
}else {//重复的节点
;
}
node.height = Math.max(height(node.left), height(node.right))+1;
return node;
}
private AvlNode<T> roateWithLeftChild(AvlNode<T> k2){
AvlNode<T> k1 = k2.left;
k2.left = k1.right;
k1.right = k2;
k1.height = Math.max(height(k1.left), height(k2.right));
k2.height = Math.max(height(k2.left),height(k2.right));
return k1;
}
private AvlNode<T> roateWithRightChild(AvlNode<T> k2){
AvlNode<T> k1 = k2.right;
k2.right = k1.left;
k1.left = k2;
k1.height = Math.max(height(k1.left), height(k1.right));
k2.height = Math.max(height(k2.left), height(k2.right));
return k1;
}
private AvlNode<T> doubleWithLeftChild(AvlNode<T> k3){
k3.left = roateWithRightChild(k3.left);
return roateWithLeftChild(k3);
}
private AvlNode<T> doubleWithRightChild(AvlNode<T> k3){
k3.right = roateWithLeftChild(k3.right);
return roateWithRightChild(k3);
}
private int compare(T val1,T val2){
return val1.compareTo(val2);
}
}