链式存储方式的分析
树存储方式的分析
能提高数据存储,读取的效率,比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树
如果该二叉树的所有叶子节点都在最后一层,并且结点总数=2^n-1, n为层数,则我们称为满二叉树。
如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。
说明:
小结:看输出父节点的顺序,就确定是前序,中序还是后序
遍历步骤:
代码:
public class 二叉树 {
public static void main(String[] args) {
BinaryTree binaryTree = new BinaryTree();
HeroNode node1 = new HeroNode(1, "A");
HeroNode node2 = new HeroNode(2, "B");
HeroNode node3 = new HeroNode(3, "C");
HeroNode node4 = new HeroNode(4, "D");
HeroNode node5 = new HeroNode(5, "E");
binaryTree.setRoot(node1);
HeroNode root = binaryTree.getRoot();
root.setLeft(node2);
root.setRight(node3);
node3.setLeft(node5);
node3.setRight(node4);
System.out.println("---------前序遍历---------");
binaryTree.preOrder(); // 1,2,3,4
System.out.println("---------中序遍历---------");
binaryTree.infixOrder(); // 2,1,3,4
System.out.println("---------后序遍历---------");
binaryTree.postOrder(); // 2,4,3,1
}
}
// 二叉树
class BinaryTree {
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
public HeroNode getRoot() {
return root;
}
// 前序遍历
public void preOrder() {
if (this.root != null) {
this.root.preOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
public void infixOrder() {
if (this.root != null) {
this.root.infixOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
public void postOrder() {
if (this.root != null) {
this.root.postOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
}
// node
class HeroNode {
private int NO;
private String name;
private HeroNode left;
private HeroNode right;
public HeroNode(int NO, String name) {
super();
this.NO = NO;
this.name = name;
}
@Override
public String toString() {
return "HeroNode{" +
"NO=" + NO +
", name='" + name + '\'' +
'}';
}
public int getNO() {
return NO;
}
public void setNO(int NO) {
this.NO = NO;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
// 前序遍历
public void preOrder() {
// 1. 先输出父节点
System.out.println(this);
// 2. 递归左子树
if (this.left != null) {
this.left.preOrder();
}
// 3. 递归右子树
if (this.right != null) {
this.right.preOrder();
}
}
// 中序遍历
public void infixOrder() {
// 1. 递归左子树
if (this.left != null) {
this.left.infixOrder();
}
// 2. 输出父节点
System.out.println(this);
// 3. 向右子树遍历
if (this.right != null) {
this.right.infixOrder();
}
}
// 后序遍历
public void postOrder() {
// 1. 递归左子树
// 2. 向右子树遍历
// 3. 输出父节点
if (this.left != null) {
this.left.postOrder();
}
if (this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
}
// 前序查找
public HeroNode preSearch(int value) {
// 1. 比较当前节点是不是
if (this.getNO() == value) {
return this;
}
// 2. 判断左节点
HeroNode resNode = null;
if (this.left != null) {
resNode = this.left.preSearch(value);
}
if (resNode != null) {
return resNode;
}
// 3. 判断右节点
if (this.right != null) {
resNode = this.right.preSearch(value);
}
return resNode;
}
// 中序查找
public HeroNode infixSearch(int value) {
HeroNode resNode = null;
if (this.left != null) {
resNode = this.left.infixSearch(value);
}
if (resNode != null) {
return resNode;
}
// 2. 判断当前节点
if (value == this.NO) {
return this;
}
// 3. 向右
if (this.right != null) {
resNode = this.right.infixSearch(value);
}
return resNode;
}
// 后序查找
public HeroNode postSearch(int value) {
// 1. 向左遍历
HeroNode resNode = null;
if (this.left != null) {
resNode = this.left.postSearch(value);
}
if (resNode != null) {
return resNode;
}
// 2. 向右
if (this.right != null) {
resNode = this.right.postSearch(value);
}
if (resNode != null) {
return resNode;
}
// 3. 判断当前节点
if (value == this.NO) {
return this;
}
return null;
}
要求
如果删除的节点是叶子节点,则删除该节点
如果删除的节点是非叶子节点,则删除该子树.
测试,删除掉5号叶子节点和3号子树.
// 删除
public void delete(int NO) {
// 1. 左节点
if (this.left != null && this.left.getNO() == NO) {
this.left = null;
return;
}
// 2. 右节点
if (this.right != null && this.right.getNO() == NO) {
this.right = null;
return;
}
// 3. 递归删除
if(this.left != null){
this.left.delete(NO);
}
if(this.right != null){
this.right.delete(NO);
}
}
// 删除
public void delete(int no){
if(this.root == null){
System.out.println("此树为空");
return;
}else {
if(this.root.getNO() == no){
this.root = null;
}else {
this.root.delete(no);
}
}
}
特点
public class 数组二叉树 {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7};
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
arrayBinaryTree.preOrder();
}
}
class ArrayBinaryTree {
private int[] arr; // 存储数据节点的数组
public ArrayBinaryTree(int[] arr) {
this.arr = arr;
}
public void preOrder() {
this.preOrder(0);
}
// 顺序存储
// 前序 index 为数组下标
public void preOrder(int index) {
if (arr == null || arr.length == 0) {
System.out.println("数组二叉树为空");
return;
}
// 输出当前元素
System.out.println(arr[index]);
// 向左递归
if ((index * 2 + 1) < arr.length) {
preOrder(2 * index + 1);
}
// 向右边递归
if ((index * 2 + 2) < arr.length) {
preOrder(2 * index + 2);
}
}
}
说明:当线索化二叉树后,Node节点的属性left和righ,有如下情况:
public class 线索二叉树 {
public static void main(String[] args) {
NewHeroNode node1 = new NewHeroNode(1, "A");
NewHeroNode node2 = new NewHeroNode(3, "B");
NewHeroNode node3 = new NewHeroNode(6, "C");
NewHeroNode node4 = new NewHeroNode(8, "D");
NewHeroNode node5 = new NewHeroNode(10, "E");
NewHeroNode node6 = new NewHeroNode(14, "F");
node1.left = node2;
node1.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
ThreeNodes threeNodes = new ThreeNodes();
threeNodes.setRoot(node1);
System.out.println(node5.getLeft());
System.out.println(node5.getRight() );
node1.infixOrder();
}
}
// 实现线索化二叉树
class NewHeroNode {
public int NO;
public String name;
public NewHeroNode left;
public NewHeroNode right;
public int leftType;
public int rightType;
public NewHeroNode(int NO, String name) {
super();
this.NO = NO;
this.name = name;
}
// 前序遍历
public void preOrder() {
// 1. 先输出父节点
System.out.println(this);
// 2. 递归左子树
if (this.left != null) {
this.left.preOrder();
}
// 3. 递归右子树
if (this.right != null) {
this.right.preOrder();
}
}
// 中序遍历
public void infixOrder() {
// 1. 递归左子树
if (this.left != null) {
this.left.infixOrder();
}
// 2. 输出父节点
System.out.println(this);
// 3. 向右子树遍历
if (this.right != null) {
this.right.infixOrder();
}
}
// 后序遍历
public void postOrder() {
// 1. 递归左子树
// 2. 向右子树遍历
// 3. 输出父节点
if (this.left != null) {
this.left.postOrder();
}
if (this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
// 前序查找
public NewHeroNode preSearch(int value) {
// 1. 比较当前节点是不是
if (this.getNO() == value) {
return this;
}
// 2. 判断左节点
NewHeroNode resNode = null;
if (this.left != null) {
resNode = this.left.preSearch(value);
}
if (resNode != null) {
return resNode;
}
// 3. 判断右节点
if (this.right != null) {
resNode = this.right.preSearch(value);
}
return resNode;
}
// 中序查找
public NewHeroNode infixSearch(int value) {
NewHeroNode resNode = null;
if (this.left != null) {
resNode = this.left.infixSearch(value);
}
if (resNode != null) {
return resNode;
}
// 2. 判断当前节点
if (value == this.NO) {
return this;
}
// 3. 向右
if (this.right != null) {
resNode = this.right.infixSearch(value);
}
return resNode;
}
// 后序查找
public NewHeroNode postSearch(int value) {
// 1. 向左遍历
NewHeroNode resNode = null;
if (this.left != null) {
resNode = this.left.postSearch(value);
}
if (resNode != null) {
return resNode;
}
// 2. 向右
if (this.right != null) {
resNode = this.right.postSearch(value);
}
if (resNode != null) {
return resNode;
}
// 3. 判断当前节点
if (value == this.NO) {
return this;
}
return null;
}
// 删除
public void delete(int NO) {
// 1. 左节点
if (this.left != null && this.left.getNO() == NO) {
this.left = null;
return;
}
// 2. 右节点
if (this.right != null && this.right.getNO() == NO) {
this.right = null;
return;
}
// 3. 递归删除
if (this.left != null) {
this.left.delete(NO);
}
if (this.right != null) {
this.right.delete(NO);
}
}
}
class ThreeNodes {
public NewHeroNode pre = null;
public NewHeroNode root = null;
// node 为当前需要线索化的节点
public void threadedNodes(NewHeroNode node) {
if (node == null) {
return;
}
// 1. 线索化左子树
threadedNodes(node.getLeft());
// 2. 线索化当前节点
// 1) 先梳理当前节点的前驱节点
if (node.getLeft() == null) {
// 让当前节点的左指针指向前驱节点
node.setLeft(pre);
// 修改当前节点的左指针的类型
node.setLeftType(1);
}
// 2) 处理后置节点
if (pre != null && pre.getRight() == null) {
pre.setRight(node);
pre.setRightType(1);
}
// 每次处理完一个节点,当前节点为下一个节点的前驱节点
pre = node;
// 3. 线索化右子树
threadedNodes(node.getRight());
}
public NewHeroNode getRoot() {
return root;
}
public void setRoot(NewHeroNode root) {
this.root = root;
threadedNodes(root);
}
}
// 遍历
public void threadList() {
// 定义一个变量存储当前节点
NewHeroNode node = root;
while (node != null) {
// 循环找到 leftType == 1 的结点,前驱节点
// 当 leftType == 1 时,说明该节点是按照线索化处理后的有效节点
while (node.getLeftType() == 0){
node = node.getLeft();
}
// 打印当前节点
System.out.println(node);
// 如果当前节点的右指针,指向的是后续节点,就一直输出
while (node.getRightType() == 1){
// 获取到当前节点的后续节点
node = node.getRight();
System.out.println(node);
}
// 替换遍历的节点
node = node.getRight();
}
}
对堆中的结点按层进行编号,映射到数组中就是下面这样
例如:arr[1] ≥ arr[2 * 1 + 1] && arr[1] ≥ arr[2 * 1 + 2]
===45 ≥ 20 && 45 ≥ 25
public static void main(String[] args) {
int[] arr = {4, 6, 8, 5, 9};
heapSort(arr);
}
// 堆排序的方法
public static void heapSort(int arr[]) {
/*adjustHeap(arr, 1, arr.length);
System.out.println(Arrays.toString(arr));
adjustHeap(arr, 0, arr.length);
System.out.println(Arrays.toString(arr));*/
// 1. 构建大 顶堆
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
System.out.println(Arrays.toString(arr));
// 2. 将堆顶元素与末尾元素进行交换, 将最大元素沉到数组末端
int temp = 0;
for (int j = arr.length - 1; j > 0; j--) {
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
adjustHeap(arr, 0, j);
}
System.out.println(Arrays.toString(arr));
}
// 讲一个数组调整成一个大顶堆
// 功能:完成将 以 i 对应的非叶子节点的数调整成大顶堆
// 举例:arr = {4,6,8,5,9} i = 1,length = 5
// arr 待调整的数组
// i 非叶子结点在数组中的索引
// length 对多少个元素进行调整
public static void adjustHeap(int arr[], int i, int length) {
int temp = arr[i]; // 取出当前元素 -->非叶子元素
// 1. 开始调整
// 左子节点, k 是 i的 左子节点
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
// k+1 > length 不比了
if (k + 1 < length && arr[k] < arr[k + 1]) { // 说明 左子节点 < 右子节点
k++; // k指向 右子节点
}
// k此时为左结点 或者 右结点
if (arr[k] > temp) { // 子节点 > 父节点
arr[i] = arr[k]; // 父节点 变为 子节点
i = k; // 非叶子结点 继续循环
} else break;
}
// for循环结束后, 已经将 以 i为父节点的数的最大值,放在了最前面。
arr[i] = temp;
}
路径和路径长度:
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。
通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层
结点的路径长度为L-1
结点的权及带权路径长度:
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
树的带权路径长度:
树的带权路径长度规定为所有叶子节点的带权路径长度之和,记为 WPL(weighted path length)
,权值越大的结点离根节点越近的二叉树才是最优二叉树。
WPL最小的就是赫夫曼树
public class 赫夫曼树 {
public static void main(String[] args) {
int[] arr = {13, 7, 8, 3, 29, 6, 1};
Node root = createHeffmanTree(arr);
preNode(root);
}
// 创建赫夫曼树
public static Node createHeffmanTree(int[] arr) {
// 1. 遍历 arr 数组
// 2. 将每一个构建成node
// 3. 将每一个放入 arrayList中
ArrayList<Node> nodes = new ArrayList<>();
for (int value : arr) {
nodes.add(new Node(value));
}
// 4. 排序 小 --> 大
// 循环处理
while (nodes.size() > 1) {
Collections.sort(nodes);
// 5. 取出权值最小的两个二叉树
Node left = nodes.get(0);
Node right = nodes.get(1);
// 6. 构建新的二叉树
Node parent = new Node(left.value + right.value);
parent.left = left;
parent.right = right;
// 7. 从 arrayList中删除处理过的二叉树
nodes.remove(left);
nodes.remove(right);
nodes.add(parent);
System.out.println(nodes);
}
return nodes.get(0);
}
// 前序遍历
public static void preNode(Node root){
if(root == null){
System.out.println("值为空");
}else {
root.preOrder();
}
}
}
// 创建节点类
class Node implements Comparable<Node> {
int value; // 权
Node left; // 左子节点
Node right; // 右子节点
public Node(int value) {
this.value = value;
}
// 前序遍历
public void preOrder(){
System.out.println(this);
if(this.left != null){
this.left.preOrder();
}
if(this.right != null){
this.right.preOrder();
}
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
@Override
public int compareTo(Node o) {
return this.value - o.value;
}
}
i like like like java do you like a java
d:1y:1 u:1 j:2 v:2 o:2l:4 k:4 e:4 i:5 a:5 :9
//各个字符对应的个数
按照上面字符出现的次数构建一颗赫夫曼树,次数作为权值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DSRiQoz7-1651491300720)(https://secure2.wostatic.cn/static/i92Yt13Bex8adYwZJEP5mb/image.png)]
根据赫夫曼树,给各个字符规定编码,向左的路径为 0,向右的路径为1。
o:1000
u: 10010
d:100110
y: 100111
a : 110
k:1110
e: 1111
j:0000
v:0001
l:001
:01
按照上面的赫夫曼编码,我们的i like like like java do you like a java
字符串对应的编码为(注意这里我们使用的无损压缩)
101``01``001101111011110100110111101111010011011110111101000010000110011001111000011001111000100100100110111011011100100001100001110
长度为:133
原来长度为 359,压缩了 62.9%
此编码满足前缀编码,即字符的编码不能是其他字符编码的前缀,不会造成匹配的多义性,赫夫曼编码是无损的处理方式。
注意:
这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼树编码也不一样,但是WPL是一样的,生成的长度都是一样的,都是最小的,
class Node2 implements Comparable<Node2> {
Byte data;
int weight;
Node2 left;
Node2 right;
public Node2(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
// 前序遍历
public void preOrder() {
System.out.println(this);
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
@Override
public int compareTo(Node2 o) {
return this.weight - o.weight;
}
@Override
public String toString() {
return "Node2{" +
"data=" + data +
", weight=" + weight +
'}';
}
}
private static List<Node2> getNodes(byte[] bytes) {
// 1. 创建一个 ArrayList
ArrayList<Node2> nodes = new ArrayList<>();
// 2. 存储灭一个byte出现的次数
HashMap<Byte, Integer> map = new HashMap<>();
for (byte by : bytes) {
//说明map中没有这个字符,我们将其加入到map中
//说明这个字符已经存在,我们让其进行总数加1
map.merge(by, 1, Integer::sum);
}
System.out.println(map);
for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
nodes.add(new Node2(entry.getKey(), entry.getValue()));
}
return nodes;
}
public static Node2 createHeffmanTree(List<Node2> nodes) {
// 4. 排序 小 --> 大
// 循环处理
while (nodes.size() > 1) {
Collections.sort(nodes);
// 5. 取出权值最小的两个二叉树
Node2 left = nodes.get(0);
Node2 right = nodes.get(1);
// 6. 构建新的二叉树
Node2 parent = new Node2(null, left.weight + right.weight);
parent.left = left;
parent.right = right;
// 7. 从 arrayList中删除处理过的二叉树
nodes.remove(left);
nodes.remove(right);
nodes.add(parent);
}
return nodes.get(0);
}
public static Map<Byte, String> getCodes(Node2 node2) {
getCodes(node2, "", stringBuilder);
return huffmanCodes;
}
private static void getCodes(Node2 node, String code, StringBuilder stringBuilder) {
StringBuilder st = new StringBuilder(stringBuilder);
//将code加入st
st.append(code);
if (node != null) {
if (node.data == null) {//非叶子节点,叶子节点的data域是存在值的
//递归处理
//向左递归
getCodes(node.left, "0", st);
//向右递归
getCodes(node.right, "1", st);
} else {
//说明找到一个叶子节点
//就表示找到某个叶子节点的最后
huffmanCodes.put(node.data, st.toString());
}
}
}
private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
// 1. 将 bytes 转成 heffman编码对应的字符串
StringBuilder stringBuilder = new StringBuilder();
// 2. 遍历 bytes 数组
for (byte b : bytes) {
stringBuilder.append(huffmanCodes.get(b));
}
int len = (stringBuilder.length() + 7) / 8;
// 创建存储压缩后的byte数组
byte[] huffmanCodeBytes = new byte[len];
int index = 0;
for (int i = 0; i < stringBuilder.length(); i += 8) { // 8位对应一个 byte
String strByte;
if (i + 8 > stringBuilder.length()) {
// 不够 8 位
strByte = stringBuilder.substring(i);
} else strByte = stringBuilder.substring(i, i + 8);
// 将 strByte 转成 一个byte,放入
huffmanCodeBytes[index] = (byte) Integer.parseInt(strByte, 2);
index++;
}
return huffmanCodeBytes;
}
private static byte[] deCode(Map<Byte, String> huffmanCodes, byte[] huffmanCodesBytes) {
// 1. 将 huffmanCodesBytes [-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
// 重新转成字符串(huffman编码对应的二进制字符串)
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < huffmanCodesBytes.length; i++) {
// 判断是不是最后一个字节
boolean flag = (i == huffmanCodesBytes.length - 1);
stringBuilder.append(byteToBitString(!flag, huffmanCodesBytes[i]));
}
// 3. 把字符串按照指定的赫夫曼编码进行解码
HashMap<String, Byte> map = new HashMap<>();
for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
// 4. 存放 byte
ArrayList<Byte> list = new ArrayList<>();
for (int i = 0; i < stringBuilder.length(); ) {
int count = 1;
boolean flag = true;
Byte b = null;
while (flag) {
String key = stringBuilder.substring(i, i + count);
b = map.get(key);
if (b != null) {
flag = false;
} else {
count++;
}
}
list.add(b);
i += count;
}
byte[] bytes = new byte[list.size()];
for (int i = 0; i < list.size(); i++) {
bytes[i] = list.get(i);
}
return bytes;
}
private static void zipFile(String srcFile, String dstFile) throws IOException {
FileInputStream inputStream = new FileInputStream(srcFile);
byte[] bytes = new byte[inputStream.available()];
// 读取文件
inputStream.read(bytes);
// 压缩完成
byte[] huffmanZipBytes = huffmanZip(bytes);
// 输出文件,存放压缩文件
OutputStream outputStream = new FileOutputStream(dstFile);
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
// 写入压缩数组
oos.writeObject(huffmanZipBytes);
// 写入赫夫曼编码
oos.writeObject(huffmanCodes);
// 写入完毕
inputStream.close();
oos.close();
outputStream.close();
}
private static void unzipFile(String zipFile, String dstFile) throws IOException, ClassNotFoundException {
FileInputStream inputStream = new FileInputStream(zipFile);
ObjectInputStream ooin = new ObjectInputStream(inputStream);
byte[] bytes;
// 读取文件
bytes = (byte[]) ooin.readObject();
Map<Byte, String> huffmanCodes = (Map<Byte, String>) ooin.readObject();
// 解压
byte[] deCode = deCode(huffmanCodes, bytes);
// 输出文件,存放压缩文件
OutputStream outputStream = new FileOutputStream(dstFile);
// 写入压缩数组
outputStream.write(deCode);
// 写入完毕
ooin.close();
inputStream.close();
outputStream.close();
}
// 5. 将前面的方法封装起来,便于我们的调用
private static byte[] huffmanZip(byte[] bytes) {
// 1. 合并
List<Node2> nodes = getNodes(bytes);
// 2. 生成赫夫曼树
Node2 root = createHeffmanTree(nodes);
// 3. 生成赫夫曼编码
Map<Byte, String> codes = getCodes(root);
// 4. 生成赫夫曼编码处理后的数据
byte[] zipBytes = zip(bytes, huffmanCodes);
return zipBytes;
}
// 将 byte 转成二进制的字符串
private static String byteToBitString(boolean flag, byte b) {
int temp = b;
// 如果是正数,还存在补高位的问题
if (flag) { // 标识是否需要高位
temp |= 256; // temp 1 --> 000;
}
String string = Integer.toBinaryString(temp); // 返回的是 temp 对应的二进制的补码
if (flag) {
return string.substring(string.length() - 8);
} else {
return string;
}
}
// 前缀遍历
public static void preOrder(Node2 root) {
root.preOrder();
}
二叉排序树:BST: (Binary Sort(Search) Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
特别说明: 如果有相同的值,可以将该节点放在左子节点或右子节点。
比如针对前面的数据(7,3,10,12,5,1,9),对应的二叉排序树为:
// Node 节点
class Node3 {
int value;
Node3 left;
Node3 right;
public Node3(int value) {
this.value = value;
}
// 添加节点
public void add(Node3 node) {
if (node == null) return;
if (this.value > node.value) {
if (this.left == null) {
this.left = node;
} else {
this.left.add(node);
}
} else {
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}
}
// 中序遍历
public void infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.infixOrder();
}
}
@Override
public String toString() {
return "Node3{" +
"value=" + value +
'}';
}
}
// 二叉排序树
class BinarySortTree {
private Node3 root;
// 添加节点
public void add(Node3 node) {
if (root == null) {
root = node;
} else {
root.add(node);
}
}
// 遍历
public void infixOrder() {
if (root != null) {
root.infixOrder();
} else {
System.out.println("当前二叉排序树为空");
}
}
}
删除叶子节点 (比如:2, 5, 9, 12)
左子结点 parent.left = null
右子结点 parent.right = null;
删除只有一颗子树的节点 比如 1
如果targetNode 有左子结点
如果 targetNode 是 parent 的左子结点
parent.left = targetNode.left;
如果 targetNode 是 parent 的右子结点
parent.right = targetNode.left;
如果targetNode 有右子结点
如果 targetNode 是 parent 的左子结点
parent.left = targetNode.right;
如果 targetNode 是 parent 的右子结点
parent.right = targetNode.right
删除有两颗子树的节点. (比如:7, 3,10 )
targetNode.value = temp
// 查找删除的节点
public Node3 search(int value) {
if (value < this.value) { // 小于当前节点 向左查
if (this.left == null) {
return null;
}
return this.left.search(value);
} else if (value > this.value) {
if (this.right == null) {
return null;
}
return this.right.search(value);
} else {
return this;
}
}
// 查找要删除节点的父节点
public Node3 searchParent(int value) {
// 当前节点就是要删除的节点的父节点,就返回
if ((this.left != null && this.left.value == value) ||
(this.right != null && this.right.value == value)) {
return this;
} else {
// 查找的小于当前节点的值,并且当前节点的左子节点不为空
if (value < this.value && this.left != null) { // 左递归
return this.left.searchParent(value);
} else if (value >= this.value && this.right != null) { // 右递归
return this.right.searchParent(value);
} else {
return null;
}
}
}
// 查找要删除的节点
public Node3 search(int value) {
if (root == null) return null;
else return root.search(value);
}
// 查找父节点
public Node3 searchParent(int value) {
if (root == null) return null;
else return root.searchParent(value);
}
// 删除节点
public void delNode(int value) {
if (root == null) return;
else {
// 1. 找节点
Node3 targetNode = search(value);
if (targetNode == null) return;
// 2. 当前二叉排序树只有一个节点,删除当前节点
if (root.left == null && root.right == null) {
root = null;
return;
}
// 3. 查找targetNode 的父节点
Node3 parent = searchParent(value);
// 4. 删除的节点是叶子节点
if (targetNode.left == null && targetNode.right == null) {
// 5. 判断是父节点的左子节点还是右
if (parent.left != null && parent.left.value == value) { // 左子节点
parent.left = null;
} else if (parent.right != null && parent.right.value == value) { // 右子节点
parent.right = null;
}
}
// 6. 删除有两颗子树的节点
else if (targetNode.left != null && targetNode.right != null) {
int min = delRightThreeMin(targetNode.right);
targetNode.value = min;
}
// 7. 删除只有一颗子树的节点
else {
// 如果要删除的节点有左子节点
if (targetNode.left != null) {
if (parent == null) {
// 当前节点为根节点
root = targetNode.left;
return;
}
// targetNode 是 parent 的左子节点
if (parent.left.value == value) {
parent.left = targetNode.left;
} else { // targetNode 是parent 的右子节点
parent.right = targetNode.left;
}
} else { // 要删除的节点有右子节点
if (parent.left.value == value) {
parent.left = targetNode.right;
} else {
// targetNode 是 parent 的右子节点
parent.right = targetNode.right;
}
}
}
}
}
// 删除 node 为根节点的二叉排序树的最小节点
public int delRightThreeMin(Node3 node) {
// node 传入的节点(当作二叉排序树的根节点)
// 返回的 以 node 为根节点的二叉排序树的最小节点的值
Node3 targetNode = node;
// 循环查找左节点, 就会找到最小值
while (targetNode.left != null) {
targetNode = targetNode.left;
}
// 这时 targetNode 就是最小值
delNode(targetNode.value);
return targetNode.value;
}
红黑树
、AVL
、替罪羊树
、Treap
、伸展树
等。创建一个新的节点newNode (以4这个值创建),创建一个新的节点,值等于当前根节点的值
把新节点的左子树设置了当前节点的左子树
newNode.left = left
把新节点的右子树设置为当前节点的右子树的左子树
newNode.right =right.left;
把当前节点的值换为右子节点的值
value=right.value;
把当前节点的右子树设置成右子树的右子树
right=right.right;
把当前节点的左子树设置为新节点
// Node 节点
class Node4 {
int value;
Node4 left;
Node4 right;
public Node4(int value) {
this.value = value;
}
// 添加节点
public void add(Node4 node) {
if (node == null) return;
if (this.value > node.value) {
if (this.left == null) {
this.left = node;
} else {
this.left.add(node);
}
} else {
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}
// 当添加完一个节点后,如果条件满足,右子树的高度 > 左子树的高度 = 1; 左旋转
if (rightHeight() - leftHeight() > 1) {
/*if (right != null && right.rightHeight() < right.leftHeight()) {
}*/
leftRotate();
}
}
// 中序遍历
public void infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.infixOrder();
}
}
// 返回当前结点的高度,
public int height() {
return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
}
// 返回左子树的高度
public int leftHeight() {
if (left == null) {
return 0;
}
return left.height();
}
// 返回右子树的高度
public int rightHeight() {
return right == null ? 0 : right.height();
}
// 左旋转方法
public void leftRotate() {
// 1. 创建新的结点
Node4 newNode = new Node4(this.value);
// 2. 把新的结点的左子树设置为当前节点的左子树
newNode.left = this.left;
// 3. 新的结点的右子树设置为过去节点的右子树的左子树
newNode.right = this.right.left;
// 4. 当前结点的值换成右子结点的值
this.value = right.value;
// 5. 把当前结点的右子树设置成右子树的右子树
this.right = right.right;
// 7. 当前节点的左子结点设置为新的结点
this.left = newNode;
}
@Override
public String toString() {
return "Node4{" +
"value=" + value +
'}';
}
}
进行右旋转
就是降低左子树的高度,这里是将9这个节点,通过右旋转,到右子树
创建一个新的节点newNode(以10这个值创建),创建―个新的节点,值等于当前根节点的值
把新节点的右子树设置了当前节点的右子树
newNode.right =right
把新节点的左子树设置为当前节点的吃子树的右子树
newNode.left=left.right;
把当前节点的值换为左子节点的值
valde=left.value,
把当前节点的左子树设置成左子树的吃子树
left=left.left;
把当前节点的右子树设置为新节点
Node
// 右旋转
public void rightRotate() {
Node4 newNode = new Node4(this.value);
newNode.right = this.right;
newNode.left = this.left.right;
this.value = this.left.value;
this.left = this.left.left;
this.right = newNode;
}
// 添加节点
public void add(Node4 node) {
if (node == null) return;
if (this.value > node.value) {
if (this.left == null) {
this.left = node;
} else {
this.left.add(node);
}
} else {
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}
// 当添加完一个节点后,如果条件满足,右子树的高度 > 左子树的高度 = 1; 左旋转
if (rightHeight() - leftHeight() > 1) {
/*if (right != null && right.rightHeight() < right.leftHeight()) {
}*/
// leftRotate();
}
if (leftHeight() - rightHeight() > 1) {
rightRotate();
}
}
解决
Node
// 添加节点
public void add(Node4 node) {
if (node == null) return;
if (this.value > node.value) {
if (this.left == null) {
this.left = node;
} else {
this.left.add(node);
}
} else {
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}
// 当添加完一个节点后,如果条件满足,右子树的高度 > 左子树的高度 = 1; 左旋转
if (rightHeight() - leftHeight() > 1) {
if (right != null && right.leftHeight() > right.rightHeight()) {
// 对右子节点进行右旋转
right.rightRotate();
}
leftRotate();
return;
}
if (leftHeight() - rightHeight() > 1) {
// 当前节点 的 左子树 的 右子树的高度 > 左子树的高度
if (left != null && left.rightHeight() > left.leftHeight()) {
// 先对当前节点的左节点进行左旋转
left.leftRotate();
}
rightRotate();
}
}
int[] array = {4, 3, 6, 5, 7, 8};
int[] array = {10, 12, 8, 9, 7, 6};
int[] array = {10, 11, 7, 6, 8, 9};
二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(比如1亿),就存在如下回题:
问题1∶
在构建二叉树时,需要多次进行i/o操作(海量数据存在数据库或文件中)。节点海量构建才叉树时,速度有影响
问题2:
B树概念—> 链接
B树通过重新组织节点,降低树的高度,并且减少i/o读写次数来提升效率。
将数列{16,24,12,32,14,26,34,10,8,28,38,20}构建成2-3树
插入规则
B-tree树就是B树,B即Balanced,平衡的意思。B-tree就是B树
前面已经介绍了2-3树和2-3-4树,他们就是B树(英语: B-tree也写成B-树),这里我们再做一个说明,我们在学习Mysql时,经常听到说某种类型的索引是基于B树或者B+树的,如图:
B树的阶:节点的最多子节点个数。比如2-3树的阶是3,2-3-4树的阶是4
B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;
重复,直到所对应的儿子指针为空,或已经是叶子结点
关键字集合分布在整颗树中,即叶子节点和非叶子节点都存放数据.
索有可能在非叶子结点结束
B+树详细概念
B+树的搜索与B树也基本相同,区别是B+树只有达到叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找
所有关键字都出现在叶子结点的链表中(即数据只能在叶子节点【也叫稠密索引】),且链表中的关键字(数据)恰好是有序的。
不可能在非叶子结点命中
非叶子结点相当于是叶子结点的索引(稀疏索引)叶子结点相当于是存储(关键字)数据的数据层
更适合文件索引系统
B树和B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然.
B*树是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针。