头歌——算法设计与分析(贪心法)

news2024/11/24 6:19:30

文章目录

  • 第1关:贪心法
    • 代码
  • 第2关:最小生成树
    • 代码
  • 第3关:Huffman 编码
    • 代码
  • 第4关:单源点最短路径
    • 代码

第1关:贪心法

相关知识
为了完成本关任务,你需要掌握:贪心法 ;。

贪心法,又称贪婪算法是一种解决问题的策略。是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。如果策略正确,那么贪心法往往是易于描述、易于实现的。

贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。

贪心法的优缺点
贪心法可以解决一些最优化问题,如:求图中的最小生成树、求哈夫曼编码……对于其他问题,贪心法一般不能得到我们所要求的答案。一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所求得的答案比较接近最优结果,贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题。

下面通过一个简单的实例题目对贪心进行详解:

例题:
乘船问题: 给出 n 个人,第 i 个人重量为 w
i

。每艘船的最大载重量均为 C,且最多只能乘两个人。用最少的船装载所有人。有解输出船的个数,无解输出 “no”。

解题分析
考虑最轻的人i,如果每个人都无法和他一起坐船,则唯一的方法就是每个人一艘船。否则,他应该选择能和他一起坐船的人中最重的一个j。这样的方法是贪心的,因此它只是让眼前的浪费最少。

程序实现
将人的重量按照从小到大排序。比j更重的人只能每人坐一艘船。这样,只需要两个下表i和j分别表示当前考虑的最轻的人和最重的人,每次先将j往左移动,直到i和j可以共坐一艘船,然后将i+1,j-1,并重复上述操作。

关键代码
int p=0,q=n-1,s=0; // p代表左索引,q代表右索引
if(a[q]>c) cout<<“no”<<endl; // 如果最大的体重(最右边索引的人)大于船的最大载重,那么直接输出 no
else{
for(int i=0;i<n;i++){ // 遍历所有人
if(p>q) break;
if(a[p]+a[q]>c) q–; // 如果最小体重的人和当前最大的人不能同船,那么当前体重最大的人就要一个人一个船。
else p++,q–; // 当前体重最小的人和当前体重最大的人同船
s++;
}
cout<<s<<endl;
}
时间复杂度:不难看出,程序的时间复杂度仅为O(n)。是最优算法。

代码

#include<iostream>

using namespace std;
const int maxn = 10000;

void fast_sort(int * a, int begin, int end) {
  int l, r, m;
  l = begin, r = end, m = (begin + end) >> 1;
  while (l <= r) {
    while (a[l] < a[m]) l++;
    while (a[r] > a[m]) r--;
    if (l <= r) {
      int t = a[l];
      a[l] = a[r];
      a[r] = t;
      l++;
      r--;
    }
  }
  if (l < end) fast_sort(a, l, end);
  if (begin < r) fast_sort(a, begin, r);
}

int main() {
  int n, c;
  int a[maxn];
  /********** Begin **********/
  cin >> n >> c;
  for (int i = 0; i < n; i++) {
    cin >> a[i];
  }
  fast_sort(a, 0, n - 1);
  int p = 0, q = n - 1, s = 0;

  if (a[q] > c) cout << "no" << endl;

  else {
    for (int i = 0; i < n; i++) { // 遍历所有人
      if (p > q) break;
      if (a[p] + a[q] > c) q--;

      else p++, q--;

      s++;
    }
    cout << s << endl;
  }
  /********** End **********/
  return 0;
}

第2关:最小生成树

在这里插入图片描述
在这里插入图片描述
一个连通图可能有多个生成树。当图中的边具有权值时,总会有一个生成树的边的权值之和小于或者等于其它生成树的边的权值之和。

Kruskal算法
不停地循环,每一次都寻找两个顶点,这两个顶点不在同一个真子集里,且边上的权值最小。

把找到的这两个顶点联合起来。

初始时,每个顶点各自属于自己的子集合,共n个子集合。

每一步操作,都会将两个子集合融合成一个,进而减少一个子集合。

结束时,所有的顶点都在同一个子集合里,这个子集合就是最小生成树。

贪心策略
贪心策略就是,每次都选择权重最小的但未形成环路的边加入到生成树中。

算法图解
初始化 将图G的边集E中的所有边按权值从小到大排序,初始化节点状态,一个节点对应一个连通分支,如下图所示,同时初始化最小生成树TE = {}。
在这里插入图片描述

找最小 在边集E中选择最小的边e1(2, 7), 权值为1。
合并 节点2和节点7属于两个不同的连通分支,因此可以将边(2, 7)加入边集TE,执行合并操作后将两个连通分支合并成一个连通分支,同时我们规定连通分支号码小的取代连通分支号码大的,如下图所示。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

代码

#include<bits/stdc++.h>

using namespace std;

int n, m; 

int ans = 0; 

int k = 0; 

int fat[200010]; 

struct node 
{
  int from, to, dis;
}
edge[200010];

bool cmp(node a, node b) 
{
  return a.dis < b.dis;
}

int father(int x)
{
  if (fat[x] != x)
    return father(fat[x]);
  else return x;
}

void unionn(int x, int y)
{
  fat[father(y)] = father(x);
}

int main() {
  cin >> m >> n;
  for (int i = 1; i <= m; i++) {
    cin >> edge[i].from >> edge[i].to >> edge[i].dis;
  }

  for (int i = 1; i <= n; i++) 
  {
    fat[i] = i;
  }

  sort(edge + 1, edge + 1 + m, cmp); 
  
  for (int i = 1; i <= m; i++) {
    if (k == n - 1) break; 
    if (father(edge[i].from) != father(edge[i].to)) 
    {
      unionn(edge[i].from, edge[i].to);
      ans += edge[i].dis; 
      k++;
    }
  }
  cout << ans;
  return 0;
}

第3关:Huffman 编码

相关知识
为了完成本关任务,你需要掌握:

c++基础;
二叉树;
最小生成树概念
哈夫曼(Huffman)编码算法是基于二叉树构建编码压缩结构的,它是数据压缩中经典的一种算法。算法根据文本字符出现的频率,重新对字符进行编码。因为为了缩短编码的长度,我们自然希望频率越高的词,编码越短,这样最终才能最大化压缩存储文本数据的空间。
  假设现在我们要对下面这句歌词“we will we will r u”进行压缩。我们可以想象,如果是使用ASCII码对这句话编码结果则为:119 101 32 119 105 108 108 32 119 101 32 119 105 108 108 32 114 32 117(十进制表示)。我们可以看出需要19个字节,也就是至少需要152位的内存空间去存储这些数据。
  很显然直接ASCII码编码是很浪费空间的,Unicode就更不用说了,下面我们先来统计一下这句话中每个字符出现的频率。如下表,按频率高低已排序:
  在这里插入图片描述
  在这里插入图片描述

代码

#include<bits/stdc++.h>

using namespace std;

typedef struct //哈夫曼树的存储表示
{
  char s;
  int weight; //权值
  int parent, lChild, rChild; //双亲及左右孩子的下标 
}
HTNode, * HuffmanTree;

void Select(HuffmanTree hT, int n, int & s1, int & s2) //选择两个最小节点 
{
  s1 = s2 = 0;
  int i;
  for (i = 1; i < n; ++i) //选择两个未有双亲的节点 
  {
    if (hT[i].parent == 0) {
      if (0 == s1) {
        s1 = i;
      } else {
        s2 = i;
        break; //后面一个for循环的标记 
      }
    }
  }
  if (hT[s1].weight > hT[s2].weight) //确保s1>s2 
  {
    int t = s1;
    s1 = s2;
    s2 = t;
  }
  for (i += 1; i < n; ++i) //选择s2即break 故从i+1开始选择两个最小节点 
  {
    if (hT[i].parent == 0) {
      if (hT[i].weight < hT[s1].weight) {
        s2 = s1;
        s1 = i;
      } else if (hT[i].weight < hT[s2].weight) {
        s2 = i;
      }
    }
  }
  //cout<<s1<<" "<<s2<<"**"<<endl;
}

void CreateHufmanTree(HuffmanTree & hT) //构造哈夫曼树 
{
  int n, m;
  cin >> n;
  m = 2 * n - 1;
  hT = new HTNode[m + 1];
  hT[0].weight = m; // 0号节点用来记录节点数量 
  for (int i = 1; i <= m; ++i) {
    hT[i].parent = hT[i].lChild = hT[i].rChild = 0; //初始化 
  }
  for (int i = 1; i <= n; ++i) {
    cin >> hT[i].s >> hT[i].weight; // 输入权值 
  }
  for (int i = n + 1; i <= m; ++i) //建立过程 
  {
    int s1, s2;
    Select(hT, i, s1, s2);
    hT[s1].parent = hT[s2].parent = i;
    hT[i].lChild = s1;
    hT[i].rChild = s2; //作为新节点的孩子 
    hT[i].weight = hT[s1].weight + hT[s2].weight; //新节点为左右孩子节点权值之和 
  }

}

int HuffmanTreeWPL_(HuffmanTree hT, int i, int deepth) //递归计算WPL 
{
  if (hT[i].lChild == 0 && hT[i].rChild == 0) {
    return hT[i].weight * deepth;
  } else {
    return HuffmanTreeWPL_(hT, hT[i].lChild, deepth + 1) + HuffmanTreeWPL_(hT, hT[i].rChild, deepth + 1);
  }
}

int HuffmanTreeWPL(HuffmanTree hT) //计算WPL 
{
  return HuffmanTreeWPL_(hT, hT[0].weight, 0);
}

void DestoryHuffmanTree(HuffmanTree & hT) //销毁哈夫曼树 
{
  delete[] hT;
  hT = NULL;
}

int main() {
  HuffmanTree hT;
  CreateHufmanTree(hT);
  cout << HuffmanTreeWPL(hT) << endl;
  DestoryHuffmanTree(hT);
  return 0;
}

第4关:单源点最短路径

相关知识
为了完成本关任务,你需要掌握:

贪心法;
Dijkstra算法;
单源点最短路径
给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定V中的一个顶点,称为源。要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通常称为单源最短路径问题。

Dijkstra算法
解决单源最短路径问题的方法之一就是Dijkstra算法。Dijkstra算法会生成一颗最短路径树,树的根为起始顶点s, 树的分支为从顶点s到图G中所有其他顶点的最短路径。此算法要求图中的所有权值均为非负数。与Prim算法类似,Dijkstra算法也采用贪心算法,它总是将当前看起来最近的最短的边加入最短路径中。

从根本上来说,Dijkstra算法通过选择一个顶点,并不断探测与之相关的边,类似广度优先搜索,找出当前距离最近的点。

图片说明
在这里插入图片描述
S集合最初为空,然后选取源点0,S集合为 {0},源点到其它所有点的距离为 {0, INF, INF, INF, INF, INF, INF, INF} 。图中蓝色表示 SPT,迭代的过程如下:
在这里插入图片描述
最终得到 SPT(最短路径树) 如下:

在这里插入图片描述

代码

#include<iostream>
using namespace std;
#define N 100
#define Min(a,b) a>b?b:a
#define INF 1000
int dis[N], bj[N];
int mp[N][N];
int n;
void djsk(int v, char** names)
{
    int i, j, k, min;
    for (i = 0; i < n; i++)
        dis[i] = mp[v][i];

    dis[v] = 0;
    
    bj[v] = 1;// 标记 已找到短路 
    cout << names[0] << "   " << dis[0] << endl;
    for (i = 0; i < n; i++)
    
    {
        min = INF; k = 0;
        for (j = 0; j < n; j++)//从未找到最短路径元素中找一个路径最短的 
            if (!bj[j] && dis[j] < min) { min = dis[j], k = j; }
        if (k != 0)
            cout << names[k] << "   " << dis[k] << endl;
        bj[k] = 1;// 标记 已找到短路 
        for (j = 0; j < n; j++)
            if (dis[j] > (dis[k] + mp[k][j]))dis[j] = dis[k] + mp[k][j];
    }
}
int main(){
	/********** Begin **********/
        cin >> n;
    char** names = new char* [5];
    char from[3], to[3];
    int len;
    for (int i = 0; i < 5; i++) {
        names[i] = new char[5];
        names[i][0] = 'v';
        names[i][1] = (i + 1) + '0';
        names[i][2] = 0;
        dis[i] = INF;
        for (int j = 0; j < 5; j++) {
            mp[i][j] = INF;
        }
    }
    fflush(stdin);
    for (int i = 0; i < n; i++) {
        cin >> from;
        cin >> to;
        cin >> len;
 
        mp[from[1] - '0' - 1][to[1] - '0' - 1] = len;
        mp[to[1] - '0' - 1][from[1] - '0' - 1] = len;
    }
    n = 5;
    djsk(0, names);
    for (int i = 0; i < 5; i++) {
        delete[] names[i];
    }
    delete[] names;
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试题:JVM(四)

new对象流程&#xff1f;&#xff08;龙湖地产&#xff09; 对象创建方法&#xff0c;对象的内存分配。&#xff08;360安全&#xff09; 1. 对象实例化 创建对象的方式有几种&#xff1f; 创建对象的步骤 指针碰撞&#xff1a;以指针为分界线&#xff0c;一边是已连续使用的…

【进阶sql】复杂sql收集及解析【mysql】

开发时会出现&#xff0c;必须写一些较复杂sql的场景 可能是给会sql的客户 提供一些统计sql 或是临时需要统计数据信息但是 开发一个统计功能有来不及的情况 也可能是报表系统组件 只支持 sql统计的情况 特地记录下这些sql 作为积累 substring 截取查询出的字符串&#xff…

Python实现全国岗位招聘信息可视化分析(源码+论文+部署讲解)

项目源码&数据源获取 利用Python实现全国岗位招聘信息可视化分析 项目背景&#xff1a; 1.为企业招聘决策提供科学的依据和参考&#xff0c;可以帮助人力资源部门、招聘机构和求职者了解当前的就业形势、行业趋势和人才需求&#xff0c;从而做出更明智的招聘和求职决策。…

Python毕业设计选题:基于django+vue的宠物寄养平台的设计与实现

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 1. 前台系统功能模块 系统首页界面 用户注册界面 用户登录界面 宠物商城界面 宠物店…

Linux云计算 |【第五阶段】CLOUD-DAY10

主要内容&#xff1a; 部署Dashboard、部署Prometheus、部署HPA集群 一、Dashboard介绍 Dashboard是基于网页的Kubernetes用户界面&#xff0c;可以使用Dashboard将容器应用部署到Kubernetes集群中&#xff0c;也可以对容器应用排错&#xff0c;还能管理集群资源。可以使用Da…

将Notepad++添加到右键菜单【一招实现】

一键添加注册表 复制以下代码保存为 Notepad.reg&#xff0c;将红框内路径修改为自己电脑的“Notepad.exe路径”后&#xff0c;再双击运行即可。 Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\*\shell\NotePad] "Notepad" "Icon""D:\\N…

Git下载-连接码云-保姆级教学(连接Gitee失败的解决)

Git介绍 码云连接 一、Git介绍 二、Git的工作机制 下载链接&#xff1a;Git - 下载软件包 三、使用步骤 创建一个wss的文件夹&#xff0c;作为‘工作空间’ 四、连接码云账号 五、连接Gitee失败的解决方法 一、Git介绍 Git是一个免费的、开源的分布式版本控制…

Spring Boot 与 Vue 共铸卓越采购管理新平台

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

DataFlow v202410 版本更新 一站式数据处理平台

DataFlow 是 OpenCSG 推出的一站式数据处理平台&#xff0c;与 CSGHub 无缝集成&#xff0c;形成数据到模型的全生命周期闭环&#xff0c;助力持续优化。平台兼容多种数据格式与来源&#xff0c;支持本地、云端和网络数据接入&#xff0c;并提供高效转换和读取工具&#xff0c;…

【搜索引擎】俄罗斯搜索引擎yandex

俄罗斯搜索引擎yandex 1997年&#xff0c;俄罗斯搜索引擎Yandex&#xff08;俄语意为&#xff1a;语言目录&#xff09;首次上线&#xff0c;已发展成为全球第四大搜索引擎和第二大非英语搜索引擎 https://yandex.com/

电脑没有下载声卡驱动怎么办?电脑声卡驱动安装方法

在日常使用电脑的过程中&#xff0c;我们可能会遇到电脑没有声音的问题&#xff0c;这往往与声卡驱动缺失或损坏有关。声卡驱动是连接电脑硬件&#xff08;声卡&#xff09;与操作系统之间的桥梁&#xff0c;确保音频信号能够正常输入输出。那么&#xff0c;当电脑没有声卡驱动…

DEVOPS: 集群伸缩原理

概述 阿里云 K8S 集群的一个重要特性&#xff0c;是集群的节点可以动态的增加或减少有了这个特性&#xff0c;集群才能在计算资源不足的情况下扩容新的节点&#xff0c;同时也可以在资源利用 率降低的时候&#xff0c;释放节点以节省费用理解实现原理&#xff0c;在遇到问题的…

git 删除远程不存在本地命令却能看到的分支

要删除远程不存在但本地却能看到的分支&#xff0c;你可以按照以下步骤操作&#xff1a; 删除本地分支&#xff1a; 如果你确定要删除的分支已经没有用处&#xff0c;可以使用以下命令来删除本地分支&#xff1a; git branch -d <branch-name>这里的 <branch-name>…

《Python游戏编程入门》注-第4章6

《Python游戏编程入门》的“轮询鼠标”内容介绍了通过轮询鼠标实现实时显示鼠标位置和按键状态的游戏。 1 游戏介绍 实时显示鼠标位置和按键状态的游戏如图1所示。 图1 实时显示鼠标位置和按键状态 从图1中可以看到&#xff0c;游戏界面主要分为上下两部分。其中&#xff0c…

ENNSP中ACL的实验配置

ACL&#xff1a;访问控制列表 1访问控制----在路由器的入或者出的接口上&#xff0c;匹配流量&#xff0c;之后产生动作---允许或拒绝 2.定义感兴趣流量-----帮助其他软件抓流量 访问控制的匹配规则&#xff1a; 拓扑图如下 基础配置 基础配置弄好后&#xff0c;随便p…

C++设计模式结构型模式———装饰模式

文章目录 一、引言二、装饰器模式三、总结 一、引言 装饰模式是一种结构型设计模式&#xff0c; 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 该模式展现出了运行时的一种扩展能力&#xff0c;以及比继承更强大和灵活的设计视角和设计能力&#x…

第7章 内容共享

第 7 章 内容共享 bilibili学习地址 github代码地址 本章介绍Android不同应用之间共享内容的具体方式&#xff0c;主要包括&#xff1a;如何利用内容组件在应用之间共享数据&#xff0c;如何使用内容组件获取系统的通讯信息&#xff0c;如何借助文件提供器在应用之间共享文件…

分布式锁(redisson,看门狗,主从一致性)

目录 分布式锁一&#xff1a;基本原理和实现方式二&#xff1a;分布式锁的实现1&#xff1a;分布式锁的误删问题2&#xff1a;解决误删问题 三&#xff1a;lua脚本解决多条命令原子性问题调用lua脚本 四&#xff1a;Redisson1&#xff1a;redisson入门2&#xff1a;redisson可重…

Java实战项目-基于SpringBoot+Vue的二手车交易系统的研究与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

JVM学习总结:类的加载篇

本文是学习尚硅谷宋红康老师主讲的尚硅谷JVM精讲与GC调优教程的总结&#xff08;文末有链接&#xff09; 本篇可能被问到的问题&#xff1a; 类的加载过程类加载器 自定义类的加载器、ClassLoader双亲委派机制&#xff0c;破坏此机制的例子 类的加载过程&#xff08;生命周期…