参考博文
树(tree
)是被称为结点(node
)的实体的集合。结点通过边(edge
)连接。每个结点都包含值或数据(value/date
),并且每结节点可能有也可能没有子结点。
树的首结点叫根结点(即root
结点)。如果这个根结点和其他结点所连接,那么根结点是父结点(parent node
,与根结点连接的是子结点(child node
)。
所有的结点都通过边(edge
)连接。它是树中很重要得一个概念,因为它负责管理节点之间的关系。
叶子结点(leaves
)是树末端,它们没有子结点。
树的高度(height
)和深度(depth
)
class TreeNode{
object element;
TreeNode firstChild;
TreeNode nextchild;
}
并不是将所有的子节点都指向父节点,因为每个节点的子节点可能很多而且未知,这样很难实现而且浪费空间。
我们采用上面的方式,父节点只记录第一个子节点。再子节点之间做链表来记录
树的结构用到的很少,我们常用到的是二叉树
在计算机科学领域,二叉树是一种树形数据结构,它的每个节点最多有两个孩子,被叫作左孩子和右孩” — Wikipedia
class BinaryTree {
public BinaryTree left; //左节点
public BinaryTree right; //右节点
public String data; //树的内容
}
简称DFS(Depth-First Search)
DFS又分为前序、中序、后序
前序:
/**
* 前序遍历
*
* @param node
*/
public static void preOrder(BinaryTree node) {
if (node != null) {
System.out.println(node.data);
if (node.left != null) {
node.left.preOrder(node.left);
}
if (node.right != null) {
node.right.preOrder(node.right);
}
}
}
中序:
代码实现:
/**
* 中序遍历
*
* @param node
*/
public static void inOrder(BinaryTree node) {
if (node != null) {
if (node.left != null) {
node.left.inOrder(node.left);
}
System.out.println(node.data);
if (node.right != null) {
node.right.inOrder(node.right);
}
}
}
后序:
/**
* 后序遍历
*
* @param node
*/
public static void postOrder(BinaryTree node) {
if (node != null) {
if (node.left != null) {
node.left.postOrder(node.left);
}
if (node.right != null) {
node.right.postOrder(node.right);
}
System.out.println(node.data);
}
}
/**
* 广度排序
*
* @param node
*/
public static void bfsOrder(BinaryTree node) {
if (node != null) {
Queue queue = new ArrayDeque();
queue.add(node);
while (!queue.isEmpty()) {
BinaryTree current_node = queue.poll();
System.out.println(current_node.data);
if (current_node.left != null) {
queue.add(current_node.left);
}
if (current_node.right != null) {
queue.add(current_node.right);
}
}
}
}
二叉查找树有时候被称为二叉有序树或二叉排序树,二叉搜索树的值存储在有序的顺序中,因此,查找表和其他的操作可以使用折半查找原理。——Wikipedia
二叉查找树中一个节点的值大于其左子树所有节点,大于右子树所有节点
代码实现:
/**
* 插入树
*
* @param node
* @param value
*/
public void insertNode(BinaryTree node, Integer value) {
if (node != null) {
if (value <= Integer.valueOf(node.data) && node.left != null) {
node.left.insertNode(node.left, value);
} else if (value <= Integer.valueOf(node.data)) {
node.left = new BinaryTree(String.valueOf(value));
} else if (value > Integer.valueOf(node.data) && node.right != null) {
node.right.insertNode(node.right, value);
} else {
node.right = new BinaryTree(String.valueOf(value));
}
}
}
看起来很简单。
该算法的强大之处是其递归部分,即第9行和第13行。这两行代码均调用 insertNode 方法,并分别为其左结点和右结点使用它。第11行和第15行则在子结点处插入新结点。
代码实现:
public boolean findNode(BinaryTree node, Integer value) {
if (node != null) {
if (value < Integer.valueOf(node.data) && node.left != null) {
return node.left.findNode(node.left, value);
}
if (value > Integer.valueOf(node.data) && node.right != null) {
return node.right.findNode(node.right, value);
}
return value == Integer.valueOf(node.data);
//returen true;
}
return false;
}
代码实现:
/**
* 删除节点
* @param node
* @param value
* @param parent
* @return
*/
public boolean removeNode(BinaryTree node, Integer value, BinaryTree parent) {
if (node != null) {
if (value < Integer.valueOf(node.data) && node.left != null) {
return node.left.removeNode(node.left, value, node);
} else if (value < Integer.valueOf(node.data)) {
return false;
} else if (value > Integer.valueOf(node.data) && node.right != null) {
return node.right.removeNode(node.right, value, node);
} else if (value > Integer.valueOf(node.data)) {
return false;
} else {
if (node.left == null && node.right == null && node == parent.left) {
parent.left = null;
node.clearNode(node);
} else if (node.left == null && node.right == null && node == parent.right) {
parent.right = null;
node.clearNode(node);
} else if (node.left != null && node.right == null && node == parent.left) {
parent.left = node.left;
node.clearNode(node);
} else if (node.left != null && node.right == null && node == parent.right) {
parent.right = node.left;
node.clearNode(node);
} else if (node.right != null && node.left == null && node == parent.left) {
parent.left = node.right;
node.clearNode(node);
} else if (node.right != null && node.left == null && node == parent.right) {
parent.right = node.right;
node.clearNode(node);
} else {
node.data=String.valueOf(node.right.findMinValue(node.right));
node.right.removeNode(node.right,Integer.valueOf(node.right.data),node);
}
return true;
}
}
return false;
}
定义:每个节点的左子树和右子树的高度最多差一的二叉查找树。
当不满足定义时,需要使用平衡方法使其满足,也就是平衡一棵树。我们做的只是一次插入或者删除的平衡。
代码实现
private static class AvlNode{
AvlNode(AnyType theElement)
{this(theElement,null,null);}
AvlNode(AnyType theElement,AvlNode lt,AvlNode rt)
{element =theElement;left = lt;right=rt;height=0;}
AnyType element;
AvlNode left;
AvlNode right;
int height;
}
private int height(AvlNode t){
return t=null?-1:t.height;
}
单旋转本质就是把老子节点作为新根节点上,然后把老跟节点作为新跟节点的子节点。
单旋转又分为左旋转和右旋转,这是根据孙子节点和根节点的大小区分的,如果孙子节点比根节点小,那么不平衡的点一定是在跟节点的左节点。这时候把左子节点作为跟节点,把老跟节点作为他的右节点,把老左节点的右子树作为新右节点的左子树,这样似乎就是以左子节点为轴,将根节点顺时针左旋转,右旋转也是相同的道理。
上面这段话是对《数据结构与算法分析》(java)第四章第四小节的理解,书中有图。这里嫌麻烦就不画了。
左旋转代码实现
private AvlNode rotateLeft(AvlNode root){
AvlNode newRoot=root.left;
root.left=newRoot.right;
newRoot.right=root;
root.height=Math.max(height(root.left),height(root.right))+1;
newRoot.height=Math.max(height(newRoot.left),root.height)+1;
return newRoot;
}
右旋同理
双旋转其实就是做两次单旋转。有时候一次单旋转不能解决问题,需要使用两次,使用情况下面说。
双旋转又分为左右旋转和右左旋转,就是根据两次单旋转的方向。
右左旋转代码实现
private AvlNode doubleRotateRightleft(AvlNode root){
root.left=rotateRight(root.left);
return rotateLeft(root);
}
如果根节点的左子树的高度比右子树的高度大于1且根节点左节点的左节点大于右节点的高度
使用左旋转
或者根节点的右子树的高度比左子树的高度大于1且根节点右节点的右节点大于左节点的高度时,
使用右旋转。
如果根节点的左子树的高度比右子树的高度大于1且根节点左节点的左节点小于右节点的高度
使用右左旋转
或者根节点的右子树的高度比左子树的高度大于1且根节点右节点的右节点小于左节点的高度时,
使用左右旋转。
具体原因可见《数据结构与算法分析》(java)第四章第四小节
由上面的搜索实现可知。搜索的时间复杂度为O(d),d为数据节点深度。
经过一系列计算d的平均值=logN,n为数据整数。
具体计算可见《数据结构与算法分析》(java)第四章第三小节