C&C++实现算法习题第三部分—贪心算法(三)

6 篇文章 0 订阅

九. 大币找零钱问题

问题描述

设有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;
}

运行结果

图1

十. 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;
}

运行结果

图2
图3

十一. 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;
}

运行结果

图4
图5

十二. 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;
}

运行结果

图6
图7

参考文献 《计算机算法设计与分析(第四版)》 王晓东 编著

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值