九. 大币找零钱问题
问题描述
设有64种不同面值的硬币,各硬币的面值分别为25元、10元、5元、1元。现要用这些面值的硬币来购物和找钱。商店里各面值的硬币有足够多。在一次购物中希望使用最少硬币个数。
实现代码
#include<iostream>
#include <math.h>
using namespace std;
void greedy(int num){
int quarter, ten, five, one;
quarter = ten = five = one = 0;
while(num != 0){
if(num > 25){
quarter ++;
num -=25;
} else if(num > 10){
ten ++;
num -=10;
} else if(num > 5) {
five ++;
num -=5;
} else {
one ++;
num -=1;
}
}
cout<<"25元"<<quarter<<"张"<<endl;
cout<<"10元"<<ten<<"张"<<endl;
cout<<"5元"<<five<<"张"<<endl;
cout<<"1元"<<one<<"张"<<endl;
}
int main()
{
//有25 10 5 1这三种钱
float money;
int num;
cout << "输入要找的钱 " << endl;
cin >> money;
num = floor(money);
cout<<"应找"<<num<<"元"<<endl;
greedy(num);
return 0;
}
运行结果
十. Prim算法
问题描述
构造G的最小生成树的Prim算法的基本思想是:首先置S= {1},然后,只要S是V的真子集,就做如下的贪心选择:选取满足条件i∈S,j∈V- S,且c[i][j]最小的边,并将顶点j添加到S中。这个过程-直进行到S=V时为止。在这个过程中选取到的所有边恰好构成G的一棵最小生成树。
实现代码
#include<stdio.h>
#define MAXNODE 30
#define MAXCOST 32767
void Prim(int c[][6],int n)
{ /*从存储序号为1的顶点出发建立连通网的最小生成树,c是邻接矩阵,n为顶
点个数(即有1~n个顶点)最终建立的最小生成树存于数组closeest中*/
int closest[MAXNODE]; //保存邻接顶点
int lowcost[MAXNODE];
int i,j,k;
int mincost;
for(i=1;i<n;i++) //初始化
{
lowcost[i]=c[0][i]; //边(u0,ui)的权值存入lowcost[i]
closest[i]=0; //假定顶点ui到顶点u0有一条边
}
lowcost[0]=0; //表示此时u0已经进入U集
closest[0]=0; //从序号为1的顶点u0出发生成最小生成树
for(i=1;i<n;i++) //在n个顶点中生成有n-1条边的最小生成树(共n-1趟),
//因为第一个顶点已经进入S集了
{
mincost=MAXCOST;
j=1;k=0;
while(j<n) //寻找未找到过(一顶点在V-U集中)的最小权值边
{
if(lowcost[j]!=0/*未进入S*/&& lowcost[j]<mincost)
{
mincost=lowcost[j]; //记下最小权值边的权值
k=j; //记下最小权值边在V-U集中的顶点序号j
}
j++;
}
printf("边:(%d,%d),权值:%d\n",closest[k]+1,k+1,mincost);
//输出最小生成树的边与权值
lowcost[k]=0; //顶点k进入U集
for(j=1;j<n;j++)
if(lowcost[j]!=0&&c[k][j]< lowcost[j])
{ /*若顶点k进入s集后使顶点k和另一顶点j(在V-U集中)构成的边权值变小
则改变lowcost[j]为这个小值,并将此最小权值的边(j,k)记入closest数组*/
lowcost[j]=c[k][j];
closest[j]=k;
}
}
}
int main()
{
int c[6][6]={{100,6,1,5,100,100},{6,100,5,100,3,100},{1,5,100,5,6,4}
,{5,100,5,100,100,2},{100,3,6,100,100,6},{100,100,4,2,6,100}};
Prim(c,6); //生成最小生成树
return 0;
}
运行结果
十一. Kruskal算法
给定无向连通带权图G=(V ,E),V = {1,2,…,n}。Kruskal算法构造G的最小生成树的基本思想是:首先将G的n个顶点看成n个孤立的连通分支,将所有的边按权从小到大排序。然后从第一条边开始,依边权递增的顺序查看每一条边,并按下述方法连接两个不同的连通分支:当查看到第k条边(v,w)时,如果端点v和w分别是当前两个不同的连通分支T1和T2中的顶点时,就用边(v,w)将T1和T2连接成一一个连通分支,然后继续查看第k+1条边;如果端点v和w在当前的同一个连通分支中,就直接再查看第k+1条边。这个过程–直进行到只剩下一个连通分支时为止。此时,这个连通分支就是G的一棵最小生成树。
实现代码
#include <iostream>
using namespace std;
#include<stdio.h>
#define MAXSIZE 30
#define MAXCOST 32767
typedef struct
{
int u; //边的起始顶点
int v; //边的终止顶点
int w; //边的权值
}Edge;
void Bubblesort(Edge R[],int e) //冒泡排序
{ //对数组R中的e条边按权值递增排序
Edge temp;
int i,j,swap;
for(i=0;i<e-1;j++) //进行e-1趟排序
{
swap=0; //置未交换标志
for(j=0;j<e-i-1;j++)
if(R[j].w>R[j+1].w)
{
temp=R[j];R[j]=R[j+1]; R[j+1]=temp; //交换R[j]和R[j+1]
swap=1; //置有交换标志
}
if(swap==0) break; //本趟比较中未出现交换则结束排序(已排好)
}
}
void Kruskal(int c[][6],int n)
{ //在顶点为n的连通网中构造最小生成树,c为连通网的邻接矩阵
int i,j,u1,v1,sn1,sn2,k;
int vest[MAXSIZE]; //用于判断两点之间是否联通的数组
Edge E[MAXSIZE]; //存放边
k=0;
for(i=0;i<n;i++) //用数组E存储连通网中每条边的两个顶点及边上权值信息
for(j=0;j<n;j++)
if(i<j&&c[i][j]!=100)
{
E[k].u=i;
E[k].v=j;
E[k].w=c[i][j];
k++;
}
Bubblesort(E,k); //采用冒泡排序对数组E中的k条边按权值递增排序
for(i=0;i<n;i++) //初始化
vest[i]=i; //给每个顶点置不同连通分量编号,即初始时有n个连通分量
k=1; //k表示当前构造生成树的第n条边,初值为1
j=0; //数组E中元素的下标,初值为0
while(k<n) //产生最小生成树的n-1条边
{
u1=E[j].u;v1=E[j].v; //取一条边的头尾顶点(最短的开始)
sn1=vest[u1];
sn2=vest[v1]; //分别得到这两个顶点所属的集合(连通分量)编号
if(sn1!=sn2) //两顶点分属于不同集合(连通分量)则该边为最小生成树的一条边
{
printf("边:(%d,%d),权值:%d\n",u1+1,v1+1,E[j].w);
k++; //生成的边数增加1
for(i=0;i<n;i++) //两个集合统一编号
if(vest[i]==sn2) //集合编号为sn2的第i号边其编号改为sn1
vest[i]=sn1;
}
j++; //扫描下一条边
}
}
int main()
{
int c[6][6]={{100,6,1,5,100,100},{6,100,5,100,3,100},{1,5,100,5,6,4}
,{5,100,5,100,100,2},{100,3,6,100,100,6},{100,100,4,2,6,100}};
Kruskal(c,6); //生成最小生成树
return 0;
}
运行结果
十二. Dijkstra算法
问题描述
Dijkstra算法是解单源最短路径问题的一个贪心算法。其基本思想是,设置顶点集合S并不断地做贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V - S中取出具有最短特殊路长度的顶点u ,将u添加到S中,同时对数组dist做必要的修改。一旦s包含了所有V中顶点, dist就记录了从源到所有其他顶点之间的最短路径长度。
实现代码
#include <iostream>
using namespace std;
#include<stdio.h>
#define MAXSIZE 6 //带权有向图中顶点的个数
#define INF 32767
void Ppath(int path[],int i,int v0)
{ //先序递归查找最短路径上的顶点
int k;
k=path[i];
if(k!=v0) //顶点vk不是源点v0时
{
Ppath(path,k,v0); //递归查找顶点vk的前一个顶点
printf("%d,",k+1); //输出顶点vk
}
}
void Dispath(int dist[],int path[],int s[],int v0,int n) //输出最短路径的函数
{
int i;
for(i=0;i<n;i++)
if(s[i]==1) //顶点vi在集合S中
{
printf("从%d到%d的最短路径长度为:%d, 路径为:",v0+1,i+1,dist[i]);
printf("%d,",v0+1); //输出路径上的源点v0
Ppath (path,i,v0); //输出路径上的中间顶点vi
printf("%d\n",i+1); //输出路径上的终点
}
else
printf("从%d到%d不存在路径\n",v0,i);
}
void Dijkstra(int c[][MAXSIZE],int v0,int n)
{
int dist[MAXSIZE],path[MAXSIZE],s[MAXSIZE]; //dist:当前每个顶点所对应的最短特殊路径长度
//path:保存最短路径上边经过的顶点序列
int i,j,k,mindis;
for(i=0;i<n;i++)
{
dist[i]=c[v0][i]; //v0到vi的最短路径初值赋给dist[i]
s[i]=0; //s[i]=0表示顶点vi属于T集
if(c[v0][i]<INF) //路径初始化,判断是否有路径
path[i]=v0; //源点v0是vi当前最短路径中的前一个顶点
else
path[i]=-1; //v0到vi没有边
}
s[v0]=1;path[v0]=0; //v0并入集合S且v0的当前最短路径中无前一个顶点
for(i=0;i<n;i++) //对除v0外的n-1顶点寻找最短路径,即循环n-1次
{
mindis=INF;
for(j=0;j<n;j++) //从当前集合T中选择一个路径长度最短的顶点vk
if(s[j]==0&&dist[j]<mindis)
{
k=j;
mindis=dist[j];
}
s[k]=1; //顶点vk加入集合S中
for(j=0;j<n;j++) //调整源点v0到集合T中任一顶点vj的路径长度
if(s[j]==0) //顶点vj在集合T中
if(c[k][j]<INF&&dist[k]+c[k][j]<dist[j])
{ //当v0到vj的路径长度小于v0到vk和vk到vj的路径长度时
dist[j]=dist[k]+c[k][j];
path[j]=k; //vk是当前最短路径中vj的前一个顶点
}
}
Dispath(dist,path,s,v0,n); //输出最短路径
}
int main()
{
int c[MAXSIZE][ MAXSIZE]={{INF,10,INF,30,100},{10,INF,50,INF,INF},
{INF,50,INF,20,10},{30,INF,20,INF,60},{100,INF,10,60,INF}}; //定义邻接矩阵g并给邻接矩阵g赋值
Dijkstra(c,0,5); //求顶点0的最短路径
return 0;
}
运行结果
参考文献 《计算机算法设计与分析(第四版)》 王晓东 编著