算法设计与分析实验报告c++实现(哈夫曼编码、最小生成树、背包问题、汽车加油问题)

一、 实验目的

1.加深学生对算法设计方法的基本思想、基本步骤、基本方法的理解与掌握;
2.提高学生利用课堂所学知识解决实际问题的能力;
3.提高学生综合应用所学知识解决实际问题的能力。

二、实验任务

用贪心算法实现:
1、哈夫曼编码问题
a.写一个程序,为给定的英文文本构造一套哈夫曼编码,并对该文本编码。
b.写一个程序,对一段用哈夫曼编码的英文文本进行解码。
c.做一个实验,测试对包含1000个词左右的一段英文文本进行哈夫曼编码时,典型的压缩率位于什么样的区间。
2、设有n个顾客同时等待一项服务,顾客i 需要的服务时间为ti ,i=1,2,3,…,n 。从时刻0开始计时,若在时刻t开始对顾客i服务,那么i的等待时间为t,应该怎么安排n个顾客的服务次序使得总的等待时间(每个顾客等待时间的总和)最少?
假设服务时间分别为{1,3,2,15,10,6,12},用贪心算法给出这个问题的解。
3、最小生成树问题(Prim算法和Kruskal算法)
设G=(V,E)是一个无向连通网,生成树上各边的权值之和称为该生成树的代价,在G的所有生成树中,代价最小的生成树称为最小生成树(Minimal Spanning Trees)。

4、背包问题
问题描述:设有编号为1、2、…、n的n个物品,它们的重量分别为w1、w2、…、wn,价值分别为v1、v2、…、vn,其中wi、vi(1≤i≤n)均为正数。
有一个背包可以携带的最大重量不超过W。求解目标:在不超过背包负重的前提下,使背包装入的总价值最大(即效益最大化),与0/1背包问题的区别是,这里的每个物品可以取一部分装入背包。
5、汽车加油问题
问题描述:一辆汽车加满油后可行驶n公里。旅途中有若干个加油站。设计一个有效算法,指出应在哪些加油站停靠加油,使沿途加油次数最少。
输入:第一行有2个正整数n和k,表示汽车加满油后可行驶n公里,且旅途中有k个加油站。接下来的1 行中,有k+1个整数,表示第k个加油站与第k-1个加油站之间的距离。第0个加油站表示出发地,汽车已加满油。第k+1个加油站表示目的地。
输出:将计算出的最少加油次数输出。如果无法到达目的地,则输出”No Solution”

三、 实验设备

实验设备:Win10 电脑
开发工具:Microsoft Visual C++

四、实验过程设计(算法设计过程)

(一)、哈夫曼编码问题

1、算法分析:
哈夫曼提出构造最优前缀码的贪心算法,由此产生的编码方案称为哈夫曼编码。
其构造步骤如下:
(1)哈夫曼算法以自底向上的方式构造表示最优前缀码的二叉树T。
(2)算法以|C|个叶结点开始,执行|C|-1次的“合并”运算后产生最终所要求的树T。
(3)假设编码字符集中每一字符c的频率是f©。以f为键值的优先队列Q用在贪心选择时有效地确定算法当前要合并的2棵具有最小频率的树。一旦2棵具有最小频率的树合并后,产生一棵新的树,其频率为合并的2棵树的频率之和,并将新树插入优先队列Q。经过n-1次的合并后,优先队列中只剩下一棵树,即所要求的树T。

2、代码实现:

#include<iostream>  
#include<string>            
using namespace std;
struct Node
{double weight;  string ch;  string code; int lchild, rchild, parent;
};void Select(Node huffTree[], int *a, int *b, int n)//找权值最小的两个a和b  
{int i;double weight = 0; //找最小的数for (i = 0; i <n; i++){if (huffTree[i].parent != -1)     //判断节点是否已经选过continue;else{if (weight == 0){weight = huffTree[i].weight;*a = i;}else{if (huffTree[i].weight < weight){weight = huffTree[i].weight;*a = i;}}}}weight = 0; //找第二小的数for (i = 0; i < n; i++){if (huffTree[i].parent != -1 || (i == *a))//排除已选过的数continue;else{if (weight == 0){weight = huffTree[i].weight;*b = i;}else{if (huffTree[i].weight  < weight){weight = huffTree[i].weight;*b = i;}}}}int temp;if (huffTree[*a].lchild < huffTree[*b].lchild)  //小的数放左边{temp = *a;*a = *b;*b = temp;}
}void Huff_Tree(Node huffTree[], int w[], string ch[], int n)
{for (int i = 0; i < 2 * n - 1; i++) //初始过程{huffTree[i].parent = -1;    huffTree[i].lchild = -1;    huffTree[i].rchild = -1;  huffTree[i].code = "";}for ( i = 0; i < n; i++)       {huffTree[i].weight = w[i];  huffTree[i].ch = ch[i];     }for (int k = n; k < 2 * n - 1; k++){int i1 = 0;int i2 = 0;Select(huffTree, &i1, &i2, k); //将i1,i2节点合成节点khuffTree[i1].parent = k;   huffTree[i2].parent = k;huffTree[k].weight = huffTree[i1].weight + huffTree[i2].weight;huffTree[k].lchild = i1;huffTree[k].rchild = i2;}
}void Huff_Code(Node huffTree[], int n)
{int i, j, k;string s = "";for (i = 0; i < n; i++)  {s = "";         j = i;                while (huffTree[j].parent != -1) //从叶子往上找到根节点{k = huffTree[j].parent;if (j == huffTree[k].lchild) //如果是根的左孩子,则记为0{s = s + "0";}else               {s = s + "1";}j = huffTree[j].parent; }cout << "字符 " << huffTree[i].ch << " 的编码:";for (int l = s.size() - 1; l >= 0; l--)    {cout << s[l];huffTree[i].code += s[l]; //保存编码}cout << endl;}
}
string Huff_Decode(Node huffTree[], int n,string s)
{cout << "解码后为:";string temp = "",str="";//保存解码后的字符串for (int i = 0; i < s.size(); i++){temp = temp + s[i];for (int j = 0; j < n; j++){    if (temp == huffTree[j].code){str=str+ huffTree[j].ch;temp = "";break;}    else if (i == s.size()-1&&j==n-1&&temp!="")//全部遍历后没有{str= "解码错误!";}}}return str;
}int main()
{//编码过程const int n=5;Node huffTree[2 * n];string str[] = { "A", "B", "C", "D", "E"};int w[] = { 30, 30, 5, 20, 15 };Huff_Tree(huffTree, w, str, n);Huff_Code(huffTree, n);//解码过程string s;cout << "输入编码:";cin >> s;cout << Huff_Decode(huffTree, n, s)<< endl;;system("pause");return 0;
}

哈夫曼编码问题
1、实验结果

img

2、算法复杂度分析
时间复杂度:O(nm^2)

(二)、服务等待问题

1、算法分析:
服务时间最小的顾客先服务,第一位顾客服务时,每一位顾客都等待A[0]个时间;第二个时,有n-1位顾客等待;以此类推,第i个顾客服务时,有n- i个顾客在等待中,由此得出 总的等待时间 time += (n - i) * A [ i ] ,最小平均等待时间为 time / n 。

2、代码实现:

#include <stdio.h>
#include <string.h>
#define SIZE 10
int A[SIZE];
void sort(int A[],int n);
double greedy(int A[],int n);
void swap(int * a,int * b);int  main()
{int n,i;// n个顾客printf("请输入顾客数:\n");scanf("%d",&n);printf("请输入每个顾客的服务时间:\n");for(i = 1;i<=n;i++){printf("No.%d\n",i);scanf("%d",&A[i-1]);}sort(A,n);printf("最小平均等待时间:%.2f\n",greedy(A,n));return 0;
}double greedy(int A[],int n)
{int i,time = 0;double t = 0;//最小平均等待时间for(i = 0;i < n;i++){time = time +(n-i)*A[i];}t = time/n;return t;
}
void sort(int A[],int n)
{int i,j;for(i = 0;i < n;i++){for(j = i+1;j < n;j++){if(A[i] > A[j])swap(&A[i],&A[j]);}}
}
void swap(int *a,int *b)
{int temp;temp = *a;*a = *b;*b = temp;
}

服务等待问题
1、实验结果

img

2、算法复杂度分析
时间复杂度:O(nlogn)

(三)、最小生成树问题

Prim算法
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

2.算法简单描述
1).输入:一个加权连通图,其中顶点集合为V,边集合为E;
2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
3).重复下列操作,直到Vnew = V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。

Kruskal算法
一种用来寻找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪婪算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。

2.算法简单描述
1).记Graph中有v个顶点,e个边
2).新建图Graphnew,Graphnew中拥有原图中相同的e个顶点,但没有边
3).将原图Graph中所有e个边按权值从小到大排序
4).循环:从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中 if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中添加这条边到图Graphnew中

2、代码实现:
Prim算法

#include <iostream>
#include <vector>
using namespace std;//Prim算法实现
void prim_test()
{int n;cin >> n;vector<vector<int> > A(n, vector<int>(n));for(int i = 0; i < n ; ++i) {for(int j = 0; j < n; ++j) {cin >> A[i][j];}}int pos, minimum;int min_tree = 0;//lowcost数组记录每2个点间最小权值,visited数组标记某点是否已访问vector<int> visited, lowcost;for (int i = 0; i < n; ++i) {visited.push_back(0);    //初始化为0,表示都没加入}visited[0] = 1;   //最小生成树从第一个顶点开始for (int i = 0; i < n; ++i) {lowcost.push_back(A[0][i]);    //权值初始化为0}for (int i = 0; i < n; ++i) {    //枚举n个顶点minimum = max_int;for (int j = 0; j < n; ++j) {    //找到最小权边对应顶点if(!visited[j] && minimum > lowcost[j]) {minimum = lowcost[j];pos = j;}}if (minimum == max_int)    //如果min = max_int表示已经不再有点可以加入最小生成树中break;min_tree += minimum;visited[pos] = 1;     //加入最小生成树中for (int j = 0; j < n; ++j) {if(!visited[j] && lowcost[j] > A[pos][j]) lowcost[j] = A[pos][j];   //更新可更新边的权值}}cout << min_tree << endl;
}int main(void)
{prim_test();return 0;
}

Kruskal算法

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;//并查集实现最小生成树
vector<int> u, v, weights, w_r, father;
int mycmp(int i, int j)
{return weights[i] < weights[j];
}
int find(int x)
{return father[x] == x ? x : father[x] = find(father[x]);
}
void kruskal_test()
{int n;cin >> n;vector<vector<int> > A(n, vector<int>(n));for(int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {cin >> A[i][j];}}int edges = 0;// 共计n*(n - 1)/2条边for (int i = 0; i < n - 1; ++i) {for (int j = i + 1; j < n; ++j) {u.push_back(i);v.push_back(j);weights.push_back(A[i][j]);w_r.push_back(edges++);}}for (int i = 0; i < n; ++i) {father.push_back(i);    // 记录n个节点的根节点,初始化为各自本身}sort(w_r.begin(), w_r.end(), mycmp); //以weight的大小来对索引值进行排序int min_tree = 0, cnt = 0;for (int i = 0; i < edges; ++i) {int e = w_r[i];    //e代表排序后的权值的索引int x = find(u[e]), y = find(v[e]);//x不等于y表示u[e]和v[e]两个节点没有公共根节点,可以合并if (x != y) {min_tree += weights[e];father[x] = y;++cnt;}}if (cnt < n - 1) min_tree = 0;cout << min_tree << endl;
}int main(void)
{kruskal_test();return 0;  }

最小生成树问题
算法复杂度分析
Prim算法:邻接矩阵:O(v^2) 邻接表:O(elog2v)
Kruskal算法:O(elog2e)

(四)、背包问题

1、算法分析:
用 f[i][v] 表示前i件物品恰放入一个容量为v的背包可以获得的最大价值,则其状态转移方程可以表示成:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]} 即:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。

2、代码实现:

#include <iostream>
#include <algorithm>
using namespace std;
struct bag
{int weight;//总重量int value;//总价值float bi;//单位重量的价值float rate;//使用率:1代表完整放入,小于1代表被分割后放入
} bags[50];
bool compare(const bag &bag1,const bag &bag2)
{return  bag1.bi>bag2.bi;
}
int main()
{int sum=0,n;float M;int j=0;cout<<"输入背包容量和物品数量:"<<endl;cin>>M>>n;for(int i=0; i<n; i++){cin>>bags[i].weight>>bags[i].value;//录入物品重量和价值。bags[i].bi=(float)bags[i].value/bags[i].weight;//计算单位重量价值。bags[i].rate=0;//初始化每件物品使用率。}sort(bags,bags+n,compare);//将物品按照单位重量价值由大到小排序for(j=0; j<n; j++){if(bags[j].weight<=M){bags[j].rate=1;sum+=bags[j].weight;M-=bags[j].weight;cout<<"重:"<<bags[j].weight<<"   价值:"<<bags[j].value<<"的物品被放入了背包"<<endl<<"放入比例:"<<bags[j].rate<<endl;}else break;}if(j<n){bags[j].rate=M/bags[j].weight;sum+=bags[j].rate*bags[j].weight;cout<<"重:"<<bags[j].weight<<"    价值:"<<bags[j].value<<"被放入了背包"<<endl<<"放入比例:"<<bags[j].rate<<endl;}return 0;
}

背包问题
1、实验结果

img

2、算法复杂度分析
时间复杂度:O(n)

(五)、汽车加油问题

1、算法分析:
汽车行驶过程中,应走到自己能走到并且离自己最远的那个加油站,在那个加油站加油后再按照同样的方法执行贪心算法。具体做法:先检测各加油站之间的距离,若发现其中有一个距离大于汽车加满油能跑的距离,则输出no solution。否则,对加油站间的距离进行逐个扫描,尽量选择往远处走,不能走了就让num++,最终统计出来的num便是最少的加油站数。

2、代码实现:

#include <iostream>using namespace std;int main()
{   int n,k;cout<<"输入n,k"<<endl;cin>>n>>k;int a[100];cout<<"输入距离"<<endl;for(int i=1;i<=k+1;i++){cin>>a[i];}int count=0;int sum=0;for( i=1;i<=k+1;i++){sum+=a[i];if(sum>=n){sum=a[i];count++;cout<<"======="<<endl;cout<<"第"<<count <<"次加油的位置:"<<a[i-1]<<endl;}}cout << "总共加油:"<<count<<"次"<< endl;return 0;
}

汽车加油问题
1、实验结果

img

2、算法复杂度分析
时间复杂度:O(n)

五、实验小结(包括问题和解决方法、心得体会等)

通过本次实验,对贪心算法有了一定的了解。贪心算法求解的问题都具有最优子结构特性,每一步都是在当前条件下做出的最优决策,但不保证最后结果依然最优。贪心算法与动态规划与很多相似之处。特别地,贪心算法适用的问题也是最优子结构。贪心算法与动态规划有一个显著的区别,就是贪心算法中,是以自顶向下的方式使用最优子结构的。贪心算法会先做选择,在当时看起来是最优的选择,然后再求解一个结果子问题,而不是先寻找子问题的最优解,然后再做选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/596937.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu部署LangChain-Chatchat

个人博客&#xff1a;https://blog.lukeewin.top 更多内容欢迎访问我的博客。 一、环境 OS: Ubuntu 20.04 PyTorch: 2.0.0 Python: 3.8 CUDA: 11.8 GPU: RTX 4090 24GB CPU: 12 vCPU Intel(R) Xeon(R) Platinum 8352V CPU 2.10GHz RAM: 90GB 硬盘: 180GB LLM: Chatglm3-6b E…

如何在Python中将HTML实体代码转换为文本

在处理HTML数据时&#xff0c;有时会遇到HTML实体代码&#xff0c;这些代码是为了在HTML中表示特殊字符而使用的。例如&#xff0c;<表示小于符号(<)&#xff0c;>表示大于符号(>)&#xff0c;&表示和符号(&)等等。那么当我们在实际操作中可能会遇到下面的…

vue+elementUI实现表格组件的封装

效果图&#xff1a; 在父组件使用表格组件 <table-listref"table":stripe"true":loading"loading":set-table-h"slotProps.setMainCardBodyH":table-data"tableData":columns"columns.tableList || []":ra…

【Ambari】Ansible自动化部署大数据集群

目录 一&#xff0e;版本说明和介绍信息 1.1 大数据组件版本 1.2 Apache Components 1.3 Databases支持版本 二&#xff0e;安装包上传和说明 三&#xff0e;服务器基础环境配置 3.1global配置修改 3.2主机名映射配置 3.3免密用户名密码配置 3.4 ansible安装 四. 安…

地面站Mission Planner从源码编译与运行

0. 环境 - win10&#xff08;基本需要100G硬盘&#xff09; - ubuntu18 1. 安装vs2022 下载 vs2022 community 在线安装包。 https://visualstudio.microsoft.com/ 打开 Visual Studio Installer 先安装 Visual Studio Community 2022本体。占用1.2GB。 Visual Studio Inst…

批量导入svg文件作为图标使用(vue3)vite-plugin-svg-icons插件的具体应用

目录 需求svg使用简述插件使用简述实现安装插件1、配置vite.config.ts2、src/main.ts引入注册脚本3、写个icon组件4、使用组件 需求 在vue3项目中&#xff0c;需要批量导入某个文件夹内数量不确定的svg文件用来作为图标&#xff0c;开发完成后能够通过增减文件夹内的svg文件&a…

【SpringCloud】Nacos 注册中心

目 录 一.认识和安装 Nacos1.Windows安装1. 下载安装包2. 解压3. 端口配置4. 启动5. 访问 2.Linux安装1. 安装JDK2. 上传安装包3. 解压4. 端口配置5. 启动 二.服务注册到 nacos1. 引入依赖2. 配置 nacos 地址3. 重启 三.服务分级存储模型1. 给 user-service 配置集群2. 同集群优…

搜索与图论——拓扑排序

有向图的拓扑排序就是图的宽度优先遍历的一个应用 有向无环图一定存在拓扑序列&#xff08;有向无环图又被称为拓扑图&#xff09;&#xff0c;有向有环图一定不存在拓扑序列。无向图没有拓扑序列。 拓扑序列&#xff1a;将一个图排成拓扑序后&#xff0c;所有的边都是从前指…

电脑端库存管理系统哪个好

库存管理系统的作用是管理仓库的各种账面&#xff0c;比较常用的就是在电脑上安装电脑端的库存管理系统进行操作&#xff0c;现今如除了电脑端库存管理系统之外&#xff0c;还有一些是手机端和平板端的&#xff0c;所以我们在管理库存的时候可以选择一些多端都能操作的库存管理…

刷题之Leetcode209题(超级详细)

209.长度最小的子数组 力扣题目链接(opens new window)https://leetcode.cn/problems/minimum-size-subarray-sum/ 给定一个含有 n 个正整数的数组和一个正整数 s &#xff0c;找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组&#xff0c;并返回其长度。如果不存在符合条…

Pytorch数据结构:GPU加速

文章目录 一、GPU加速1. 检查GPU可用性&#xff1a;2. GPU不可用需要具体查看问题3. 指定设备4.将张量和模型转移到GPU5.执行计算&#xff1a;6.将结果转移回CPU 二、转移原理1. 数据和模型的存储2. 数据传输3. 计算执行4. 设备管理5.小结 三、to方法的参数类型 一、GPU加速 .…

PyCharm使用指南(个性化设置、开发必备插件、常用快捷键)

&#x1f947;作者简介&#xff1a;CSDN内容合伙人、新星计划第三季Python赛道Top1 &#x1f525;本文已收录于Python系列专栏&#xff1a; 零基础学Python &#x1f4ac;订阅专栏后可私信博主进入Python学习交流群&#xff0c;进群可领取Python视频教程以及Python相关电子书合…