堆排序
堆排序基本介绍
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆大顶堆举例说明 我们对堆中的结点按层进行编号,映射到数组中就是下面这个样子: 大顶堆特点:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] // i 对应第几个节点,i从0开始编号小顶堆举例说明 小顶堆特点:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2] // i 对应第几个节点,i从0开始编号一般升序采用大顶堆,降序采用小顶堆
堆排序基本思想
堆排序的基本思想是:
将待排序序列构造成一个大顶堆此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。 可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了.
堆排序步骤图解说明
要求:给你一个数组 {4,6,8,5,9} , 要求使用堆排序法,将数组升序排序。
堆排序代码实现
要求:给你一个数组 {4,6,8,5,9} , 要求使用堆排序法,将数组升序排序。
代码
package tree
;
import java
.text
.SimpleDateFormat
;
import java
.util
.Arrays
;
import java
.util
.Date
;
public class HeapSort {
public static void main(String
[] args
) {
int[] arr
= new int[8000000];
for (int i
= 0; i
< 8000000; i
++) {
arr
[i
] = (int) (Math
.random() * 80000);
}
Date date1
= new Date();
SimpleDateFormat simpleDateFormat
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str
= simpleDateFormat
.format(date1
);
System
.out
.println("排序前的时间为:" + date1Str
);
heapSort(arr
);
Date date2
= new Date();
String date2Str
= simpleDateFormat
.format(date2
);
System
.out
.println("排序后的时间为:" + date2Str
);
}
public static void heapSort(int[] arr
) {
int temp
= 0;
for (int i
= arr
.length
/ 2 - 1; i
>= 0; i
--) {
adjustHeap(arr
, i
, arr
.length
);
}
for (int i
= arr
.length
- 1; i
> 0; i
--) {
temp
= arr
[i
];
arr
[i
] = arr
[0];
arr
[0] = temp
;
adjustHeap(arr
, 0, i
);
}
}
public static void adjustHeap(int[] arr
, int i
, int length
) {
int temp
= arr
[i
];
for (int j
= i
* 2 + 1; j
< length
; j
= j
* 2 + 1) {
if (j
+ 1 < length
&& arr
[j
] < arr
[j
+ 1]) {
j
++;
}
if (arr
[j
] > temp
) {
arr
[i
] = arr
[j
];
i
= j
;
} else {
break;
}
}
arr
[i
] = temp
;
}
}
赫夫曼树
基本介绍
给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree), 还有的书翻译为霍夫曼树。赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
赫夫曼树几个重要概念和举例说明
路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weighted path length) ,权值越大的结点离根结点越近的二叉树才是最优二叉树。WPL最小的就是赫夫曼树
赫夫曼树创建思路图解
给你一个数列 {13, 7, 8, 3, 29, 6, 1},要求转成一颗赫夫曼树. 思路分析(示意图):
构成赫夫曼树的步骤:
从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树取出根节点权值最小的两颗二叉树组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
代码
package huffmantree
;
import java
.util
.ArrayList
;
import java
.util
.Arrays
;
import java
.util
.Collections
;
import java
.util
.List
;
public class HuffmanTree {
public static void main(String
[] args
) {
int[] a
= {13, 7, 8, 3, 29, 6, 1};
Arrays
.sort(a
);
Node root
= createHuffmanTree(a
);
preOrder(root
);
}
public static void preOrder(Node root
) {
if (root
!= null
) {
root
.preOrder();
} else {
System
.out
.println("该树为空树");
}
}
public static Node
createHuffmanTree(int[] arr
) {
List
<Node> nodes
= new ArrayList<Node>();
for (int value
: arr
) {
nodes
.add(new Node(value
));
}
while (nodes
.size() > 1) {
Collections
.sort(nodes
);
Node leftNode
= nodes
.get(0);
Node rightNode
= nodes
.get(1);
Node parent
= new Node(leftNode
.value
+ rightNode
.value
);
parent
.left
= leftNode
;
parent
.right
= rightNode
;
nodes
.remove(leftNode
);
nodes
.remove(rightNode
);
nodes
.add(parent
);
}
return nodes
.get(0);
}
}
class Node implements Comparable<Node> {
int value
;
Node left
;
Node right
;
public Node(int value
) {
this.value
= value
;
}
@Override
public String
toString() {
return "Node{" +
"value=" + value
+
'}';
}
@Override
public int compareTo(Node o
) {
return this.value
- o
.value
;
}
public void preOrder() {
System
.out
.print(this.value
+ " ");
if (this.left
!= null
) {
this.left
.preOrder();
}
if (this.right
!= null
) {
this.right
.preOrder();
}
}
}
赫夫曼编码
基本介绍
赫夫曼编码也翻译为 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码
原理剖析
通信领域中信息的处理方式1-定长编码 i like like like java do you like a java // 共40个字符(包括空格)
105 32 108 105 107 101 32 108 105 107 101 32 108 105 107 101 32 106 97 118 97 32 100 111 32 121 111 117 32 108 105 107 101 32 97 32 106 97 118 97 //对应Ascii码
01101001 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101010 01100001 01110110 01100001 00100000 01100100 01101111 00100000 01111001 01101111 01110101 00100000 01101100 01101001 01101011 01100101 00100000 01100001 00100000 01101010 01100001 01110110 01100001 //对应的二进制
按照二进制来传递信息,总的长度是 359 (包括空格)
通信领域中信息的处理方式2-变长编码
i like like like java do you like a java // 共40个字符(包括空格)
d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 // 各个字符对应的个数 0= , 1=a, 10=i, 11=e, 100=k, 101=l, 110=o, 111=v, 1000=j, 1001=u, 1010=y, 1011=d 说明:按照各个字符出现的次数进行编码,原则是出现次数越多的,则编码越小,比如 空格出现了9 次, 编码为0 ,其它依次类推.
按照上面给各个字符规定的编码,则我们在传输 “i like like like java do you like a java” 数据时,编码就是 10010110100… 字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码, 即不能匹配到重复的编码
通信领域中信息的处理方式3-赫夫曼编码
i like like like java do you like a java // 共40个字符(包括空格)
d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 // 各个字符对应的个数 按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值.(图后)
赫夫曼编码实现
分析
图解
赫夫曼编码是无损处理方案
注意
这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl 是一样的,都是最小的, 比如: 如果我们让每次生成的新的二叉树总是排在权值相同的二叉树的最后一个,则生成的二叉树为:
赫夫曼编码实践
最佳实践-数据压缩(创建赫夫曼树)
将给出的一段文本,比如 “i like like like java do you like a java” , 根据前面的讲的赫夫曼编码原理,对其进行数据压缩处理 ,形式如 "1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110 "
步骤1:根据赫夫曼编码压缩数据的原理,需要创建 “i like like like java do you like a java” 对应的赫夫曼树.思路:前面已经分析过了,而且我们已然讲过了构建赫夫曼树的具体实现。
代码实现(创建赫夫曼树)
private static Node
createHuffmanTree(List
<Node> nodes
) {
while (nodes
.size() > 1) {
Collections
.sort(nodes
);
Node leftNode
= nodes
.get(0);
Node rightNode
= nodes
.get(1);
Node parent
= new Node(null
, leftNode
.weight
+ rightNode
.weight
);
parent
.left
= leftNode
;
parent
.right
= rightNode
;
nodes
.remove(leftNode
);
nodes
.remove(rightNode
);
nodes
.add(parent
);
}
return nodes
.get(0);
}
最佳实践-数据压缩(生成赫夫曼编码和赫夫曼编码后的数据)
我们已经生成了 赫夫曼树, 下面我们继续完成任务
生成赫夫曼树对应的赫夫曼编码 , 如下表: =01 a=100 d=11000 u=11001 e=1110 v=11011 i=101 y=11010 j=0010 k=1111 l=000 o=0011使用赫夫曼编码来生成赫夫曼编码数据 ,即按照上面的赫夫曼编码,将"i like like like java do you like a java" 字符串生成对应的编码数据, 形式如下.1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100思路:前面已经分析过了,而且我们讲过了生成赫夫曼编码的具体实现。
代码实现(生成赫夫曼编码和赫夫曼编码后的数据)
static Map
<Byte, String> huffmanCodes
= new HashMap<Byte, String>();
static StringBuilder stringBuilder
= new StringBuilder();
private static void getCodes(Node node
, String code
, StringBuilder stringBuilder
) {
StringBuilder stringBuilder1
= new StringBuilder(stringBuilder
);
stringBuilder1
.append(code
);
if (node
!= null
) {
if (node
.data
== null
) {
getCodes(node
.left
, "0", stringBuilder1
);
getCodes(node
.right
, "1", stringBuilder1
);
} else {
huffmanCodes
.put(node
.data
, stringBuilder1
.toString());
}
}
}
public static byte[] zip(byte[] bytes
, Map
<Byte, String> huffmanCodes
) {
StringBuilder stringBuilder
= new StringBuilder();
for (byte b
: bytes
) {
stringBuilder
.append(huffmanCodes
.get(b
));
}
int len
;
if (stringBuilder
.length() % 8 == 0) {
len
= stringBuilder
.length() / 8;
} else {
len
= stringBuilder
.length() / 8 + 1;
}
byte[] huffmanCondeBytes
= new byte[len
];
int index
= 0;
for (int i
= 0; i
< stringBuilder
.length(); i
+= 8) {
String strByte
;
if (i
+ 8 > stringBuilder
.length()) {
strByte
= stringBuilder
.substring(i
);
} else {
strByte
= stringBuilder
.substring(i
, i
+ 8);
}
huffmanCondeBytes
[index
] = (byte) Integer
.parseInt(strByte
, 2);
index
++;
}
return huffmanCondeBytes
;
}
最佳实践-数据解压(使用赫夫曼编码解码)
使用赫夫曼编码来解码数据,具体要求是
前面我们得到了赫夫曼编码和对应的编码byte[] , 即:[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]现在要求使用赫夫曼编码, 进行解码,又重新得到原来的字符串"i like like like java do you like a java"思路:解码过程,就是编码的一个逆向操作。
代码实现(使用赫夫曼编码解压)
private static String
byteToBitString(boolean flag
, byte b
) {
int temp
= b
;
if (flag
) {
temp
|= 256;
}
String str
= Integer
.toBinaryString(temp
);
if (flag
) {
str
= str
.substring(str
.length() - 8);
}
return str
;
}
public static byte[] decode(Map
<Byte, String> huffmanCodes
, byte[] huffmanBytes
) {
StringBuilder stringBuilder
= new StringBuilder();
for (int i
= 0; i
< huffmanBytes
.length
; i
++) {
boolean flag
= (i
== huffmanBytes
.length
- 1);
stringBuilder
.append(byteToBitString(!flag
, huffmanBytes
[i
]));
}
Map
<String, Byte> map
= new HashMap<String, Byte>();
for (Map
.Entry
<Byte, String> entry
: huffmanCodes
.entrySet()) {
map
.put(entry
.getValue(), entry
.getKey());
}
List
<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
) {
count
++;
} else {
flag
= false;
}
}
list
.add(b
);
i
+= count
;
}
byte[] b
= new byte[list
.size()];
for (int i
= 0; i
< b
.length
; i
++) {
b
[i
] = list
.get(i
);
}
return b
;
}
最佳实践-文件压缩
我们学习了通过赫夫曼编码对一个字符串进行编码和解码, 下面我们来完成对文件的压缩和解压,
具体要求:给你一个图片文件,要求对其进行无损压缩, 看看压缩效果如何。思路:读取文件-> 得到赫夫曼编码表 -> 完成压缩
代码实现(文件压缩)
public static void zipFile(String srcFile
,String dstFile
){
FileInputStream is
= null
;
OutputStream os
= null
;
ObjectOutputStream oos
= null
;
try {
is
= new FileInputStream(srcFile
);
byte[] b
= new byte[is
.available()];
is
.read(b
);
byte[] huffmanBytes
= huffmanZip(b
);
os
= new FileOutputStream(dstFile
);
oos
= new ObjectOutputStream(os
);
oos
.writeObject(huffmanBytes
);
oos
.writeObject(huffmanCodes
);
}catch (Exception e
){
System
.out
.println(e
.getMessage());
}finally {
try {
is
.close();
os
.close();
oos
.close();
}catch (Exception e
){
System
.out
.println(e
.getMessage());
}
}
}
最佳实践-文件解压(文件恢复)
具体要求:将前面压缩的文件,重新恢复成原来的文件。思路:读取压缩文件(数据和赫夫曼编码表)-> 完成解压(文件恢复)
public static void unZip(String zipFile
,String dstFile
){
InputStream is
= null
;
ObjectInputStream ois
= null
;
OutputStream os
= null
;
try {
is
= new FileInputStream(zipFile
);
ois
= new ObjectInputStream(is
);
byte[] huffmanBytes
= (byte[])ois
.readObject();
Map
<Byte,String> huffmanCodes
= (Map
<Byte, String>)ois
.readObject();
byte[] bytes
= decode(huffmanCodes
, huffmanBytes
);
os
= new FileOutputStream(dstFile
);
os
.write(bytes
);
}catch (Exception e
){
System
.out
.println(e
.getMessage());
}finally {
try {
is
.close();
ois
.close();
os
.close();
}catch (Exception e
){
System
.out
.println(e
.getMessage());
}
}
}
赫夫曼编码压缩文件注意事项
如果文件本身就是经过压缩处理的,那么使用赫夫曼编码再压缩效率不会有明显变化, 比如视频,ppt 等等文件赫夫曼编码是按字节来处理的,因此可以处理所有的文件(二进制文件、文本文件)如果一个文件中的内容,重复的数据不多,压缩效果也不会很明显.
赫夫曼编码代码汇总
package huffmancode
;
import java
.io
.*
;
import java
.util
.*
;
import java
.util
.zip
.ZipFile
;
public class HuffmanCode {
public static void main(String
[] args
) {
String zipFile
= "d://dst.zip";
String dstFile
= "d://src2.pdf";
unZip(zipFile
,dstFile
);
System
.out
.println("解压成功");
}
public static void zipFile(String srcFile
,String dstFile
){
FileInputStream is
= null
;
OutputStream os
= null
;
ObjectOutputStream oos
= null
;
try {
is
= new FileInputStream(srcFile
);
byte[] b
= new byte[is
.available()];
is
.read(b
);
byte[] huffmanBytes
= huffmanZip(b
);
os
= new FileOutputStream(dstFile
);
oos
= new ObjectOutputStream(os
);
oos
.writeObject(huffmanBytes
);
oos
.writeObject(huffmanCodes
);
}catch (Exception e
){
System
.out
.println(e
.getMessage());
}finally {
try {
is
.close();
os
.close();
oos
.close();
}catch (Exception e
){
System
.out
.println(e
.getMessage());
}
}
}
public static void unZip(String zipFile
,String dstFile
){
InputStream is
= null
;
ObjectInputStream ois
= null
;
OutputStream os
= null
;
try {
is
= new FileInputStream(zipFile
);
ois
= new ObjectInputStream(is
);
byte[] huffmanBytes
= (byte[])ois
.readObject();
Map
<Byte,String> huffmanCodes
= (Map
<Byte, String>)ois
.readObject();
byte[] bytes
= decode(huffmanCodes
, huffmanBytes
);
os
= new FileOutputStream(dstFile
);
os
.write(bytes
);
}catch (Exception e
){
System
.out
.println(e
.getMessage());
}finally {
try {
is
.close();
ois
.close();
os
.close();
}catch (Exception e
){
System
.out
.println(e
.getMessage());
}
}
}
public static byte[] decode(Map
<Byte, String> huffmanCodes
, byte[] huffmanBytes
) {
StringBuilder stringBuilder
= new StringBuilder();
for (int i
= 0; i
< huffmanBytes
.length
; i
++) {
boolean flag
= (i
== huffmanBytes
.length
- 1);
stringBuilder
.append(byteToBitString(!flag
, huffmanBytes
[i
]));
}
Map
<String, Byte> map
= new HashMap<String, Byte>();
for (Map
.Entry
<Byte, String> entry
: huffmanCodes
.entrySet()) {
map
.put(entry
.getValue(), entry
.getKey());
}
List
<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
) {
count
++;
} else {
flag
= false;
}
}
list
.add(b
);
i
+= count
;
}
byte[] b
= new byte[list
.size()];
for (int i
= 0; i
< b
.length
; i
++) {
b
[i
] = list
.get(i
);
}
return b
;
}
private static String
byteToBitString(boolean flag
, byte b
) {
int temp
= b
;
if (flag
) {
temp
|= 256;
}
String str
= Integer
.toBinaryString(temp
);
if (flag
) {
str
= str
.substring(str
.length() - 8);
}
return str
;
}
private static byte[] huffmanZip(byte[] bytes
) {
List
<Node> nodes
= getNodes(bytes
);
Node huffmanTreeRoot
= createHuffmanTree(nodes
);
Map
<Byte, String> huffmanCodes
= getCodes(huffmanTreeRoot
);
byte[] huffmanCodeBytes
= zip(bytes
, huffmanCodes
);
return huffmanCodeBytes
;
}
public static byte[] zip(byte[] bytes
, Map
<Byte, String> huffmanCodes
) {
StringBuilder stringBuilder
= new StringBuilder();
for (byte b
: bytes
) {
stringBuilder
.append(huffmanCodes
.get(b
));
}
int len
;
if (stringBuilder
.length() % 8 == 0) {
len
= stringBuilder
.length() / 8;
} else {
len
= stringBuilder
.length() / 8 + 1;
}
byte[] huffmanCondeBytes
= new byte[len
];
int index
= 0;
for (int i
= 0; i
< stringBuilder
.length(); i
+= 8) {
String strByte
;
if (i
+ 8 > stringBuilder
.length()) {
strByte
= stringBuilder
.substring(i
);
} else {
strByte
= stringBuilder
.substring(i
, i
+ 8);
}
huffmanCondeBytes
[index
] = (byte) Integer
.parseInt(strByte
, 2);
index
++;
}
return huffmanCondeBytes
;
}
private static Map
<Byte, String> getCodes(Node root
) {
if (root
== null
) {
return null
;
} else {
getCodes(root
.left
, "0", stringBuilder
);
getCodes(root
.right
, "1", stringBuilder
);
}
return huffmanCodes
;
}
private static List
<Node> getNodes(byte[] bytes
) {
ArrayList
<Node> nodes
= new ArrayList<>();
Map
<Byte, Integer> counts
= new HashMap<>();
for (byte b
: bytes
) {
counts
.merge(b
, 1, Integer
::sum
);
}
for (Map
.Entry
<Byte, Integer> entry
: counts
.entrySet()) {
nodes
.add(new Node(entry
.getKey(), entry
.getValue()));
}
return nodes
;
}
private static Node
createHuffmanTree(List
<Node> nodes
) {
while (nodes
.size() > 1) {
Collections
.sort(nodes
);
Node leftNode
= nodes
.get(0);
Node rightNode
= nodes
.get(1);
Node parent
= new Node(null
, leftNode
.weight
+ rightNode
.weight
);
parent
.left
= leftNode
;
parent
.right
= rightNode
;
nodes
.remove(leftNode
);
nodes
.remove(rightNode
);
nodes
.add(parent
);
}
return nodes
.get(0);
}
static Map
<Byte, String> huffmanCodes
= new HashMap<Byte, String>();
static StringBuilder stringBuilder
= new StringBuilder();
private static void getCodes(Node node
, String code
, StringBuilder stringBuilder
) {
StringBuilder stringBuilder1
= new StringBuilder(stringBuilder
);
stringBuilder1
.append(code
);
if (node
!= null
) {
if (node
.data
== null
) {
getCodes(node
.left
, "0", stringBuilder1
);
getCodes(node
.right
, "1", stringBuilder1
);
} else {
huffmanCodes
.put(node
.data
, stringBuilder1
.toString());
}
}
}
private static void preOrder(Node root
) {
if (root
!= null
) {
root
.preOrder();
} else {
System
.out
.println("该树为空!");
}
}
}
class Node implements Comparable<Node> {
Byte data
;
int weight
;
Node left
;
Node right
;
public Node(Byte data
, int weight
) {
this.data
= data
;
this.weight
= weight
;
}
@Override
public String
toString() {
return "Node{" +
"data=" + data
+
", weight=" + weight
+
'}';
}
@Override
public int compareTo(Node o
) {
return this.weight
- o
.weight
;
}
public void preOrder() {
System
.out
.print(this.data
+ ":" + this.weight
+ " ");
if (this.left
!= null
) {
this.left
.preOrder();
}
if (this.right
!= null
) {
this.right
.preOrder();
}
}
}
二叉排序树
先看一个需求
给你一个数列 (7, 3, 10, 12, 5, 1, 9),要求能够高效的完成对数据的查询和添加。
解决方案分析
使用数组 数组未排序, 优点:直接在数组尾添加,速度快。 缺点:查找速度慢. 数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保证数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢。使用链式存储-链表不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动。使用二叉排序树
二叉排序树介绍
二叉排序树:BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。 特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点 比如针对前面的数据 (7, 3, 10, 12, 5, 1, 9) ,对应的二叉排序树为:
二叉排序树创建和遍历
一个数组创建成对应的二叉排序树,并使用中序遍历二叉排序树,比如: 数组为 Array(7, 3, 10, 12, 5, 1, 9) , 创建成对应的二叉排序树为 :
代码
package binarysourtree
;
public class BinarySortTreeDemo {
public static void main(String
[] args
) {
int[] arr
= {7, 3, 10, 12, 5, 1, 9};
BinarySortTree binarySortTree
= new BinarySortTree();
for(int a
:arr
){
binarySortTree
.add(new Node(a
));
}
binarySortTree
.infixOrder();
}
}
class BinarySortTree {
private Node root
;
public void add(Node node
) {
if (root
== null
) {
root
= node
;
} else {
root
.add(node
);
}
}
public void infixOrder() {
if (root
!= null
) {
root
.infixOrder();
} else {
System
.out
.println("二叉排序树为空");
}
}
}
class Node {
int value
;
Node left
;
Node right
;
public Node(int value
) {
this.value
= value
;
}
public void add(Node node
) {
if (node
== null
) {
return;
}
if (node
.value
< this.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
);
}
}
}
@Override
public String
toString() {
return "Node{" +
"value=" + value
+
'}';
}
public void infixOrder() {
if (this.left
!= null
) {
this.left
.infixOrder();
}
System
.out
.println(this);
if (this.right
!= null
) {
this.right
.infixOrder();
}
}
}
二叉排序树的删除
二叉排序树的删除情况比较复杂,有下面三种情况需要考虑
删除叶子节点 (比如:2, 5, 9, 12)删除只有一颗子树的节点 (比如:1)删除有两颗子树的节点. (比如:7, 3,10 )
思路分析
代码实现
package binarysourtree
;
public class BinarySortTreeDemo {
public static void main(String
[] args
) {
int[] arr
= {7, 3, 10, 12, 5, 1, 9};
BinarySortTree binarySortTree
= new BinarySortTree();
for (int a
: arr
) {
binarySortTree
.add(new Node(a
));
}
System
.out
.println("中序遍历二叉树");
binarySortTree
.infixOrder();
binarySortTree
.delNode(7);
System
.out
.println("删除节点后· ··");
binarySortTree
.infixOrder();
}
}
class BinarySortTree {
private Node root
;
public Node
search(int value
) {
if (root
== null
) {
return null
;
} else {
return root
.search(value
);
}
}
public Node
searchParent(int value
) {
if (root
== null
) {
return null
;
} else {
return root
.searchParent(value
);
}
}
public int delRightTreeMin(Node node
) {
Node target
= node
;
while (target
.left
!= null
) {
target
= target
.left
;
}
delNode(target
.value
);
return target
.value
;
}
public void delNode(int value
) {
if (root
== null
) {
return;
} else {
Node targetNode
= search(value
);
if (targetNode
== null
) {
return;
}
if (root
.left
== null
&& root
.right
== null
) {
root
= null
;
return;
}
Node parentNode
= searchParent(value
);
if (targetNode
.left
== null
&& targetNode
.right
== null
) {
if (parentNode
.left
!= null
&& parentNode
.left
.value
== value
) {
parentNode
.left
= null
;
} else if (parentNode
.right
!= null
&& parentNode
.right
.value
== value
) {
parentNode
.right
= null
;
}
} else if (targetNode
.left
!= null
&& targetNode
.right
!= null
) {
int minVal
= delRightTreeMin(targetNode
.right
);
targetNode
.value
= minVal
;
} else {
if (targetNode
.left
!= null
) {
if (parentNode
.left
.value
== value
) {
parentNode
.left
= targetNode
.left
;
} else {
parentNode
.right
= targetNode
.left
;
}
} else {
if (parentNode
.left
.value
== value
) {
parentNode
.left
= targetNode
.right
;
} else {
parentNode
.right
= targetNode
.right
;
}
}
}
}
}
public void add(Node node
) {
if (root
== null
) {
root
= node
;
} else {
root
.add(node
);
}
}
public void infixOrder() {
if (root
!= null
) {
root
.infixOrder();
} else {
System
.out
.println("二叉排序树为空");
}
}
}
class Node {
int value
;
Node left
;
Node right
;
public Node
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 Node
search(int value
) {
if (this.value
== value
) {
return this;
} else if (value
< this.value
) {
if (this.left
!= null
) {
return this.left
.search(value
);
} else {
return null
;
}
} else {
if (this.right
== null
) {
return null
;
} else {
return this.right
.search(value
);
}
}
}
public Node(int value
) {
this.value
= value
;
}
public void add(Node node
) {
if (node
== null
) {
return;
}
if (node
.value
< this.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
);
}
}
}
@Override
public String
toString() {
return "Node{" +
"value=" + value
+
'}';
}
public void infixOrder() {
if (this.left
!= null
) {
this.left
.infixOrder();
}
System
.out
.println(this);
if (this.right
!= null
) {
this.right
.infixOrder();
}
}
}
平衡二叉树
看一个案例(说明二叉排序树可能的问题)
给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在. 上图BST 存在的问题分析:
左子树全部为空,从形式上看,更像一个单链表.插入速度没有影响查询速度明显降低(因为需要依次比较), 不能发挥BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢解决方案-平衡二叉树(AVL)
基本介绍
平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树, 可以保证查询效率较高。具有以下特点:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。举例说明, 看看下面哪些AVL树, 为什么?
应用案例-单旋转(左旋转)
要求: 给你一个数列,创建出对应的平衡二叉树.数列 {4,3,6,5,7,8}
思路分析(示意图)
代码实现
private void leftRotate() {
Node newNode
= new Node(value
);
newNode
.left
= left
;
newNode
.right
= right
.left
;
value
= right
.value
;
right
= right
.right
;
left
= newNode
;
}
应用案例-单旋转(右旋转)
要求: 给你一个数列,创建出对应的平衡二叉树.数列 {10,12, 8, 9, 7, 6}
思路分析(示意图)
代码实现
private void rightRotate() {
Node newNode
= new Node(value
);
newNode
.right
= right
;
newNode
.left
= left
.right
;
value
= left
.value
;
left
= left
.left
;
right
= newNode
;
}
应用案例-双旋转
前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列
int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL树.int[] arr = {2,1,6,5,7,3}; // 运行原来的代码可以看到,并没有转成 AVL树
双旋转分析
平衡二叉树完整代码
双旋转主要体现在每次添加节点的时候,根据左右子树判断如何旋转,在调整前,需要根据左右子树的左右子树高度情况判断是否需要对子树的子树进行旋转。即结点对象Node的add方法中后两个if语句中的if判断语句。
package avl
;
import java
.util
.Map
;
public class AVLTreeDemo {
public static void main(String
[] args
) {
int[] arr
= {10, 11, 7, 6, 8, 9};
AVLTree avlTree
= new AVLTree();
for (int a
: arr
) {
avlTree
.add(new Node(a
));
}
System
.out
.println("中序遍历···");
avlTree
.infixOrder();
System
.out
.println("平衡处理后~");
System
.out
.println("树的高度:" + avlTree
.getRoot().height());
System
.out
.println("树的左子树高度:" + avlTree
.getRoot().leftHeight());
System
.out
.println("树的右子树高度:" + avlTree
.getRoot().rightHeight());
}
}
class AVLTree {
private Node root
;
public Node
getRoot() {
return root
;
}
public Node
search(int value
) {
if (root
== null
) {
return null
;
} else {
return root
.search(value
);
}
}
public Node
searchParent(int value
) {
if (root
== null
) {
return null
;
} else {
return root
.searchParent(value
);
}
}
public int delRightTreeMin(Node node
) {
Node target
= node
;
while (target
.left
!= null
) {
target
= target
.left
;
}
delNode(target
.value
);
return target
.value
;
}
public void delNode(int value
) {
if (root
== null
) {
return;
} else {
Node targetNode
= search(value
);
if (targetNode
== null
) {
return;
}
if (root
.left
== null
&& root
.right
== null
) {
root
= null
;
return;
}
Node parentNode
= searchParent(value
);
if (targetNode
.left
== null
&& targetNode
.right
== null
) {
if (parentNode
.left
!= null
&& parentNode
.left
.value
== value
) {
parentNode
.left
= null
;
} else if (parentNode
.right
!= null
&& parentNode
.right
.value
== value
) {
parentNode
.right
= null
;
}
} else if (targetNode
.left
!= null
&& targetNode
.right
!= null
) {
int minVal
= delRightTreeMin(targetNode
.right
);
targetNode
.value
= minVal
;
} else {
if (targetNode
.left
!= null
) {
if (parentNode
!= null
) {
if (parentNode
.left
.value
== value
) {
parentNode
.left
= targetNode
.left
;
} else {
parentNode
.right
= targetNode
.left
;
}
} else {
root
= targetNode
.left
;
}
} else {
if (parentNode
!= null
) {
if (parentNode
.left
.value
== value
) {
parentNode
.left
= targetNode
.right
;
} else {
parentNode
.right
= targetNode
.right
;
}
} else {
root
= targetNode
.right
;
}
}
}
}
}
public void add(Node node
) {
if (root
== null
) {
root
= node
;
} else {
root
.add(node
);
}
}
public void infixOrder() {
if (root
!= null
) {
root
.infixOrder();
} else {
System
.out
.println("二叉排序树为空");
}
}
}
class Node {
int value
;
Node left
;
Node right
;
public Node(int value
) {
this.value
= value
;
}
private void leftRotate() {
Node newNode
= new Node(value
);
newNode
.left
= left
;
newNode
.right
= right
.left
;
value
= right
.value
;
right
= right
.right
;
left
= newNode
;
}
private void rightRotate() {
Node newNode
= new Node(value
);
newNode
.right
= right
;
newNode
.left
= left
.right
;
value
= left
.value
;
left
= left
.left
;
right
= newNode
;
}
public int height() {
return Math
.max(left
== null
? 0 : left
.height(), right
== null
? 0 : right
.height()) + 1;
}
public int leftHeight() {
return left
== null
? 0 : left
.height();
}
public int rightHeight() {
return right
== null
? 0 : right
.height();
}
public Node
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 Node
search(int value
) {
if (this.value
== value
) {
return this;
} else if (value
< this.value
) {
if (this.left
!= null
) {
return this.left
.search(value
);
} else {
return null
;
}
} else {
if (this.right
== null
) {
return null
;
} else {
return this.right
.search(value
);
}
}
}
public void add(Node node
) {
if (node
== null
) {
return;
}
if (node
.value
< this.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
);
}
}
if (rightHeight() - leftHeight() > 1) {
if (right
!= null
&& right
.leftHeight() > right
.rightHeight()) {
right
.rightRotate();
leftRotate();
}else{
leftRotate();
}
return;
}
if (leftHeight() - rightHeight() > 1) {
if (left
!= null
&& left
.rightHeight() > left
.leftHeight()) {
left
.leftRotate();
rightRotate();
}else{
rightRotate();
}
}
}
@Override
public String
toString() {
return "Node{" +
"value=" + value
+
'}';
}
public void infixOrder() {
if (this.left
!= null
) {
this.left
.infixOrder();
}
System
.out
.println(this);
if (this.right
!= null
) {
this.right
.infixOrder();
}
}
}