트리 이론(트리 구조, 이진 트리, 이진 검색 트리, 레드-블랙 트리, B트리, B+트리, 허프만 트리, 힙 트리)
트리 구조 개념
트리 구조의 중요한 용어:
-
노드: 트리의 요소입니다.
-
부모-자식 관계: 노드를 연결하는 가장자리
-
하위 트리: 노드가 1보다 크면 나머지 노드는 하위 트리라는 상호 분리된 집합으로 나뉩니다.
-
차수: 노드가 가진 하위 트리의 수를 노드의 차수라고 합니다.
-
리프: 차수가 0인 노드
-
자식: 노드 하위 트리의 루트를 자식 노드라고 합니다.
-
부모: 자식 노드에 해당
-
형제: 동일한 상위 노드
-
숲: 깊은 숲은 서로 분리된 N개의 나무로 구성됩니다.
-
노드의 높이: 노드에서 리프 노드까지의 가장 긴 경로
-
노드 깊이: 루트 노드에서 노드까지의 간선 수
-
노드의 레이어 수: 노드의 깊이 + 1
-
트리 높이: 루트 노드의 높이
이진 트리
이진 트리: 특별한 트리 구조로, 각 노드는 최대 두 개의 하위 트리를 가집니다.
이진 트리의 N번째 수준에는 최대 2^(N-1)
하나의 노드가 있습니다. 2^N-1
최대 노드 수가 있습니다 .
분류:
- 완전 이진 트리: 리프 노드 외에도 각 노드에는 두 개의 왼쪽 및 오른쪽 자식 노드가 있습니다.
- 완전한 이진 트리: 마지막 레이어를 제외하고 다른 노드의 수가 최대에 도달해야 하며 마지막 레이어의 노드는 모두 왼쪽으로 연속적으로 배열됩니다.
생각하다
완전 이진 트리와 완전 이진 트리를 나누어야 하는 이유는 무엇입니까? 완전한 이진 트리는 완전한 이진 트리의 부분 집합일 뿐이라는 것을 정의상 볼 수 있기 때문입니다.
배열: 고성능, 완전한 이진 트리를 위한 공간 낭비만 아니라면
연결 목록: 구현도 가능, 성능은 배열만큼 높지 않음
이진 트리 순회
- 중요한 공식: 루트 노드 출력! 하위 트리
- 서문: 루트 왼쪽 및 오른쪽(ABCDEFGHK)
- 순서: 왼쪽 루트 오른쪽(BCDAEFGHK)
- 후위: 왼쪽 및 오른쪽 루트(BCDEFGHKA)
/**
* 二叉树--前中后链式遍历
* 前: A B C D E F G H K
* 中: B C D A E F G H K
* 后: B C D E F G H K A
* 层:
* A
* B E
* C F
* D G
* H K
*/
@Data
class TreeNode {
private char data;
private TreeNode left;
private TreeNode right;
public TreeNode(char data, TreeNode left, TreeNode right) {
this.data = data;
this.left = left;
this.right = right;
}
}
public class BinaryTree {
public void print(TreeNode node) {
System.out.print(node.getData() + " ");
}
public void pre(TreeNode root) {
//前序:根(输出) 左 右 A B C D E F G H K
print(root);
if (root.getLeft() != null) {
pre(root.getLeft()); //认为是子树,分解子问题
}
if (root.getRight() != null) {
pre(root.getRight());
}
}
public void in(TreeNode root) {
//中序:左 根(输出) 右 B C D A E F G H K
if (root.getLeft() != null) {
pre(root.getLeft()); //认为是子树,分解子问题
}
print(root);
if (root.getRight() != null) {
pre(root.getRight());
}
}
public void post(TreeNode root) {
//后序:左 右 根(输出) B C D E F G H K A
if (root.getLeft() != null) {
pre(root.getLeft()); //认为是子树,分解子问题
}
if (root.getRight() != null) {
pre(root.getRight());
}
print(root);
}
public List<List<Character>> level(TreeNode root) {
//层次遍历
if (root == null) return Collections.EMPTY_LIST;
List<List<Character>> res = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
List<Character> raw = new ArrayList<>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode item = queue.poll();
raw.add(item.getData());
if (item.getLeft() != null) {
queue.add(item.getLeft());
}
if (item.getRight() != null) {
queue.add(item.getRight());
}
}
res.add(raw);
}
return res;
}
public static void main(String[] args) {
TreeNode D = new TreeNode('D', null,null);
TreeNode H = new TreeNode('H', null,null);
TreeNode K = new TreeNode('K', null,null);
TreeNode C = new TreeNode('C', D,null);
TreeNode G = new TreeNode('G', H,K);
TreeNode B = new TreeNode('B', null,C);
TreeNode F = new TreeNode('F', G,null);
TreeNode E = new TreeNode('E', F,null);
TreeNode A = new TreeNode('A', B,E);
BinaryTree tree = new BinaryTree();
System.out.print("前: ");
tree.pre(A);
System.out.println();
System.out.print("中: ");
tree.in(A);
System.out.println();
System.out.print("后: ");
tree.post(A);
System.out.println();
System.out.println("层: ");
List<List<Character>> res = tree.level(A);
for (List<Character> re : res) {
for (Character character : re) {
System.out.print(character + " ");
}
System.out.println();
}
}
}
이진 검색 트리(이진 검색 트리, 이진 정렬 번호)
- 왼쪽 하위 트리가 비어 있지 않으면 왼쪽 하위 트리의 노드 값이 루트 노드보다 작습니다.
- 오른쪽 하위 트리가 비어 있지 않으면 오른쪽 하위 트리의 노드 값이 루트 노드보다 큽니다.
- 하위 트리도 위의 두 가지 사항을 따릅니다.
중위 순회 - 왼쪽 루트(출력) 오른쪽: 0 3 4 5 6 8
성능 분석
- 로그 찾기
- nlogn 삽입
- 삭제
-
- 삭제할 노드는 리프 노드 O(1)
-
- 삭제할 노드는 하나의 하위 트리(왼쪽 또는 오른쪽)만 가집니다. O(1)
-
- 삭제할 노드에는 두 개의 하위 트리가 있습니다. 후속 노드를 찾고 후속 노드의 왼쪽 하위 트리는 비어 있어야 합니다.
-
/**
* 二叉搜索树 增删改查
*/
class BinaryNodeTeacher {
int data;
BinaryNodeTeacher left;
BinaryNodeTeacher right;
BinaryNodeTeacher parent;
public BinaryNodeTeacher(int data) {
this.data = data;
this.left = null;
this.right = null;
this.parent = null;
}
}
public class BinarySearchTreeTeacher {
public BinaryNodeTeacher find(BinaryNodeTeacher root, int key) {
BinaryNodeTeacher current = root;
while (current != null) {
if (key < current.data) {
current = current.left;
} else if (key > current.data) {
current = current.right;
} else {
return current;
}
}
return null;
}
public void insert(BinaryNodeTeacher root, int data) {
if (root.data < data) {
if (root.right != null) {
insert(root.right, data);
} else {
BinaryNodeTeacher newNode = new BinaryNodeTeacher(data);
newNode.parent = root;
root.right = newNode;
}
} else {
if (root.left != null) {
insert(root.left, data);
} else {
BinaryNodeTeacher newNode = new BinaryNodeTeacher(data);
newNode.parent = root;
root.left = newNode;
}
}
}
public BinaryNodeTeacher finSuccessor(BinaryNodeTeacher node) {
// 查找node的后继节点
if (node.right == null) {
// 表示没有右边 那就没有后继
return node;
}
BinaryNodeTeacher cur = node.right;
BinaryNodeTeacher pre = node.right; // 开一个额外的空间 用来返回后继节点,因为我们要找到为空的时候,那么其实返回的是上一个节点
while (cur != null) {
pre = cur;
cur = cur.left; // 注意后继节点是要往左边找,因为右边的肯定比左边的大,我们要找的是第一个比根节点小的,所以只能往左边
}
return pre; // 因为cur会变成null,实际我们是要cur的上一个点,所以就是pre来代替
}
public BinaryNodeTeacher remove(BinaryNodeTeacher root, int data) {
// 删除data
BinaryNodeTeacher delNode = find(root, data);
if (delNode == null) {
System.out.println("要删除的值不在树中");
return root;
}
// 1.删除的点没有左右子树
if (delNode.left == null && delNode.right == null) {
if (delNode == root) {
root = null;
} else if (delNode.parent.data < delNode.data) {
// 说明删除的点是右子节点
delNode.parent.right = null;
} else {
delNode.parent.left = null;
}
} else if (delNode.left != null && delNode.right != null) {
// 2.删除的节点有两颗子节点
BinaryNodeTeacher successor = finSuccessor(delNode); // 先找的后继节点
// 后继节点和删除节点进行交换,首先后继节点的左节点是肯定为空的
successor.left = delNode.left; // 后继的左边变为删除的左边
successor.left.parent = successor; // 删除点的左边parent指向后继节点
// 再来看后继节点的右边
if (successor.right != null && successor.parent != delNode) {
// 后继节点有右边,这其实就是下面情况3的第一种
successor.right.parent = successor.parent;
successor.parent.left = successor.right;
successor.right = delNode.right;
successor.right.parent = successor;
}else if(successor.right == null) {
//如果后继节点没有右边,那其实就是情况1,没有左右子树
if(successor.parent != delNode) {
//如果后继节点的parent不等于删除的点 那么就需要把删除的右子树赋值给后继节点
successor.parent.left = null; //注意原来的后继节点上的引用要删掉,否则会死循环
successor.right = delNode.right;
successor.right.parent = successor;
}
}
// 替换做完接下来就要删除节点了
if (delNode == root) {
successor.parent = null;
root = successor;
return root;
}
successor.parent = delNode.parent;
if (delNode.data > delNode.parent.data) {
// 删除的点在右边,关联右子树
delNode.parent.right = successor;
} else {
delNode.parent.left = successor;
}
} else {
// 3.删除点有一个节点
if (delNode.right != null) {
// 有右节点
if (delNode == root) {
root = delNode.right;
return root;
}
delNode.right.parent = delNode.parent; // 把右节点的parent指向删除点的parent
// 关联父节点的左右子树
if (delNode.data < delNode.parent.data) {
// 删除的点在左边
delNode.parent.left = delNode.right;
} else {
delNode.parent.right = delNode.right;
}
} else {
if (delNode == root) {
root = delNode.left;
return root;
}
delNode.left.parent = delNode.parent;
if (delNode.data < delNode.parent.data) {
delNode.parent.left = delNode.left;
} else {
delNode.parent.right = delNode.left;
}
}
}
return root;
}
public void inOrde(BinaryNodeTeacher root) {
if (root != null) {
inOrde(root.left);
System.out.print(root.data);
inOrde(root.right);
}
}
// 用于获得树的层数
public int getTreeDepth(BinaryNodeTeacher root) {
return root == null ? 0 : (1 + Math.max(getTreeDepth(root.left), getTreeDepth(root.right)));
}
/**
*
* 测试用例
* 15
* 10
* 19
* 8
* 13
* 16
* 28
* 5
* 9
* 12
* 14
* 20
* 30
* -1
* 删除:15 8 5 10 12 19 16 14 30 9 13 20 28
*
* 15
* / \
* 10 19
* / \ / \
* 8 13 16 28
* / \ / \ / \
* 5 9 12 14 20 30
*/
public static void main(String[] args) {
BinarySearchTreeTeacher binarySearchTree = new BinarySearchTreeTeacher();
BinaryNodeTeacher root = null;
Scanner cin = new Scanner(System.in);
int t = 1;
System.out.println("二叉搜索树假定不存重复的子节点,重复可用链表处理,请注意~~");
System.out.println("请输入根节点:");
int rootData = cin.nextInt();
root = new BinaryNodeTeacher(rootData);
System.out.println("请输入第" + t + "个点:输入-1表示结束");
while (true) {
//
int data = cin.nextInt();
if (data == -1)
break;
binarySearchTree.insert(root, data);
t++;
System.out.println("请输入第" + t + "个点:输入-1表示结束");
}
binarySearchTree.show(root); //找的别人写的打印二叉树形结构,感觉还不错,可以更加清晰
System.out.println("删除测试:");
while(true) {
System.out.println("请输入要删除的点:-1表示结束");
int key = cin.nextInt();
root = binarySearchTree.remove(root, key);
binarySearchTree.show(root);
if(root == null) {
System.out.println("树已经没有数据了~~");
break;
}
}
}
private void writeArray(BinaryNodeTeacher currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
// 保证输入的树不为空
if (currNode == null)
return;
// 先将当前节点保存到二维数组中
res[rowIndex][columnIndex] = String.valueOf(currNode.data);
// 计算当前位于树的第几层
int currLevel = ((rowIndex + 1) / 2);
// 若到了最后一层,则返回
if (currLevel == treeDepth)
return;
// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
int gap = treeDepth - currLevel - 1;
// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
if (currNode.left != null) {
res[rowIndex + 1][columnIndex - gap] = "/";
writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
}
// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
if (currNode.right != null) {
res[rowIndex + 1][columnIndex + gap] = "\\";
writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
}
}
public void show(BinaryNodeTeacher root) {
if (root == null) {
System.out.println("EMPTY!");
return ;
}
// 得到树的深度
int treeDepth = getTreeDepth(root);
// 最后一行的宽度为2的(n - 1)次方乘3,再加1
// 作为整个二维数组的宽度
int arrayHeight = treeDepth * 2 - 1;
int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
// 用一个字符串数组来存储每个位置应显示的元素
String[][] res = new String[arrayHeight][arrayWidth];
// 对数组进行初始化,默认为一个空格
for (int i = 0; i < arrayHeight; i++) {
for (int j = 0; j < arrayWidth; j++) {
res[i][j] = " ";
}
}
// 从根节点开始,递归处理整个树
writeArray(root, 0, arrayWidth / 2, res, treeDepth);
// 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
for (String[] line : res) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < line.length; i++) {
sb.append(line[i]);
if (line[i].length() > 1 && i <= line.length - 1) {
i += line[i].length() > 4 ? 2 : line[i].length() - 1;
}
}
System.out.println(sb.toString());
}
}
}
레드-블랙 트리(실험실: AVL 균형 이진 트리) 이진 검색 트리가 연결 목록으로 퇴보함
레드-블랙 트리의 속성:
- 각 노드는 빨간색 또는 검은색입니다.
- Red 노드(검은 노드는 괜찮음)를 연결하는 것은 불가능하며 각 리프 노드는 블랙 빈 노드(NIL)입니다. 즉, 리프 노드는 데이터를 저장하지 않습니다.
- 루트 노드는 모두 검은색 루트입니다.
- 각 노드에 대해 해당 노드에서 도달 가능한 리프 노드까지의 모든 경로에는 동일한 수의 블랙 노드가 포함됩니다.
삽입 시 회전 및 색상 변환 규칙:
- 색상 변경 상황: 현재 노드의 부모 노드는 빨간색이고 조부모 노드의 다른 자식 노드
도 빨간색입니다. (삼촌 노드):
(1) 부모 노드를 흑색으로 설정
(2) 삼촌을 흑색으로 설정
(3) 할아버지, 즉 아버지의 아버지를 적색(할아버지)으로 설정
(4) 포인터를 에 정의 할아버지 노드(Grandpa)는 현재 작업으로 설정됩니다. - 왼쪽 회전: 현재 부모 노드가 빨간색이고 삼촌이 검은색이고 현재 노드가 오른쪽 하위 트리인 경우. 왼손잡이
부모 노드를 왼손잡이로 사용합니다. 부모 노드에 대한 포인터 - 오른쪽 회전: 현재 부모 노드가 빨간색이고 삼촌이 검은색이고 현재 노드가 왼쪽 하위 트리인 경우. 오른쪽 회전
(1) 부모 노드를 검은색으로 바꿉니다.
(2) 할아버지 노드를 빨간색으로 바꿉니다(할아버지).
(3) 할아버지 노드와 함께 회전합니다.(할아버지)
왼손잡이 오른
손잡이
레드-블랙 트리 적용
- 해시맵
- 트리맵
- Windows 하단: 찾기
- Linux 프로세스 스케줄링, nginx 등
복사
public class RedBlackTree {
private final int R = 0;
private final int B = 1;
private class Node {
int key = -1;
int color = B; // 颜色
Node left = nil; // nil表示的是叶子结点
Node right = nil;
Node p = nil;
Node(int key) {
this.key = key;
}
@Override
public String toString() {
return "Node [key=" + key + ", color=" + color + ", left=" + left.key + ", right=" + right.key + ", p=" + p.key + "]" + "\r\n";
}
}
private final Node nil = new Node(-1);
private Node root = nil;
public void printTree(Node node) {
if (node == nil) {
return;
}
printTree(node.left);
System.out.print(node.toString());
printTree(node.right);
}
private void insert(Node node) {
Node temp = root;
if (root == nil) {
root = node;
node.color = B;
node.p = nil;
} else {
node.color = R;
while (true) {
if (node.key < temp.key) {
if (temp.left == nil) {
temp.left = node;
node.p = temp;
break;
} else {
temp = temp.left;
}
} else if (node.key >= temp.key) {
if (temp.right == nil) {
temp.right = node;
node.p = temp;
break;
} else {
temp = temp.right;
}
}
}
fixTree(node);
}
}
private void fixTree(Node node) {
while (node.p.color == R) {
Node y = nil;
if (node.p == node.p.p.left) {
y = node.p.p.right;
if (y != nil && y.color == R) {
node.p.color = B;
y.color = B;
node.p.p.color = R;
node = node.p.p;
continue;
}
if (node == node.p.right) {
node = node.p;
rotateLeft(node);
}
node.p.color = B;
node.p.p.color = R;
rotateRight(node.p.p);
} else {
y = node.p.p.left;
if (y != nil && y.color == R) {
node.p.color = B;
y.color = B;
node.p.p.color = R;
node = node.p.p;
continue;
}
if (node == node.p.left) {
node = node.p;
rotateRight(node);
}
node.p.color = B;
node.p.p.color = R;
rotateLeft(node.p.p);
}
}
root.color = B;
}
void rotateLeft(Node node) {
if (node.p != nil) {
if (node == node.p.left) {
node.p.left = node.right;
} else {
node.p.right = node.right;
}
node.right.p = node.p;
node.p = node.right;
if (node.right.left != nil) {
node.right.left.p = node;
}
node.right = node.right.left;
node.p.left = node;
} else {
Node right = root.right;
root.right = right.left;
right.left.p = root;
root.p = right;
right.left = root;
right.p = nil;
root = right;
}
}
void rotateRight(Node node) {
if (node.p != nil) {
if (node == node.p.left) {
node.p.left = node.left;
} else {
node.p.right = node.left;
}
node.left.p = node.p;
node.p = node.left;
if (node.left.right != nil) {
node.left.right.p = node;
}
node.left = node.left.right;
node.p.right = node;
} else {
Node left = root.left;
root.left = root.left.right;
left.right.p = root;
root.p = left;
left.right = root;
left.p = nil;
root = left;
}
}
public void creatTree() {
int data[]= {
23,32,15,221,3};
Node node;
System.out.println(Arrays.toString(data));
for(int i = 0 ; i < data.length ; i++) {
node = new Node(data[i]);
insert(node);
}
printTree(root);
}
/**
* [23, 32, 15, 221, 3]
* Node [key=3, color=0, left=-1, right=-1, p=15]
* Node [key=15, color=1, left=3, right=-1, p=23]
* Node [key=23, color=1, left=15, right=32, p=-1]
* Node [key=32, color=1, left=-1, right=221, p=23]
* Node [key=221, color=0, left=-1, right=-1, p=32]
*/
public static void main(String[] args) {
RedBlackTree bst = new RedBlackTree();
bst.creatTree();
}
}
B트리&B+트리
B-트리와 B+트리의 차이점
- B-tree의 모든 노드는 데이터를 저장합니다.
- B-트리 리프 노드에 연결된 목록이 없습니다.
데이터베이스 인덱스는 어떤 종류의 데이터 구조입니까? 왜 그렇게 효율적으로 찾을 수 있습니까? 어떤 종류의 알고리즘이 사용됩니까?
select * from table where id = 10
select * from table where id > 12
select * from table where id > 12 and id < 20
이진 검색 트리를 변환합니다.
위의 모든 SQL 문을 해결할 수 있습니다.
효율성 로그인
2^32=21억;
IO: 디스크에서 데이터를 읽는 것을 말합니다. 32개의 레이어를 32번 읽습니다. CPU, 메모리, IO;
IO가 디스크에서 한 번 읽을 수 있는 데이터의 양은 얼마입니까? 컴퓨터 구성의 원리. 페이지. 페이지,
4KB Int는 얼마나 많은 공간을 차지합니까? 4비
생각하다
질문 1: 검색 효율: 32회(B+Tree 다중 포크 트리)
질문 2: 쿼리 수: (B+Tree 범위)
질문 3: 디스크 IO: 이 문제를 해결하십시오.(B+Tree는 리프에만 데이터 주소를 저장합니다. 노드)
B+트리 데이터 구조
Mysql이 B+Tree를 사용하여 문제를 해결하는 방법
Mysql은 일반적으로 16kb인 페이지 크기에 의해 결정됩니다. bigint 기본 키 유형으로 인덱스를 생성하는 데 얼마나 많은 공간이 사용됩니까?
int 8바이트, 하나의 포인터는 4바이트로 계산, 하나의 페이지 노드: 16kb/(8+8)=1k 키 값 + 포인터 3차: 1024
1024 1024 =10 7374 1824
색인을 올바르게 작성하는 방법:
- 너무 많은 인덱스는 B+tree의 삽입과 삭제를 유지해야 하기 때문에 너무 많을 수 없으며 인덱스가 너무 많으면 삽입 속도가 느려집니다.
- 인덱싱된 필드는 '%%'와 같이 사용할 수 없습니다. 그렇지 않으면 유효하지 않습니다.
- 인덱싱을 위한 필드 유형은 너무 클 수 없습니다. 필드가 작을수록 순서가 커지고 효율성이 높아집니다. Int 및 bigint, varchar(10), varchar(100), text, lontext; B+Tree. 전체 텍스트 인덱스
- 인덱싱을 위한 필드 값은 너무 많고 같을 수 없습니다.수학에는 더 많은 해싱(이산)이라는 것이 있습니다.예를 들어 성별을 인덱싱하면 어떻게 됩니까? 왼쪽의 값은 모두 동일하며 절반으로 필터링할 수 없습니다. 사용자 성별은 별도로 색인화됨 0 1
- 공동 인덱스의 가장 왼쪽 일치 원리. 이름 = 'mx' 및 id = 1인 사용자로부터 *를 선택합니다. mysql이 구문 분석할 때 (id, name)에 구축된 내 인덱스가 자동으로 최적화됩니다.
Select * from user where name = ‘mx’ and age=10
(id, name, age)에 기반한 내 인덱스 - NOT IN은 not in (1,2,3)에 인덱싱되지 않습니다. In 값이 너무 많으면 mysql이 오류를 보고합니다.
허프만 트리(Huffman tree, Huffman coding, prefix coding) -- 압축 소프트웨어, 통신 텔레그램
텔레그램의 디자인:
1. 암호화된 텔레그램은 짧을수록 좋고, 전송 속도는 빠르다
2. 크랙하기 어렵다
3. 해독하기 쉽다
4. 암호화 트리 변경이 빠르다
5. 가역적이다
다음 세 이진 트리의 가중 경로 길이의 합을 계산합니다.
WPL(a):7*2+5*2+2*2+4*2=36()
WPL(b):7*3+5*3+2*1+4*2=46()
WPL(c):7*1+5*2+2*3+4*3=35()
왼쪽 노드의 가장자리는 0으로 설정되고
오른쪽 노드의 가장자리는 1로 설정됩니다.
© 허프만 코딩은
A:0
B:10
C:110
D:111
허프만 트리 구축:
1. 매번 가장 작은 값을 가진 두 개의 노드를 가져와 하위 트리로 형성합니다.
2. 원래 두 점을 제거합니다
. 3. 그런 다음 구성된 하위 트리를 원래 시퀀스에 넣습니다.
4. 마지막 점만 남을 때까지 1 2 3을 반복합니다.
/**
* 赫夫曼树
*/
class HuffmanNode implements Comparable<HuffmanNode> {
String chars;
int fre; //频率 权重
HuffmanNode parent;
HuffmanNode left;
HuffmanNode right;
@Override
public int compareTo(HuffmanNode o) {
return this.fre - o.fre;
}
}
public class HuffmanTree {
HuffmanNode root;
List<HuffmanNode> leafs; //叶子节点
Map<Character, Integer> weights; //叶子节点
Map<Character, String> charmap;
Map<String, Character> mapchar;
public HuffmanTree(Map<Character,Integer> weights) {
this.weights = weights;
leafs = new ArrayList<>();
charmap = new HashMap<>();
mapchar = new HashMap<>();
}
public void code() {
for (HuffmanNode node : leafs) {
Character c = new Character(node.chars.charAt(0));
HuffmanNode current = node;
String code = "";
do {
if (current.parent != null && current == current.parent.left) {
//left
code = "0" + code;
} else {
code = "1" + code;
}
current = current.parent;
} while (current.parent != null);
charmap.put(c, code);
mapchar.put(code, c);
}
}
public void createTree() {
Character keys[] = weights.keySet().toArray(new Character[0]);
PriorityQueue<HuffmanNode> priorityQueue = new PriorityQueue<>();
for (Character key : keys) {
HuffmanNode huffmanNode = new HuffmanNode();
huffmanNode.chars = key.toString();
huffmanNode.fre = weights.get(key);
priorityQueue.add(huffmanNode);
leafs.add(huffmanNode);
}
int len = priorityQueue.size();
for (int i = 1; i <= len-1; i++) {
HuffmanNode n1 = priorityQueue.poll();
HuffmanNode n2 = priorityQueue.poll();
HuffmanNode newNode = new HuffmanNode();
newNode.fre = n1.fre + n2.fre;
newNode.chars = n1.chars + n2.chars;
newNode.left = n1;
newNode.right = n2;
n1.parent = newNode;
n2.parent = newNode;
priorityQueue.add(newNode);
}
root = priorityQueue.poll();
}
public String encode(String body) {
StringBuilder builder = new StringBuilder();
for (char c : body.toCharArray()) {
builder.append(charmap.get(c));
}
return builder.toString();
}
public String decode(String body) {
StringBuilder builder = new StringBuilder();
while (!body.equals("")) {
for (String code : mapchar.keySet()) {
if (body.startsWith(code)) {
body = body.replaceFirst(code,"");
builder.append(mapchar.get(code));
}
}
}
return builder.toString();
}
/**
* a : 10110
* b : 01
* c : 1010
* d : 00
* e : 11
* f : 10111
* g : 100
* encode: 0010111101111010
* decode: dffc
*/
public static void main(String[] args) {
Map<Character, Integer> weights = new HashMap<>();
weights.put('a',3);
weights.put('b',24);
weights.put('c',6);
weights.put('d',20);
weights.put('e',34);
weights.put('f',4);
weights.put('g',12);
HuffmanTree huffmanTree = new HuffmanTree(weights);
huffmanTree.createTree();
huffmanTree.code();
for (Map.Entry<Character, String> entry : huffmanTree.charmap.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
String encode = huffmanTree.encode("dffc");
System.out.println("encode: " + encode);
String decode = huffmanTree.decode(encode);
System.out.println("decode: " + decode);
}
}
나무 더미
8 4 20 7 3 1 25 14 17 수열이 주어졌다고 가정합니다.
힙 트리를 사용하여 정렬:
- 순서대로 완전한 이진 트리에 첫 번째 저장: 힙 구축
- 리프가 아닌 마지막 노드에서 힙화합니다. 마지막 리프 노드가 아닌 마지막 비리프 노드인 이유는 무엇입니까? (마지막 리프 노드는 좌우 노드가 없어 비교 불가)
힙 삽입에는 두 가지 구현이 있습니다.
- 상향식
- 위에서 아래로
삽입 프로세스를 힙화라고 합니다.
**
* 堆树
*
* 建堆
* 排序
*
* 1. 优先级队列问题: 删除最大的
* 2. top n 热搜排行榜问题:1000万的数字
* 3. 定时器 堆顶
* 4. 给你1亿不重复的数字,求出top10,前10最大的数字,还可动态添加
*/
public class HeapTree {
//建大顶堆
public static void maxHeap(int data[], int parent, int end) {
int leftSon = parent * 2 + 1; //下标从0开始+1
while (leftSon < end) {
int temp = leftSon;
//temp表示的是 我们左右节点大的那一个
if (leftSon + 1 < end && data[leftSon] < data[leftSon + 1]) {
temp = leftSon + 1; //右节点比左节点大
}
//比较左右节点大的那一个temp和父节点比较大小
if (data[parent] > data[temp]) {
return; //不需要交换
} else {
int t = data[parent]; //交换
data[parent] = data[temp];
data[temp] = t;
parent = temp; //继续堆化
leftSon = parent * 2 + 1;
}
}
}
public static void heapSort(int data[]) {
int len = data.length;
//从后向上建
//建堆从哪里开始 最后一个的父元素开始(len/2 - 1)(父元素中:最后一个父元素是第几个,从0开始)
for (int parent = len/2 - 1; parent >= 0; parent--) {
//nlog(n)
maxHeap(data, parent, len);
}
//从上向下建
//最后一个数和第一个数交换
int headHeap = 0;
for (int tailHeap = len - 1; tailHeap > 0; tailHeap--) {
//nlog(n)
int temp = data[headHeap];
data[headHeap] = data[tailHeap];
data[tailHeap] = temp;
maxHeap(data, headHeap, tailHeap);
}
}
/**
* Arrays: [1, 3, 4, 7, 8, 14, 17, 20, 25]
*/
public static void main(String[] args) {
int data[] = {
8, 4, 20, 7, 3, 1, 25, 14, 17};
heapSort(data);
System.out.printf("Arrays: " + Arrays.toString(data));
}
}
탑케이
class TopK {
private int k = 10;
private int data[] = new int[k];
public void topK() {
Random random = new Random();
long time = System.currentTimeMillis();
int size = 0;
boolean init = false;
for (int i = 0; i < 100000000; i++) {
int num = random.nextInt(100000000);
if (size < k) {
data[size] = num;
size++;
} else {
if (!init) {
for (int j = k/2 - 1; j >=0; j--) {
minHeap(data, 0, k);
}
init = true;
}
if (num > data[0]) {
data[0] = num;
minHeap(data, 0, k);
}
}
}
System.out.println("耗时:" + (System.currentTimeMillis() - time) + "ms \n");
for (int datum : data) {
System.out.print(datum+", ");
}
}
private void minHeap(int[] data, int start, int end) {
int parent = start;
int son = parent * 2 + 1;
while (son < end) {
int temp = son;
if (son + 1 < end && data[son] > data[son +1]) {
//右节点比左节点大
temp = son + 1; //根号右节点和父节点
}
//temp表示我们左右节点小的那一个
if (data[parent] < data[temp]) {
return;
} else {
int t = data[parent];
data[temp] = t;
parent = temp;
son = parent * 2 + 1;
}
}
return;
}
public static void main(String[] args) {
TopK topK = new TopK();
topK.topK();
}
}