数组,链式,树结构的优缺点分析
数组:通过下标访问元素速度快,但是如果检索某个值或者插入值会导致数组的整体移动,效率较低
链式:插入数值或者删除数值效率较高,而在检索时效率较低
树结构:提高数据读取,存储效率,既可以保障数据的检索速度,同时也可以保障数据的插入,删除,修改的速度
关于树的一些概念
节点
根节点
父节点
子节点
叶子节点:没有子节点的节点
节点的权:节点值
满二叉树:所有叶子节点都在最后一层,并且节点总数为2的n次方-1,n为层数
完全二叉树:所有叶子节点都在最后一层或者倒数第二层,并且最后一层的叶子节点在左面连续,倒数第二层在右面连续
二叉树的前序,中序,后序遍历
1.概念描述:
前序遍历:先输出父节点,再遍历左子树,最后右子树
中序遍历:先遍历左子树,再输出父节点,最后右子树
后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
2.思路分析
前序遍历:先输出当前节点,如果左子节点不为空,就递归前序遍历,如果右子节点不为空,就递归前序遍历
中序遍历:如果左子节点不为空,就递归中序遍历,输出当前节点,如果右子节点不为空,就递归中序遍历
后序遍历:如果左子节点不为空,就递归后序遍历,如果右子节点不为空,就递归后序遍历,输出当前节点
3.代码实现
package tree;
public class BinaryTreeDemo {
public static void main(String[] args) {
StudentNode root = new StudentNode(1,"qq");
StudentNode node2 = new StudentNode(2,"ww");
StudentNode node3 = new StudentNode(3,"ee");
StudentNode node4 = new StudentNode(4,"rr");
StudentNode node5 = new StudentNode(5,"dd");
BinaryTree binaryTree = new BinaryTree();
binaryTree.setRoot(root);
root.setLeft(node2);
root.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
binaryTree.preOrder();
binaryTree.infixOrder();
binaryTree.afterOrder();
}
}
//二叉树类
class BinaryTree{
private StudentNode root ;
public void setRoot(StudentNode root) {
this.root = root;
}
//前序遍历
public void preOrder(){
if(this.root != null){
this.root.preOrder();
}
}
//中序遍历
public void infixOrder(){
if(this.root != null){
this.root.infixOrder();
}
}
//后序遍历
public void afterOrder(){
if(this.root != null){
this.root.afterOrder();
}
}
}
//节点类
class StudentNode{
private int id;
private String name;
//左子节点
private StudentNode left;
//右子节点
private StudentNode right;
public StudentNode(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public StudentNode getLeft() {
return left;
}
public void setLeft(StudentNode left) {
this.left = left;
}
public StudentNode getRight() {
return right;
}
public void setRight(StudentNode right) {
this.right = right;
}
@Override
public String toString() {
return "StudentNode{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
//前序遍历
public void preOrder(){
System.out.println(this);
if(this.left != null){
this.left.preOrder();
}
if(this.right != null){
this.right.preOrder();
}
}
//中序遍历
public void infixOrder(){
if(this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null){
this.right.infixOrder();
}
}
//后序遍历
public void afterOrder(){
if(this.left != null){
this.left.afterOrder();
}
if(this.right != null){
this.right.afterOrder();
}
System.out.println(this);
}
}
二叉树前序,中序,后序查找
1.思路分析
2.代码实现
//节点类
//前序查找
public StudentNode preSearch(int id){
System.out.println("前序查找");
if(this.id == id){
return this;
}
StudentNode sn = null;
if(this.left!=null){
sn = this.left.preSearch(id);
}
if(sn != null){
return sn;
}
if(this.right!=null){
sn = this.right.preSearch(id);
}
return sn;
}
//中序查找
public StudentNode midSearch(int id){
StudentNode sn = null;
if(this.left!=null){
sn = this.left.midSearch(id);
}
if(sn != null){
return sn;
}
System.out.println("中序查找");
if(this.id == id){
return this;
}
if(this.right!=null){
sn = this.right.midSearch(id);
}
return sn;
}
//后序查找
public StudentNode afterSearch(int id){
StudentNode sn = null;
if(this.left!=null){
sn = this.left.afterSearch(id);
}
if(sn != null){
return sn;
}
if(this.right!=null){
sn = this.right.afterSearch(id);
}
if(sn != null){
return sn;
}
System.out.println("后序查找");
if(this.id == id){
return this;
}
return sn;
}
//二叉树类
//前序查找
public StudentNode preSearch(int id){
if(this.root!=null){
return this.root.preSearch(id);
}else {
return null;
}
}
//中序查找
public StudentNode midSearch(int id){
if(this.root!=null){
return this.root.midSearch(id);
}else {
return null;
}
}
//后序查找
public StudentNode afterSearch(int id){
if(this.root!=null){
return this.root.afterSearch(id);
}else {
return null;
}
}
二叉树的删除
1.规定:如果是叶子节点直接删除,如果是非叶子节点,就删除该子树
2.思路分析:因为二叉树是单向的,所以需要判断当前节点的子节点是否需要删除
如果当前节点的左子节点存在,并且就是要删除的节点,就将当前节点的左子节点设置为空
如果当前节点的右子节点存在,并且就是要删除的节点,就将当前节点的右子节点设置为空
如果以上两步都没有找到需要删除的节点,那么就向左递归
如果以上三步都没有找到需要删除的节点,那么就向右递归
注意:需要先判断root节点是否是需要删除的节点
3.代码实现
//节点类
public void delete(int id){
if(this.left!=null && this.left.id == id){
this.left = null;
return;
}
if(this.right!=null && this.right.id == id){
this.right = null;
return;
}
if(this.left!=null){
this.left.delete(id);
}
if(this.right!=null){
this.right.delete(id);
}
}
//删除节点
public void delete(int id){
if(root.getId() == id){
root = null;
}else{
root.delete(id);
}
}
顺序存储二叉树
1.概念:数组存储方式可以与树存储方式相互转换,要求遍历数组时依然可以实现前序,中序,后序遍历
2.特点描述:通常只考虑完全二叉树,第n个元素的左子节点为2n+1,第n个元素的右子节点为2n+2,第n个元素的父节点为(n-1)/2,n表示二叉树中的第几个元素(从0开始)
3.代码实现
package tree;
public class ArrayBinaryTree {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7};
ArrayTree at = new ArrayTree(arr);
at.preOrder();
}
}
class ArrayTree{
private int[] arr;
public ArrayTree(int[] arr) {
this.arr = arr;
}
public void preOrder(){
preOrder(0);
}
//前序遍历
public void preOrder(int index){
if(arr == null || arr.length == 0){
System.out.println("数组为空");
}
System.out.println(arr[index]);
if((index*2+1) < arr.length){
preOrder(index*2+1);
}
if((index*2+2) < arr.length){
preOrder(index*2+2);
}
}
}
线索化二叉树
1.简介:有二叉树
此时中序遍历结果为:8,3,10,1,14,6,此时8,10,14的左右节点都为空,6的右节点为空,我们希望充分利用这些节点,所以我们让8的左子节点指向前驱节点(null),让8的右子节点指向后继节点(3),以此类推
2.问题注意:当你根据1的操作完成线索化二叉树后,有以下问题,left连接的可能是左子树,也有可能是前驱节点,right连接的可能是右子树,也有可能是后继节点,需要一个变量来区分
3.代码实现
package tree.threadedBinaryTree;
public class ThreadedBinaryTree {
public static void main(String[] args) {
StudNode root = new StudNode(1,"11");
StudNode sn1 = new StudNode(3,"33");
StudNode sn2 = new StudNode(6,"66");
StudNode sn3 = new StudNode(8,"88");
StudNode sn4 = new StudNode(10,"1010");
StudNode sn5 = new StudNode(14,"1414");
root.setLeft(sn1);
root.setRight(sn2);
sn1.setLeft(sn3);
sn1.setRight(sn4);
sn2.setLeft(sn5);
ThreadedBinary tb = new ThreadedBinary();
tb.setRoot(root);
tb.threadedTree(root);
System.out.println(sn2.getLeft());
System.out.println(sn2.getRight());
}
}
//二叉树类
class ThreadedBinary{
private StudNode root ;
private StudNode pre = null;
public void setRoot(StudNode root) {
this.root = root;
}
//线索化二叉树
public void threadedTree(StudNode sn){
//中序线索化二叉树
if(sn.getLeft() != null){
threadedTree(sn.getLeft());
}
if(sn.getLeft()==null){
sn.setLeft(pre);
sn.setLeftType(1);
}
if(pre!=null && pre.getRight()==null){
pre.setRight(sn);
pre.setRightType(1);
}
pre=sn;
if(sn.getRight() != null){
threadedTree(sn.getRight());
}
}
//前序遍历
public void preOrder(){
if(this.root != null){
this.root.preOrder();
}
}
//中序遍历
public void infixOrder(){
if(this.root != null){
this.root.infixOrder();
}
}
//后序遍历
public void afterOrder(){
if(this.root != null){
this.root.afterOrder();
}
}
}
//节点类
class StudNode{
private int id;
private String name;
//左子节点
private StudNode left;
//右子节点
private StudNode right;
//为线索二叉树做准备
//左右节点的状态,0表示连接的是左子节点或者右子节点,1表示连接的是前驱节点或者后继节点
private int leftType;
private int rightType;
public int getLeftType() {
return leftType;
}
public void setLeftType(int leftType) {
this.leftType = leftType;
}
public int getRightType() {
return rightType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
public StudNode(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public StudNode getLeft() {
return left;
}
public void setLeft(StudNode left) {
this.left = left;
}
public StudNode getRight() {
return right;
}
public void setRight(StudNode right) {
this.right = right;
}
@Override
public String toString() {
return "StudentNode{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
//前序遍历
public void preOrder(){
System.out.println(this);
if(this.left != null){
this.left.preOrder();
}
if(this.right != null){
this.right.preOrder();
}
}
//中序遍历
public void infixOrder(){
if(this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null){
this.right.infixOrder();
}
}
//后序遍历
public void afterOrder(){
if(this.left != null){
this.left.afterOrder();
}
if(this.right != null){
this.right.afterOrder();
}
System.out.println(this);
}
}
4.中序线索化二叉树遍历代码实现
//遍历中序线索化二叉树
public void midOrderThreaded(){
StudNode sn = root;
while(sn != null){
//一直向左找,直到找到8为止
while(sn.getLeftType()==0){
sn = sn.getLeft();
}
System.out.println(sn);
//只要是后继节点就继续输出
while(sn.getRightType()==1){
sn = sn.getRight();
System.out.println(sn);
}
sn = sn.getRight();
}
}
赫夫曼树
1.基本概念介绍:
路径:在一棵树中,从一个节点往下可以达到的孩子或者孙子节点之间的通路
路径长度:通路中分支的数目
节点的权:若将树中节点赋给一个有着某种含义的数值,那么这个数值称之为节点的权
带权路径长度:从根节点到该节点之间的路径长度与该节点的权的乘积
树的带权路径长度:所有叶子节点的带权路径长度之和,记为WPL,权值越大的节点距离根节点越近的二叉树才是最优二叉树,WPL最小的就是赫夫曼树
2.赫夫曼树思路分析:
假如有数列{13,7,8,3,29,6,1}形成赫夫曼树
a.先将该数列从小到大排序,将每一个数字看成一个节点,把每个节点看成一个二叉树
b.取出根节点权值最小的两棵二叉树
c.组成一个新的二叉树,该新二叉树的根节点的权值是前面两个二叉树根节点权值的总和
d.再将新二叉树,在次排序,重复上述过程,直到该数列只剩下一个值,此时赫夫曼树成功
3.代码实现
package huffmantree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HuffmanTree {
public static void main(String[] args) {
int[] arr = {13,7,8,3,29,6,1};
Node root = huffManTree(arr);
preNode(root);
}
//生成赫夫曼树
public static Node huffManTree(int[] arr){
List<Node> list = new ArrayList<Node>();
for(int val : arr){
list.add(new Node(val));
}
while(list.size()>1){
//排序
Collections.sort(list);
//取最小的两个
Node left = list.get(0);
Node right = list.get(1);
//生成新的
Node nNode = new Node(left.value+right.value);
nNode.left = left;
nNode.right = right;
list.remove(left);
list.remove(right);
list.add(nNode);
}
return list.get(0);
}
//前序遍历
public static void preNode(Node root){
root.preNode();
}
}
class Node implements Comparable<Node>{
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
public void preNode(){
System.out.println(this);
if(this.left!=null){
this.left.preNode();
}
if(this.right!=null){
this.right.preNode();
}
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//排序
@Override
public int compareTo(Node o) {
return this.value - o.value;
}
}
赫夫曼编码
1.几种编码方式的区别:
定长编码:将字符转换成为二进制码来传递数据,这样效率比较低
变长编码:将字符串中每个字母的数量查询出来,根据数量去转换成二进制码,这样效率较高,但是容易产生二义性
赫夫曼编码:使用赫夫曼树进行编码,将字符串中每个字母的数量查询出来,生成赫夫曼树,根据赫夫曼树生成二进制码,这样效率高,并且没有二义性
2.思路分析:
a.首先将字符串中的每个字母的数量查出来
b.按照上面查出的字母数量生成一个赫夫曼树,权值就是次数
c.根据赫夫曼树,给每一个字符确定编码,这里规定向左为0,向右为1
d.根据赫夫曼编码对字符串进行编码
3.使用赫夫曼编码进行数据压缩和解压
思路:首先将字符串1中的每个字母的数量查出来,之后生成赫夫曼树,得到赫夫曼编码,将字符串1通过赫夫曼编码变成字符串2,再将字符串2 8个一组变成byte,放入byte数组,完成压缩,解压时,先将byte数组变成字符串2,在通过赫夫曼编码将字符串2变成字符串1,完成解压
4.代码实现
package huffmantree;
import sun.rmi.server.InactiveGroupException;
import java.io.*;
import java.util.*;
public class HuffmanCode {
public static void main(String[] args) {
String str = "i like like like java do you like a java cde";
byte[] bytes = str.getBytes();
Byte[] bytes1 = zip(bytes);
System.out.println(Arrays.toString(bytes1));
byte[] bs = decode(mapCodes,bytes1);
System.out.println("解压之后"+new String(bs));
zipFile("d://112.png","d://224.zip");
upZipFile("d://224.zip","d://113.png");
}
//解压缩文件
public static void upZipFile(String fileZip,String file){
FileInputStream fis = null;
ObjectInputStream ois = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(fileZip);
ois = new ObjectInputStream(fis);
Byte[] bs = (Byte[]) ois.readObject();
Map<Byte,String > map = (Map<Byte, String>) ois.readObject();
byte[] b = decode(map,bs);
fos = new FileOutputStream(file);
fos.write(b);
}catch (Exception e){
e.printStackTrace();
}finally {
try {
fis.close();
ois.close();
fos.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
//压缩文件
public static void zipFile(String file,String fileZip){
FileInputStream is = null;
FileOutputStream os = null;
ObjectOutputStream oos = null;
try {
is = new FileInputStream(file);
byte[] bs = new byte[is.available()];
is.read(bs);
Byte[] b = zip(bs);
os = new FileOutputStream(fileZip);
oos = new ObjectOutputStream(os);
oos.writeObject(b);
oos.writeObject(mapCodes);
}catch (Exception e){
e.printStackTrace();
}finally {
try {
is.close();
os.close();
oos.close();
}catch (Exception e){
}
}
}
//解压缩
public static byte[] decode(Map<Byte,String> map,Byte[] bytes){
//将压缩后的数组变成字符串
StringBuilder stringBuilder = new StringBuilder();
for(int i=0;i<bytes.length;i++){
stringBuilder.append(byteToBitString(bytes[i]));
}
stringBuilder.append(last);
//将字符串翻译成为byte数组
Map<String,Byte> map1 = new HashMap<String,Byte>();
for(Map.Entry<Byte,String> entry:map.entrySet()){
map1.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 str = stringBuilder.substring(i,i+count);
b = map1.get(str);
if(b == null){
count++;
}else{
flag = false;
}
}
list.add(b);
i+=count;
}
byte[] bs = new byte[list.size()];
for(int i =0;i<list.size();i++){
bs[i] = list.get(i);
}
return bs;
}
//将一个byte转换成字符串
public static String byteToBitString(byte b){
int temp = b;
temp |= 256;
String str = Integer.toBinaryString(temp);
return str.substring(str.length()-8);
}
//压缩
public static Byte[] zip(byte[] bytes){
//整理byte数组
List<Code> list = toList(bytes);
//生成赫夫曼树
Code root = toHuffManTree(list);
//按照赫夫曼树得到赫夫曼编码
toHuffmanCode(root,"",stringBuilder);
//得到压缩后的数组
Byte[] bytes1 = tobyteArrays(bytes,mapCodes);
return bytes1;
}
//通过赫夫曼编码翻译str,再将翻译过后的字符串8位一组变成byte[]
public static Byte[] tobyteArrays(byte[] bytes,Map<Byte,String> mapCodes){
//将bytes通过赫夫曼编码生成字符串
StringBuilder stringBuilder = new StringBuilder();
for(byte by : bytes){
stringBuilder.append(mapCodes.get(by));
}
//将字符串8位一组再变成一个新得byte[]起到压缩的目的
int len;
if(stringBuilder.length()%8 == 0){
len = stringBuilder.length()/8;
}else{
len = stringBuilder.length()/8+1;
}
Byte[] returnBytes = new Byte[len-1];
String byteString = null;
int index = 0;
for(int i=0;i<stringBuilder.length();i+=8){
if(i+8>stringBuilder.length()){
byteString = stringBuilder.substring(i);
last = byteString;
}else{
byteString = stringBuilder.substring(i,i+8);
returnBytes[index] = (byte)Integer.parseInt(byteString,2);
index ++;
}
}
return returnBytes;
}
//从赫夫曼树中获取赫夫曼编码
static Map<Byte,String> mapCodes = new HashMap<>();
static StringBuilder stringBuilder = new StringBuilder();
static String last = new String();
public static Map<Byte,String> toHuffmanCode(Code code,String string ,StringBuilder stringBuilder){
StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
stringBuilder2.append(string);
if(code != null){
if(code.byt == null){
//向左递归
toHuffmanCode(code.left,"0",stringBuilder2);
//向右递归
toHuffmanCode(code.right,"1",stringBuilder2);
}else{
mapCodes.put(code.byt,stringBuilder2.toString());
}
}
return mapCodes;
}
//前序遍历
public static void preOrder(Code root){
if(root == null){
return;
}else{
root.preOrder();
}
}
//生成赫夫曼树
public static Code toHuffManTree(List<Code> list){
while(list.size() > 1){
Collections.sort(list);
Code left = list.get(0);
Code right = list.get(1);
Code nCode = new Code(null,left.times+right.times);
nCode.left = left;
nCode.right =right;
list.remove(left);
list.remove(right);
list.add(nCode);
}
return list.get(0);
}
//整理数据,方便生成赫夫曼树
public static List<Code> toList(byte[] bytes){
Map<Byte,Integer> map = new HashMap<Byte,Integer>();
List<Code> list = new ArrayList<Code>();
for(int i=0;i<bytes.length;i++){
if(map.get(bytes[i]) != null){
map.put(bytes[i],map.get(bytes[i])+1);
}else {
map.put(bytes[i],1);
}
}
for(Map.Entry<Byte,Integer> entry : map.entrySet()){
list.add(new Code(entry.getKey(),entry.getValue()));
}
return list;
}
}
class Code implements Comparable<Code>{
Byte byt;
int times;
Code left;
Code right;
public Code(Byte byt, int times) {
this.byt = byt;
this.times = times;
}
@Override
public String toString() {
return "Code{" +
"byt=" + byt +
", times=" + times +
'}';
}
//排序
@Override
public int compareTo(Code o) {
return this.times - o.times;
}
//前序遍历
public void preOrder(){
System.out.println(this);
if(this.left != null){
this.left.preOrder();
}
if(this.right != null){
this.right.preOrder();
}
}
}