前言
这一次讲述的实验是拟解决生活中常见的问题:背包问题。
在物品集合种选择合适的物品放入背包,在放入背包中的物品总重量不超过背包容量的前提下,希望背包的物品总价值最大。
说实话,一开始看到题目和题目要求,是真的不知道怎么入手,等到写代码的时候自己的逻辑也比较绕,也可能是因为需要可视化的原因吧,如果是控制台,可能显示的就没那么麻烦了
一、题目说明
背包问题呢,有很多种解决方式,目前我们讲述的是贪心算法
以前在学数据结构时候,贪心算法就是噩梦,现在一开始就要贪心算法,一开始看到题目的时候还是很怕的。
题目呢,就是我们自己输入背包的容量,然后输入物品个数和每个物品的重量和价值,看看能不能将物品装入背包,并且找出给定实例的最优解
emmmm 是不是这样子看起来还蛮简单的
我们往下接着看
二、贪心算法
求解步骤、分析
根据我们输入的物品的重量和价值,
就可以求出每个物品的单位重量的价值=(价值/重量),简单来说就是物品贵重嘛
贪心算法:就是尽可能的装入背包,尽可能将单位重量的价值高的、多的装入背包,(就是先将贵重物品装入背包
单位重量价值最高的装入背包了之后呢,就选单位重量价值第二高的,然后以此类推,直到背包没满没有物品了,不然就直到背包满
所以会有出现装入比例是小数的情况
接下来来看代码
我是使用Android来呈现可视化的
三、代码实现
主界面
activity_main.xml
垂直线性布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="number"
android:hint="背包容量"/>
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_margin="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/weight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext"
android:inputType="number"
android:hint="物品重量"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_margin="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/values"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="物品价值"
android:imeOptions="actionNone"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/define"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定"
android:textColor="#CC9933"
app:cornerRadius="12dp"
app:strokeWidth="2dp"
app:strokeColor="#CC9933"
style="@style/materialButton"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/greed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="贪心算法"
android:textColor="#CC9933"
app:cornerRadius="12dp"
app:strokeWidth="2dp"
app:strokeColor="#CC9933"
style="@style/materialButton"
/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/goodsRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<style name="materialButton" parent="Theme.MaterialComponents.DayNight.DarkActionBar.Bridge">
<item name="android:textColor">#CC9933</item>
<item name="android:paddingLeft">25dp</item>
<item name="android:paddingRight">25dp</item>
<item name="android:paddingTop">10dp</item>
<item name="android:paddingBottom">10dp</item>
<item name="android:layout_marginLeft">10dp</item>
<item name="backgroundTint">@color/white</item>
<item name="icon">@drawable/right</item>
<item name="iconTint">#CC9933</item>
<item name="iconSize">20dp</item>
<item name="iconPadding">10dp</item>
</style>
这里呢 写了三个输入框,分别是用来输入背包容量和物品的重量和价值
最后使用按钮确定
然后将输入的物品重量和价值在下面显示
然后是RecyclerView中,使用CardView
recy_main.xml
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
app:cardCornerRadius="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="3dp"
android:id="@+id/goodsCard">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_marginBottom="10dp"
android:paddingLeft="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="物品的重量:"/>
<TextView
android:id="@+id/goodsWeight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="15sp"
android:paddingLeft="10dp"/>
</LinearLayout>
<LinearLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_marginBottom="10dp"
android:paddingLeft="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="物品的价值:"/>
<TextView
android:id="@+id/goodsValues"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="15sp"
android:paddingLeft="10dp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
适配器
既然有了RecyclerView 那肯定是少不了Adapter的
RecyclerViewAdapter
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private Context context;
private List<Data> listView;
public RecyclerViewAdapter(List<Data> listView) {
this.listView = listView;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null) {
context = parent.getContext();
}
View view = LayoutInflater.from(context).inflate(R.layout.recy_main, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerViewAdapter.ViewHolder holder, int position) {
Data data = listView.get(position);
holder.weight.setText(String.valueOf(data.getWeight()));
holder.values.setText(String.valueOf(data.getValues())); //holder.values.setText(data.getValues());
}
@Override
public int getItemCount() {
return listView.size();
}
public void add(Data data) {
if (listView == null) {
listView = new LinkedList<>();
}
listView.add(data);
notifyDataSetChanged();
}
static class ViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
TextView weight;
TextView values;
public ViewHolder(@NonNull View itemView) {
super(itemView);
cardView = (CardView) itemView.findViewById(R.id.goodsCard);
weight = (TextView) itemView.findViewById(R.id.goodsWeight);
values = (TextView) itemView.findViewById(R.id.goodsValues);
}
}
}
这个没什么好解释的吧,Android必会的RecyclerView
主活动
记得定义接口,监听事件
private RecyclerView recyclerView;
private RecyclerViewAdapter recyclerViewAdapter;
private List<Data> dataList = new ArrayList<>();
private TextInputEditText number; //容量
private TextInputEditText weight; //重量
private TextInputEditText values; //价值
private Button define;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 1);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerViewAdapter = new RecyclerViewAdapter(dataList);
recyclerView.setAdapter(recyclerViewAdapter);
}
private void init() {
goodsWeight.add(0);
goodsValues.add(0);
goodsScale.add(0);
//初始化边界条件,关乎之后数组边界溢出问题
recyclerView = (RecyclerView) findViewById(R.id.goodsRecycler);
number = (TextInputEditText) findViewById(R.id.number);
weight = (TextInputEditText) findViewById(R.id.weight);
values = (TextInputEditText) findViewById(R.id.values);
define = (Button) findViewById(R.id.define);
define.setOnClickListener(this);
Button greed = (Button) findViewById(R.id.greed);
greed.setOnClickListener(this);
Button dynamic = (Button) findViewById(R.id.dynamic);
dynamic.setOnClickListener(this);
}
开头呢 就是基本的RecyclerView 和控件的绑定
然后是两个按钮的点击事件,一个是确定,然后可以将数据显示的
另一个就是主角贪心算法了
然后这里就出现疑问了,哎呀 那个add那些是什么呢?
我们接下来讲
物品信息保存
物品的重量和价值,输入进来了呢 我们肯定要保存起来啦,不然之后怎么进行运算呢
定义一个新的类Goods 使用动态数组
//将输入的值存入动态数组
public class Goods {
public static ArrayList<Integer> goodsWeight = new ArrayList<Integer>(); //重量
public static ArrayList<Integer> goodsValues = new ArrayList<Integer>(); //价值
//用于贪心算法,放入背包的比例,例如全部放入为1 (因为贪心算法尽可能多的放入背包)
public static ArrayList<Integer> goodsScale = new ArrayList<Integer>();
}
这样子 我们就将数据都存起来了 至于为什么这个动态数组先add(0)呢?
我这是为了方便以后的动态规划就这样写了
不过如果你不写 但是接下来的贪心算法 还是按照我的话
就要仔细考虑数组越界的问题了哦
点击事件
让我们回到主活动,还有点击事件没写呢
public void onClick(View view) {
switch (view.getId()) {
case R.id.define:
//进行判断,保证两个输入框都有值
if (weight.getText() == null || weight.getText().length() == 0 || values.getText() == null || values.getText().length() == 0) {
//未输入完全则提示输入不完全
Toast.makeText(this, "请完整输入物品重量和价值", Toast.LENGTH_SHORT).show();
} else {
int n = Integer.parseInt(weight.getText().toString()); //获取物品重量输入值
int m = Integer.parseInt(values.getText().toString()); //获取物品价值输入值
Data goods = new Data(n, m); //创建Data对象
//dataList.add(goods);
recyclerViewAdapter.add(goods); //加入适配器Adapter 用于显示
goodsWeight.add(n); //将值保存入动态数组
goodsValues.add(m); //将值保存入动态数组
weight.setText("");
values.setText(""); //保证每次输入后,输入框的值清空保证下次输入
//保证每次输入后,取消输入框光标
values.setImeOptions(EditorInfo.IME_ACTION_NONE);
weight.setImeOptions(EditorInfo.IME_ACTION_NONE);
}
break;
case R.id.greed:
//判断保证输入背包容量,不至于让计算时候背包容量空值
//在上面已经对两个动态数组进行了边界初始化,所以数组长度已经是1
if (number.getText() == null || number.getText().length() == 0 || goodsWeight.size() == 1 || goodsValues.size() == 1) {
//信息提示
Toast.makeText(this, "请完整输入背包容量或者物品重量和价值", Toast.LENGTH_SHORT).show();
} else {
//到下一个活动求解贪心算法
Intent pageGreed = new Intent(MainActivity.this, PageGreed.class);
//将背包容量传值入贪心算法的活动
int volume = Integer.parseInt(number.getText().toString()); //获取背包容量输入框的值
pageGreed.putExtra("goodsVolume", volume); //Intent对象.putExtra(键值,实际值)
//键值类似指针用于找到值
startActivity(pageGreed);
}
break;
}
}
里面的注释 搭配起来看 应该很容易理解了
首先就是 保证三个输入框都要有东西 不然点按钮就会闪退
然后就是将输入的值进行获取 ,这里要转类型 这个是要注意的
然后就是将输入的值添加到RecyclerView当中进行显示和保存到数组当中
然后还有到贪心算法的页面,顺带将背包容量的值传过去
get和set就不用多说了
public class Data { //输入框取值赋值
private int weight; //重量
private int values; //价值
public Data(int weight, int values) {
this.weight = weight;
this.values = values;
}
}
贪心算法
关键 他来了!
界面
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".page.PageGreed"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/greedRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/greedCard"
app:cardCornerRadius="15dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="3dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_marginBottom="10dp">
<TextView
android:layout_weight="1"
android:paddingLeft="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="物品的重量:"/>
<TextView
android:layout_weight="1"
android:paddingLeft="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="物品的价值:"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="10dp">
<TextView
android:layout_weight="1"
android:id="@+id/greedWeight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="重量"
android:textSize="15sp"
android:paddingLeft="10dp"/>
<TextView
android:layout_weight="1"
android:id="@+id/greedValues"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="价值"
android:textSize="15sp"
android:paddingLeft="10dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_weight="1"
android:layout_marginBottom="10dp"
android:paddingLeft="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="物品放入比例:"/>
<TextView
android:id="@+id/greedScale"
android:layout_weight="1"
android:layout_marginBottom="10dp"
android:paddingLeft="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="比例:"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
界面还是简单的CardView
Adapter也还是那个基本的适配器
public class GreedAdapter extends RecyclerView.Adapter<GreedAdapter.ViewHolder> {
private Context context;
private List<Good> list = new ArrayList<>();
public GreedAdapter(List<Good> list) {
this.list = list;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null) {
context = parent.getContext();
}
View view = LayoutInflater.from(context).inflate(R.layout.recy_greed, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Good good = list.get(position+1);
//Log.d("Greed","位置position"+ String.valueOf(position));
//Log.d("Greed","list长度" + String.valueOf(list.size()));
holder.greedWeight.setText(String.valueOf(good.getWeight()));
holder.greedValues.setText(String.valueOf(good.getValues()));
holder.greedScale.setText(String.valueOf(good.getScale()));
}
@Override
public int getItemCount() {
return list.size()-1;
}
static class ViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
TextView greedWeight;
TextView greedValues;
TextView greedScale;
public ViewHolder(@NonNull View itemView) {
super(itemView);
cardView = (CardView) itemView.findViewById(R.id.greedCard);
greedWeight = (TextView) itemView.findViewById(R.id.greedWeight);
greedValues = (TextView) itemView.findViewById(R.id.greedValues);
greedScale = (TextView) itemView.findViewById(R.id.greedScale);
}
}
}
理解size-1 和 position+1哦
然后关键之处呢 就在Activity
//贪心算法
public class PageGreed extends AppCompatActivity {
int i;
private RecyclerView recyclerView;
private GreedAdapter greedAdapter;
private List<Good> goodsList = new ArrayList<>();
private Good[] good;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_page_greed);
//使用键值获取之前传的值,第二个参数为当键值为空时候的默认值
int volume = getIntent().getIntExtra("goodsVolume", 0); //传值进来的背包容量
Sort(); //排序
IntoBackpack(volume); //装入背包操作
init();
//initGoods(); //显示装入的物品(贪心算法)
}
private void init() {
initGoods();
recyclerView = (RecyclerView) findViewById(R.id.greedRecycler);
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 1);
recyclerView.setLayoutManager(gridLayoutManager);
greedAdapter = new GreedAdapter(goodsList);
recyclerView.setAdapter(greedAdapter);
}
//装入背包
private void IntoBackpack(int backpackScale) {
for (i = 1; i <= goodsWeight.size() - 1; i++) {
goodsScale.add(0); //初始值,都未装入背包,0为未装入,1为装入
}
for (i = 1; i <= goodsWeight.size() - 1; i++) {
if (backpackScale < goodsWeight.get(i)) { //背包容量不够物品重量大,不能完全装下
break;
}
goodsScale.set(i, 1); //可以完全装下,则改为1,标记已经装下
backpackScale -= goodsWeight.get(i); //装入后修改背包容量
// backpackScale = backpackScale - goodsWeight.get(i)
}
if (i < goodsWeight.size()) {
goodsScale.set(i, backpackScale / goodsWeight.get(i));
}
}
private void initGoods() {
good = new Good[goodsWeight.size()];
//物品重量价值等初始化第一位元素都为0,是否影响 MainActivity 55行
good[0] = new Good(0, 0, 0);
for (int j = 1; j <= goodsWeight.size() - 1; j++) {
good[j] = new Good(goodsWeight.get(j), goodsValues.get(j), goodsScale.get(j));
//goodsList.add(good);
//good = new Good[]{new Good(goodsWeight.get(j), goodsValues.get(j), goodsScale.get(j))};
}
goodsList.clear();
for (int x = 0; x < good.length; x++) {
goodsList.add(good[x]);
}
}
//Collections工具类 sort swap reverse
//排序算法
void Sort() {
int i, j;
int temp1, temp2;
for (i = 1; i <= goodsWeight.size() - 1; i++) {
for (j = 1; j < goodsWeight.size() - i; j++) {
temp1 = goodsValues.get(j) / goodsWeight.get(j);
temp2 = goodsValues.get(j + 1) / goodsWeight.get(j + 1);
if (temp1 < temp2) {
//arrayList.set() 在数组的指定位置修改元素,并将旧元素返回
//java.Collections.swap(list, 0, 1); list 集合 oldPosition 需要调换的元素下标 newPosition 被调换的元素下标
Collections.swap(goodsWeight, goodsWeight.indexOf(goodsWeight.get(j)), goodsWeight.indexOf(goodsWeight.get(j + 1)));
Collections.swap(goodsValues, goodsValues.indexOf(goodsValues.get(j)), goodsValues.indexOf(goodsValues.get(j + 1)));
}
}
}
}
}
我一段段的来解释说明
首先呢 我们要排序 所谓排序呢
也就是找到单位重量价值最大的 然后将他们的位置更改
ps:我在写的时候出现了很严重的且多处下标越界问题,要仔细考虑为什么for循环这样写
然后就是装入背包的问题 用了一个新的动态数组来保存装入比例
然后对容量进行考虑 修改装入比例
最后到了又是一个很绕的地方
我在一开始写的时候,不知道数组元素 我怎么用RecyclerView显示
因为我的数据 都是保存在动态数组里面的
后面经过点拨 是这样写的 如果有更好的办法 也欢迎指出
我多写了一个类用于将数据元素拿出来作为数据显示
Good类(是不是有点绕进去了 前面有一个Goods类)
//将动态数组每一个元素取出(一个物品的重量、价值和贪心算法放入比例为一组),作为值显示
public class Good {
private int weight;
private int values;
private int scale;
public Good(int weight, int values, int scale) {
this.weight = weight;
this.values = values;
this.scale = scale;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getValues() {
return values;
}
public void setValues(int values) {
this.values = values;
}
public int getScale() {
return scale;
}
public void setScale(int scale) {
this.scale = scale;
}
}
通过这种方式 我就将物品经过贪心算法的装入情况显示了
因为我要显示在RecyclerView 又要new进去 就要根据动态数据的长度来定义数组 再new
写的时候也很烦哎 数组数组又是数组 怎么哪哪都是数组
四、结果
ps:其实呢 代码还是有瑕疵的
就是我都是使用的int类型
贪心算法的装入比小数显示是为0的
这个是可以改进改进的
总结
这就是目前背包问题的第一部分 关于使用贪心算法的
之后还有01背包问题
有动态规划的 也有蛮力法的
我们一步一步地解决背包问题
难理解的地方可能就是动态数组和Goods和Good类,还没理清楚可以提问🤔😀
ps:实验学习,代码有繁琐和可优化之处,相互指出相互学习
to be continued……