Java实现猜底牌问题(贪婪法)
1 问题描述
设计一种策略,使在下面的游戏中,期望提问的次数达到最小。有一副纸牌,是由1张A,2张2,3张3,…9张9组成的,一共包含45张牌。有人从这副牌洗过的牌中抽出一张牌,问一连串可以回答是或否的问题来确定这副牌的点数。
2 解决方案
2.1 贪婪法原理简介
贪婪法的核心是,所做的每一步选择都必须满足以下条件:
(1)可行的:即它必须满足问题的约束。
(2)局部最优:它是当前步骤中所有可行选择中最佳的局部选择。
(3)不可取消:即选择一旦做出,在算法的后面步骤中就无法改变了。
这些条件即要求:在每一步中,它要求“贪婪”地选择最佳操作,并希望通过一系列局部的最优选择,能够产生一个整个问题的(全局的)最优解。
2.2 哈夫曼树及编码简介
哈夫曼树,树中所有的左向边都标记为0,而所有的右向边都标记为1.可以通过记录从根到字符叶子的简单路径上的标记来获得一个字符的哈夫曼编码。
要了解哈夫曼树,首先了解一下哈夫曼算法,哈夫曼算法满足以下两步:
第一步:初始化n个单节点的树,并为它们标上字母表中的字符。把每个字符的概率记在树的根节点中,用来指出树的权重(更一般地来说,树的权重等于树中所有叶子节点的概率之和)。
第二步:重复下面的步骤,直到只剩下一棵单独的树。找到两棵权重最小的树(如果权重相同,则任意选取其一)。把它们作为新树中的左右子树,并把其权重之和作为新的权重记录在新树的根节点中。
树的带权路径长度:如果树中每个叶子上都带有一个权值,则把树中所有叶子的带权路径长度之和称为树的带权路径长度。
设某二叉树有n个带权值的叶子结点,则该二叉树的带权路径长度记为:
公式中,Wk为第k个叶子结点的权值;Lk为该结点的路径长度。
针对猜底牌问题,此处需要构建哈夫曼树,通过哈夫曼编码的长度来确定具体回答是或否的个数。此处把哈夫曼编码中的0代表回答否,1代表回答是,根据哈夫曼编码中的0或者1的总个数来确定提问的次数。
首先,创建一个树的节点类:
package com.liuzhen.huffman;
/*定义一个Node类,其类型为T(PS:即在初始化时自行选择相关数据类型),该类用于定义哈夫曼树中一个节点的类型
* 该类并实现Compareble接口
* Compareble接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序 ,类的 compareTo 方法被称为它的自然比较方法 。
* 实现此接口的对象列表(和数组)可以通过 Collections.sort (或Arrays.sort )进行自动排序。
*/
public class Node<T> implements Comparable<Node<T>> {
private T data; //二叉树节点的名称
private int weight; //二叉树节点的权重
private Node<T> left; //节点的左孩子
private Node<T> right; //节点的右孩子
private String code = ""; //二叉树节点的哈夫曼编码,初始化为空
//构造函数
public Node(T data, int weight) {
this.data = data;
this.weight = weight;
}
/*
* toString的好处是在碰到“println”之类的输出方法时会自动调用,不用显式打出来
* 它是Object里面已经有了的方法,此处将该方法进行重写
* 它通常只是为了方便输出,比如System.out.println(xx),括号里面的“xx”如果不是String类型的话,
* 就自动调用xx的toString()方法
* 此处就可以输出一个Node类对象时,直接输出toString()方法体中返回的字符串
*/
@Override
public String toString() {
return "data:" + this.data + ",weight:" + this.weight +",code:"+this.code+ "; ";
}
@Override
public int compareTo(Node<T> o) {
//此处实现Compareble接口中方法compaerto,对Node类对象进行从大到小排序
if (o.weight > this.weight) {
return 1;
} else if (o.weight < this.weight) {
return -1;
}
return 0;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Node<T> getLeft() {
return left;
}
public void setLeft(Node<T> left) {
this.left = left;
}
public Node<T> getRight() {
return right;
}
public void setRight(Node<T> right) {
this.right = right;
}
}
其次,创建一个用于构建哈夫曼树和输出哈夫曼树的类:
package com.liuzhen.huffman;
import java.util.*;
public class HuffmanTree<T> {
//构建哈夫曼树方法,其返回类型为Node<T>
public static <T> Node<T> createTree(List<Node<T>> nodes) {
while (nodes.size() > 1) {
Collections.sort(nodes); //调用Node<T>类中实现接口的排序方法,对节点对象进行从大到小排序
Node<T> left = nodes.get(nodes.size() - 1); //获取链表中最小的元素,作为左孩子
Node<T> right = nodes.get(nodes.size() - 2); //获取链表中第二小的元素,作为右孩子
Node<T> parent = new Node<T>(null, left.getWeight()
+ right.getWeight()); //左孩子和右孩子权重相加的和,作为其父节点
parent.setLeft(left);
parent.setRight(right);
nodes.remove(left);
nodes.remove(right);
nodes.add(parent); //将父节点加入链表中
}
return nodes.get(0);
}
//使用广度优先遍历法,返回哈夫曼树的结果队列
public static <T> List<Node<T>> breath(Node<T> root) {
//定义最终返回的结果队列
List<Node<T>> list = new ArrayList<Node<T>>();
/*Queue(队列)特性是先进先出,队尾插入,队首删除
* Queue使用时要尽量避免Collection的add()和remove()方法,而是要
* 使用offer()来加入元素,使用poll()来获取并移除元素
* LinkedList类实现了Queue接口,因此,可以把LinkedList当成Queue来使用
*/
Queue<Node<T>> queue = new LinkedList<>();
// root.setCode(""); //哈夫曼树根节点的哈夫曼编码设定为空字符串“”
queue.offer(root);
while (!queue.isEmpty()) {
Node<T> pNode = queue.poll(); //使用poll()来获取并移除元素
list.add(pNode);
if (pNode.getLeft() != null) {
String code = pNode.getCode(); //获取父节点的哈夫曼编码
code += "0"; //左孩子的哈夫曼编码加字符串“0”
pNode.getLeft().setCode(code);
queue.offer(pNode.getLeft()); //使用offer()来加入元素
}
if (pNode.getRight() != null) {
String code = pNode.getCode(); //获取父节点的哈夫曼编码
code += "1"; //右孩子的哈夫曼编码加字符串“1”
pNode.getRight().setCode(code);
queue.offer(pNode.getRight());
}
}
return list;
}
}
最后,输入题目中数据(对于题目的A,1,…,9,我分别取代号为字母A,B,C,…,其张数的大小即为哈夫曼树中相应节点的权重),得到输出最终结果:
package com.liuzhen.huffman;
import java.util.*;
public class HuffmanTreeTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Node<String>> nodes = new ArrayList<Node<String>>();
nodes.add(new Node<String>("A", 1));
nodes.add(new Node<String>("B", 2));
nodes.add(new Node<String>("C", 3));
nodes.add(new Node<String>("D", 4));
nodes.add(new Node<String>("E", 5));
nodes.add(new Node<String>("F", 6));
nodes.add(new Node<String>("G", 7));
nodes.add(new Node<String>("H", 8));
nodes.add(new Node<String>("I", 9));
Node<String> root = HuffmanTree.createTree(nodes);
System.out.println(HuffmanTree.breath(root));
}
}
运行结果
[data:null,weight:45,code:; , data:null,weight:18,code:0; , data:null,weight:27,code:1; , data:null,weight:9,code:00; , data:I,weight:9,code:01; , data:null,weight:12,code:10; , data:null,weight:15,code:11; , data:D,weight:4,code:000; , data:E,weight:5,code:001; , data:null,weight:6,code:100; , data:F,weight:6,code:101; , data:G,weight:7,code:110; , data:H,weight:8,code:111; , data:null,weight:3,code:1000; , data:C,weight:3,code:1001; , data:A,weight:1,code:10000; , data:B,weight:2,code:10001; ]
Java实现猜底牌问题(贪婪法)的更多相关文章
- 【ACM小白成长撸】--贪婪法解硬币找零问题
question:假设有一种货币,它有面值为1分.2分.5分和1角的硬币,最少需要多少个硬币来找出K分钱的零钱.按照贪婪法的思想,需要不断地使用面值最大的硬币.如果找零的值小于最大的硬币值,则尝试第二 ...
- 3、Java调用C语言(JNA法)
这个方法挺方便的……(改写“二.Java调用C语言(JNative法)“的例子) 一.访问https://github.com/twall/jna ,下载jna-4.1.0.jar(版本不同名字不同) ...
- 初识Java,猜字游戏
import java.util.*; public class caizi{ public static void main(String[] args){ Scanner in=new Scann ...
- Java实现猜字母游戏
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAABRQAAAE9CAYAAAB6Cu4FAAAgAElEQVR4nOy995OUR77u2f/H3tjdey ...
- Java将Excel中科学计数法解析成数字
需要注意的是一般的科学表达式是1.8E12 1.8E-12 而在Excel中的科学表达式是1.8E+12 1.8E-12 我写的科学计数法的正则表达式是(-?\d+\.?\d*)[Ee]{1}[\+- ...
- Java实现猜数字,附带提示功能。
很简单的一段代码: package com.changeyd.demo; import java.util.Random;import java.util.Scanner;public class M ...
- java & python猜数字游戏对比
1.java版 package day03; import java.util.Random;import java.util.Scanner; /** * 猜数字游戏 * 随机生成一个1-100之间 ...
- 【JAVA】猜数字
import java.util.*; public class GN { public static void main(String arg[]) { ;// 数字标记 ;// 位置标记 ;// ...
- java学习 猜数字
package study; import java.util.Scanner; /** * 猜数字小游戏 * * @author carry * */ public class GuessNumbe ...
随机推荐
- 什么是HTTP
什么是HTTP 什么是 HTTP ?你肯定立马跳出:"HTTP 是超文本传输协议,就是 HyperText Transfer Protocol". 这样回答还是过于简单,那到底什么 ...
- Mysql 常用函数(6)- replace 函数
Mysql常用函数的汇总,可看下面系列文章 https://www.cnblogs.com/poloyy/category/1765164.html replace 的作用 将某些字符串替换成新的字符 ...
- CentOS7编译和安装GCC7.5
CentOS7编译和安装GCC7.5 一. 环境介绍: CentOS7 虚拟机连上了互联网(为什么要强调这点呢,因为CentOS7每次进入系统,都需要手动点击右上角的Connect,才能连上互联 ...
- Python-AES加密算法接口测试
前言 先前已经学过了Python-SHA256加密算法接口测试,今天我跟大家讲解一下AES加密算法的接口如何写python脚本进行测试. 一:安装pycryptodome模块 pip install ...
- kubernetes pod的弹性伸缩———基于pod自定义custom metrics(容器的IO带宽)的HPA
背景 自Kubernetes 1.11版本起,K8s资源采集指标由Resource Metrics API(Metrics Server 实现)和Custom metrics api(Promet ...
- iscroll在谷歌浏览器中bug
https://segmentfault.com/q/1010000008489619 iscroll 在安卓app嵌套html页面时,导致列表页滑动不起来,并且在chorme浏览器中使用手机模式,也 ...
- SpringAOP注解报错:java.lang.IllegalArgumentException: error at ::0 can't find referenced pointcut selectAll
原因 我使用的aspectjweaver.jar版本是1.5.1,版本过低,导致报错. 需要下载高本版的aspectjweaver.jar. 解决办法 在这里下载:https://mvnreposit ...
- 检查可执行App类型是否为executable (腾讯上线预审核报错)otool工具使用
https://blog.csdn.net/lovechris00/article/details/81561627 查看IPA文件的路径 1,解压缩 xcode导出的xxx.ipa文件 2,然后在解 ...
- poj3683 2-SAT 同上一道
Priest John's Busiest Day Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 10151 Accep ...
- java-五大内存图
jrm—Java虚拟机在进行程序运行时会向cpu申请一个内存约为10%左右,该内存被jrm分为5大区域 一:栈内存(stack)用来存储变量 当栈消失时,变量也随之消失.二:堆内存(heap)在Jav ...