最近在学习数据结构,但一直没做笔记,只知道敲代码。现在开始记录自己所敲类容。
一、准备知识
1、优先队列:是带有优先权的元素序列。普通队列实现是采用先进先出的方式,优先队列是优先权最小的元素先出队列。优优先队列的长度是所包含元素的个数。空优先队列长度为0;
2、完全二叉树:有严格的形状要求,从根结点到每一层的结点从左到右连续填充,除了最后一层外,其余各层都是满的,并且最下一层的结点都集中在该层的最左边。
3、最小堆:如果一棵二叉树每一个结点存储的值都小于或等于其子节点的值,则称该二叉树具有堆序性质。堆是具有堆序性质的完全二叉树。
二、堆的操作(数组实现)
1、堆的插入:1)扩展堆到下一个空闲位置,作为一个待填充的槽;
2)比较槽所对应的父结点与新插入元素的大小,如果小于或等于新插入的元素,则将新结点复制到槽结点。否则将槽所对应的父结点值复制到槽结点处,同时将槽上移到父节点处。
3)反复执行2)将父节点下移动,槽结点上移操作。直到父节点值小于或等于要插入的元素的值,或者槽到达根结点,这时将插入元素值复制到槽结点。
2、堆的删除:1)复制堆最后一个元素到变量last中,同时将最后一个元素置为null;
2)删除根结点(优先权最小结点),同时将根结点处作为一个槽;
3)获取槽结点子节点中最小元素值与last元素值做比较,如果大于last结点值,则将last结点值复制到槽结点,同时结束。否则将子节点中最小值复制到槽结点,同时将槽结点下移动到最小子结点位置。
4)反复执行3)将槽结点下移动,直到last大于任何一个子节点或者槽结点到达一个叶结点位置,这时将last值复制到槽结点并结束。
3、堆排序:每次从堆中获取最小权值,获取的序列就是一个有序序列。
三、Huffman编码:一种变长的编码。Huffman编码为字符分配代码,其长度取决于对应字母使用的频率或权。每个字符的Huffman编码是从Huffman树(一种满二叉树)中获得。Huffman树中的每个叶结点对应一个字符,叶结点对应的权重就为该字符使用的频率,其目的按照最小外部路径权重建立一棵二叉树。一个叶结点的加权路径长度定义为权乘以深度。具有最小外部路径权重的二叉树就是(对于给定的叶节点集合)具有加权路径长度之和最小的二叉树。权大的叶结点深度小,因而它相对于总路径长度的花费最小。因此其他叶结点的权值小,则在树的较深处。
1、建立n个结点的Huffman树过程
1)创建n个初始的Huffman树,每棵树只包含一个结点,叶结点的记录对应字符及其频率。将n棵树按照权的大小进行排序,接着获取两颗权最小的树,把他们作为两颗子树链接到一个分支结点下面,且分支结点的权为该两个结点的权值和,再把所得到的新树放回到序列中适当位置,使得权顺序保持为升序。重复上述步骤,知道序列中只剩下一个元素,则Huffman树建立完毕。
四、代码实现
1、优先队列ADT
package com.structure.huffman;
/**
* 优先队列ADT
* @author hudp
* 2011-11-28
*/
public interface PriorityQueue {
//添加新节点
void insert(Comparable val);
//移除节点
Comparable remove();
//返回节点数
int size();
//获取优先级最小的节点
Comparable get();
//清除数据
void clear();
}
2、堆实现优先队列
package com.structure.huffman;
/**
* 采用最小堆实现优先队列
* @author hudp
* 2011-11-28
*/
public class HeapPriorityQueue implements PriorityQueue {
private final int DEFAULT_SIZE = 10;//默认大小10
private Comparable[] array;
private int count ;
public HeapPriorityQueue(){
array = new Comparable[DEFAULT_SIZE];
count = 0;
}
@Override
public void insert(Comparable val) {
if(array.length >= count){
expand();
}
int holePoint = count ++;
while(holePoint > 0){
int parentPoint = (holePoint -1)/2;
if(array[parentPoint].compareTo(val) < 0){
break;
}
array[holePoint] = array[parentPoint];
holePoint = parentPoint;
}
array[holePoint] = val;
}
//扩展数组长度为先前的2倍
private void expand() {
Comparable[] newArr = new Comparable[array.length * 2];
for(int i = 0 ; i < array.length ; i ++){
newArr[i] = array[i];
}
array = newArr;
}
@Override
public Comparable remove() {
if(count <= 0){
throw new IllegalStateException("error count !");
}
Comparable min = array[0];
Comparable last = array[--count];
array[count] = null;
int holePoint = 0;
int child = holePoint * 2 + 1;//left节点
while(child < count){
if(child + 1 < count && array[child].compareTo(array[child+1]) > 0){
child ++;
}
if(array[child].compareTo(last) > 0){
break;
}
array[holePoint] = array[child];
holePoint = child;
child = holePoint * 2 + 1;
}
array[holePoint] = last;
return min;
}
@Override
public int size() {
return count;
}
@Override
public Comparable get() {
if(count <= 0 ){
throw new IllegalStateException("no data !");
}
return array[0];
}
@Override
public void clear() {
for(int i = 0 ; i < count ; i ++){
array[i] = null;
}
count = 0;
}
}
3、Huffman树实现
package com.structure.huffman;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Huffman tree实现
* @author hudp
* 2011-11-28
*/
public class HuffmanTree {
private CodeMember[] codeTable;
private final int SYS_LEN = 256;//可以接受256个8位字符
private HuffNode root;//Huffman树根
public HuffmanTree() {
codeTable = new CodeMember[SYS_LEN];
root = null;
}
//Huffman树节点
private class HuffNode implements Comparable {
char letter ;//ASCII字符
int frequency;//出现频率
HuffNode left, right;
public HuffNode() {
letter = '\0';
frequency = 0;
left = right = null;
}
@Override
public int compareTo(Object other) {
HuffNode otherNode = (HuffNode) other;
return this.frequency = otherNode.frequency;
}
}
//编码表
private class CodeMember {
String code;
int frequency;
public CodeMember() {
frequency = 0;
code = "";
}
}
//创建树
public void buildHuffTree(String text) {
for (int i = 0; i < text.length(); i++) {
int k = text.charAt(i);
if (null == codeTable[k]) {
codeTable[k] = new CodeMember();
}
codeTable[k].frequency++;
}
PriorityQueue pq = new HeapPriorityQueue();
root = buildHuffTree(pq);
createCode();
}
//创建树
private HuffNode buildHuffTree(PriorityQueue pq) {
for (int i = 0; i < codeTable.length; i++) {
if (null != codeTable[i]) {
HuffNode node = new HuffNode();
node.letter = (char) i;
node.frequency = codeTable[i].frequency;
pq.insert(node);
}
}
while (pq.size() > 1) {
HuffNode childLeft = (HuffNode) pq.remove();
HuffNode childRight = (HuffNode) pq.remove();
HuffNode parent = new HuffNode();
parent.frequency = childLeft.frequency + childRight.frequency;
parent.left = childLeft;
parent.right = childRight;
pq.insert(parent);
}
return (HuffNode) pq.remove();
}
//创建编码
private void createCode() {
String code = "";
createCode(root, code);
}
private void createCode(HuffNode ref, String code) {
if (ref == null) {
return;
} else if (null == ref.left && null == ref.right) {
int i = ref.letter;
codeTable[i].code = code;
} else {
createCode(ref.left, code + "0");
createCode(ref.right, code + "1");
}
}
//编码
public String encode(String text) {
StringBuilder sbf = new StringBuilder();
int len = text.length();
for (int i = 0; i < len; i++) {
int k = text.charAt(i);
if (null == codeTable[k]) {
return null;
}
sbf.append(codeTable[k].code);
}
return sbf.toString();
}
//解码
public String decode(String code) {
StringBuilder sbf = new StringBuilder();
int k = 0;
while (k < code.length()) {
HuffNode ref = root;
while (null != ref.left && null != ref.right) {
int l = code.charAt(k);
if ('0' == l) {
ref = ref.left;
} else if ('1' == l) {
ref = ref.right;
} else {
throw new IllegalArgumentException("error input!");
}
k++;
}
sbf.append(ref.letter);
}
return sbf.toString();
}
public static void main(String[] args) throws IOException {
System.err.println("input data :");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
HuffmanTree ht = new HuffmanTree();
String text = br.readLine();
ht.buildHuffTree(text);
String encode = ht.encode(text);
System.err.println("edcode:" + encode);
System.err.println("decode:" + ht.decode(encode));
}
}
说明:
1、编码:字符用编码标记:从根结点开始,分别把'0'(对应左子节点那条边)或'1'(对应右子结点的那条边),其编码就是从根结点到对应字符叶结点路径的二进制编码。在遍历的过程中,每当遇到一个叶结点,就把该结点包含的字符所对应的编码串填入编码表中。
2、反编码:从左到右逐位扫描编码串,直到确定一个字符位置。(从根结点开始进行反编码,根据每一位的值'0‘或'1‘选择左分支还是右分支,直到到达一个叶结点,取得其对应的字符)
3、前缀特性:如果一组编码中任何一个编码都不是另外一个编码的前缀,则称这组代码符合前缀特性。这种前缀特性保证了代码串被反编码时不会有多种可能。由于任何一个编码的前缀都对应一个分支结点,而每个编码都对应一个字符,所以Huffman编码符合前缀特性。