• 关于 有限序列出问题什么情况 的搜索结果

问题

Java 序列化的高级认识 热:报错

kun坤 2020-06-07 21:41:37 0 浏览量 回答数 1

回答

一、基础篇 1.1、Java基础 面向对象的特征:继承、封装和多态 final, finally, finalize 的区别 Exception、Error、运行时异常与一般异常有何异同 请写出5种常见到的runtime exception int 和 Integer 有什么区别,Integer的值缓存范围 包装类,装箱和拆箱 String、StringBuilder、StringBuffer 重载和重写的区别 抽象类和接口有什么区别 说说反射的用途及实现 说说自定义注解的场景及实现 HTTP请求的GET与POST方式的区别 Session与Cookie区别 列出自己常用的JDK包 MVC设计思想 equals与==的区别 hashCode和equals方法的区别与联系 什么是Java序列化和反序列化,如何实现Java序列化?或者请解释Serializable 接口的作用 Object类中常见的方法,为什么wait notify会放在Object里边? Java的平台无关性如何体现出来的 JDK和JRE的区别 Java 8有哪些新特性 1.2、Java常见集合 List 和 Set 区别 Set和hashCode以及equals方法的联系 List 和 Map 区别 Arraylist 与 LinkedList 区别 ArrayList 与 Vector 区别 HashMap 和 Hashtable 的区别 HashSet 和 HashMap 区别 HashMap 和 ConcurrentHashMap 的区别 HashMap 的工作原理及代码实现,什么时候用到红黑树 多线程情况下HashMap死循环的问题 HashMap出现Hash DOS攻击的问题 ConcurrentHashMap 的工作原理及代码实现,如何统计所有的元素个数 手写简单的HashMap 看过那些Java集合类的源码 1.3、进程和线程 线程和进程的概念、并行和并发的概念 创建线程的方式及实现 进程间通信的方式 说说 CountDownLatch、CyclicBarrier 原理和区别 说说 Semaphore 原理 说说 Exchanger 原理 ThreadLocal 原理分析,ThreadLocal为什么会出现OOM,出现的深层次原理 讲讲线程池的实现原理 线程池的几种实现方式 线程的生命周期,状态是如何转移的 可参考:《Java多线程编程核心技术》 1.4、锁机制 说说线程安全问题,什么是线程安全,如何保证线程安全 重入锁的概念,重入锁为什么可以防止死锁 产生死锁的四个条件(互斥、请求与保持、不剥夺、循环等待) 如何检查死锁(通过jConsole检查死锁) volatile 实现原理(禁止指令重排、刷新内存) synchronized 实现原理(对象监视器) synchronized 与 lock 的区别 AQS同步队列 CAS无锁的概念、乐观锁和悲观锁 常见的原子操作类 什么是ABA问题,出现ABA问题JDK是如何解决的 乐观锁的业务场景及实现方式 Java 8并法包下常见的并发类 偏向锁、轻量级锁、重量级锁、自旋锁的概念 可参考:《Java多线程编程核心技术》 1.5、JVM JVM运行时内存区域划分 内存溢出OOM和堆栈溢出SOE的示例及原因、如何排查与解决 如何判断对象是否可以回收或存活 常见的GC回收算法及其含义 常见的JVM性能监控和故障处理工具类:jps、jstat、jmap、jinfo、jconsole等 JVM如何设置参数 JVM性能调优 类加载器、双亲委派模型、一个类的生命周期、类是如何加载到JVM中的 类加载的过程:加载、验证、准备、解析、初始化 强引用、软引用、弱引用、虚引用 Java内存模型JMM 1.6、设计模式 常见的设计模式 设计模式的的六大原则及其含义 常见的单例模式以及各种实现方式的优缺点,哪一种最好,手写常见的单利模式 设计模式在实际场景中的应用 Spring中用到了哪些设计模式 MyBatis中用到了哪些设计模式 你项目中有使用哪些设计模式 说说常用开源框架中设计模式使用分析 动态代理很重要!!! 1.7、数据结构 树(二叉查找树、平衡二叉树、红黑树、B树、B+树) 深度有限算法、广度优先算法 克鲁斯卡尔算法、普林母算法、迪克拉斯算法 什么是一致性Hash及其原理、Hash环问题 常见的排序算法和查找算法:快排、折半查找、堆排序等 1.8、网络/IO基础 BIO、NIO、AIO的概念 什么是长连接和短连接 Http1.0和2.0相比有什么区别,可参考《Http 2.0》 Https的基本概念 三次握手和四次挥手、为什么挥手需要四次 从游览器中输入URL到页面加载的发生了什么?可参考《从输入URL到页面加载发生了什么》 二、数据存储和消息队列 2.1、数据库 MySQL 索引使用的注意事项 DDL、DML、DCL分别指什么 explain命令 left join,right join,inner join 数据库事物ACID(原子性、一致性、隔离性、持久性) 事物的隔离级别(读未提交、读以提交、可重复读、可序列化读) 脏读、幻读、不可重复读 数据库的几大范式 数据库常见的命令 说说分库与分表设计 分库与分表带来的分布式困境与应对之策(如何解决分布式下的分库分表,全局表?) 说说 SQL 优化之道 MySQL遇到的死锁问题、如何排查与解决 存储引擎的 InnoDB与MyISAM区别,优缺点,使用场景 索引类别(B+树索引、全文索引、哈希索引)、索引的原理 什么是自适应哈希索引(AHI) 为什么要用 B+tree作为MySQL索引的数据结构 聚集索引与非聚集索引的区别 遇到过索引失效的情况没,什么时候可能会出现,如何解决 limit 20000 加载很慢怎么解决 如何选择合适的分布式主键方案 选择合适的数据存储方案 常见的几种分布式ID的设计方案 常见的数据库优化方案,在你的项目中数据库如何进行优化的 2.2、Redis Redis 有哪些数据类型,可参考《Redis常见的5种不同的数据类型详解》 Redis 内部结构 Redis 使用场景 Redis 持久化机制,可参考《使用快照和AOF将Redis数据持久化到硬盘中》 Redis 集群方案与实现 Redis 为什么是单线程的? 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级 使用缓存的合理性问题 Redis常见的回收策略 2.3、消息队列 消息队列的使用场景 消息的重发补偿解决思路 消息的幂等性解决思路 消息的堆积解决思路 自己如何实现消息队列 如何保证消息的有序性 三、开源框架和容器 3.1、SSM/Servlet Servlet的生命周期 转发与重定向的区别 BeanFactory 和 ApplicationContext 有什么区别 Spring Bean 的生命周期 Spring IOC 如何实现 Spring中Bean的作用域,默认的是哪一个 说说 Spring AOP、Spring AOP 实现原理 动态代理(CGLib 与 JDK)、优缺点、性能对比、如何选择 Spring 事务实现方式、事务的传播机制、默认的事务类别 Spring 事务底层原理 Spring事务失效(事务嵌套),JDK动态代理给Spring事务埋下的坑,可参考《JDK动态代理给Spring事务埋下的坑!》 如何自定义注解实现功能 Spring MVC 运行流程 Spring MVC 启动流程 Spring 的单例实现原理 Spring 框架中用到了哪些设计模式 Spring 其他产品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等) 有没有用到Spring Boot,Spring Boot的认识、原理 MyBatis的原理 可参考《为什么会有Spring》 可参考《为什么会有Spring AOP》 3.2、Netty 为什么选择 Netty 说说业务中,Netty 的使用场景 原生的 NIO 在 JDK 1.7 版本存在 epoll bug 什么是TCP 粘包/拆包 TCP粘包/拆包的解决办法 Netty 线程模型 说说 Netty 的零拷贝 Netty 内部执行流程 Netty 重连实现 3.3、Tomcat Tomcat的基础架构(Server、Service、Connector、Container) Tomcat如何加载Servlet的 Pipeline-Valve机制 可参考:《四张图带你了解Tomcat系统架构!》 四、分布式 4.1、Nginx 请解释什么是C10K问题或者知道什么是C10K问题吗? Nginx简介,可参考《Nginx简介》 正向代理和反向代理. Nginx几种常见的负载均衡策略 Nginx服务器上的Master和Worker进程分别是什么 使用“反向代理服务器”的优点是什么? 4.2、分布式其他 谈谈业务中使用分布式的场景 Session 分布式方案 Session 分布式处理 分布式锁的应用场景、分布式锁的产生原因、基本概念 分布是锁的常见解决方案 分布式事务的常见解决方案 集群与负载均衡的算法与实现 说说分库与分表设计,可参考《数据库分库分表策略的具体实现方案》 分库与分表带来的分布式困境与应对之策 4.3、Dubbo 什么是Dubbo,可参考《Dubbo入门》 什么是RPC、如何实现RPC、RPC 的实现原理,可参考《基于HTTP的RPC实现》 Dubbo中的SPI是什么概念 Dubbo的基本原理、执行流程 五、微服务 5.1、微服务 前后端分离是如何做的? 微服务哪些框架 Spring Could的常见组件有哪些?可参考《Spring Cloud概述》 领域驱动有了解吗?什么是领域驱动模型?充血模型、贫血模型 JWT有了解吗,什么是JWT,可参考《前后端分离利器之JWT》 你怎么理解 RESTful 说说如何设计一个良好的 API 如何理解 RESTful API 的幂等性 如何保证接口的幂等性 说说 CAP 定理、BASE 理论 怎么考虑数据一致性问题 说说最终一致性的实现方案 微服务的优缺点,可参考《微服务批判》 微服务与 SOA 的区别 如何拆分服务、水平分割、垂直分割 如何应对微服务的链式调用异常 如何快速追踪与定位问题 如何保证微服务的安全、认证 5.2、安全问题 如何防范常见的Web攻击、如何方式SQL注入 服务端通信安全攻防 HTTPS原理剖析、降级攻击、HTTP与HTTPS的对比 5.3、性能优化 性能指标有哪些 如何发现性能瓶颈 性能调优的常见手段 说说你在项目中如何进行性能调优 六、其他 6.1、设计能力 说说你在项目中使用过的UML图 你如何考虑组件化、服务化、系统拆分 秒杀场景如何设计 可参考:《秒杀系统的技术挑战、应对策略以及架构设计总结一二!》 6.2、业务工程 说说你的开发流程、如何进行自动化部署的 你和团队是如何沟通的 你如何进行代码评审 说说你对技术与业务的理解 说说你在项目中遇到感觉最难Bug,是如何解决的 介绍一下工作中的一个你认为最有价值的项目,以及在这个过程中的角色、解决的问题、你觉得你们项目还有哪些不足的地方 6.3、软实力 说说你的优缺点、亮点 说说你最近在看什么书、什么博客、在研究什么新技术、再看那些开源项目的源代码 说说你觉得最有意义的技术书籍 工作之余做什么事情、平时是如何学习的,怎样提升自己的能力 说说个人发展方向方面的思考 说说你认为的服务端开发工程师应该具备哪些能力 说说你认为的架构师是什么样的,架构师主要做什么 如何看待加班的问题

徐刘根 2020-03-31 11:22:08 0 浏览量 回答数 0

回答

迭代法  迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程,跟迭代法相对应的是直接法(或者称为一次解法),即一次性解决问题。迭代法又分为精确迭代和近似迭代。“二分法”和“牛顿迭代法”属于近似迭代法。   迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。   利用迭代算法解决问题,需要做好以下三个方面的工作:   一、确定迭代变量。在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。   二、建立迭代关系式。所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以使用递推或倒推的方法来完成。   三、对迭代过程进行控制。在什么时候结束迭代过程。这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需要进一步分析出用来结束迭代过程的条件。   例 1 : 一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,问到第 12 个月时,该饲养场共有兔子多少只。   分析: 这是一个典型的递推问题。我们不妨假设第 1 个月时兔子的只数为 u 1 ,第 2 个月时兔子的只数为 u 2 ,第 3 个月时兔子的只数为 u 3 ,……根据题意,“这种兔子从出生的下一个月开始,每月新生一只兔子”,则有   u 1 = 1 , u 2 = u 1 + u 1 × 1 = 2 , u 3 = u 2 + u 2 × 1 = 4 ,……   根据这个规律,可以归纳出下面的递推公式:   u n = u n - 1 × 2 (n ≥ 2)   对应 u n 和 u n - 1 ,定义两个迭代变量 y 和 x ,可将上面的递推公式转换成如下迭代关系:   y=x*2   x=y   让计算机对这个迭代关系重复执行 11 次,就可以算出第 12 个月时的兔子数。参考程序如下:   cls   x=1   for i=2 to 12   y=x*2   x=y   next i   print y   end   例 2 : 阿米巴用简单分裂的方式繁殖,它每分裂一次要用 3 分钟。将若干个阿米巴放在一个盛满营养参液的容器内, 45 分钟后容器内充满了阿米巴。已知容器最多可以装阿米巴 220,220个。试问,开始的时候往容器内放了多少个阿米巴。请编程序算出。   分析: 根据题意,阿米巴每 3 分钟分裂一次,那么从开始的时候将阿米巴放入容器里面,到 45 分钟后充满容器,需要分裂 45/3=15 次。而“容器最多可以装阿米巴2^ 20 个”,即阿米巴分裂 15 次以后得到的个数是 2^20 。题目要求我们计算分裂之前的阿米巴数,不妨使用倒推的方法,从第 15 次分裂之后的 2^20 个,倒推出第 15 次分裂之前(即第 14 次分裂之后)的个数,再进一步倒推出第 13 次分裂之后、第 12 次分裂之后、……第 1 次分裂之前的个数。   设第 1 次分裂之前的个数为 x 0 、第 1 次分裂之后的个数为 x 1 、第 2 次分裂之后的个数为 x 2 、……第 15 次分裂之后的个数为 x 15 ,则有   x 14 =x 15 /2 、 x 13 =x 14 /2 、…… x n-1 =x n /2 (n ≥ 1)   因为第 15 次分裂之后的个数 x 15 是已知的,如果定义迭代变量为 x ,则可以将上面的倒推公式转换成如下的迭代公式:   x=x/2 ( x 的初值为第 15 次分裂之后的个数 2^20 )   让这个迭代公式重复执行 15 次,就可以倒推出第 1 次分裂之前的阿米巴个数。因为所需的迭代次数是个确定的值,我们可以使用一个固定次数的循环来实现对迭代过程的控制。参考程序如下:   cls   x=2^20   for i=1 to 15   x=x/2   next i   print x   end   ps:java中幂的算法是Math.pow(2, 20);返回double,稍微注意一下   例 3 : 验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1 。如此经过有限次运算后,总可以得到自然数 1 。人们把谷角静夫的这一发现叫做“谷角猜想”。   要求:编写一个程序,由键盘输入一个自然数 n ,把 n 经过有限次运算后,最终变成自然数 1 的全过程打印出来。   分析: 定义迭代变量为 n ,按照谷角猜想的内容,可以得到两种情况下的迭代关系式:当 n 为偶数时, n=n/2 ;当 n 为奇数时, n=n*3+1 。用 QBASIC 语言把它描述出来就是:   if n 为偶数 then   n=n/2   else   n=n*3+1   end if   这就是需要计算机重复执行的迭代过程。这个迭代过程需要重复执行多少次,才能使迭代变量 n 最终变成自然数 1 ,这是我们无法计算出来的。因此,还需进一步确定用来结束迭代过程的条件。仔细分析题目要求,不难看出,对任意给定的一个自然数 n ,只要经过有限次运算后,能够得到自然数 1 ,就已经完成了验证工作。因此,用来结束迭代过程的条件可以定义为: n=1 。参考程序如下:   cls   input "Please input n=";n   do until n=1   if n mod 2=0 then   rem 如果 n 为偶数,则调用迭代公式 n=n/2   n=n/2   print "—";n;   else   n=n*3+1   print "—";n;   end if   loop   end   迭代法   迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行:   (1) 选一个方程的近似根,赋给变量x0;   (2) 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0;   (3) 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。   若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为:   【算法】迭代法求方程的根   { x0=初始近似根;   do {   x1=x0;   x0=g(x1); /*按特定的方程计算新的近似根*/   } while ( fabs(x0-x1)>Epsilon);   printf(“方程的近似根是%f\n”,x0);   }   迭代算法也常用于求方程组的根,令   X=(x0,x1,…,xn-1)   设方程组为:   xi=gi(X) (I=0,1,…,n-1)   则求方程组根的迭代算法可描述如下:   【算法】迭代法求方程组的根   { for (i=0;i   x=初始近似根;   do {   for (i=0;i   y=x;   for (i=0;i   x=gi(X);   for (delta=0.0,i=0;i   if (fabs(y-x)>delta) delta=fabs(y-x);   } while (delta>Epsilon);   for (i=0;i   printf(“变量x[%d]的近似根是 %f”,I,x);   printf(“\n”);   }   具体使用迭代法求根时应注意以下两种可能发生的情况:   (1) 如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制;   (2) 方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。   递归   递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。   能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。   【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。   斐波那契数列为:0、1、1、2、3、……,即:   fib(0)=0;   fib(1)=1;   fib(n)=fib(n-1)+fib(n-2) (当n>1时)。   写成递归函数有:   int fib(int n)   { if (n==0) return 0;   if (n==1) return 1;   if (n>1) return fib(n-1)+fib(n-2);   }   递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。   在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。   在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。   由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。   【问题】 组合问题   问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为: (1)5、4、3 (2)5、4、2 (3)5、4、1   (4)5、3、2 (5)5、3、1 (6)5、2、1   (7)4、3、2 (8)4、3、1 (9)4、2、1   (10)3、2、1   分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m 个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。   【程序】   # include   # define MAXN 100   int a[MAXN];   void comb(int m,int k)   { int i,j;   for (i=m;i>=k;i--)   { a[k]=i;   if (k>1)   comb(i-1,k-1);   else   { for (j=a[0];j>0;j--)   printf(“%4d”,a[j]);   printf(“\n”);   }   }   }   void main()   { a[0]=3;   comb(5,3);   }   【问题】 背包问题   问题描述:有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。   设n 件物品的重量分别为w0、w1、…、wn-1,物品的价值分别为v0、v1、…、vn-1。采用递归寻找物品的选择方案。设前面已有了多种选择的方案,并保留了其中总价值最大的方案于数组option[ ],该方案的总价值存于变量maxv。当前正在考察新方案,其物品选择情况保存于数组cop[ ]。假定当前方案已考虑了前i-1件物品,现在要考虑第i件物品;当前方案已包含的物品的重量之和为tw;至此,若其余物品都选择是可能的话,本方案能达到的总价值的期望值为tv。算法引入tv是当一旦当前方案的总价值的期望值也小于前面方案的总价值maxv时,继续考察当前方案变成无意义的工作,应终止当前方案,立即去考察下一个方案。因为当方案的总价值不比maxv大时,该方案不会被再考察,这同时保证函数后找到的方案一定会比前面的方案更好。   对于第i件物品的选择考虑有两种可能:   (1) 考虑物品i被选择,这种可能性仅当包含它不会超过方案总重量限制时才是可行的。选中后,继续递归去考虑其余物品的选择。   (2) 考虑物品i不被选择,这种可能性仅当不包含物品i也有可能会找到价值更大的方案的情况。   按以上思想写出递归算法如下:   try(物品i,当前选择已达到的重量和,本方案可能达到的总价值tv)   { /*考虑物品i包含在当前方案中的可能性*/   if(包含物品i是可以接受的)   { 将物品i包含在当前方案中;   if (i   try(i+1,tw+物品i的重量,tv);   else   /*又一个完整方案,因为它比前面的方案好,以它作为最佳方案*/   以当前方案作为临时最佳方案保存;   恢复物品i不包含状态;   }   /*考虑物品i不包含在当前方案中的可能性*/   if (不包含物品i仅是可男考虑的)   if (i   try(i+1,tw,tv-物品i的价值);   else   /*又一个完整方案,因它比前面的方案好,以它作为最佳方案*/   以当前方案作为临时最佳方案保存;   }

沉默术士 2019-12-02 01:25:10 0 浏览量 回答数 0

新用户福利专场,云服务器ECS低至96.9元/年

新用户福利专场,云服务器ECS低至96.9元/年

回答

“程序设计 = 算法 + 数据结构”是瑞士计算机科学家Niklaus Wirth于1976年出版的一本书的书名,很快就成了在计算机工作者之间流传的一句名言。斗转星移,尽管新技术方法不断涌现,这句名言依然焕发着无限的生命力,它借助面向对象知识的普及,使数据结构技术更加完善和易于使用。由此,也说明了数据结构在计算机学科中的地位和不可替代的独特作用。 然而,在可视化程序设计的今天,借助于集成开发环境我们可以很方便、快捷地开发部署应用程序,程序设计似乎不再只是计算机专业的人员的专利,很多人以为,只要掌握了几种开发工具就可以成为编程高手了,其实这是一个误区。纵然,我们可以很熟练地掌握一门程序设计语言、熟练地运用各种IDE开发应用程序,但是我们写出的代码是否是优良的。我们的设计是否合理。代码执行是否是高效的。代码风格是否是有美感的。更甚的说我们所写出代码的是否是艺术。 在长达几年的时间内,我总是陷在了一个误区里面:即认为工程能力和算法能力是不相干的两回事,我们似乎可以很轻松地完成一个工程项目,至少我在做一些MIS系统的时候一直都是这么认为的,甚至觉得根本不需要所谓的算法或数据结构。当时一直想不通的是为什么Google、百度这样牛的公司却对ACMer们如此青睐,对于这种招聘的标准感到疑惑不解。为什么他们不在技术(多线程、网络编程、分布式系统等)上做要求,却偏偏只关注这么一小块的算法设计。 我曾经反复地告诉自己“程序设计 = 算法 + 数据结构”在70年代提出是受限于计算机硬件,当时的内存不足、计算能力不强,程序需要设计足够精巧细致。再看当前主流的计算机配置,比70年代的大型机运算能力还要强大,我们好像完全不用担心算法设计的问题。报着这样的想法,我向来都不太重视算法,而且工程中对算法的需求并不多。 只是有一天,我突然发现我只是片面地关注其中一个方面,硬件能力是提升了,但同时人们所面对的信息、数据、运算任务的规模也是极大的膨胀了,而且膨胀的规模比硬件本身运算能力提升的规模还要大很多。算法和数据结构不仅没有贬值,反而比之前那个时代显得更为重要。试想,在互联网迅猛发展的今天,一个中等规模的企业每天所产生的数据量能达到GB级甚至TB级。要处理这样的海量数据不是说单纯的硬件运算能力上来就解决了的,设计优良的算法和数据结构设计能够在1分钟之内完成任务,而一个糟糕的设计则可能需要1个小时的运行。 一般认为,一个数据结构是由数据元素依据某种逻辑联系组织起来的,这种对数据元素间逻辑关系的描述称为数据结构。许多大型系统的构造经验表明,系统实现的困难程度和系统构造的质量都严重的依赖于是否选择了最优的数据结构。许多时候,确定了数据结构后,算法就容易得到了。当然,有些情况下事情也会反过来,我们根据特定算法来选择数据结构与之适应。算法则可以理解为有基本运算及规定的运算顺序所构成的完整的解题步骤,或者看成按照要求设计好的有限的确切的计算序列,并且这样的步骤和序列可以解决一类问题。 总的来说,数据结构和算法并不是一门教你编程的课,它们可以脱离任何的计算机程序设计语言,而只需要从抽象意义上去概括描述。说的简单一点,数据结构是一门告诉你数据在计算机里如何组织的课程,而算法是一门告诉你数据在计算机里如何运算的课程,前者是结构学、后者是数学。程序设计就像盖房子,数据结构是砖、瓦,而算法则是设计图纸。你若想盖房子首先必须要有原材料(数据结构),但这些原材料并不能自动地盖起你想要的房子,你必须按照设计图纸(算法)一砖一瓦地去砌,这样你才能拥有你想要的房子。数据结构是程序设计这座大厦的基础,没有基础,无论设计有多么高明,这座大厦不可能建造起来。算法则是程序设计之灵魂,它是程序设计的思想所在,没有灵魂没有思想那不叫程序,只是一堆杂乱无章的符号而已。在程序设计中,数据结构就像物质,而算法则是意识,这在哲学上可以理解为:意识是依赖与物质而存在的,物质是由意识而发展的。双方相互依赖,缺一不可。 当然最经典的数据结构是有限的,包括线性表、栈、队列、串、数组、二叉树、树、图、查找表等,而算法则是琳琅满目的,多种多样的。就好像数据结构是人体的各种组织、器官,算法则是人的思想。你可以用自己的思想去支配你的身体各个可以运动的器官随意运动。如果你想吃苹果,你可以削皮吃,可以带皮吃,只要你愿意,甚至你可以不洗就吃。但无论如何,你的器官还是你的器官,就那么几样,目的只有一个就是吃苹果,而方式却是随心所欲的。这就是算法的灵活性、不固定性。因此可以这样说:数据结构是死的,而算法是活的。 我花了四年时间才走出这个误区,值得庆幸的是不算太晚,而我的梦想是要做一名优秀的架构师,缺乏数据结构和算法的深厚功底,很难设计出高水平的具有专业水准的架构和应用,数据结构和算法则是我实现梦想最坚实的基石。现在,也正是我需要开始沉淀的时刻。程序设计这项伟大的工程,教授于我的将不仅仅是技术这么简单,我期待它能给我以更深的思考与感悟,激发我对生命的热爱,对理想的执着,对卓越的追求。

琴瑟 2019-12-02 01:22:02 0 浏览量 回答数 0

回答

  迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。   利用迭代算法解决问题,需要做好以下三个方面的工作:   一、确定迭代变量。在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。   二、建立迭代关系式。所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以使用递推或倒推的方法来完成。   三、对迭代过程进行控制。在什么时候结束迭代过程。这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需要进一步分析出用来结束迭代过程的条件。   例 1 : 一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,问到第 12 个月时,该饲养场共有兔子多少只。   分析: 这是一个典型的递推问题。我们不妨假设第 1 个月时兔子的只数为 u 1 ,第 2 个月时兔子的只数为 u 2 ,第 3 个月时兔子的只数为 u 3 ,……根据题意,“这种兔子从出生的下一个月开始,每月新生一只兔子”,则有   u 1 = 1 , u 2 = u 1 + u 1 × 1 = 2 , u 3 = u 2 + u 2 × 1 = 4 ,……   根据这个规律,可以归纳出下面的递推公式:   u n = u n - 1 × 2 (n ≥ 2)   对应 u n 和 u n - 1 ,定义两个迭代变量 y 和 x ,可将上面的递推公式转换成如下迭代关系:   y=x*2   x=y   让计算机对这个迭代关系重复执行 11 次,就可以算出第 12 个月时的兔子数。参考程序如下:   cls   x=1   for i=2 to 12   y=x*2   x=y   next i   print y   end   例 2 : 阿米巴用简单分裂的方式繁殖,它每分裂一次要用 3 分钟。将若干个阿米巴放在一个盛满营养参液的容器内, 45 分钟后容器内充满了阿米巴。已知容器最多可以装阿米巴 2 20 个。试问,开始的时候往容器内放了多少个阿米巴。请编程序算出。   分析: 根据题意,阿米巴每 3 分钟分裂一次,那么从开始的时候将阿米巴放入容器里面,到 45 分钟后充满容器,需要分裂 45/3=15 次。而“容器最多可以装阿米巴 2 20 个”,即阿米巴分裂 15 次以后得到的个数是 2 20 。题目要求我们计算分裂之前的阿米巴数,不妨使用倒推的方法,从第 15 次分裂之后的 2 20 个,倒推出第 15 次分裂之前(即第 14 次分裂之后)的个数,再进一步倒推出第 13 次分裂之后、第 12 次分裂之后、……第 1 次分裂之前的个数。   设第 1 次分裂之前的个数为 x 0 、第 1 次分裂之后的个数为 x 1 、第 2 次分裂之后的个数为 x 2 、……第 15 次分裂之后的个数为 x 15 ,则有   x 14 =x 15 /2 、 x 13 =x 14 /2 、…… x n-1 =x n /2 (n ≥ 1)   因为第 15 次分裂之后的个数 x 15 是已知的,如果定义迭代变量为 x ,则可以将上面的倒推公式转换成如下的迭代公式:   x=x/2 ( x 的初值为第 15 次分裂之后的个数 2 20 )   让这个迭代公式重复执行 15 次,就可以倒推出第 1 次分裂之前的阿米巴个数。因为所需的迭代次数是个确定的值,我们可以使用一个固定次数的循环来实现对迭代过程的控制。参考程序如下:   cls   x=2^20   for i=1 to 15   x=x/2   next i   print x   end   例 3 : 验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1 。如此经过有限次运算后,总可以得到自然数 1 。人们把谷角静夫的这一发现叫做“谷角猜想”。   要求:编写一个程序,由键盘输入一个自然数 n ,把 n 经过有限次运算后,最终变成自然数 1 的全过程打印出来。   分析: 定义迭代变量为 n ,按照谷角猜想的内容,可以得到两种情况下的迭代关系式:当 n 为偶数时, n=n/2 ;当 n 为奇数时, n=n*3+1 。用 QBASIC 语言把它描述出来就是:   if n 为偶数 then   n=n/2   else   n=n*3+1   end if   这就是需要计算机重复执行的迭代过程。这个迭代过程需要重复执行多少次,才能使迭代变量 n 最终变成自然数 1 ,这是我们无法计算出来的。因此,还需进一步确定用来结束迭代过程的条件。仔细分析题目要求,不难看出,对任意给定的一个自然数 n ,只要经过有限次运算后,能够得到自然数 1 ,就已经完成了验证工作。因此,用来结束迭代过程的条件可以定义为: n=1 。参考程序如下:   cls   input "Please input n=";n   do until n=1   if n mod 2=0 then   rem 如果 n 为偶数,则调用迭代公式 n=n/2   n=n/2   print "—";n;   else   n=n*3+1   print "—";n;   end if   loop   end   迭代法   迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行:   (1) 选一个方程的近似根,赋给变量x0;   (2) 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0;   (3) 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。   若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为:   【算法】迭代法求方程的根   { x0=初始近似根;   do {   x1=x0;   x0=g(x1); /*按特定的方程计算新的近似根*/   } while ( fabs(x0-x1)>Epsilon);   printf(“方程的近似根是%f\n”,x0);   }   迭代算法也常用于求方程组的根,令   X=(x0,x1,…,xn-1)   设方程组为:   xi=gi(X) (I=0,1,…,n-1)   则求方程组根的迭代算法可描述如下:   【算法】迭代法求方程组的根   { for (i=0;i   x=初始近似根;   do {   for (i=0;i   y=x;   for (i=0;i   x=gi(X);   for (delta=0.0,i=0;i   if (fabs(y-x)>delta) delta=fabs(y-x);   } while (delta>Epsilon);   for (i=0;i   printf(“变量x[%d]的近似根是 %f”,I,x);   printf(“\n”);   }   具体使用迭代法求根时应注意以下两种可能发生的情况:   (1) 如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制;   (2) 方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。   递归   递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。   能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。   【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。   斐波那契数列为:0、1、1、2、3、……,即:   fib(0)=0;   fib(1)=1;   fib(n)=fib(n-1)+fib(n-2) (当n>1时)。   写成递归函数有:   int fib(int n)   { if (n==0) return 0;   if (n==1) return 1;   if (n>1) return fib(n-1)+fib(n-2);   }   递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。   在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。   在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。   由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。   【问题】 组合问题   问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为: (1)5、4、3 (2)5、4、2 (3)5、4、1   (4)5、3、2 (5)5、3、1 (6)5、2、1   (7)4、3、2 (8)4、3、1 (9)4、2、1   (10)3、2、1   分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m 个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。   【程序】   # include   # define MAXN 100   int a[MAXN];   void comb(int m,int k)   { int i,j;   for (i=m;i>=k;i--)   { a[k]=i;   if (k>1)   comb(i-1,k-1);   else   { for (j=a[0];j>0;j--)   printf(“%4d”,a[j]);   printf(“\n”);   }   }   }   void main()   { a[0]=3;   comb(5,3);   }   【问题】 背包问题   问题描述:有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。   设n 件物品的重量分别为w0、w1、…、wn-1,物品的价值分别为v0、v1、…、vn-1。采用递归寻找物品的选择方案。设前面已有了多种选择的方案,并保留了其中总价值最大的方案于数组option[ ],该方案的总价值存于变量maxv。当前正在考察新方案,其物品选择情况保存于数组cop[ ]。假定当前方案已考虑了前i-1件物品,现在要考虑第i件物品;当前方案已包含的物品的重量之和为tw;至此,若其余物品都选择是可能的话,本方案能达到的总价值的期望值为tv。算法引入tv是当一旦当前方案的总价值的期望值也小于前面方案的总价值maxv时,继续考察当前方案变成无意义的工作,应终止当前方案,立即去考察下一个方案。因为当方案的总价值不比maxv大时,该方案不会被再考察,这同时保证函数后找到的方案一定会比前面的方案更好。   对于第i件物品的选择考虑有两种可能:   (1) 考虑物品i被选择,这种可能性仅当包含它不会超过方案总重量限制时才是可行的。选中后,继续递归去考虑其余物品的选择。   (2) 考虑物品i不被选择,这种可能性仅当不包含物品i也有可能会找到价值更大的方案的情况。   按以上思想写出递归算法如下:   try(物品i,当前选择已达到的重量和,本方案可能达到的总价值tv)   { /*考虑物品i包含在当前方案中的可能性*/   if(包含物品i是可以接受的)   { 将物品i包含在当前方案中;   if (i   try(i+1,tw+物品i的重量,tv);   else   /*又一个完整方案,因为它比前面的方案好,以它作为最佳方案*/   以当前方案作为临时最佳方案保存;   恢复物品i不包含状态;   }   /*考虑物品i不包含在当前方案中的可能性*/   if (不包含物品i仅是可男考虑的)   if (i   try(i+1,tw,tv-物品i的价值);   else   /*又一个完整方案,因它比前面的方案好,以它作为最佳方案*/   以当前方案作为临时最佳方案保存;   }   为了理解上述算法,特举以下实例。设有4件物品,它们的重量和价值见表:   物品 0 1 2 3   重量 5 3 2 1   价值 4 4 3 1   并设限制重量为7。则按以上算法,下图表示找解过程。由图知,一旦找到一个解,算法就进一步找更好的佳。如能判定某个查找分支不会找到更好的解,算法不会在该分支继续查找,而是立即终止该分支,并去考察下一个分支。   按上述算法编写函数和程序如下:   【程序】   # include   # define N 100   double limitW,totV,maxV;   int option[N],cop[N];   struct { double weight;   double value;   }a[N];   int n;   void find(int i,double tw,double tv)   { int k;   /*考虑物品i包含在当前方案中的可能性*/   if (tw+a.weight<=limitW)   { cop=1;   if (i   else   { for (k=0;k   option[k]=cop[k];   maxv=tv;   }   cop=0;   }   /*考虑物品i不包含在当前方案中的可能性*/   if (tv-a.value>maxV)   if (i   else   { for (k=0;k   option[k]=cop[k];   maxv=tv-a.value;   }   }   void main()   { int k;   double w,v;   printf(“输入物品种数\n”);   scanf((“%d”,&n);   printf(“输入各物品的重量和价值\n”);   for (totv=0.0,k=0;k   { scanf(“%1f%1f”,&w,&v);   a[k].weight=w;   a[k].value=v;   totV+=V;   }   printf(“输入限制重量\n”);   scanf(“%1f”,&limitV);   maxv=0.0;   for (k=0;k find(0,0.0,totV);   for (k=0;k   if (option[k]) printf(“%4d”,k+1);   printf(“\n总价值为%.2f\n”,maxv);   }   作为对比,下面以同样的解题思想,考虑非递归的程序解。为了提高找解速度,程序不是简单地逐一生成所有候选解,而是从每个物品对候选解的影响来形成值得进一步考虑的候选解,一个候选解是通过依次考察每个物品形成的。对物品i的考察有这样几种情况:当该物品被包含在候选解中依旧满足解的总重量的限制,该物品被包含在候选解中是应该继续考虑的;反之,该物品不应该包括在当前正在形成的候选解中。同样地,仅当物品不被包括在候选解中,还是有可能找到比目前临时最佳解更好的候选解时,才去考虑该物品不被包括在候选解中;反之,该物品不包括在当前候选解中的方案也不应继续考虑。对于任一值得继续考虑的方案,程序就去进一步考虑下一个物品。   【程序】   # include   # define N 100   double limitW;   int cop[N];   struct ele { double weight;   double value;   } a[N];   int k,n;   struct { int ;   double tw;   double tv;   }twv[N];   void next(int i,double tw,double tv)   { twv.=1;   twv.tw=tw;   twv.tv=tv;   }   double find(struct ele *a,int n)   { int i,k,f;   double maxv,tw,tv,totv;   maxv=0;   for (totv=0.0,k=0;k   totv+=a[k].value;   next(0,0.0,totv);   i=0;   While (i>=0)   { f=twv.;   tw=twv.tw;   tv=twv.tv;   switch(f)   { case 1: twv.++;   if (tw+a.weight<=limitW)   if (i   { next(i+1,tw+a.weight,tv);   i++;   }   else   { maxv=tv;   for (k=0;k   cop[k]=twv[k].!=0;   }   break;   case 0: i--;   break;   default: twv.=0;   if (tv-a.value>maxv)   if (i   { next(i+1,tw,tv-a.value);   i++;   }   else   { maxv=tv-a.value;   for (k=0;k   cop[k]=twv[k].!=0;   }   break;   }   }   return maxv;   }   void main()   { double maxv;   printf(“输入物品种数\n”);   scanf((“%d”,&n);   printf(“输入限制重量\n”);   scanf(“%1f”,&limitW);   printf(“输入各物品的重量和价值\n”);   for (k=0;k   scanf(“%1f%1f”,&a[k].weight,&a[k].value);   maxv=find(a,n);   printf(“\n选中的物品为\n”);   for (k=0;k   if (option[k]) printf(“%4d”,k+1);   printf(“\n总价值为%.2f\n”,maxv);   }   递归的基本概念和特点   程序调用自身的编程技巧称为递归( recursion)。   一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。   一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。   注意:   (1) 递归就是在过程或函数里调用自身;   (2) 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。

小哇 2019-12-02 01:25:19 0 浏览量 回答数 0

回答

迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。 利用迭代算法解决问题,需要做好以下三个方面的工作: 一、确定迭代变量。在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。 二、建立迭代关系式。所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以使用递推或倒推的方法来完成。 三、对迭代过程进行控制。在什么时候结束迭代过程。这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需要进一步分析出用来结束迭代过程的条件。 例 1 : 一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,问到第 12 个月时,该饲养场共有兔子多少只。 分析: 这是一个典型的递推问题。我们不妨假设第 1 个月时兔子的只数为 u 1 ,第 2 个月时兔子的只数为 u 2 ,第 3 个月时兔子的只数为 u 3 ,……根据题意,“这种兔子从出生的下一个月开始,每月新生一只兔子”,则有 u 1 = 1 , u 2 = u 1 + u 1 × 1 = 2 , u 3 = u 2 + u 2 × 1 = 4 ,…… 根据这个规律,可以归纳出下面的递推公式: u n = u n - 1 × 2 (n ≥ 2) 对应 u n 和 u n - 1 ,定义两个迭代变量 y 和 x ,可将上面的递推公式转换成如下迭代关系: y=x*2 x=y 让计算机对这个迭代关系重复执行 11 次,就可以算出第 12 个月时的兔子数。参考程序如下: cls x=1 for i=2 to 12 y=x*2 x=y next i print y end 例 2 : 阿米巴用简单分裂的方式繁殖,它每分裂一次要用 3 分钟。将若干个阿米巴放在一个盛满营养参液的容器内, 45 分钟后容器内充满了阿米巴。已知容器最多可以装阿米巴 2 20 个。试问,开始的时候往容器内放了多少个阿米巴。请编程序算出。 分析: 根据题意,阿米巴每 3 分钟分裂一次,那么从开始的时候将阿米巴放入容器里面,到 45 分钟后充满容器,需要分裂 45/3=15 次。而“容器最多可以装阿米巴 2 20 个”,即阿米巴分裂 15 次以后得到的个数是 2 20 。题目要求我们计算分裂之前的阿米巴数,不妨使用倒推的方法,从第 15 次分裂之后的 2 20 个,倒推出第 15 次分裂之前(即第 14 次分裂之后)的个数,再进一步倒推出第 13 次分裂之后、第 12 次分裂之后、……第 1 次分裂之前的个数。 设第 1 次分裂之前的个数为 x 0 、第 1 次分裂之后的个数为 x 1 、第 2 次分裂之后的个数为 x 2 、……第 15 次分裂之后的个数为 x 15 ,则有 x 14 =x 15 /2 、 x 13 =x 14 /2 、…… x n-1 =x n /2 (n ≥ 1) 因为第 15 次分裂之后的个数 x 15 是已知的,如果定义迭代变量为 x ,则可以将上面的倒推公式转换成如下的迭代公式: x=x/2 ( x 的初值为第 15 次分裂之后的个数 2 20 ) 让这个迭代公式重复执行 15 次,就可以倒推出第 1 次分裂之前的阿米巴个数。因为所需的迭代次数是个确定的值,我们可以使用一个固定次数的循环来实现对迭代过程的控制。参考程序如下: cls x=2^20 for i=1 to 15 x=x/2 next i print x end 例 3 : 验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1 。如此经过有限次运算后,总可以得到自然数 1 。人们把谷角静夫的这一发现叫做“谷角猜想”。 要求:编写一个程序,由键盘输入一个自然数 n ,把 n 经过有限次运算后,最终变成自然数 1 的全过程打印出来。 分析: 定义迭代变量为 n ,按照谷角猜想的内容,可以得到两种情况下的迭代关系式:当 n 为偶数时, n=n/2 ;当 n 为奇数时, n=n*3+1 。用 QBASIC 语言把它描述出来就是: if n 为偶数 then n=n/2 else n=n*3+1 end if 这就是需要计算机重复执行的迭代过程。这个迭代过程需要重复执行多少次,才能使迭代变量 n 最终变成自然数 1 ,这是我们无法计算出来的。因此,还需进一步确定用来结束迭代过程的条件。仔细分析题目要求,不难看出,对任意给定的一个自然数 n ,只要经过有限次运算后,能够得到自然数 1 ,就已经完成了验证工作。因此,用来结束迭代过程的条件可以定义为: n=1 。参考程序如下: cls input "Please input n=";n do until n=1 if n mod 2=0 then rem 如果 n 为偶数,则调用迭代公式 n=n/2 n=n/2 print "—";n; else n=n*3+1 print "—";n; end if loop end 迭代法 迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行: (1) 选一个方程的近似根,赋给变量x0; (2) 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0; (3) 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。 若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为: 【算法】迭代法求方程的根 { x0=初始近似根; do { x1=x0; x0=g(x1); /*按特定的方程计算新的近似根*/ } while ( fabs(x0-x1)>Epsilon); printf(“方程的近似根是%f\n”,x0); } 迭代算法也常用于求方程组的根,令 X=(x0,x1,…,xn-1) 设方程组为: xi=gi(X) (I=0,1,…,n-1) 则求方程组根的迭代算法可描述如下: 【算法】迭代法求方程组的根 { for (i=0;i x=初始近似根; do { for (i=0;i y=x; for (i=0;i x=gi(X); for (delta=0.0,i=0;i if (fabs(y-x)>delta) delta=fabs(y-x); } while (delta>Epsilon); for (i=0;i printf(“变量x[%d]的近似根是 %f”,I,x); printf(“\n”); } 具体使用迭代法求根时应注意以下两种可能发生的情况: (1) 如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制; (2) 方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。 递归 递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。 能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。 【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。 斐波那契数列为:0、1、1、2、3、……,即: fib(0)=0; fib(1)=1; fib(n)=fib(n-1)+fib(n-2) (当n>1时)。 写成递归函数有: int fib(int n) { if (n==0) return 0; if (n==1) return 1; if (n>1) return fib(n-1)+fib(n-2); } 递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。 在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。 在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。 由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。 【问题】 组合问题 问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为: (1)5、4、3 (2)5、4、2 (3)5、4、1 (4)5、3、2 (5)5、3、1 (6)5、2、1 (7)4、3、2 (8)4、3、1 (9)4、2、1 (10)3、2、1 分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m 个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。 【程序】 # include # define MAXN 100 int a[MAXN]; void comb(int m,int k) { int i,j; for (i=m;i>=k;i--) { a[k]=i; if (k>1) comb(i-1,k-1); else { for (j=a[0];j>0;j--) printf(“%4d”,a[j]); printf(“\n”); } } } void main() { a[0]=3; comb(5,3); } 【问题】 背包问题 问题描述:有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。 设n 件物品的重量分别为w0、w1、…、wn-1,物品的价值分别为v0、v1、…、vn-1。采用递归寻找物品的选择方案。设前面已有了多种选择的方案,并保留了其中总价值最大的方案于数组option[ ],该方案的总价值存于变量maxv。当前正在考察新方案,其物品选择情况保存于数组cop[ ]。假定当前方案已考虑了前i-1件物品,现在要考虑第i件物品;当前方案已包含的物品的重量之和为tw;至此,若其余物品都选择是可能的话,本方案能达到的总价值的期望值为tv。算法引入tv是当一旦当前方案的总价值的期望值也小于前面方案的总价值maxv时,继续考察当前方案变成无意义的工作,应终止当前方案,立即去考察下一个方案。因为当方案的总价值不比maxv大时,该方案不会被再考察,这同时保证函数后找到的方案一定会比前面的方案更好。 对于第i件物品的选择考虑有两种可能: (1) 考虑物品i被选择,这种可能性仅当包含它不会超过方案总重量限制时才是可行的。选中后,继续递归去考虑其余物品的选择。 (2) 考虑物品i不被选择,这种可能性仅当不包含物品i也有可能会找到价值更大的方案的情况。 按以上思想写出递归算法如下: try(物品i,当前选择已达到的重量和,本方案可能达到的总价值tv) { /*考虑物品i包含在当前方案中的可能性*/ if(包含物品i是可以接受的) { 将物品i包含在当前方案中; if (i try(i+1,tw+物品i的重量,tv); else /*又一个完整方案,因为它比前面的方案好,以它作为最佳方案*/ 以当前方案作为临时最佳方案保存; 恢复物品i不包含状态; } /*考虑物品i不包含在当前方案中的可能性*/ if (不包含物品i仅是可男考虑的) if (i try(i+1,tw,tv-物品i的价值); else /*又一个完整方案,因它比前面的方案好,以它作为最佳方案*/ 以当前方案作为临时最佳方案保存; } 为了理解上述算法,特举以下实例。设有4件物品,它们的重量和价值见表: 物品 0 1 2 3 重量 5 3 2 1 价值 4 4 3 1 并设限制重量为7。则按以上算法,下图表示找解过程。由图知,一旦找到一个解,算法就进一步找更好的佳。如能判定某个查找分支不会找到更好的解,算法不会在该分支继续查找,而是立即终止该分支,并去考察下一个分支。 按上述算法编写函数和程序如下: 【程序】 # include # define N 100 double limitW,totV,maxV; int option[N],cop[N]; struct { double weight; double value; }a[N]; int n; void find(int i,double tw,double tv) { int k; /*考虑物品i包含在当前方案中的可能性*/ if (tw+a.weight<=limitW) { cop=1; if (i else { for (k=0;k option[k]=cop[k]; maxv=tv; } cop=0; } /*考虑物品i不包含在当前方案中的可能性*/ if (tv-a.value>maxV) if (i else { for (k=0;k option[k]=cop[k]; maxv=tv-a.value; } } void main() { int k; double w,v; printf(“输入物品种数\n”); scanf((“%d”,&n); printf(“输入各物品的重量和价值\n”); for (totv=0.0,k=0;k { scanf(“%1f%1f”,&w,&v); a[k].weight=w; a[k].value=v; totV+=V; } printf(“输入限制重量\n”); scanf(“%1f”,&limitV); maxv=0.0; for (k=0;k find(0,0.0,totV); for (k=0;k if (option[k]) printf(“%4d”,k+1); printf(“\n总价值为%.2f\n”,maxv); } 作为对比,下面以同样的解题思想,考虑非递归的程序解。为了提高找解速度,程序不是简单地逐一生成所有候选解,而是从每个物品对候选解的影响来形成值得进一步考虑的候选解,一个候选解是通过依次考察每个物品形成的。对物品i的考察有这样几种情况:当该物品被包含在候选解中依旧满足解的总重量的限制,该物品被包含在候选解中是应该继续考虑的;反之,该物品不应该包括在当前正在形成的候选解中。同样地,仅当物品不被包括在候选解中,还是有可能找到比目前临时最佳解更好的候选解时,才去考虑该物品不被包括在候选解中;反之,该物品不包括在当前候选解中的方案也不应继续考虑。对于任一值得继续考虑的方案,程序就去进一步考虑下一个物品。 【程序】 # include # define N 100 double limitW; int cop[N]; struct ele { double weight; double value; } a[N]; int k,n; struct { int ; double tw; double tv; }twv[N]; void next(int i,double tw,double tv) { twv.=1; twv.tw=tw; twv.tv=tv; } double find(struct ele *a,int n) { int i,k,f; double maxv,tw,tv,totv; maxv=0; for (totv=0.0,k=0;k totv+=a[k].value; next(0,0.0,totv); i=0; While (i>=0) { f=twv.; tw=twv.tw; tv=twv.tv; switch(f) { case 1: twv.++; if (tw+a.weight<=limitW) if (i { next(i+1,tw+a.weight,tv); i++; } else { maxv=tv; for (k=0;k cop[k]=twv[k].!=0; } break; case 0: i--; break; default: twv.=0; if (tv-a.value>maxv) if (i { next(i+1,tw,tv-a.value); i++; } else { maxv=tv-a.value; for (k=0;k cop[k]=twv[k].!=0; } break; } } return maxv; } void main() { double maxv; printf(“输入物品种数\n”); scanf((“%d”,&n); printf(“输入限制重量\n”); scanf(“%1f”,&limitW); printf(“输入各物品的重量和价值\n”); for (k=0;k scanf(“%1f%1f”,&a[k].weight,&a[k].value); maxv=find(a,n); printf(“\n选中的物品为\n”); for (k=0;k if (option[k]) printf(“%4d”,k+1); printf(“\n总价值为%.2f\n”,maxv); } 递归的基本概念和特点 程序调用自身的编程技巧称为递归( recursion)。 一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。 一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。 注意: (1) 递归就是在过程或函数里调用自身; (2) 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。

马铭芳 2019-12-02 01:24:44 0 浏览量 回答数 0

回答

迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程,跟迭代法相对应的是直接法,即一次性解决问题。迭代法又分为精确迭代和近似迭代。“二分法”和“牛顿迭代法”属于近似迭代法。迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。 迭代是数值分析中通过从一个初始估计出发寻找一系列近似解来解决问题(一般是解方程或者方程组)的过程,为实现这一过程所使用的方法统称为迭代法(Iterative Method)。 一般可以做如下定义:对于给定的线性方程组x=Bx+f(这里的x、B、f同为矩阵,任意线性方程组都可以变换成此形式),用公式x(k+1)=Bx(k)+f(括号中为上标,代表迭代k次得到的x,初始时k=0)逐步带入求近似解的方法称为迭代法(或称一阶定常迭代法)。如果k趋向无穷大时limx(k)存在,记为x*,称此迭代法收敛。显然x*就是此方程组的解,否则称为迭代法发散。 跟迭代法相对应的是直接法(或者称为一次解法),即一次性的快速解决问题,例如通过开方解决方程x +3= 4。一般如果可能,直接解法总是优先考虑的。但当遇到复杂问题时,特别是在未知量很多,方程为非线性时,我们无法找到直接解法(例如五次以及更高次的代数方程没有解析解,参见阿贝耳定理),这时候或许可以通过迭代法寻求方程(组)的近似解。 最常见的迭代法是牛顿法。其他还包括最速下降法、共轭迭代法、变尺度迭代法、最小二乘法、线性规划、非线性规划、单纯型法、惩罚函数法、斜率投影法、遗传算法、模拟退火等等。 利用迭代算法解决问题,需要做好以下三个方面的工作: 确定迭代变量 在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。 建立迭代关系式 所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以顺推或倒推的方法来完成。 对迭代过程进行控制 在 什么时候结束迭代过程。这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数 是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需 要进一步分析出用来结束迭代过程的条件。 举例 例 1 :一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,问到第 12 个月时,该饲养场共有兔子多少只。 分析:这是一个典型的递推问题。我们不妨假设第 1 个月时兔子的只数为 u 1 ,第 2 个月时兔子的只数为 u 2 ,第 3 个月时兔子的只数为 u 3 ,……根据题意,“这种兔子从出生的下一个月开始,每月新生一只兔子”,则有 u 1 = 1 , u 2 = u 1 + u 1 × 1 = 2 , u 3 = u 2 + u 2 × 1 = 4 ,…… 根据这个规律,可以归纳出下面的递推公式: u n = u(n - 1)× 2 (n ≥ 2) 对应 u n 和 u(n - 1),定义两个迭代变量 y 和 x ,可将上面的递推公式转换成如下迭代关系: y=x*2 x=y 让计算机对这个迭代关系重复执行 11 次,就可以算出第 12 个月时的兔子数。参考程序如下: cls x=1 for i=2 to 12 y=x*2 x=y next i print y end 例 2 :阿米巴用简单分裂的方式繁殖,它每分裂一次要用 3 分钟。将若干个阿米巴放在一个盛满营养参液的容器内, 45 分钟后容器内充满了阿米巴。已知容器最多可以装阿米巴 220,220个。试问,开始的时候往容器内放了多少个阿米巴。请编程序算出。 分析:根据题意,阿米巴每 3 分钟分裂一次,那么从开始的时候将阿米巴放入容器里面,到 45 分钟后充满容器,需要分裂 45/3=15 次。而“容器最多可以装阿米巴2^ 20 个”,即阿米巴分裂 15 次以后得到的个数是 2^20。题目要求我们计算分裂之前的阿米巴数,不妨使用倒推的方法,从第 15 次分裂之后的 2^20 个,倒推出第 15 次分裂之前(即第 14 次分裂之后)的个数,再进一步倒推出第 13 次分裂之后、第 12 次分裂之后、……第 1 次分裂之前的个数。 设第 1 次分裂之前的个数为 x 0 、第 1 次分裂之后的个数为 x 1 、第 2 次分裂之后的个数为 x 2 、……第 15 次分裂之后的个数为 x 15 ,则有 x 14 =x 15 /2 、 x 13 =x 14 /2 、…… x n-1 =x n /2 (n ≥ 1) 因为第 15 次分裂之后的个数 x 15 是已知的,如果定义迭代变量为 x ,则可以将上面的倒推公式转换成如下的迭代公式: x=x/2 (x 的初值为第 15 次分裂之后的个数 2^20) 让这个迭代公式重复执行 15 次,就可以倒推出第 1 次分裂之前的阿米巴个数。因为所需的迭代次数是个确定的值,我们可以使用一个固定次数的循环来实现对迭代过程的控制。参考程序如下: cls x=2^20 for i=1 to 15 x=x/2 next i print x end ps:java中幂的算法是Math.pow(2,20);返回double,稍微注意一下 例 3 :验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1。如此经过有限次运算后,总可以得到自然数 1。人们把谷角静夫的这一发现叫做“谷角猜想”。 要求:编写一个程序,由键盘输入一个自然数 n ,把 n 经过有限次运算后,最终变成自然数 1 的全过程打印出来。 分析:定义迭代变量为 n ,按照谷角猜想的内容,可以得到两种情况下的迭代关系式:当 n 为偶数时, n=n/2 ;当 n 为奇数时, n=n*3+1。用 QBASIC 语言把它描述出来就是: if n 为偶数 then n=n/2 else n=n*3+1 end if 这就是需要计算机重复执行的迭代过程。这个迭代过程需要重复执行多少次,才能使迭代变量 n 最终变成自然数 1 ,这是我们无法计算出来的。因此,还需进一步确定用来结束迭代过程的条件。仔细分析题目要求,不难看出,对任意给定的一个自然数 n ,只要经过有限次运算后,能够得到自然数 1 ,就已经完成了验证工作。因此,用来结束迭代过程的条件可以定义为:n=1。参考程序如下: cls input "Please input n=";n do until n=1 if n mod 2=0 then rem 如果 n 为偶数,则调用迭代公式 n=n/2 n=n/2 print "—";n; else n=n*3+1 print "—";n; end if loop end 迭代法开平方: #include<stdio.h> #include<math.h> void main() { double a,x0,x1; printf("Input a:\n"); scanf("%lf",&a);//为什么在VC6.0中不能写成“scanf("%f",&a);”。 if(a<0) printf("Error!\n"); else { x0=a/2; x1=(x0+a/x0)/2; do { x0=x1; x1=(x0+a/x0)/2; }while(fabs(x0-x1)>=1e-6); } printf("Result:\n"); printf("sqrt(%g)=%g\n",a,x1); } 求平方根的迭代公式:x1=1/2*(x0+a/x0)。 算法:1.先自定一个初值x0,作为a的平方根值,在我们的程序中取a/2作为a的初值;利用迭代公式求出一个x1。此值与真正的a的平方根值相比,误差很大。 ⒉把新求得的x1代入x0中,准备用此新的x0再去求出一个新的x1. ⒊利用迭代公式再求出一个新的x1的值,也就是用新的x0又求出一个新的平方根值x1,此值将更趋近于真正的平方根值。 ⒋比较前后两次求得的平方根值x0和x1,如果它们的差值小于我们指定的值,即达到我们要求的精度,则认为x1就是a的平方根值,去执行步骤5;否则执行步骤2,即循环进行迭代。 迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行: ⑴ 选一个方程的近似根,赋给变量x0; ⑵ 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0; ⑶ 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤⑵的计算。 若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为: 【算法】迭代法求方程的根 { x0=初始近似根; do { x1=x0; x0=g(x1); /*按特定的方程计算新的近似根*/ } while (fabs(x0-x1)>Epsilon); printf(“方程的近似根是%f\n”,x0); } 迭代算法也常用于求方程组的根,令 X=(x0,x1,…,xn-1) 设方程组为: xi=gi(X) (I=0,1,…,n-1) 则求方程组根的迭代算法可描述如下: 【算法】迭代法求方程组的根 { for (i=0;i x=初始近似根; do { for (i=0;i y=x; for (i=0;i x=gi(X); for (delta=0.0,i=0;i if (fabs(y-x)>delta) delta=fabs(y-x); } while (delta>Epsilon); for (i=0;i printf(“变量x[%d]的近似根是 %f”,I,x); printf(“\n”); } 具体使用迭代法求根时应注意以下两种可能发生的情况: ⑴ 如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制; ⑵ 方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。 递归 递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。 能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。 【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。 斐波那契数列为:0、1、1、2、3、……,即: fib(0)=0; fib⑴=1; fib(n)=fib(n-1)+fib(n-2) (当n>1时)。 写成递归函数有: int fib(int n) { if (n==0) return 0; if (n==1) return 1; if (n>1) return fib(n-1)+fib(n-2); } 递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问 题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算 fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib⑴和fib(0),分别能 立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。 在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib⑴和fib(0)后,返回得到fib⑵的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。 在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。 由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。 【问题】 组合问题 问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为:⑴5、4、3 ⑵5、4、2 ⑶5、4、1 ⑷5、3、2 ⑸5、3、1 ⑹5、2、1 ⑺4、3、2 ⑻4、3、1 ⑼4、2、1 ⑽3、2、1 分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m 个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递 归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。 【程序】 # include # define MAXN 100 int a[MAXN]; void comb(int m,int k) { int i,j; for (i=m;i>=k;i--) { a[k]=i; if (k>1) comb(i-1,k-1); else { for (j=a[0];j>0;j--) printf(“%4d”,a[j]); printf(“\n”); } } } void main() { a[0]=3; comb(5,3); } 【问题】 背包问题 问题描述:有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。 设n 件物品的重量分别为w0、w1、…、wn-1,物品的价值分别为v0、v1、…、vn-1。采用递归寻找物品的选择方案。设前面已有了多种选择的方案,并 保留了其中总价值最大的方案于数组option[ ],该方案的总价值存于变量maxv。当前正在考察新方案,其物品选择情况保存于数组cop[ ]。假定当前方案已考虑了前i-1件物品,现在要考虑第i件物品;当前方案已包含的物品的重量之和为tw;至此,若其余物品都选择是可能的话,本方案能达 到的总价值的期望值为tv。算法引入tv是当一旦当前方案的总价值的期望值也小于前面方案的总价值maxv时,继续考察当前方案变成无意义的工作,应终止 当前方案,立即去考察下一个方案。因为当方案的总价值不比maxv大时,该方案不会被再考察,这同时保证函数后找到的方案一定会比前面的方案更好。 对于第i件物品的选择考虑有两种可能: ⑴ 考虑物品i被选择,这种可能性仅当包含它不会超过方案总重量限制时才是可行的。选中后,继续递归去考虑其余物品的选择。 ⑵ 考虑物品i不被选择,这种可能性仅当不包含物品i也有可能会找到价值更大的方案的情况。 按以上思想写出递归算法如下: try(物品i,当前选择已达到的重量和,本方案可能达到的总价值tv) { /*考虑物品i包含在当前方案中的可能性*/ if(包含物品i是可以接受的) { 将物品i包含在当前方案中; if (i try(i+1,tw+物品i的重量,tv); else /*又一个完整方案,因为它比前面的方案好,以它作为最佳方案*/ 以当前方案作为临时最佳方案保存; 恢复物品i不包含状态; } /*考虑物品i不包含在当前方案中的可能性*/ if (不包含物品i仅是可男考虑的) if (i try(i+1,tw,tv-物品i的价值); else /*又一个完整方案,因它比前面的方案好,以它作为最佳方案*/ 以当前方案作为临时最佳方案保存; } 为了理解上述算法,特举以下实例。设有4件物品,它们的重量和价值见表: 物品 0 1 2 3 重量 5 3 2 1 价值 4 4 3 1 并设限制重量为7。则按以上算法,下图表示找解过程。由图知,一旦找到一个解,算法就进一步找更好的佳。如能判定某个查找分支不会找到更好的解,算法不会在该分支继续查找,而是立即终止该分支,并去考察下一个分支。 按上述算法编写函数和程序如下: 【程序】 # include # define N 100 double limitW,totV,maxV; int option[N],cop[N]; struct { double weight; double value; }a[N]; int n; void find(int i,double tw,double tv) { int k; /*考虑物品i包含在当前方案中的可能性*/ if (tw+a.weight<=limitW) { cop=1; if (i else { for (k=0;k option[k]=cop[k]; maxv=tv; } cop=0; } /*考虑物品i不包含在当前方案中的可能性*/ if (tv-a.value>maxV) if (i else { for (k=0;k option[k]=cop[k]; maxv=tv-a.value; } } void main() { int k; double w,v; printf(“输入物品种数\n”); scanf((“%d”,&n); printf(“输入各物品的重量和价值\n”); for (totv=0.0,k=0;k { scanf(“%1f%1f”,&w,&v); a[k].weight=w; a[k].value=v; totV+=V; } printf(“输入限制重量\n”); scanf(“%1f”,&limitV); maxv=0.0; for (k=0;k find(0,0.0,totV); for (k=0;k if (option[k]) printf(“%4d”,k+1); printf(“\n总价值为%.2f\n”,maxv); } 作为对比,下面以同样的解题思想,考虑非递归的程序解。为了提高找解速度,程序不是简单地逐一生成所有候选解,而是 从每个物品对候选解的影响来形成值得进一步考虑的候选解,一个候选解是通过依次考察每个物品形成的。对物品i的考察有这样几种情况:当该物品被包含在候选 解中依旧满足解的总重量的限制,该物品被包含在候选解中是应该继续考虑的;反之,该物品不应该包括在当前正在形成的候选解中。同样地,仅当物品不被包括在 候选解中,还是有可能找到比目前临时最佳解更好的候选解时,才去考虑该物品不被包括在候选解中;反之,该物品不包括在当前候选解中的方案也不应继续考虑。 对于任一值得继续考虑的方案,程序就去进一步考虑下一个物品。 【程序】 # include # define N 100 double limitW; int cop[N]; struct ele { double weight; double value; } a[N]; int k,n; struct { int ; double tw; double tv; }twv[N]; void next(int i,double tw,double tv) { twv.=1; twv tw=tw; twv tv=tv; } double find(struct ele *a,int n) { int i,k,f; double maxv,tw,tv,totv; maxv=0; for (totv=0.0,k=0;k totv+=a[k].value; next(0,0.0,totv); i=0; While (i>=0) { f=twv.; tw=twv tw; tv=twv tv; switch(f) { case 1: twv.++; if (tw+a.weight<=limitW) if (i { next(i+1,tw+a.weight,tv); i++; } else { maxv=tv; for (k=0;k cop[k]=twv[k].!=0; } break; case 0: i--; break; default: twv.=0; if (tv-a.value>maxv) if (i { next(i+1,tw,tv-a.value); i++; } else { maxv=tv-a.value; for (k=0;k cop[k]=twv[k].!=0; } break; } } return maxv; } void main() { double maxv; printf(“输入物品种数\n”); scanf((“%d”,&n); printf(“输入限制重量\n”); scanf(“%1f”,&limitW); printf(“输入各物品的重量和价值\n”); for (k=0;k scanf(“%1f%1f”,&a[k].weight,&a[k].value); maxv=find(a,n); printf(“\n选中的物品为\n”); for (k=0;k if (option[k]) printf(“%4d”,k+1); printf(“\n总价值为%.2f\n”,maxv); }

云篆 2019-12-02 01:25:10 0 浏览量 回答数 0

问题

《公交线路客流预测》攻略-附平台mr示例代码

楠兮 2019-12-01 21:31:10 16683 浏览量 回答数 5

问题

如何给项目选择最合适的编程语言?

chaipanpan 2019-12-01 21:04:01 9813 浏览量 回答数 0

问题

详解递归 6月18日【今日算法】

游客ih62co2qqq5ww 2020-06-20 12:04:38 2 浏览量 回答数 0

问题

备战大厂每日挑战算法,坚持打卡更有社区定制周边奖品等你赢!

被纵养的懒猫 2020-04-07 11:41:45 5309 浏览量 回答数 5

回答

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 [编辑本段]基本概念 * 若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数(Hash function),按这个思想建立的表为散列表。 * 对不同的关键字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),这种现象称冲突。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数H(key)和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“象” 作为记录在表中的存储位置,这种表便称为散列表,这一映象过程称为散列造表或散列,所得的存储位置称散列地址。 * 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少冲突。 [编辑本段]常用的构造散列函数的方法 散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位ǐ 1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a•key + b,其中a和b为常数(这种散列函数叫做自身函数) 2. 数字分析法 3. 平方取中法 4. 折叠法 5. 随机数法 6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。 [编辑本段]处理冲突的方法 1. 开放寻址法:Hi=(H(key) + di) MOD m, i=1,2,…, k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列,可有下列三种取法: 1. di=1,2,3,…, m-1,称线性探测再散列; 2. di=1^2, (-1)^2, 2^2,(-2)^2, (3)^2, …, ±(k)^2,(k<=m/2)称二次探测再散列; 3. di=伪随机数序列,称伪随机探测再散列。 == 2. 再散列法:Hi=RHi(key), i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。 3. 链地址法(拉链法) 4. 建立一个公共溢出区 [编辑本段]查找的性能分析 散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。在介绍的三种处理冲突的方法中,产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。 查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素: 1. 散列函数是否均匀; 2. 处理冲突的方法; 3. 散列表的装填因子。 散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度 α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。 实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。 了解了hash基本定义,就不能不提到一些著名的hash算法,MD5 和 SHA-1 可以说是目前应用最广泛的Hash算法,而它们都是以 MD4 为基础设计的。那么他们都是什么意思呢? 这里简单说一下: (1) MD4 MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的,MD 是 Message Digest 的缩写。它适用在32位字长的处理器上用高速软件实现--它是基于 32 位操作数的位操作来实现的。 (2) MD5 MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好 (3) SHA-1 及其他 SHA1是由NIST NSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。 那么这些Hash算法到底有什么用呢? Hash算法在信息安全方面的应用主要体现在以下的3个方面: (1) 文件校验 我们比较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能力,它们一定程度上能检测并纠正数据传输中的信道误码,但却不能防止对数据的恶意破坏。 MD5 Hash算法的"数字指纹"特性,使它成为目前应用最广泛的一种文件完整性校验和(Checksum)算法,不少Unix系统有提供计算md5 checksum的命令。 (2) 数字签名 Hash 算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运算速度较慢,所以在数字签名协议中,单向散列函数扮演了一个重要的角色。 对 Hash 值,又称"数字摘要"进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的。而且这样的协议还有其他的优点。 (3) 鉴权协议 如下的鉴权协议又被称作挑战--认证模式:在传输信道是可被侦听,但不可被篡改的情况下,这是一种简单而安全的方法。 MD5、SHA1的破解 2004年8月17日,在美国加州圣芭芭拉召开的国际密码大会上,山东大学王小云教授在国际会议上首次宣布了她及她的研究小组近年来的研究成果——对MD5、HAVAL-128、MD4和RIPEMD等四个著名密码算法的破译结果。 次年二月宣布破解SHA-1密码。 [编辑本段]实际应用 以上就是一些关于hash以及其相关的一些基本预备知识。那么在emule里面他具体起到什么作用呢? 大家都知道emule是基于P2P (Peer-to-peer的缩写,指的是点对点的意思的软件), 它采用了"多源文件传输协议”(MFTP,the Multisource FileTransfer Protocol)。在协议中,定义了一系列传输、压缩和打包还有积分的标准,emule 对于每个文件都有md5-hash的算法设置,这使得该文件独一无二,并且在整个网络上都可以追踪得到。 什么是文件的hash值呢? MD5-Hash-文件的数字文摘通过Hash函数计算得到。不管文件长度如何,它的Hash函数计算结果是一个固定长度的数字。与加密算法不同,这一个Hash算法是一个不可逆的单向函数。采用安全性高的Hash算法,如MD5、SHA时,两个不同的文件几乎不可能得到相同的Hash结果。因此,一旦文件被修改,就可检测出来。 当我们的文件放到emule里面进行共享发布的时候,emule会根据hash算法自动生成这个文件的hash值,他就是这个文件唯一的身份标志,它包含了这个文件的基本信息,然后把它提交到所连接的服务器。当有他人想对这个文件提出下载请求的时候, 这个hash值可以让他人知道他正在下载的文件是不是就是他所想要的。尤其是在文件的其他属性被更改之后(如名称等)这个值就更显得重要。而且服务器还提供了,这个文件当前所在的用户的地址,端口等信息,这样emule就知道到哪里去下载了。 一般来讲我们要搜索一个文件,emule在得到了这个信息后,会向被添加的服务器发出请求,要求得到有相同hash值的文件。而服务器则返回持有这个文件的用户信息。这样我们的客户端就可以直接的和拥有那个文件的用户沟通,看看是不是可以从他那里下载所需的文件。 对于emule中文件的hash值是固定的,也是唯一的,它就相当于这个文件的信息摘要,无论这个文件在谁的机器上,他的hash值都是不变的,无论过了多长时间,这个值始终如一,当我们在进行文件的下载上传过程中,emule都是通过这个值来确定文件。 那么什么是userhash呢? 道理同上,当我们在第一次使用emule的时候,emule会自动生成一个值,这个值也是唯一的,它是我们在emule世界里面的标志,只要你不卸载,不删除config,你的userhash值也就永远不变,积分制度就是通过这个值在起作用,emule里面的积分保存,身份识别,都是使用这个值,而和你的id和你的用户名无关,你随便怎么改这些东西,你的userhash值都是不变的,这也充分保证了公平性。其实他也是一个信息摘要,只不过保存的不是文件信息,而是我们每个人的信息。 那么什么是hash文件呢? 我们经常在emule日志里面看到,emule正在hash文件,这里就是利用了hash算法的文件校验性这个功能了,文章前面已经说了一些这些功能,其实这部分是一个非常复杂的过程,目前在ftp,bt等软件里面都是用的这个基本原理,emule里面是采用文件分块传输,这样传输的每一块都要进行对比校验,如果错误则要进行重新下载,这期间这些相关信息写入met文件,直到整个任务完成,这个时候part文件进行重新命名,然后使用move命令,把它传送到incoming文件里面,然后met文件自动删除,所以我们有的时候会遇到hash文件失败,就是指的是met里面的信息出了错误不能够和part文件匹配,另外有的时候开机也要疯狂hash,有两种情况一种是你在第一次使用,这个时候要hash提取所有文件信息,还有一种情况就是上一次你非法关机,那么这个时候就是要进行排错校验了。 关于hash的算法研究,一直是信息科学里面的一个前沿,尤其在网络技术普及的今天,他的重要性越来越突出,其实我们每天在网上进行的信息交流安全验证,我们在使用的操作系统密钥原理,里面都有它的身影,特别对于那些研究信息安全有兴趣的朋友,这更是一个打开信息世界的钥匙,他在hack世界里面也是一个研究的焦点。 一般的线性表、树中,记录在结构中的相对位置是随机的即和记录的关键字之间不存在确定的关系,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较”的基础上,查找的效率与比较次数密切相关。理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。因而查找时,只需根据这个对应关系f找到给定值K的像f(K)。若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上,由此不需要进行比较便可直接取得所查记录。在此,称这个对应关系f为哈希函数,按这个思想建立的表为哈希表(又称为杂凑法或散列表)。 哈希表不可避免冲突(collision)现象:对不同的关键字可能得到同一哈希地址 即key1≠key2,而hash(key1)=hash(key2)。具有相同函数值的关键字对该哈希函数来说称为同义词(synonym)。 因此,在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。可如下描述哈希表:根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映象到一个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表。 对于动态查找表而言,1) 表长不确定;2)在设计查找表时,只知道关键字所属范围,而不知道确切的关键字。因此,一般情况需建立一个函数关系,以f(key)作为关键字为key的录在表中的位置,通常称这个函数f(key)为哈希函数。(注意:这个函数并不一定是数学函数) 哈希函数是一个映象,即:将关键字的集合映射到某个地址集合上,它的设置很灵活,只要这个地址集合的大小不超出允许范围即可。 现实中哈希函数是需要构造的,并且构造的好才能使用的好。 用途:加密,解决冲突问题。。。。 用途很广,比特精灵中就使用了哈希函数,你可 以自己看看。 具体可以学习一下数据结构和算法的书。 [编辑本段]字符串哈希函数 (著名的ELFhash算法) int ELFhash(char *key) return h%MOD; }

晚来风急 2019-12-02 01:22:24 0 浏览量 回答数 0

问题

图解九大数据结构 6月13日 【今日算法】

游客ih62co2qqq5ww 2020-06-17 13:17:00 29 浏览量 回答数 1

问题

图解!24张图彻底弄懂九大常见数据结构! 7月22日 【今日算法】

游客ih62co2qqq5ww 2020-07-27 13:19:32 6 浏览量 回答数 1

问题

线性表 7月8日 【今日算法】

游客ih62co2qqq5ww 2020-07-09 07:47:37 504 浏览量 回答数 1

问题

【精品问答】Python二级考试题库

珍宝珠 2019-12-01 22:03:38 1146 浏览量 回答数 2

问题

Netty实现原理浅析 1、总体结构 2、网络模型 3、 buffer 4、Ch?400报错

爱吃鱼的程序员 2020-06-04 11:53:36 3 浏览量 回答数 1

回答

当我可以在60秒内在类似硬件上执行相同操作时,您的查询要花2个小时才能执行,这是一件非常错误的事情。 以下某些内容可能会有所帮助... 为您的引擎调整MySQL 检查服务器配置并进行相应优化。以下某些资源应该是有用的。 http://www.mysqlperformanceblog.com/2006/09/29/what-to-tune-in-mysql-server-after-installation/ http://www.mysqlperformanceblog.com/ http://www.highperfmysql.com/ http://forge.mysql.com/wiki/ServerVariables http://dev.mysql.com/doc/refman/5.0/en/server-system-variables.html http://www.xaprb.com/blog/2006/07/04/how-to-exploit-mysql-index-optimizations/ http://jpipes.com/presentations/perf_tuning_best_practices.pdf http://jpipes.com/presentations/index_coding_optimization.pdf http://www.jasny.net/?p=36 现在不那么明显了... 考虑使用存储过程来处理数据服务器端 为什么不处理MySQL内部的所有数据,从而不必将大量数据发送到应用程序层?以下示例使用游标在2分钟内循环和处理服务器端5000万行。我不是游标的忠实拥护者,尤其是在MySQL游标非常有限的地方,但是我猜想您会循环结果集并进行某种形式的数值分析,因此在这种情况下使用游标是合理的。 简化的myisam结果表-基于您的密钥。 drop table if exists results_1mregr_c_ew_f; create table results_1mregr_c_ew_f ( id int unsigned not null auto_increment primary key, rc tinyint unsigned not null, df int unsigned not null default 0, val double(10,4) not null default 0, ts timestamp not null default now(), key (rc, df) ) engine=myisam; 我生成了1亿行数据,其中关键字段的基数与您的示例大致相同: show indexes from results_1mregr_c_ew_f; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Index_type ===== ========== ======== ============ =========== ========= =========== ========== results_1mregr_c_ew_f 0 PRIMARY 1 id A 100000000 BTREE results_1mregr_c_ew_f 1 rc 1 rc A 2 BTREE results_1mregr_c_ew_f 1 rc 2 df A 223 BTREE 存储过程 我创建了一个简单的存储过程,该过程将获取所需的数据并对其进行处理(使用与示例相同的where条件) drop procedure if exists process_results_1mregr_c_ew_f; delimiter # create procedure process_results_1mregr_c_ew_f ( in p_rc tinyint unsigned, in p_df int unsigned ) begin declare v_count int unsigned default 0; declare v_done tinyint default 0; declare v_id int unsigned; declare v_result_cur cursor for select id from results_1mregr_c_ew_f where rc = p_rc and df > p_df; declare continue handler for not found set v_done = 1; open v_result_cur; repeat fetch v_result_cur into v_id; set v_count = v_count + 1; -- do work... until v_done end repeat; close v_result_cur; select v_count as counter; end # delimiter ; 观察到以下运行时: call process_results_1mregr_c_ew_f(0,60); runtime 1 = 03:24.999 Query OK (3 mins 25 secs) runtime 2 = 03:32.196 Query OK (3 mins 32 secs) call process_results_1mregr_c_ew_f(1,60); runtime 1 = 04:59.861 Query OK (4 mins 59 secs) runtime 2 = 04:41.814 Query OK (4 mins 41 secs) counter 23000002 (23 million rows processed in each case) 嗯,性能有些令人失望,因此进入下一个想法。 考虑使用innodb引擎(令人震惊的恐怖) 为什么是innodb?因为它具有聚簇索引!您会发现使用innodb的插入速度较慢,但​​希望读取速度会更快,因此这是值得的折衷选择。 通过聚集索引访问行的速度很快,因为行数据位于索引搜索所位于的同一页上。如果表很大,则与使用不同于索引记录的页面存储行数据的存储组织相比,聚集索引体系结构通常可以节省磁盘I / O操作。例如,MyISAM将一个文件用于数据行,将另一个文件用于索引记录。 更多信息在这里: http://dev.mysql.com/doc/refman/5.0/en/innodb-index-types.html 简化的innodb结果表 drop table if exists results_innodb; create table results_innodb ( rc tinyint unsigned not null, df int unsigned not null default 0, id int unsigned not null, -- cant auto_inc this !! val double(10,4) not null default 0, ts timestamp not null default now(), primary key (rc, df, id) -- note clustered (innodb only !) composite PK ) engine=innodb; innodb的一个问题是它不支持构成复合键一部分的auto_increment字段,因此您必须自己使用序列生成器,触发器或其他方法(可能是在填充结果表本身的应用程序中)提供递增的键值?? 同样,我生成了1亿行数据,这些键字段的基数与您的示例大致相同。如果这些数字与myisam示例不匹配,请不要担心,因为innodb估计基数,因此它们不会完全相同。(但它们-使用相同的数据集) show indexes from results_innodb; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Index_type ===== ========== ======== ============ =========== ========= =========== ========== results_innodb 0 PRIMARY 1 rc A 18 BTREE results_innodb 0 PRIMARY 2 df A 18 BTREE results_innodb 0 PRIMARY 3 id A 100000294 BTREE 存储过程 存储过程与上面的myisam示例完全相同,但是从innodb表中选择数据。 declare v_result_cur cursor for select id from results_innodb where rc = p_rc and df > p_df; 结果如下: call process_results_innodb(0,60); runtime 1 = 01:53.407 Query OK (1 mins 53 secs) runtime 2 = 01:52.088 Query OK (1 mins 52 secs) call process_results_innodb(1,60); runtime 1 = 02:01.201 Query OK (2 mins 01 secs) runtime 2 = 01:49.737 Query OK (1 mins 50 secs) counter 23000002 (23 million rows processed in each case) 比myisam引擎实施快约2-3分钟!(innodb FTW) 分而治之 在使用游标的服务器端存储过程中处理结果可能不是最佳解决方案,尤其是因为MySQL不支持诸如数组和复杂数据结构之类的东西,而这些东西在3GL语言(如C#等)或什至在其他数据库(如作为Oracle PL / SQL。 因此,这里的想法是将成批的数据返回到应用程序层(C#等),然后可以将结果添加到基于集合的数据结构中,然后在内部处理数据。 存储过程 该存储过程需要3个参数rc,df_low和df_high,这使您可以选择以下数据范围: call list_results_innodb(0,1,1); -- df 1 call list_results_innodb(0,1,10); -- df between 1 and 10 call list_results_innodb(0,60,120); -- df between 60 and 120 etc... 显然,df范围越高,您将提取的数据越多。 drop procedure if exists list_results_innodb; delimiter # create procedure list_results_innodb ( in p_rc tinyint unsigned, in p_df_low int unsigned, in p_df_high int unsigned ) begin select rc, df, id from results_innodb where rc = p_rc and df between p_df_low and p_df_high; end # delimiter ; 我还敲出了一个myisam版本,除了所使用的表外,该版本完全相同。 call list_results_1mregr_c_ew_f(0,1,1); call list_results_1mregr_c_ew_f(0,1,10); call list_results_1mregr_c_ew_f(0,60,120); 基于上面的光标示例,我希望innodb版本的性能优于myisam版本。 我开发了一个快速且肮脏的多线程C#应用程序,该应用程序将调用存储过程并将结果添加到集合中以进行后查询处理。您不必使用线程,可以按顺序完成相同的批处理查询方法,而不会造成性能损失。 每个线程(QueryThread)选择一个df数据范围,循环结果集,并将每个结果(行)添加到结果集合中。 class Program { static void Main(string[] args) { const int MAX_THREADS = 12; const int MAX_RC = 120; List<AutoResetEvent> signals = new List<AutoResetEvent>(); ResultDictionary results = new ResultDictionary(); // thread safe collection DateTime startTime = DateTime.Now; int step = (int)Math.Ceiling((double)MAX_RC / MAX_THREADS) -1; int start = 1, end = 0; for (int i = 0; i < MAX_THREADS; i++){ end = (i == MAX_THREADS - 1) ? MAX_RC : end + step; signals.Add(new AutoResetEvent(false)); QueryThread st = new QueryThread(i,signals[i],results,0,start,end); start = end + 1; } WaitHandle.WaitAll(signals.ToArray()); TimeSpan runTime = DateTime.Now - startTime; Console.WriteLine("{0} results fetched and looped in {1} secs\nPress any key", results.Count, runTime.ToString()); Console.ReadKey(); } } 运行时观察如下: Thread 04 done - 31580517 Thread 06 done - 44313475 Thread 07 done - 45776055 Thread 03 done - 46292196 Thread 00 done - 47008566 Thread 10 done - 47910554 Thread 02 done - 48194632 Thread 09 done - 48201782 Thread 05 done - 48253744 Thread 08 done - 48332639 Thread 01 done - 48496235 Thread 11 done - 50000000 50000000 results fetched and looped in 00:00:55.5731786 secs Press any key 因此,在不到60秒的时间内获取了5000万行并将其添加到集合中。 我使用myisam存储过程尝试了同样的事情,该过程花了2分钟才能完成。 50000000 results fetched and looped in 00:01:59.2144880 secs 移至innodb 在我的简化系统中,myisam表的性能不会太差,因此可能不值得迁移到innodb。如果您确实决定将结果数据复制到innodb表中,请按照以下步骤进行操作: start transaction; insert into results_innodb select <fields...> from results_1mregr_c_ew_f order by ; commit; 在将整个事物插入并包装到事务中之前,通过innodb PK对结果进行排序将加快速度。 我希望其中一些证明是有帮助的。 祝好运

保持可爱mmm 2020-05-18 11:14:35 0 浏览量 回答数 0

问题

Nginx性能为什么如此吊

小柒2012 2019-12-01 21:20:47 15038 浏览量 回答数 3
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 云栖号物联网 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 2020阿里巴巴研发效能峰会 企业建站模板 云效成长地图 高端建站 云栖号弹性计算 阿里云云栖号 云栖号案例 云栖号直播