这是我参与更文挑战的第6天,活动详情查看: 更文挑战
一、树的基础知识快速了解:
1.1树的基本特点
如上图所示:
- 有一个根节点,一般称为root节点
- 每一个元素都被称为node
- 除了root节点外,其余的节点都会被分成n个互不相交的集合,子树。递归
1.2 树的基本术语
- 结点:树形结构里面的元素
- 子树:当结点大于1时,其余的结点分为的互不相交的集合称为子树
- 度:一个结点拥有的子树数量称为结点的度
- 叶子:度为0的结点
- 孩子:结点的子树的根称为孩子结点
- 双亲:和孩子结点对应
- 兄弟:同一个双亲结点
- 深度:结点的最大层次称为树的深度,计算时间复杂度用
- 森林:由N个互不相交的树构成深林
二、最常用的树-----二叉树
2.1 二叉树 的定义:
- 每个节点至多只有两颗子树
如上图所示,2,3,4是二叉树,1不是。
- 二叉树和满二叉树(也叫平衡二叉树如图三所示)的特点: 满二叉树共有的节点树:2^n-1,n为满二叉树的深度,如图三,共有3层,深度为3,n=3;套入公式得节点树为7。 满二叉树的第n层的节点个数是2^(n-1) 二叉树,可能不是满二叉树,所以节点是小于2^n-1的
2.2二叉树的前中后序遍历
- 前序遍历(根、左、右): A、B、C、D、E、F、G、H、I
- 中序遍历(左、根、右): C、B、D、A、F、E、H、G、I -后序遍历(左、右、根): C、D、B、F、H、I、G、E、A
2.3 代码实现搜索二叉树
如上图所示:中序遍历是有序的才是一颗搜索二叉树
1、2、3、4、5、6、7、8、9
以下代码完成了一刻搜索二叉树的构建、提供了一个中序遍历的方法、查询任意一个节点在不在构建的搜索二叉树中。搜索二叉树的查找效率很高,为 o(logn)(从根节点找,比根节点小,就从左边找,比根节点大,就从右边找)。
/**
* @author liujun
* 手撕一颗搜索二叉树(中序遍历的时候,能从小到大排列)
*/
public class BuildBinarySearchTree {
private int data;
private BuildBinarySearchTree left;
private BuildBinarySearchTree right;
public BuildBinarySearchTree(int data) {
this.data = data;
}
public BuildBinarySearchTree getLeft() {
return left;
}
public void setLeft(BuildBinarySearchTree left) {
this.left = left;
}
public BuildBinarySearchTree getRight() {
return right;
}
public void setRight(BuildBinarySearchTree right) {
this.right = right;
}
//提供构建一颗搜索二叉树(中序遍历的时候必须是有序的)方法
public static void insert(BuildBinarySearchTree root,int data){
//新插入的节点比根节点大,则放入根结点的右边
if (root.data<data){
if (root.getRight()==null){
BuildBinarySearchTree buildBinarySearchTree = new BuildBinarySearchTree(data);
root.setRight(buildBinarySearchTree);
}else {
insert(root.getRight(),data);
}
}else if (root.data>data){
//新插入的节点比根节点大,则放入根结点的左边
if (root.getLeft()==null){
BuildBinarySearchTree buildBinarySearchTree = new BuildBinarySearchTree(data);
root.setLeft(buildBinarySearchTree);
}else {
insert(root.getLeft(),data);
}
}
}
//提供一个中序遍历的方法
static void in(BuildBinarySearchTree root){
if (root.getLeft()!=null)
in(root.getLeft());
System.out.println(root.data);
if (root.getRight()!=null)
in(root.getRight());
}
static int findCount = 0;
//提供一个查找的方法
static void findData(BuildBinarySearchTree root,int searchData){
try {
if (root.data<searchData){
findCount++;
findData(root.getRight(),searchData);
}else if(root.data>searchData){
findCount++;
findData(root.getLeft(),searchData);
}else {
System.out.println("一共查找了:"+findCount+"次"+"找到的结点是:"+ root+" 找的数是:"+ root.data);
}
}catch (NullPointerException e){
System.out.println("该二叉搜索树中没有查找到...");
}
}
public static void main(String[] args) {
int[] data = {5,4,7,3,1,9,6,2,8};
BuildBinarySearchTree buildBinarySearchTree = new BuildBinarySearchTree(data[0]);
for (int i = 1; i < data.length; i++) {
insert(buildBinarySearchTree,data[i]);
}
//遍历输出,看搜索二叉树是否排列好
in(buildBinarySearchTree);
//从构建好的搜索二叉树查询某个数是否存在二叉树之中
findData(buildBinarySearchTree,3);
}
}
执行结果
1
2
3
4
5
6
7
8
9
一共查找了:2次找到的结点是:com.jack.tree.BuildBinarySearchTree@6d6f6e28 找的数是:3
2.4 最优二叉树----哈夫曼树
要求带权重路径的总和最短
(a):72+52+22+42=36
(b):73+53+21+42=46
(c):71+52+23+43=35
哈夫曼树在战争期间常被用来做电报的加解密树 如c所示 a----编码后-->1 b----编码后-->001 c----编码后-->000 d----编码后--->0001
代码实现哈夫曼树
/**
* @author liujun
* 实现一颗哈夫曼树,并提供加解密方法
*/
class MyNode implements Comparable<MyNode>{
private String data;
private int weight;
private MyNode parentNode;
private MyNode leftNode;
private MyNode rightNode;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public MyNode getParentNode() {
return parentNode;
}
public void setParentNode(MyNode parentNode) {
this.parentNode = parentNode;
}
public MyNode getLeftNode() {
return leftNode;
}
public void setLeftNode(MyNode leftNode) {
this.leftNode = leftNode;
}
public MyNode getRightNode() {
return rightNode;
}
public void setRightNode(MyNode rightNode) {
this.rightNode = rightNode;
}
@Override
public int compareTo(MyNode o) {
return this.weight>o.weight?1:-1;
}
}
public class HumanTree {
private MyNode root;
private List<MyNode> leafNodeList;
private Map<Character,Integer> weightMap;
public HumanTree(Map<Character, Integer> weightMap) {
this.leafNodeList = new ArrayList<>();
this.weightMap = weightMap;
}
//构建树
public void createTree(){
//使用优先队列从小到大排列
PriorityBlockingQueue<MyNode> myNodeQueue = new PriorityBlockingQueue<>();
Set<Character> characters = weightMap.keySet();
for (Character character : characters) {
MyNode myNode = new MyNode();
myNode.setData(character+"");
myNode.setWeight(weightMap.get(character));
myNodeQueue.add(myNode);
leafNodeList.add(myNode);
}
//合
for (int i = 0; i < leafNodeList.size()-1; i++) {
//取出最小的两个做叶子节点
MyNode leftNode = myNodeQueue.poll();
MyNode rightNode = myNodeQueue.poll();
MyNode parentNode = new MyNode();
//断言
assert leftNode != null;
assert rightNode != null;
parentNode.setData(leftNode.getData()+rightNode.getData());
parentNode.setLeftNode(leftNode);
parentNode.setRightNode(rightNode);
leftNode.setParentNode(parentNode);
rightNode.setParentNode(parentNode);
myNodeQueue.add(parentNode);
}
//队列的最后一个作为整颗哈夫曼数的根节点
root=myNodeQueue.poll();
System.out.println("哈夫曼树构建完成");
}
//编码
public Map<String,String> encode(){
Map<String,String> encodeMap = new HashMap<>();
for (MyNode currentNode : leafNodeList) {
MyNode temp = currentNode;
String code = "";
while (currentNode.getParentNode() != null) {
if (currentNode.getParentNode().getLeftNode() == currentNode) {
code = 0 + code;
currentNode = currentNode.getParentNode();
} else {
code = 1 + code;
currentNode = currentNode.getParentNode();
}
}
encodeMap.put(temp.getData(), code);
}
return encodeMap;
}
//解码
Map<String,String> decodeMap =new LinkedHashMap<>();
public Map<String,String> decode(List<String> encodeList){
for (String encode : encodeList) {
MyNode currentNode = new MyNode();
MyNode localRoot = root;
for (int i = 0; i < encode.length(); i++) {
char c = encode.charAt(i);
if (c == '0'&& localRoot.getLeftNode()!=null){
currentNode = localRoot.getLeftNode();
localRoot = localRoot.getLeftNode();
}else if (c=='1' && localRoot.getRightNode()!=null){
currentNode = localRoot.getRightNode();
localRoot = localRoot.getRightNode();
}else {
//如果在最后一个 字节未找到该节点证明在哈夫曼树中没有对应的解密报文
System.out.println("未找到该节点的解密报文"+encode);
}
}
//找到了就把密文和解密报文存入map
decodeMap.put(encode,currentNode.getData());
}
return decodeMap;
}
public static void main(String[] args) {
//定义报文和报文的权重
Map<Character,Integer> weightMap = new HashMap<>();
weightMap.put('A',7);
weightMap.put('B',5);
weightMap.put('C',2);
weightMap.put('D',4);
weightMap.put('E',8);
HumanTree humanTree = new HumanTree(weightMap);
//构建哈夫曼树
humanTree.createTree();
//
Map<String, String> encode = humanTree.encode();
List<String> encodeList = new ArrayList<>();
//遍历加密
System.out.println("编码");
for (String s : encode.keySet()) {
System.out.println(s+"-->"+encode.get(s));
encodeList.add(encode.get(s));
}
//遍历解密
System.out.println("-----------");
System.out.println("解码");
Map<String, String> decodeMap = humanTree.decode(encodeList);
for (String s : decodeMap.keySet()) {
System.out.println(s+"---->"+decodeMap.get(s));
}
}
}
//运行结果
哈夫曼树构建完成
编码
A-->01
B-->001
C-->0000
D-->0001
E-->1
-----------
解码
01---->A
001---->B
0000---->C
0001---->D
1---->E
2.5 代码实现Collections.sort()底层用到的合并排序
如上图所示,把一个数组,不断拆分为最小的粒度,然后再排序、合并
代码实现:
/**
* 模拟了合并排序
*/
public class MergeSort {
public static void mergeSort(int[] data, int left, int right) {
if (left < right) {
//分
//递归二分化为最小单位
int mid = (left + right) / 2;
mergeSort(data, left, mid);
mergeSort(data, mid + 1, right);
//合
merge(data,left,mid,right);
}
}
public static void merge(int[] data,int left,int mid,int right){
//定义一个临时的数组
int[] temp = new int[data.length];
//定义两个指针,记录要比较数,左边第一个
int point1 = left;
int point2 = mid+1;
//定义一个临时的下标,记录插入temp数组的角标
int local = left;
//小的数插入临时数组的左边
while (point1<=mid && point2 <=right){
if (data[point1]<data[point2]){
temp[local] = data[point1];
point1++;
local++;
}else {
temp[local] = data[point2];
point2++;
local++;
}
}
//防止左边还有的数没有插入
while (point1<=mid){
temp[local++] = data[point1++];
}
//防止右边还有的数没有插入
while (point2<=right){
temp[local++] = data[point2++];
}
//把临时数组的值赋值给原数组
if (right + 1 - left >= 0) System.arraycopy(temp, left, data, left, right + 1 - left);
}
public static void main(String[] args) {
int[] data = {1, 3, 4, 5, 63, 2, 4, 6, 9, 10,-1};
mergeSort(data,0,data.length-1);
System.out.println(Arrays.toString(data));
}
}
//运行结果
[-1, 1, 2, 3, 4, 4, 5, 6, 9, 10, 63]
Process finished with exit code 0
2.6代码实现hashMap
(红黑树那里未实现)
/**
* 实现hashMap中数组加链表的数据结构
* @param <K>
* @param <V>
*/
class Entry<K, V> {
//键
public K key;
//值
public V value;
//模拟hash冲突的时候,链表的数据结构
public Entry<K, V> next;
//hash冲突的个数
public int cap;
public Entry(K key, V value, Entry<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
}
public class MyHashMap<K, V> {
//MyHashMap的默认容量
private static final int Default_size = 1 << 4;
//模拟hashMap的数组结构
private Entry<K, V>[] data;
//用户自定义的hashMap容量
private int capacity;
//记录数组已添加了的entry个数,方便0.75f扩容
private int size;
//提供无参构造方法和有参构造法
public MyHashMap() {
this(Default_size);
}
public MyHashMap(int capacity) {
//判断用户传入的容量
if (capacity <= 0) {
this.capacity = Default_size;
} else {
this.capacity = capacity;
}
data = new Entry[capacity];
}
//提供一个put方法
public void put(K key, V value) {
//先对传入的key进行hash
int hashKey = hash(key);
//新建一个entry
Entry<K, V> newEntry = new Entry<>(key, value, null);
Entry<K, V> entry = data[hashKey];
//发生了hash冲突,比较链表的key
while (entry != null) {
if (entry.key.equals(key)) {
//键相同,值覆盖
entry.value = value;
//下面的操作都不执行了
return;
}
//不相等的话遍历链表,直到找到最后一个entry为null,那么entry下移,头插新的entry
entry = entry.next;
}
newEntry.next = entry;
data[hashKey] = newEntry;
size++;
}
//提供一个get方法
public V get(K key) {
//hashMap可以允许有一个key为null,这里不模拟那种情况
if (key != null) {
int hash = hash(key);
Entry<K, V> entry = data[hash];
while (entry != null) {
if (entry.key.equals(key)) {
return entry.value;
}
entry = entry.next;
}
}
return null;
}
//引用源码的hash方法
int hash(Object key) {
int h = 0;
if (key == null)
h = 0;
else {
h = key.hashCode() ^ (h >>> 16); // 无符号右移16位
}
return h % capacity;
}
public static void main(String[] args) {
MyHashMap hashMap = new MyHashMap();
hashMap.put("1", "a");
hashMap.put("2", "b");
hashMap.put("3", "c");
hashMap.put("4", "d");
System.out.println(hashMap.get("1"));
System.out.println(hashMap.get("3"));
System.out.println(hashMap.get("5"));
}
}
//运行结果
a
c
null
Process finished with exit code 0
2.7红黑树的特征:
- 每个结点不是红色就是黑色
- 不可能有连在一起的红色结点
- 根结点都是黑色
- 每个红色结点的两个子结点都是黑色
- 任一结点到其子树中每个叶子节点的路径都有相同数量 的黑色结点
旋转和颜色变换规则: 1.变颜色的情况:当前结点的父亲是红色,且它的祖父结点的另一个子结点 也是红色。(叔叔结点): (1)把父节点设为黑色 (2)把叔叔也设为黑色 (3)把祖父也就是父亲的父亲设为红色 (4)把指针定义到祖父结点设为当前要操作的.
2.左旋:当前父结点是红色,叔叔是黑色的时候,且当前的结点是右子树。左旋 以父结点作为左旋。
3.右旋:当前父结点是红色,叔叔是黑色的时候,且当前的结点是左子树。右旋 (1)把父结点变为黑色 (2)把祖父结点变为红色 (爷爷) (3)以祖父结点旋转(爷爷)
2.8 B+树
B+树作为MySql的索引树,如图所示,他的分叉是大于2的,子树会根据插入的数据,分裂,上移。之所以MySql不采用二叉树,是因为MySql的数据是储存在磁盘上的,磁盘的分区结构,使用B+树的效率高。取一次磁盘扇区的数据,很大一块数据可以一次性取出来。