学习笔记:数据结构树
一、二叉树的优点:
二叉树结合了有序数组和链表的优点,在树中查找数据项和在有序数组中查找一样快,插入和删除数据项的速度和链表一样,克服了在数组中插入数据项慢的缺点,在链表中查找慢的缺点。
二、二叉搜索树
一个节点的左子节点的关键字值小于这个节点,右子节点的关键子值大于等于这个父节点。
三、实现二叉树
1.首先实现节点类
节点类包括数据值和对其它节点的引用。
class Node {
int data;
Node left;
Node right;
public Node() {
}
public Node(int data) {
this.data = data;
}
public Node(int data, Node left, Node right) {
this.data = data;
this.left = left;
this.right = right;
}
}
2.二叉树的实现
只需要维护一个根节点就行了。
3.二叉树的主要操作
查找方法:
- 维护一个指针,从根节点开始访问,判断节点的关键字与要寻找的数据是否大小相等
- 数据小于节点关键值,指针受访问节点的左子节点,反之大于,指针受访问节点的右子节点
- 如果指针最后指向null,说明已经到达树的末端,找不到;如果找到就退出循环
插入新节点(二叉搜索树):
- 维护两个指针,从根节点开始,一个指针指向被访问节点,另一个指向被访问的父节点
- 比较要插入的节点关键字与受访问的节点的关键字大小,数据小于节点关键值,指针受访问节点的左子节点,反之大于,指针受访问节点的右子节点
- 如果左子节点或右子节点为空,就在此位置插入新节点,即将受访问节点的一个引用指向新节点;如果不为空,就循环访问下去直到找到空节点。
数的遍历:可以按照特定的顺序访问树的每一个节点,常见的有中序遍历、前序遍历、后序遍历
中序遍历会使二叉搜索树按照关键字升序被访问到。
中序遍历的递归简单访问方法:
- 访问这个节点的左子节点
- 访问这个节点的关键字
- 访问这个节点的右子节点
类似的,前序遍历的顺序使根左右,后序遍历的顺序是左右根。
寻找最大值或最小值:
-最小值:从根节点开始访问,不断访问节点的左子节点,直到受访问的节点没有左子节点
-最大值:从根节点开始访问,不断访问节点的右子节点,直到受访问的节点没有右子节点
删除节点是二叉树中最复杂的操作,大致可以将要删除的节点分为三类
1.删除的节点没有子节点
-这种节点最好处理,只需要找到这个要删除的节点,改变其父节点的引用,同时要维护一个变量记录删除的节点是不是其父节点左子节点还是右子节点
-特殊情况要删除的节点是根节点,需要维护root
2.删除的节点只有一个子节点
-假如删除的节点有一个左子节点,处理操作和上面一样,找到这个要删除的节点,改变其父节点的引用,让这个父节点引用指向删除节点的左子节点。同样要维护一个变量记录删除的节点是不是其父节点左子节点还是右子节点
-特殊情况要删除的节点是根节点,需要维护root
-假如删除的节点有一个右子节点,处理方法类似
3.删除的节点有两个子节点
删除的节点如果有两个子节点,不是用它的子节点来代替它的位置,而是用它的中序后续来顶替它,就是它后面的序列值中最小的那个,分为两种情况
-
中序后续就是删除节点的右子节点
-找到这个要删除的节点,改变其父节点的引用,让这个父节点的子节点引用指向这个中序后续(就是删除节点的右子节点),然后这个中序后继的左子节点指向删除节点的左子节点。同时要维护一个变量记录删除的节点是不是其父节点左子节点还是右子节点
-特殊情况要删除的节点是根节点,需要维护root -
中序后续是删除节点的右子节点的左子树中的左子节点
-首先也是要找到这个要删除的节点
-其次,找到这个删除节点的中序后续是哪个节点:维护一个指针指向要删除节点的右子节点,访问这个节点的左子节点,直到受访问的左子节点没有左子节点。
-处理这个中序后续:让其父节点的左子节点指向中序后续的右子节点,中序后续的右子节点的引用指向删除节点的右子节点
-顶替删除节点:改变删除父节点的引用,让这个父节点的子节点引用指向这个中序后续节点,然后这个中序后继的左子节点指向删除节点的左子节点,这样就连上
public class BinaryTree {
//维护根
Node root;
public BinaryTree() {
}
//查找节点:查找效率取决于数的层数
public Node find(int key) {
//维护一个指针
Node cur = root;
while (cur.data != key) {
if (key < cur.data) {
cur = cur.left;
} else if(key > cur.data) {
cur = cur.right;
}
if (cur == null) { //找不到,表明节点不存在
return null;
}
}
return cur;
}
//插入一个节点
public boolean insert(int data) {
//创建一个新节点
Node newNode = new Node(data);
//判断是否要维护根指针
if (root == null) {
root = newNode;
return true;
}
//维护两个指针,寻找合适的插入位置
Node cur= root;
Node parent = null;
while (true) {
parent = cur;
if (data < cur.data) {
cur = cur.left;
if (cur == null) {
parent.left = newNode;
return true;
}
} else if (data >= cur.data) {
cur = cur.right;
if (cur == null) {
parent.right = newNode;
return true;
}
}
}
}
//数的遍历
public static void inorder(Node root) {
if (root != null) {
inorder(root.left);
System.out.print(root.data+" ");
inorder(root.right);
}
}
//查找最小值
public int minData() {
//维护一个指针
Node cur = root;
while (cur.left != null) {
cur = cur.left;
}
return cur.data;
}
//查找最大值
public int maxData() {
//维护一个指针
Node cur = root;
while (cur.right != null) {
cur =cur.right;
}
return cur.data;
}
//删除节点
public boolean delete(int key) {
//维护两个指针,一个指向要删除的节点,一个指向其父节点
Node parent = root;
Node cur = root;
//判断要删除的节点是左子节点还是右子节点
boolean isLeft = false;
//寻找节点
while (cur.data != key) {
parent = cur;
if (key < cur.data) {
isLeft = true;
cur = cur.left;
} else if (key > cur.data) {
isLeft = false;
cur = cur.right;
}
if (cur == null) {
return false;
}
} //找到要删除的节点后退出
//删除没有子节点的节点
if (cur.left == null && cur.right == null) {
//需要维护root指针
if (cur == null) {
root = null;
} else if (isLeft) { //要删除的节点是左子节点
parent.left = null;
} else if (!isLeft) { //要删除的节点是右子节点
parent.right = null;
}
}
//删除只有一个子节点的节点
//只有左子节点
else if (cur.right == null) {
//判断是否要维护root
if (cur == root) {
root = root.left;
} else if (isLeft) {
parent.left = cur.left;
} else if (!isLeft) {
parent.right = cur.left;
}
}
//只有右子节点
else if (cur.left == null) {
if (cur == root) {
root = root.right;
} else if (isLeft) {
parent.left = cur.right;
} else if (!isLeft) {
parent.right = cur.right;
}
}
//删除有两个子节点的节点
else {
//获取删除节点的后继
Node sub = getSub(cur);
//连接删除节点的父节点和删除节点的后继节点
//判断是否要维护root指针
if (cur == root) {
sub.left = root.left;
root = sub;
} else if(isLeft) {
parent.left = sub;
sub.left = cur.left;
} else {
parent.right = sub;
sub.left = cur.left;
}
}
return true;
}
public Node getSub(Node delete) {
Node cur = delete.right;
Node sub = delete.right;
Node subParent = null;
while (cur != null) {
subParent = sub;
sub = cur;
cur = cur.left;
}
if (sub != delete.right) {
subParent.left = sub.right;
sub.right = delete.right;
}
return sub;
}
}
测试代码:
public class TreeTest {
@Test
public void binaryTreeTest() {
BinaryTree binaryTree = new BinaryTree();
//测试插入节点
binaryTree.insert(34);
binaryTree.insert(20);
binaryTree.insert(22);
binaryTree.insert(65);
binaryTree.insert(12);
binaryTree.insert(2);
binaryTree.insert(8);
BinaryTree.inorder(binaryTree.root);
//测试查找方法
System.out.println(binaryTree.find(22));
//测试寻找最大值最小值
System.out.println(binaryTree.maxData());
System.out.println(binaryTree.minData());
//测试删除节点
//删除没有子节点的节点
System.out.println(binaryTree.delete(8));
binaryTree.inorder(binaryTree.root);
//删除有一个子节点的节点
System.out.println(binaryTree.delete(12));
binaryTree.inorder(binaryTree.root);
//删除有两个子节点的节点
binaryTree.insert(60);
binaryTree.insert(67);
binaryTree.insert(66);
System.out.println(binaryTree.delete(65));
binaryTree.inorder(binaryTree.root);
}
}
运行结果:
2 8 12 20 22 34 65
要寻找的元素地址: idea.tree.Node@3c5a99da
最大值: 65
最小值: 2
是否删除成功: true
2 12 20 22 34 65
是否删除成功: true
2 20 22 34 65
是否删除成功: true
2 20 22 34 60 66 67