哈夫曼树

一、哈夫曼树的定义:

哈夫曼树:又称最优二叉树,是一种带权路径最短的树。

树的路径长度:从树根到树中每一个节点的路径长度之和。

结点之间的路径长度:从一个结点到另一个结点之间的分支数目。

结点的带权路径长度:从该结点到树根之间的路径长度与节点上权的乘积。

树的带权路径长度:Weighted Path Length of Tree,简称为WPL,树中所有叶子结点的带权路径长度之和,记作:       

              
 

WPL最小的二叉树就称作最有二叉树或哈夫曼树。

 

二、构造哈夫曼树:

1、给出n个带有权值的结点,组成一个结点集合M

2、从集合M中选出权值最小的两个结点A,B,权值分别为,构造一棵二叉树TT的根结点C权值为 

3、删除结点AB,将结点C加入到集合M中;

4、重复步骤23,直至M中只剩下一个结点为止。

 

三、哈弗曼编码

从哈夫曼树根结点开始,对左子树分配“0”,右子树分配“1”,直至叶子结点,然后,将树根沿每条路径到达叶子结点的代码排列起来,便可以得到哈夫曼编码。

 

四、练习:

将一个字符串转换成一棵HFM树,并打印出每个字符的HFM编码,将HFM树图形化。

1、先写一个节点类Node,里面存放字符串(一个字符组成的字符串)、字符串出现的次数、节点所在位置的横坐标、节点所在位置的纵坐标、左子结点、右子结点、父节点。

/**
 * HFM树的节点类
 * @author zr
 *
 */
public class Node {
	private String s;//字符串
	private int count;//字符串s出现的次数
	private int x;//节点所在位置的横坐标
	private int y;//节点所在位置的纵坐标
	private Node left;//左子节点
	private Node right;//右子结点
	private Node parent;//父节点
	
	public Node getParent() {
		return parent;
	}
	public void setParent(Node parent) {
		this.parent = parent;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
	public String getS() {
		return s;
	}
	public void setS(String s) {
		this.s = s;
	}
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
	public Node getLeft() {
		return left;
	}
	public void setLeft(Node left) {
		this.left = left;
	}
	public Node getRight() {
		return right;
	}
	public void setRight(Node right) {
		this.right = right;
	}

}

 2、写一个HFM类:

(1)先把字符串转换成节点数组,其中需要求出某个字符在字符串中出现的次数,然后,我们需要获得一个无重复字符的字符串noRepeat,最后,将每个字符和它出现的次数放到一个Node对象中,最后,转换成一个节点数组。

 

/**
	 * 字符串c在字符串s中出现的次数
	 * @param c 需要统计的对象字符串c
	 * @param s 字符串c所在的字符串
	 * @return 返回字符串c在字符串s中出现的次数
	 */
	public int getCount(String c,String s){
		int count=0;
		for(int i=0;i<s.length();i++){
			String ch=""+s.charAt(i);
			if(c.equals(ch)){
				count++;
			}
		}
		return count;
	}
	
	/**
	 * 把字符串转换成节点数组
	 * @return 返回节点数组
	 */
	public Node [] toNodeArray(){
		String noRepeat="";
		
		for(int i=0;i<s.length();i++){
			String c=""+s.charAt(i);
			if(noRepeat.indexOf(c)==-1){
				noRepeat+=c;
			}
		}
		Node [] nodes=new Node[noRepeat.length()];
		for(int i=0;i<noRepeat.length();i++){
			Node node=new Node();
			String ch=""+noRepeat.charAt(i);
			node.setS(ch);
			int count=this.getCount(ch, s);
			node.setCount(count);
			nodes[i]=node;
		}
		return nodes;
	}

 (2)将节点按字符出现次数的大小排序,这里采用冒泡排序;

注意,当nodes[i].getCount()>nodes[j].getCount())时,要交换的是节点对象,而不是count.

/**
	 * 把节点数组排序
	 * @param nodes 节点数组
	 */
	public void sort(Node [] nodes){
		for(int i=0;i<nodes.length;i++){
			for(int j=i+1;j<nodes.length;j++){
				if(nodes[i].getCount()>nodes[j].getCount()){
					Node temp=nodes[i];
					nodes[i]=nodes[j];
					nodes[j]=temp;
				}
			}
		}
	}

 

(3)创建HFM树,完成(1)(2)步之后,这里我们采用循环,循环条件:节点数组长度>1;

a、从排好序的Node节点中选出两个count值最小的节点n1,n2,

b、再实例化一个Node对象,其count值为n1,n2的count值之和,

c、新建一个节点数组nodes2,长度为原节点数组nodes长度-1,把n3放在nodes[0]位置,原节点数组nodes中除n1,n2外的剩余节点的放到nodes2中

d、把nodes指向nodes2

 

 

/**
	 * 创建HFM树
	 * @return 返回根节点
	 */
	public Node createHFM(){
		Node [] nodes=this.toNodeArray();
		
		while(nodes.length>1){
			this.sort(nodes);
			Node n1=nodes[0];
			Node n2=nodes[1];
			Node n3=new Node();
			n3.setCount(n1.getCount()+n2.getCount());
			n3.setLeft(n1);
			n3.setRight(n2);
			n1.setParent(n3);
			n2.setParent(n3);
			
			Node [] nodes2=new Node[nodes.length-1];
			nodes2[0]=n3;
			for(int i=0;i<nodes.length-2;i++){
				nodes2[i+1]=nodes[i+2];
			}
			nodes=nodes2;
			
		
		}
		return nodes[0];
	}

 

 

 

(4)打印HFM编码:当遇到叶子节点是开始打印;当遇到的不是叶子节点,则采用递归,则可得到HFM编码。

/**
	 * 
	 * 打印HFM编码
	 * @param node 根节点
	 * @param code 上一个节点的编码
	 * @return 返回根节点
	 */
	private Node print(Node node,String code){
		if(node.getLeft()==null&&node.getRight()==null){
			
			System.out.println("字符"+node.getS()+"出现的次数为:"+node.getCount()+",哈夫曼编码为:"+code);
			if(node.getX()!=0&&node.getY()!=0){
				g.setColor(Color.white);
				g.fillRect(node.getX(), node.getY()+65, 50, 30);
				g.setColor(Color.black);
				g.drawString(code, node.getX()+25, node.getY()+80);
			}
		}else{
			if(node.getLeft()!=null){
				print(node.getLeft(),code+"0");
			}
			if(node.getRight()!=null){
				
				print(node.getRight(),code+"1");
			}
		}
		return node;
	}
	
	/**
	 * 打印HFM编码
	 */
	public void print(){
		Node node=this.createHFM();
		this.print(node, "");
		
	}
	

 

(5)画出HFM树:当遇到的节点没有父节点,即该节点节为根节点时,画出根节点,给出权值,接着,可以画出该父节点的左右子节点及其权值;否则,遇到左子点不为空时,可以通过递归,画出所有左子结点,遇到右子结点不为空时,也通过递归,画出所有右子结点。最后,遇到叶子结点时,打印出字符及其HFM编码。

 

 

 

/**
	 * 画出HFM树
	 */
	public void draw(){
		
		Node node=this.createHFM();
		this.drawHFM(node);
	}
	
	/**
	 * 画出HFM树并打印出每个叶子结点的HFM编码
	 * @param node 根节点
	 */
	private void drawHFM(Node node){
		if(node.getParent()==null){
			node.setX(300);
			node.setY(100);
			x1=node.getX();
			y1=node.getY();
			String sc1=Integer.toString(node.getCount());
			g.setColor(Color.yellow);
			g.fillOval(x1, y1, 50, 50);
			g.setColor(Color.black);
			g.drawString(sc1, x1+25, y1+25);	
			if(node.getLeft()!=null){
				Node left=node.getLeft();
				left.setX(x1-100);
				left.setY(y1+100);
				int x2=left.getX();
				int y2=left.getY();
				String sc2=Integer.toString(left.getCount());
				g.setColor(Color.yellow);
				g.fillOval(x2, y2, 50, 50);
				g.setColor(Color.black);
				g.drawString(sc2, x2+25, y2+25);
				g.drawLine(x2+25, y2, x1+25, y1+50);
				g.drawString("0", (x1+x2+50)/2-10, (y1+y2+50)/2);
				x4=x1;
				y4=y1;
				drawHFM(left);
			}
			if(node.getRight()!=null){
				Node right=node.getRight();
				right.setX(x4+100);
				right.setY(y4+100);
				int x3=right.getX();
				int y3=right.getY();
				String sc3=Integer.toString(right.getCount());
				g.setColor(Color.yellow);
				g.fillOval(x3, y3, 50, 50);
				g.setColor(Color.black);
				g.drawString(sc3, x3+25, y3+25);
				g.drawLine(x3+25, y3, x4+25, y4+50);
				g.drawString("1", (x4+x3+50)/2+10, (y4+y3+50)/2);
				drawHFM(right);
			}
		}else{
			if(node.getLeft()!=null){
				x1=node.getX();
				y1=node.getY();
				Node left=node.getLeft();
				left.setX(x1-30);
				left.setY(y1+100);
				int x2=left.getX();
				int y2=left.getY();
				String sc2=Integer.toString(left.getCount());
				g.setColor(Color.yellow);
				g.fillOval(x2, y2, 50, 50);
				g.setColor(Color.black);
				g.drawString(sc2, x2+25, y2+25);
				g.drawLine(x2+25, y2, x1+25, y1+50);
				g.drawString("0", (x1+x2+50)/2-10, (y1+y2+50)/2);
				drawHFM(left);
			}
			if(node.getRight()!=null){
				x1=node.getX();
				y1=node.getY();
				Node right=node.getRight();
				right.setX(x1+30);
				right.setY(y1+100);
				int x3=right.getX();
				int y3=right.getY();
				String sc3=Integer.toString(right.getCount());
				g.setColor(Color.yellow);
				g.fillOval(x3, y3, 50, 50);
				g.setColor(Color.black);
				g.drawString(sc3, x3+25, y3+25);
				g.drawLine(x3+25, y3, x1+25, y1+50);
				g.drawString("1", (x1+x3+50)/2+10, (y1+y3+50)/2);
				drawHFM(right);
			}
			
			if(node.getLeft()==null&&node.getRight()==null){
				String str=node.getS();
				g.drawString(str, node.getX()+25, node.getY()+60);
			}
			
		}
		//打印HFM编码
		this.print(node, "");
		
	}

 

 

任意给出一个字符串,画出的HFM编码如下:


哈夫曼树
 


哈夫曼树
 

 

你可能感兴趣的:(哈夫曼树)