数据结构和算法(六):贪心算法、分治算法、回溯算法、动态规划、拓扑排序

从广义上来讲:数据结构就是一组数据的存储结构 , 算法就是操作数据的方法

数据结构是为算法服务的,算法是要作用在特定的数据结构上的。

10个最常用的数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie树

10个最常用的算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法

本文总结了20个最常用的数据结构和算法,不管是应付面试还是工作需要,只要集中精力攻克这20个知识点就足够了。

数据结构和算法(一):复杂度、数组、链表、栈、队列的传送门

数据结构和算法(二):递归、排序、通用排序算法的传送门

数据结构和算法(三):二分查找、跳表、散列表、哈希算法的传送门

数据结构和算法(四):二叉树、红黑树、递归树、堆和堆排序、堆的应用的传送门

数据结构和算法(五):图、深度优先搜索和广度优先搜索、字符串匹配算法、Trie树、AC自动机的传送门

数据结构和算法(六):贪心算法、分治算法、回溯算法、动态规划、拓扑排序的传送门

第二十六章 贪心算法
一、什么是贪心算法
    1. 贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择,也就是说不从整体考虑,而是从局部看来是最优解,所以贪心算法得到的结果不一定是最优的。
    1. 贪心算法没有固定的算法解决框架,算法的关键就是贪婪策略的选择,根据不同问题选择不同的策略。
    1. 贪心算法的适用场景比较有限,更多是用来指导设计基础算法,比如最小生成树算法、单源最短路径算法等。
二、使用贪心算法的解决问题的思路
    1. 当我们看到此类数据时,首先要联想到贪心算法:针对一组数据,我们定义了限制值和期望值,希望从中选择几个数据,在满足限制值的前提下,期望值最大。(例如:从宝库中只能拿100kg的物品,从黄金、白银、纯铁怎么选择,使得价值最大,这里面限制值就是100kg,期望值就是价值最大。)
    1. 将问题抽象成限制值、期望值后,就可以尝试选择合适的贪婪策略去解决了,在刚才那个例子中,贪婪策略就是尽量多拿单价最高的金属。
    1. 选择不同的贪婪策略后,看下贪心算法产生的结果是否是最优的。从实践的角度来说,大部分能用贪心算法解决的问题,贪心算法的正确性都是显而易见的,也不需要严格证明。
三、贪心算法实战分析
    1. 分糖果
    • (1). 假设我们有m个糖果和n个孩子,要把糖果分给孩子吃,糖果多孩子少,每个糖果的大小不等,每个孩子对糖果的需求也不相同,只要糖果的大小超过了孩子的需求,那么这个孩子就会得到满足,请问:如何分配糖果,才能满足最多数量的孩子呢?

    • (2). 解决这个问题的第一步,我们把问题抽象成:在n个孩子中,选择一部分孩子分配糖果,使得满足的孩子最多。这里m个糖果就是限制值,最多满足的孩子就是期望值。

    • (3). 解决这个问题的第二步,尝试用贪心算法解决。对应一个孩子来说,如果小的糖果就可以解决,那么没必要用大的糖果,所以分配糖果的时候我们可以用需求最小的孩子开始分配。

    • (4). 我们的分配策略就是:每次从剩下的孩子中选择需求最小的孩子,分配给能满足他的最少糖果,这样的分配方案,就是满足孩子最多的方案,这也是显而易见的最优方案。

    1. 区间覆盖
    • (1). 假设有n个区间,区间的起始端点和结束端点分别是[a1,a2]、[b1,b2]...,我们从这n个区间内选取一部分不相交的区间,问:怎么选择,才能使得不相交的区间个数最多呢?

    • (2). 解决问题第一步,我们把问题抽象化,假设n个区间的最左侧是min端点,最右侧是max端点,这个问题就相当于,从n个区间中选取几个不相交的区间,从左到右将[min,max]覆盖完。

    • (3). 解决问题的第二步,选择合适的贪婪策略尝试解决,我们每次选择的时候,选择左边端点不重合,右边端点尽可能小的区间,使得剩下的区域尽可能大,就可以放置更多的区间。如下图:


      区间覆盖.png
    1. 如何用贪心算法实现霍夫曼压缩编码
    • (1). 假设有1000个字符,每个字符占1个字节,一共占1000个字节,也就是8000bit的存储空间;如果我们统计发现这1000个字符,只有6种字符,分别为a、b、c、d、e、f的话,我们就可以用3个bit来表示他们,如下图,这样我们就可以只占用3000bit的空间了,比原来节省了很多,那么我们还有更节省空间的方法吗?(3个bit其实可以存放8种不同的字符,2 x 2 x 2 = 8)
    a(000)、b(001)、c(010)、d(011)、e(100)、f(101)
    
    • (2). 霍夫曼编码就登场了,霍夫曼编码广泛应用于数据压缩中,压缩率在20%~90%之间,霍夫曼编码不仅会考察文本中有多少个字符,还会统计字符出现的频率,根据频率不同,选择不同长度的编码,频率高的字符选用短编码,频率低的字符选用稍长编码,霍夫曼编码试图使用不等长的编码方式,来进一步增加压缩效率。

    • (3). 由于霍夫曼编码是不等长的,所以解压缩的时候就比较困难,不知道该读取1位还是2位还是3位来解压缩,所以为了避免这种歧义,霍夫曼编码要求各个字符的编码之间,不能出现一个字符的编码是另一个字符编码的前缀。

    • (4). 在上面那个例子中,我们假设这6个字符出现的频率从高到低依次是a、b、c、d、e、f,我们采用霍夫曼编码的方式进行编码后,就是下图的样子,任意一个字符的编码都不是其他字符编码的前缀,在解压缩的时候,就可以以 读取尽可能长的可解压二进制串 的方式来解压,就不会出现解压歧义了,通过霍夫曼编码来压缩,这1000个字符只需要占用2100bit的存储空间就够了。

      采用霍夫曼编码对1000个字符进行压缩

    • (5). 霍夫曼编码的思想并不难理解,但是如何根据字符出现的频率,选用不同长度的编码呢? 这里可以这样处理,如下图:把每个字符都看作一个节点,把频率最低的两个节点f、c组合在一起生成一个父节点x,x的频率为f、c频率之和,再把x节点和频率次低的d节点组合生成父节点y,一直重复下去,直到把字符处理完;接下来,给每条边上画一个权值,指向左节点的边统一记做0,指向右节点的边统一记做1,那么从根节点到叶子节点的路径,就是叶子节点对应字符的霍夫曼编码了。


      霍夫曼编码如何根据字符频率选编码
第二十七章 分治算法
一、什么是分治算法?
    1. 分治算法的核心思想就是四个字:分而治之,就是将原问题分解成n个小问题,解决这些小问题后,将结果合并,就可以得到原问题的解了。
    1. 想用分治算法解决问题,一般需要满足以下条件:
    • (1). 原问题与分解成的小问题具有相同模式
    • (2). 子问题之间没有关联性,可以独立求解(需要跟动态规划区分开)
    • (3). 具有分解终止条件,也就是说,当问题足够小时,可以直接求解
    • (4). 可以将子问题合并成原问题,并且合并操作复杂度不能太高,不然就起不到降低总体算法复杂度的目的了
二、分治思想在海量数据处理中的应用
    1. 假设要给10G的订单数据按照金额进行排序,但是我们机器的内存只有2G,无法一次性加载到内存,也就无法单纯的利用快排、归并算法来解决了,就可以使用分治思想来解决
    1. 面对10G的订单数据,我们可以先扫描一遍订单,根据订单金额划分出几个金额区间,例如1到100元的放到一个文件中,100到200元的放到另一个文件中,以此类推,这样每个小文件都可以加载到内存中,最后将这些小文件合并,就得到有序的10G订单数据了。
    1. 如果订单数据存储在类似于GFS的分布式系统上,就可以将多个小文件并行加载到多台机器上并行处理,这样处理速度就会加快很多,这就是分治思想的一个应用。
第二十八章 回溯算法
第二十九章 初始动态规划
第三十章 动态规划实战
第三十一章 拓扑排序
一、什么是拓扑排序?
    1. 从局部有序推断出全局的顺序就叫做拓扑排序,例如,我们穿衣服的时候是有一定顺序的,你必须先穿袜子才能穿鞋,你必须先穿内裤才能穿秋裤,假设我们有8件衣服,可以按照下面的顺序进行穿衣服,就可以满足局部关系的前提下满足全局有序。
      拓扑排序.png
    1. 我们知道,算法是构建在具体数据结构之上的,我们想进行拓扑排序,就需要先把问题背景抽象成具体的数据结构,我们把衣服之间的依赖关系抽象成一个有向图,每件衣服对应图中的一个,依赖关系就是顶点之间的,而且这个图不仅是有向图,还得是有向无环图,因为图中一旦有了环,就无法进行拓扑排序了,所以拓扑排序是基于有向无环图的一个算法。抽象成的数据结构如下:

public class Graph {private int v; // 顶点的个数private LinkedList<Integer> adj[]; // 邻接表public Graph(int v) {this.v = v;adj = new LinkedList[v];for (int i=0; i<v; ++i) {adj[i] = new LinkedList<>();}}public void addEdge(int s, int t) { // s先于t,边s->tadj[s].add(t);}
}
二、如何在有向无环图上进行拓扑排序?
    1. Kahn算法
    • (1). 我们从图中找到入度为0的顶点,进行输出(如果一个顶点的入度为0,就意味着没有顶点先于这个顶点了,所以这个顶点就应该输出了),并且把这个顶点从图中删除,即把这个顶点可达的顶点的入度都减一;

    • (2). 然后我们重复上述过程,直到输出所有顶点,这样输出的序列就是拓扑排序之后的序列了。(输出的序列就是满足局部依赖关系的全局序列)

    • (3). 具体代码实现如下


public void topoSortByKahn() {int[] inDegree = new int[v]; // 统计每个顶点的入度for (int i = 0; i < v; ++i) {for (int j = 0; j < adj[i].size(); ++j) {int w = adj[i].get(j); // i->winDegree[w]++;}}LinkedList<Integer> queue = new LinkedList<>();for (int i = 0; i < v; ++i) {if (inDegree[i] == 0) queue.add(i);}while (!queue.isEmpty()) {int i = queue.remove();System.out.print("->" + i);for (int j = 0; j < adj[i].size(); ++j) {int k = adj[i].get(j);inDegree[k]--;if (inDegree[k] == 0) queue.add(k);}}
}
    1. DFS算法
    • (1). 首先根据邻接表,构造出逆邻接表,在逆邻接表中,边s->t表示s依赖于t,也就是s后于t执行。
    • (2). 然后我们递归处理每个顶点,先输出这个顶点可以到达的所有顶点,然后在输出它自己。
    • (3). 代码实现如下:

public void topoSortByDFS() {// 先构建逆邻接表,边s->t表示,s依赖于t,t先于sLinkedList<Integer> inverseAdj[] = new LinkedList[v];for (int i = 0; i < v; ++i) { // 申请空间inverseAdj[i] = new LinkedList<>();}for (int i = 0; i < v; ++i) { // 通过邻接表生成逆邻接表for (int j = 0; j < adj[i].size(); ++j) {int w = adj[i].get(j); // i->winverseAdj[w].add(i); // w->i}}boolean[] visited = new boolean[v];for (int i = 0; i < v; ++i) { // 深度优先遍历图if (visited[i] == false) {visited[i] = true;dfs(i, inverseAdj, visited);}}
}private void dfs(int vertex, LinkedList<Integer> inverseAdj[], boolean[] visited) {for (int i = 0; i < inverseAdj[vertex].size(); ++i) {int w = inverseAdj[vertex].get(i);if (visited[w] == true) continue;visited[w] = true;dfs(w, inverseAdj, visited);} // 先把vertex这个顶点可达的所有顶点都打印出来之后,再打印它自己System.out.print("->" + vertex);
}
三、Kahn算法和DFS算法进行拓扑排序的时间复杂度
    1. 从Kahn算法的代码中可以看出,每个顶点和每条边都被访问了一次,所以时间复杂度是O(V+E),V是顶点个数,E是边的个数
    1. 从DFS算法可以看出,每个顶点被访问两次,每条边被访问一次,所以时间复杂度也是O(V+E),V是顶点个数,E是边的个数
    1. 如果我们想知道数据库中所有用户的推荐关系之间,有没有存在环,就可以使用拓扑排序,把用户之间的的推荐关系从数据库加载到内存中,构建成今天所讲的这种有向图数据结构,再利用拓扑排序,就可以很快检测出是否存在环了。
第三十二章 最短路径
第三十三章 位图
最后编辑于:2024-10-27 15:08:47


喜欢的朋友记得点赞、收藏、关注哦!!!

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

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

相关文章

顺序表和链表(一)

目录 线性表 一、顺序表 <1>顺序表 &#xff08;1&#xff09;静态顺序表 &#xff08;2&#xff09;动态顺序表-按需申请 <2>链表 &#xff08;1&#xff09;单链表 &#xff08;2&#xff09;双链表 主程序&#xff08;test.c&#xff09; 头文件&#…

# Ubuntu 达人九步养成记(1)

Ubuntu 达人九步养成记&#xff08;1&#xff09; 目录&#xff1a; 一、ubuntu基本安装 二、设置语言环境 三、设置服务器镜像源 四、在启动栏添加终端图标 五、使用apt更新和升级系统软件 六、使用apt安装软件 七、使用apt删除软件以及apt-get 八、deb格式及谷歌浏览…

QT——TCP网络调试助手

目录 一.项目展示 ​编辑 二.开发流程 三.QTcpServer、QTcpSocket、QUdpSocket类的学习 1.QTcpServer服务端 2.QTcpSocket客户端 3.Udp通信 四.网络调试助手 1.首先我们实现当用户选择不同协议类型时不同的UI组件如何切换 2.实现打开/关闭按键图片的切换 方式一&…

导航栏渐变色iOS

- (void)viewDidLoad {[super viewDidLoad];// 设置导航栏属性self.navigationBar.translucent NO;[self.navigationBar setTitleTextAttributes:{NSForegroundColorAttributeName : [UIColor whiteColor], NSFontAttributeName:[UIFont boldSystemFontOfSize:28]}];// 修复iO…

《Web性能权威指南》-浏览器API与协议-读书笔记

本文是《Web性能权威指南》第四部分——浏览器API与协议的读书笔记。 第一部分——网络技术概览&#xff0c;请参考网络技术概览&#xff1b; 第二部分——无线网络性能&#xff0c;请参考无线网络性能&#xff1b; 第三部分——HTTP&#xff0c;请参考HTTP。 浏览器网络概述 …

使用TypeORM进行数据库操作

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用TypeORM进行数据库操作 引言 TypeORM 简介 安装 TypeORM 配置 TypeORM 定义实体 连接数据库 运行项目 高级功能 事务管理 关…

ESP-HaloPanel:用 ESP32-C2 打造超低成本智能家居面板

项目简介 在生活品质日益提升的今天&#xff0c;智能家居系统已经走进了千家万户&#xff0c;并逐渐成为现代生活的一部份。与此同时&#xff0c;一款设计精致、体积轻盈、操作简便的全屋智能家居控制面板&#xff0c;已经成为众多家庭的新宠。这种高效、直观的智能化的解决方…

Hadoop生态圈框架部署(四)- Hadoop完全分布式部署

文章目录 前言一、Hadoop完全分布式部署&#xff08;手动部署&#xff09;1. 下载hadoop2. 上传安装包2. 解压hadoop安装包3. 配置hadoop配置文件3.1 虚拟机hadoop1修改hadoop配置文件3.1.1 修改 hadoop-env.sh 配置文件3.3.2 修改 core-site.xml 配置文件3.3.3 修改 hdfs-site…

数据建模圣经|数据模型资源手册卷3,数据建模最佳实践

简介 本书采用了类设计模式的方式对数据模型进行高度抽象总结&#xff0c;展现了常见的数据模型构建模型等模型的作用、层次、分类、地位、沟通方式&#xff0c;和业务规则。使用一个强大的数据模型模式的数据建模&#xff0c;评估特定与广义模型的优缺点&#xff0c;有助于你改…

【力扣】Go语言回溯算法详细实现与方法论提炼

文章目录 一、引言二、回溯算法的核心概念三、组合问题1. LeetCode 77. 组合2. LeetCode 216. 组合总和III3. LeetCode 17. 电话号码的字母组合4. LeetCode 39. 组合总和5. LeetCode 40. 组合总和 II小结 四、分割问题6. LeetCode 131. 分割回文串7. LeetCode 93. 复原IP地址小…

#渗透测试#SRC漏洞挖掘# 信息收集-Shodan进阶之Mongodb未授权访问

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

Golang--流程控制

1、分支结构 1.1 if分支 单分支 语法&#xff1a;if 条件表达式 { 逻辑代码 } 当条件表达式为true时&#xff0c;就会执行代码块的代码。条件表达式左右的()可以不写&#xff0c;也建议不写 if和表达式中间&#xff0c;一定要有空格在Golang中&#xff0c;{}是必须有的,就算你…

【补补漏洞吧 | 02】等保测评ZooKeeperElasticsearch未授权访问漏洞补漏方法

一、项目背景 客户新系统上线&#xff0c;因为行业网络安全要求&#xff0c;需要做等保测评&#xff0c; 通过第三方漏扫工具扫描系统&#xff0c;漏扫报告显示ZooKeeper和 Elasticsearch 服务各拥有一个漏洞&#xff0c;具体结果如下&#xff1a; 1、ZooKeeper 未授权访问【…

Serverless + AI 让应用开发更简单

本文整理自 2024 云栖大会&#xff0c;阿里云智能高级技术专家&#xff0c;史明伟演讲议题《Serverless AI 让应用开发更简单》 随着云计算和人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;企业对于高效、灵活且成本效益高的解决方案的需求日益增长。本文旨在…

从0开始学PHP面向对象内容之(类,对象,构造/析构函数)

上期我们讲了面向对象的一些基本信息&#xff0c;这期让我们详细的了解一下 一、面向对象—类 1、PHP类的定义语法&#xff1a; <?php class className {var $var1;var $var2 "constant string";function classfunc ($arg1, $arg2) {[..]}[..] } ?>2、解…

(八)JavaWeb后端开发——Tomcat

目录 1.Web服务器概念 2.tomcat 1.Web服务器概念 服务器&#xff1a;安装了服务器软件的计算机服务器软件&#xff1a;接收用户的请求&#xff0c;处理请求&#xff0c;做出响应web服务器软件&#xff1a;在web服务器软件中&#xff0c;可以部署web项目&#xff0c;让用户通…

【Linux系列】Linux 和 Unix 系统中的`set`命令与错误处理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Nuxt.js 应用中的 nitro:config 事件钩子详解

title: Nuxt.js 应用中的 nitro:config 事件钩子详解 date: 2024/11/2 updated: 2024/11/2 author: cmdragon excerpt: nitro:config 是 Nuxt 3 中的一个生命周期钩子,允许开发者在初始化 Nitro 之前自定义 Nitro 的配置。Nitro 是 Nuxt 3 的服务器引擎,负责处理请求、渲…

[论文阅读]LOGAN: Membership Inference Attacks Against Generative Models

LOGAN: Membership Inference Attacks Against Generative Models https://arxiv.org/abs/1705.07663v4 Proceedings on Privacy Enhancing Technologies &#xff08;PoPETs&#xff09;&#xff0c;第 2019 卷&#xff0c;第 1 期。 这篇文章是17年的一篇文章&#xff0c;…

使用Vite构建现代化前端应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Vite构建现代化前端应用 引言 Vite 简介 安装 Vite 创建项目 启动开发服务器 项目结构 配置 Vite 开发模式 生产构建 使用插…