什么是树
树这种数据结构在生活中非常常见,比如去图书馆找一本书,书是按照不同的分类来摆放的。比如电脑中的磁盘文件夹等等。使用树结构存储数据后,会出奇的高效。见名知意,树这种数据结构就像一个倒着的树一样,也会有树根,树的枝杈,还有树叶。
二叉树名词解释
- 根节点:最上面节点叫根节点,根节点没有父节点,一个二叉树只存在一个根节点
- 子节点:每个父节点下面的节点叫做子节点,分为左和右,可以有一个,也可以有两个,最多也只能有两个
- 叶子节点:如果一个节点没有任何的子节点了,那么就称作叶子节点
- 左右子树:二叉树具有天然的递归结构性值,根节点下每个子节点也可以组成自己的一颗二叉树。
二分搜索树
- 二分搜索树是一个二叉树
- 二分搜索树的每一个节点的值,都大于左子树所有节点的值,小于右子树所有节点的值
- 存储的元素必须是有可比较性,不一定是数字,也可以实现Compable接口比较细节
以下都属于二叉树。首先二叉树不是平衡二叉树,多个节点少个节点都无所谓,深度不一样也无所谓。
元素的插入操作
先用28跟41比大小,比41小,根据二叉树特性,比当前节点小的节点都会在左面,所以28往左走,跟22比,28比22大,大的就往右走和33比,28比33小,小的往左边走,一看正好33左面为空,有个地方能放进去,最后将28存储到33节点左孩子节点的位置。
public class BinarySearchTree<E extends Comparable>{
private class Node {
public E e;
public Node left,right;
public Node(E e) {
this.e = e;
this.left = null;
this.right = null;
}
}
private Node root; //根节点
private int size;
public boolean isEmpty() {
return size == 0;
}
public int getSize() {
return size;
}
public BinarySearchTree() {
root = null;
size = 0;
}
public void add(E e) {
//如果此时没有根节点
if(root == null) {
//添加元素到根节点
root = new Node(e);
size++;
}
else {
add(root, e);//代表有根节点
}
}
private void add(Node root,E e)
{
//1.最根本的问题。和当前节点比较,看往那边放,前提是子节点为空的情况下
if(e.equals(root.e)) {
return;
}
else if(e.compareTo(root.e) < 0 && root.left == null) {
root.left = new Node(e);
size++;
return;
}
else if(e.compareTo(root.e) > 0 && root.right == null) {
root.right = new Node(e);
size++;
return;
}
//2.把问题转换为更小的问题过程。这个add方法解决的就是根据当前节点比较,把元素插入合适的位置
if(e.compareTo(root.e) < 0) {//往左放
add(root.left,e);
}
else{ //相等一开始已经判断过了,这里只需要写else就可以了
add(root.right,e);
}
}
}
是否包含某个元素
private boolean contains(Node node,E e)
{
if(node == null)
return false;
if(e.compareTo(node.e) == 0) { //找到了对应的节点
return true;
}
else if (e.compareTo(node.e) > 0) { //大于当前节点就向右继续递归,否则向左递归继续找
return contains(node.right,e);
}
else{
return contains(node.left,e);
}
}
二分搜索树的遍历
每个元素都有三次遍历机会,分别是在调用两侧递归函数之前,在两侧递归函数中间,和在两侧递归函数之后。
前序遍历
public void preOrder()
{
preOrder(root);
}
private void preOrder(Node node) {
if(node == null)
return;
System.out.print(node.e + "---");
preOrder(node.left);
preOrder(node.right);
}
中序遍历
public void inOrder() {
inOrder(root);
}
private void inOrder(Node node) {
if(node == null)
return;
inOrder(node.left);
System.out.print(node.e + "---");
inOrder(node.right);
}
后序遍历
public void postOrder() {
postOrder(root);
}
private void postOrder(Node node) {
if(node == null)
return;
postOrder(node.left);
postOrder(node.right);
System.out.print(node.e + "---");
}
如下添加了这些元素之后,树的结构应该是这样的
int[] arr = {12,23,45,6,8,3,21};
for(int x = 0; x < arr.length; x++)
{
tree.add(arr\[x\]);
}
tree.preOrder();
//--------------------------------
12
/ \
6 23
/ \ / \
3 8 21 45
前序遍历:12 - 6 -3 - 8 - 23 - 21 - 45
中序遍历:3 - 6 - 8 - 12 - 21 - 23 - 45
后序遍历:3 - 8 - 6 - 21 - 45 - 23 -12
查找最小值和最大值
因为二分查找树的特性,最小值一定在最左侧,如果一个节点的左子节点为null了,那么这个节点就是最小值。相反最大值在右侧。
//找到最大值和最小值
public E getMin()
{
Node min = getMin(root);
return min.e;
}
private Node getMin(Node node)
{
if(node.left == null)
return node;
return getMin(node.left);
}
//找到最大值和最小值
public E getMax()
{
Node min = getMax(root);
return min.e;
}
private Node getMax(Node node)
{
if(node.right == null)
return node;
return getMax(node.right);
}
删除最大和最小值
和查找最大值最小值思想差不多,只不过要注意下面这种情况。16的左子节点为空,所以这个最小值是16,删除16之后,要把16的右子节点和28关联上。也就是递归的时候,要把当前节点的左孩子节点和删除最值后返回的节点关联上。
//删除最小元素
public E removeMin()
{
E min = getMin();
root = removeMin(root);
return min;
}
private Node removeMin(Node node)
{
if(node.left == null) { //这里如果node的右子节点为空,逻辑也是成立的
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
//删除最大元素
public E removeMax()
{
E max = getMax();
root = removeMax(root);
return max;
}
private Node removeMax(Node node)
{
if(node.right == null) { //这里如果node的右子节点为空,逻辑也是成立的
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
}
node.right = removeMax(node.right);
return node;
}
删除任意节点
比如要删除58这个节点,如果58没有左孩子或者右孩子其中一个节点,那么逻辑是和删除最大最小值是一样的,假设没有50这个左孩子节点,那么删除了58之后,将他的右侧节点60返回就好了。但如果58两侧都有节点,删除了58之后就面临着谁来当这个"老大"的问题,这个老大有两个人可以当,一个是59,一个是53。也就是右子树的最小值或者左子树的最大值。
知道这个节点以后,就可以维护这个节点的关系,最后给上层返回这个节点即可。那么维护关系其实就是让59的左孩子节点变成当前58的左孩子节点,让59的右孩子节点变成除了59以外的新节点。最后把58的关联关系干掉即可。
public void deleteNode(E e)
{
root = deleteNode(root,e);
}
private Node deleteNode(Node node, E e) {
if(node == null)
return null;
if(e.compareTo(node.e) > 0) {
node.right = deleteNode(node.right,e);
return node;
}
else if(e.compareTo(node.e) < 0) {
node.left = deleteNode(node.left,e);
return node;
}
else{ //此时节点相等
//左子树为空
if(node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
}
//左子树为空
if(node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
}
//1.找到待删除节点的接班人,接班人就是当前右子节点中最小的那个
//2.用接班人顶替待删除节点
Node successor = getMin(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。