动态规划


Dynamic Programming

Starfish (starfish.h@china.com)

摘要

本文介绍了动态规划的基本思想和基本步骤,通过实例研究了利用动态规划设计算法的具体途径,讨论了动态规划的一些实现技巧,并将动态规划和其他一些算法作了比较,最后还简单介绍了动态规划的数学理论基础和当前最新的研究成果。

目录

参考文献

引言——由一个问题引出的算法

考虑以下问题

[例1] 最短路径问题

现有一张地图,各结点代表城市,两结点间连线代表道路,线上数字表示城市间的距离。如图1所示,试找出从结点A到结点E的最短距离。

图 1

我们可以用深度优先搜索法来解决此问题,该问题的递归式为

其中是与v相邻的节点的集合,w(v,u)表示从v到u的边的长度。

具体算法如下:

function MinDistance(v):integer;
begin
 if v=E then return 0
  else
   begin
    min:=maxint;
    for 所有没有访问过的节点i do
     if v和i相邻 then 
      begin
        标记i访问过了;
        t:=v到i的距离+MinDistance(i);
        标记i未访问过;        
        if t<min then min=t;
      end;  
   end;
end;

开始时标记所有的顶点未访问过,MinDistance(A)就是从A到E的最短距离。

这个程序的效率如何呢?我们可以看到,每次除了已经访问过的城市外,其他城市都要访问,所以时间复杂度为O(n!),这是一个“指数级”的算法,那么,还有没有更好的算法呢?

首先,我们来观察一下这个算法。在求从B1到E的最短距离的时候,先求出从C2到E的最短距离;而在求从B2到E的最短距离的时候,又求了一遍从C2到E的最短距离。也就是说,从C2到E的最短距离我们求了两遍。同样可以发现,在求从C1、C2到E的最短距离的过程中,从D1到E的最短距离也被求了两遍。而在整个程序中,从D1到E的最短距离被求了四遍。如果在求解的过程中,同时将求得的最短距离"记录在案",随时调用,就可以避免这种情况。于是,可以改进该算法,将每次求出的从v到E的最短距离记录下来,在算法中递归地求MinDistance(v)时先检查以前是否已经求过了MinDistance(v),如果求过了则不用重新求一遍,只要查找以前的记录就可以了。这样,由于所有的点有n个,因此不同的状态数目有n个,该算法的数量级为O(n)。

更进一步,可以将这种递归改为递推,这样可以减少递归调用的开销。

请看图1,可以发现,A只和Bi相邻,Bi只和Ci相邻,...,依此类推。这样,我们可以将原问题的解决过程划分为4个阶段,设S1={A},S2={B1,B2},S3={C1,C2,C3,C4},S4={D1,D2,D3},Fk(u)表示从Sk中的点u到E的最短距离,则

并且有边界条件

显然可以递推地求出F1(A),也就是从A到E的最短距离。这种算法的复杂度为O(n),因为所有的状态总数(节点总数)为n,对每个状态都只要遍历一次,而且程序很简洁。

具体算法如下:

procedure DynamicProgramming;
 begin
  F5[E]:=0;
  for i:=4 downto 1 do
     for each u ∈Sk do
      begin
       Fk[u]:=无穷大;
       for each v∈Sk+1∩δ(u) do
         if Fk[u]>w(u,v)+Fk+1[v] then Fk[u]:=w(u,v)+Fk+1[v];
   end;
  输出F1[A]; 
 end;

这种高效算法,就是动态规划算法

动态规划的基本概念

动态规划的发展及研究内容

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957年出版了他的名著Dynamic Programming,这是该领域的第一本著作。

动态规划问世以来,在经济管理、生产调度、工程技术和最优控制等方面得到了广泛的应用。例如最短路线、库存管理、资源分配、设备更新、排序、装载等问题,用动态规划方法比用其它方法求解更为方便。

虽然动态规划主要用于求解以时间划分阶段的动态过程的优化问题,但是一些与时间无关的静态规划(如线性规划、非线性规划),只要人为地引进时间因素,把它视为多阶段决策过程,也可以用动态规划方法方便地求解。

多阶段决策问题

多阶段决策过程,是指这样的一类特殊的活动过程,问题可以按时间顺序分解成若干相互联系的阶段,在每一个阶段都要做出决策,全部过程的决策是一个决策序列。要使整个活动的总体效果达到最优的问题,称为多阶段决策问题

例1是一个多阶段决策问题的例子,下面是另一个多阶段决策问题的例子:

[例2] 生产计划问题

工厂生产某种产品,每单位(千件)的成本为1(千元),每次开工的固定成本为3(千元),工厂每季度的最大生产能力为6(千件)。经调查,市场对该产品的需求量第一、二、三、四季度分别为 2,3,2,4(千件)。如果工厂在第一、二季度将全年的需求都生产出来,自然可以降低成本(少付固定成本费),但是对于第三、四季度才能上市的产品需付存储费,每季每千件的存储费为0.5(千元)。还规定年初和年末这种产品均无库存。试制订一个生产计划,即安排每个季度的产量,使一年的总费用(生产成本和存储费)最少。

决策过程的分类

根据过程的时间变量是离散的还是连续的,分为离散时间决策过程(discrete-time decision process),即多阶段决策过程和连续时间决策过程(continuous-time decision process);根据过程的演变是确定的还是随机的,分为确定性决策过程(deterministic decision process)随机性决策过程(stochastic decision process),其中应用最广的是确定性多阶段决策过程。

动态规划模型的基本要素

一个多阶段决策过程最优化问题的动态规划模型通常包含以下要素:

1.阶段

阶段(step)是对整个过程的自然划分。通常根据时间顺序或空间特征来划分阶段,以便按阶段的次序解优化问题。阶段变量一般用k=1,2,..,n表示。在例1中由A出发为k=1,由Bi(i=1,2)出发为k=2,依此下去从Di(i=1,2,3)出发为k=4,共n=4个阶段。在例2中按照第一、二、三、四季度分为k=1,2,3,4,共4个阶段。

2.状态

状态(state)表示每个阶段开始时过程所处的自然状况。它应该能够描述过程的特征并且具有无后向性,即当某阶段的状态给定时,这个阶段以后过程的演变与该阶段以前各阶段的状态无关,即每个状态都是过去历史的一个完整总结。通常还要求状态是直接或间接可以观测的。

描述状态的变量称状态变量(state variable)。变量允许取值的范围称允许状态集合(set of admissible states)。用xk表示第k阶段的状态变量,它可以是一个数或一个向量。用Xk表示第k阶段的允许状态集合。在例1中x2可取B1,B2,X2={B1,B2}。

n个阶段的决策过程有n+1个状态变量,xn+1表示xn演变的结果,在例1中x5取E。

根据过程演变的具体情况,状态变量可以是离散的或连续的。为了计算的方便有时将连续变量离散化;为了分析的方便有时又将离散变量视为连续的。

状态变量简称为状态

3.决策

当一个阶段的状态确定后,可以作出各种选择从而演变到下一阶段的某个状态,这种选择手段称为决策(decision),在最优控制问题中也称为控制(control)

描述决策的变量称决策变量(decision variable)。变量允许取值的范围称允许决策集合(set of admissible decisions)。用uk(xk)表示第k阶段处于状态xk时的决策变量,它是xk的函数,用Uk(xk)表示了xk的允许决策集合。在例1中u2(B1)可取C1,C2,C3

决策变量简称决策

4.策略

决策组成的序列称为策略(policy)。由初始状态x1开始的全过程的策略记作p1n(x1),即p1n(x1)={u1(x1),u2(x2),...,un(xn)}。由第k阶段的状态xk开始到终止状态的后部子过程的策略记作pkn(xk),即pkn(xk)={uk(xk),uk+1(xk+1),...,un(xn)}。类似地,由第k到第j阶段的子过程的策略记作pkj(xk)={uk(xk),uk+1(xk+1),...,uj(xj)}。对于每一个阶段k的某一给定的状态xk,可供选择的策略pkj(xk)有一定的范围,称为允许策略集合(set of admissible policies),用P1n(x1),Pkn(xk),Pkj(xk)表示。

5.状态转移方程

在确定性过程中,一旦某阶段的状态和决策为已知,下阶段的状态便完全确定。用状态转移方程(equation of state)表示这种演变规律,写作

例1中状态转移方程为:xk+1=uk(xk)

6.指标函数和最优值函数

指标函数(objective function)是衡量过程优劣的数量指标,它是关于策略的数量函数,从阶段k到阶段n的指标函数用Vkn(xk,pkn(xk))表示,k=1,2,...,n。

能够用动态规划解决的问题的指标函数应具有可分离性,即Vkn可表为xk,uk,Vk+1 n 的函数,记为:

其中函数是一个关于变量Vk+1 n单调递增的函数。这一性质保证了最优化原理(principle of optimality)的成立,是动态规划的适用前提。

过程在第j 阶段的阶段指标取决于状态xj和决策uj,用vj(xj,uj)表示。阶段k到阶段n的指标由vj(j=k,k+1,..n)组成,常见的形式有:

阶段指标之和,即

阶段指标之积,即

阶段指标之极大(或极小),即

这些形式下第k到第j阶段子过程的指标函数为Vkj(xk,uk,xk+1,...,xj+1)。可以发现,上述(3)-(5)三个指标函数的形式都满足最优性原理。在例1中指标函数为(3)的形式,其中vj(xj,uj)是边<xj,uj(xj)>的权(边的长度),uj(xj)表示从xj出发根据决策uj(xj)下一步所到达的节点。

根据状态转移方程,指标函数Vkn还可以表示为状态xk和策略pkn的函数,即Vkn(xk,pkn)。在xk给定时指标函数Vkn对pkn的最优值称为最优值函数(optimal value function),记作fk(xk),即

其中opt可根据具体情况取max或min。上式的意义是,对于某个阶段k的某个状态xk,从该阶段k到最终目标阶段n的最优指标函数值等于从xk出发取遍所有能策略pkn所得到的最优指标值中最优的一个。

7.最优策略和最优轨线

使指标函数Vkn达到最优值的策略是从k开始的后部子过程的最优策略,记作pkn*={uk*,..un*},p1n*又是全过程的最优策略,简称最优策略(optimal policy)。从初始状态x1(=x1*)出发,过程按照p1n*和状态转移方程演变所经历的状态序列{x1*,x2*,..,xn+1*}称最优轨线(optimal trajectory)

动态规划的基本定理和基本方程

动态规划发展的早期阶段,从简单逻辑出发给出了所谓最优性原理,然后在最优策略存在的前提下导出基本方程,再由这个方程求解最优策略。后来在动态规划的应用过程中发现,最优性原理不是对任何决策过程普遍成立,它与基本方程不是无条件等价,二者之间也不存在任何确定的蕴含关系。基本方程在动态规划中起着更为本质的作用。

[基本定理]

对于初始状态x1∈X1,策略p1n*={u1*,..un*}是最优策略的充要条件是对于任意的k,1<k<=n,有

[推论]

若p1n*∈P1n(x1)是最优策略,则对于任意的k,1<k<n,它的子策略pkn*对于由x1和p1,k-1*确定的以xk*为起点的第k到n后部子过程而言,也是最优策略。

上述推论称为最优化原理,它给出了最优策略的必要条件,通常略述为:不论过去的状态和决策如何,对于前面的决策形成的当前的状态而言,余下的各个决策必定构成最优策略。

根据基本定理的推论可以得到动态规划的基本方程

其中是决策过程的终端条件,为一个已知函数。当xn+1只取固定的状态时称固定终端;当xn+1可在终端集合Xn+1中变动时称自由终端。最终要求的最优指标函数满足(10)式:

(9)式是一个递归公式,如果目标状态确定,当然可以直接利用该公式递归求出最优值(这种递归方法将在后文介绍,称作备忘录法),但是一般在实际应用中我们通常将该递归公式改为递推公式求解,这样一般效率会更高一些。

动态规划的适用条件

任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。同样,动态规划也并不是万能的。适用动态规划的问题必须满足最优化原理和无后效性。

1.最优化原理(最优子结构性质)

最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质

图2

例如图2中,若路线I和J是A到C的最优路径,则根据最优化原理,路线J必是从B到C的最优路线。这可用反证法证明:假设有另一路径J'是B到C的最优路径,则A到C的路线取I和J'比I和J更优,矛盾。从而证明J'必是B到C的最优路径。

最优化原理是动态规划的基础,任何问题,如果失去了最优化原理的支持,就不可能用动态规划方法计算。动态规划的最优化理在其指标函数的可分离性和单调性中得到体现。根据最优化原理导出的动态规划基本方程是解决一切动态规划问题的基本方法。

可以看出,例1是满足最优化原理的。

2.无后向性

将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性

如果用前面的记号来描述无后向性,就是:对于确定的xk,无论p1,k-1如何,最优子策略pkn*是唯一确定的,这种性质称为无后向性。

[例3] Bitonic旅行路线问题

欧几里德货郎担问题是对平面给定的n个点确定一条连结各点的、闭合的最短游历路线问题。图3(a)给出了七个点问题的解。Bitonic旅行路线问题是欧几里德货郎担问题的简化,这种旅行路线先从最左边开始,严格地由左至右到最右边的点,然后再严格地由右至左到出发点,求路程最短的路径长度。图3(b)给出了七个点问题的解。

图3

这两个问题看起来很相似。但实质上是不同的。为了方便讨论,我将每个顶点标记了号码。由于必然经过最右边的顶点7,所以一条路(P1-P2)可以看成两条路(P1-7)与(P2-7)的结合。所以,这个问题的状态可以用两条道路结合的形式表示。我们可以把这些状态中,两条路中起始顶点相同的状态归于一个阶段,设为阶段[P1,P2]。

那么,对于Bitonic旅行路线问题来说,阶段[P1,P2]如果可以由阶段[Q1,Q2]推出,则必须满足的条件就是:P1<Q1或P2<Q2。例如,阶段[3,4]中的道路可以由阶段[3,5]中的道路加一条边4-5得出,而阶段[3,5]的状态却无法由阶段[3,4]中的状态得出,因为Bitonic旅行路线要求必须严格地由左到右来旅行。所以如果我们已经知道了阶段[3,4]中的状态,则阶段[3,5]中的状态必然已知。因此我们可以说,Bitonic问题满足无后向性,可以用动态规划来解决。

有些问题乍一看好像有后向性,但如果按照某种合理的方式重新划分阶段,就可以发现其本质上是无后向性的,所以关键是阶段的合理划分,这一点将在动态规划的技巧中详细阐述。

3.子问题的重叠性

在例1中我们看到,动态规划将原来具有指数级复杂度的搜索算法改进成了具有多项式时间的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。以Bitonic旅行路线问题为例,这个问题也可以用搜索算法来解决。动态规划的时间复杂度为O(n2),搜索算法的时间复杂度为O(n!) ,但从空间复杂度来看,动态规划算法为O(n2),而搜索算法为O(n),搜索算法反而优于动态规划算法。选择动态规划算法是因为动态规划算法在空间上可以承受,而搜索算法在时间上却无法承受,所以我们舍空间而取时间。

设原问题的规模为n,容易看出,当子问题树中的子问题总数是n的超多项式函数,而不同的子问题数只是n的多项式函数时,动态规划法显得特别有意义,此时动态规划法具有线性时间复杂性。所以,能够用动态规划解决的问题还有一个显著特征:子问题的重叠性。这个性质并不是动态规划适用的必要条件,但是如果该性质无法满足,动态规划算法同其他算法相比就不具备优势。

动态规划的基本思想

前文主要介绍了动态规划的一些理论依据,我们将前文所说的具有明显的阶段划分和状态转移方程的动态规划称为标准动态规划,这种标准动态规划是在研究多阶段决策问题时推导出来的,具有严格的数学形式,适合用于理论上的分析。在实际应用中,许多问题的阶段划分并不明显,这时如果刻意地划分阶段法反而麻烦。一般来说,只要该问题可以划分成规模更小的子问题,并且原问题的最优解中包含了子问题的最优解(即满足最优子化原理),则可以考虑用动态规划解决。

动态规划的实质是分治思想解决冗余,因此,动态规划是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。

由此可知,动态规划法与分治法贪心法类似,它们都是将问题实例归纳为更小的、相似的子问题,并通过求解子问题产生一个全局最优解。其中贪心法的当前选择可能要依赖已经作出的所有选择,但不依赖于有待于做出的选择和子问题。因此贪心法自顶向下,一步一步地作出贪心选择;而分治法中的各个子问题是独立的 (即不包含公共的子子问题),因此一旦递归地求出各子问题的解后,便可自下而上地将子问题的解合并成问题的解。但不足的是,如果当前选择可能要依赖子问题的解时,则难以通过局部的贪心策略达到全局最优解;如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题。

解决上述问题的办法是利用动态规划。该方法主要应用于最优化问题,这类问题会有多种可能的解,每个解都有一个值,而动态规划找出其中最优(最大或最小)值的解。若存在若干个取最优值的解的话,它只取其中的一个。在求解过程中,该方法也是通过求解局部子问题的解达到全局最优解,但与分治法和贪心法不同的是,动态规划允许这些子问题不独立,(亦即各子问题可包含公共的子子问题)也允许其通过自身子问题的解作出选择,该方法对每一个子问题只解一次,并将结果保存起来,避免每次碰到时都要重复计算。

因此,动态规划法所针对的问题有一个显著的特征,即它所对应的子问题树中的子问题呈现大量的重复。动态规划法的关键就在于,对于重复出现的子问题,只在第一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。

动态规划算法的基本步骤

设计一个标准的动态规划算法,通常可按以下几个步骤进行:

  1. 划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。注意这若干个阶段一定要是有序的或者是可排序的(即无后向性),否则问题就无法用动态规划求解。
  2. 选择状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
  3. 确定决策并写出状态转移方程:之所以把这两步放在一起,是因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以,如果我们确定了决策,状态转移方程也就写出来了。但事实上,我们常常是反过来做,根据相邻两段的各状态之间的关系来确定决策。
  4. 写出规划方程(包括边界条件):动态规划的基本方程是规划方程的通用形式化表达式。一般说来,只要阶段、状态、决策和状态转移确定了,这一步还是比较简单的。

动态规划的主要难点在于理论上的设计,一旦设计完成,实现部分就会非常简单。根据动态规划的基本方程可以直接递归计算最优值,但是一般将其改为递推计算,实现的大体上的框架如下:

标准动态规划的基本框架

1.  对fn+1(xn+1)初始化;    {边界条件}
2.  for k:=n downto 1 do 
3.      for 每一个xk∈Xk do
4.        for 每一个uk∈Uk(xk) do
            begin
5.            fk(xk):=一个极值;                 {∞或-∞}
6.            xk+1:=Tk(xk,uk);                  {状态转移方程}
7.            t:=φ(fk+1(xk+1),vk(xk,uk));       {基本方程(9)式}
8.            if  t比fk(xk)更优 then fk(xk):=t; {计算fk(xk)的最优值}
           end;  
9.  t:=一个极值;                               {∞或-∞}
10. for 每一个x1∈X1 do
11.     if f1(x1)比t更优 then t:=f1(x1);       {按照10式求出最优指标}
12. 输出t;

但是,实际应用当中经常不显式地按照上面步骤设计动态规划,而是按以下几个步骤进行:

  1. 分析最优解的性质,并刻划其结构特征。
  2. 递归地定义最优值。
  3. 以自底向上的方式或自顶向下的记忆化方法(备忘录法)计算出最优值。
  4. 根据计算最优值时得到的信息,构造一个最优解。

步骤(1)--(3)是动态规划算法的基本步骤。在只需要求出最优值的情形,步骤(4)可以省略,若需要求出问题的一个最优解,则必须执行步骤(4)。此时,在步骤(3)中计算最优值时,通常需记录更多的信息,以便在步骤(4)中,根据所记录的信息,快速地构造出一个最优解。

动态规划的实例分析

下面我们将通过实例来分析动态规划的设计步骤和具体应用。例1已经在前文介绍过了。例1和例2是标准的动态规划,有明显的阶段和状态转移方程;例3、例4、例5、例6是没有明显阶段划分的动态规划,也是一般常见的形式,其中对例4、例5、例6作了比较详细的分析;例7是比较特殊的动态规划,例8是两重动态规划(即为了解决问题要进行两次动态规划)的例子。

更多实例请参阅动态规划问题集

动态规划的技巧——阶段的划分和状态的表示

在动态规划的设计过程中,阶段的划分和状态的表示是非常重要的两步,这两步会直接影响该问题的计算复杂性,有时候阶段划分或状态表示的不合理还会使得动态规划法不适用。

[例9] 街道问题

在下图中找出从左下角到右上角的最短路径,每步只能向右方或上方走。

这是一道简单而又典型的动态规划题,许多介绍动态规划的书与文章中都拿它来做例子。通常,书上的解答是这样的:

按照图中的虚线来划分阶段,即阶段变量k表示走过的步数,而状态变量xk表示当前处于这一阶段上的哪一点。这时的模型实际上已经转化成了一个特殊的多段图。用决策变量uk=0表示向右走,uk=1表示向上走,则状态转移方程如下:

(这里的row是地图竖直方向的行数)

我们看到,这个状态转移方程需要根据k的取值分两种情况讨论,显得非常麻烦。相应的,把它代入规划方程而付诸实现时,算法也很繁。因而我们在实现时,一般是不会这么做的,而代之以下面方法:

(这里Distance表示相邻两点间的边长)

这样做确实要比上面的方法简单多了,但是它已经破坏了动态规划的本来面目,而不存在明确的阶段特征了。如果说这种方法是以地图中的行(A、B、C、D)来划分阶段的话,那么它的"状态转移"就不全是在两个阶段之间进行的了。

也许这没什么大不了的,因为实践比理论更有说服力。但是,如果我们把题目扩展一下:在地图中找出从左下角到右上角的两条路径,两条路径中的任何一条边都不能重叠,并且要求两条路径的总长度最短。这时,再用这种"简单"的方法就不太好办了。

如果非得套用这种方法的话,则最优指标函数就需要有四维的下标,并且难以处理两条路径"不能重叠"的问题。

而我们回到原先"标准"的动态规划法,就会发现这个问题很好解决,只需要加一维状态变量就成了。即用xk=(ak,bk)分别表示两条路径走到阶段k时所处的位置,相应的,决策变量也增加一维,用uk=(xk,yk)分别表示两条路径的行走方向。状态转移时将两条路径分别考虑

在写规划方程时,只要对两条路径走到同一个点的情况稍微处理一下,减少可选的决策个数:

从这个例子可以看出,合理地划分阶段和选择状态可以给解题带来方便。

[例10] LITTLE SHOP OF FLOWERS (IOI’99)

PROBLEM

You want to arrange the window of your flower shop in a most pleasant way. You have F bunches of flowers, each being of a different kind, and at least as many vases ordered in a row. The vases are glued onto the shelf and are numbered consecutively 1 through V, where V is the number of vases, from left to right so that the vase 1 is the leftmost, and the vase V is the rightmost vase. The bunches are moveable and are uniquely identified by integers between 1 and F. These id-numbers have a significance: They determine the required order of appearance of the flower bunches in the row of vases so that the bunch i must be in a vase to the left of the vase containing bunch j whenever i < j. Suppose, for example, you have a bunch of azaleas (id-number=1), a bunch of begonias (id-number=2) and a bunch of carnations (id-number=3). Now, all the bunches must be put into the vases keeping their id-numbers in order. The bunch of azaleas must be in a vase to the left of begonias, and the bunch of begonias must be in a vase to the left of carnations. If there are more vases than bunches of flowers then the excess will be left empty. A vase can hold only one bunch of flowers.

Each vase has a distinct characteristic (just like flowers do). Hence, putting a bunch of flowers in a vase results in a certain aesthetic value, expressed by an integer. The aesthetic values are presented in a table as shown below. Leaving a vase empty has an aesthetic value of 0.

 V A S E S
1 2 3 4 5
Bunches 1 (azaleas) 723-5-2416
2 (begonias) 521-41023
3 (carnations) -21 5-4-2020

According to the table, azaleas, for example, would look great in vase 2, but they would look awful in vase 4.

To achieve the most pleasant effect you have to maximize the sum of aesthetic values for the arrangement while keeping the required ordering of the flowers. If more than one arrangement has the maximal sum value, any one of them will be acceptable. You have to produce exactly one arrangement.

ASSUMPTIONS

  • 1 <= F <= 100 where F is the number of the bunches of flowers. The bunches are numbered 1 through F.
  • F <= V <= 100 where V is the number of vases.
  • -50 <= Aij<= 50 where Aij is the aesthetic value obtained by putting the flower bunch i into the vase j.

INPUT

The input is a text file named flower.inp.

  • The first line contains two numbers: F, V.
  • The following F lines: Each of these lines contains V integers, so that Aij is given as the jth number on the (i+1)st line of the input file.

OUTPUT

The output must be a text file named flower.out consisting of two lines:

  • The first line will contain the sum of aesthetic values for your arrangement.
  • The second line must present the arrangement as a list of F numbers, so that the k’th number on this line identifies the vase in which the bunch k is put.

EXAMPLE

flower.inp:

3 5
7 23 -5 -24 16
5 21 -4 10 23
-21 5 -4 -20 20

flower.out:

53
2 4 5

EVALUATION

  • Your program will be allowed to run 2 seconds.
  • No partial credit can be obtained for a test case.

本题虽然是IOI’99中较为简单的一题,但其中大有文章可作。说它简单,是因为它有序,因此我们一眼便可看出这题应该用动态规划来解决。但是,如何动态规划呢?如何划分阶段,又如何选择状态呢?

<方法1>

以花束的编号来划分阶段。在这里,第k阶段布置第k束花,共有F束花,有F+1个阶段,增加第F+1阶段是为了计算的方便;状态变量xk表示第k束花所在的花瓶。而对于每一个状态xk,决策uk就是第k+1束花放置的花瓶号;最优指标函数fk(xk)表示从第k束花到第n束花所得到的最大美学值;A(i,j)是花束i插在花瓶j中的美学值,V是花瓶总数,F是花的总数。

状态转移方程为

规划方程为

边界条件为:

,

事实上这是一个虚拟的边界。

最后要求的最大美学价值是

<方法2>

方法1的规划方程中的允许决策空间:xk+1≤uk≤V-(F-k)+1 比较麻烦,因此有待改进。还是以花束的编号来划分阶段,第k阶段布置第k束花;状态变量xk表示第k束花所在的花瓶;注意,这里我们考虑倒过来布置花瓶,即从第F束花开始布置到第1束花。于是状态变量uk表示第k-1束花所在的花瓶;最优指标fk(xk)表示从第一束花到第k束花所获得的美学价值;A(i,j)是花束i插在花瓶j中的美学值,V是花瓶总数,F是花的总数。则状态转移方程为:

规划方程为:

增加的虚拟边界条件为:

最后要求的最大美学价值是:

可以看出,这种方法实质上和方法1没有区别,但是允许决策空间的表示变得简单了。

<方法3>

以花瓶的数目来划分阶段,第k个阶段决定花瓶k中是否放花;状态变量xk表示前k个花瓶中放了多少花;而对于任意一个状态xk,决策就是第xk束花是否放在第k个花瓶中,用变量uk=1或0来表示。最优指标函数fk(xk)表示前k个花瓶中插了xk束花,所能取得的最大美学值。注意,这里仍然是倒过来考虑。

状态转移方程为

规划方程为

边界条件为

三种不同的方法都成功地解决了问题,只不过因为阶段的划分不同,状态的表示不同,决策的选择有多有少,所以算法的时间复杂度也就不同。

这个例子具有很大的普遍性。有很多的多阶段决策问题都有着不止一种的阶段划分方法,因而往往就有不止一种的规划方法。有时各种方法所产生的效果是差不多的,但更多的时候,就像我们的例子一样,两种方法会在某个方面有些区别。所以,在用动态规划解题的时候,可以多想一想是否有其它的解法。对于不同的解法,要注意比较,好的算法好在哪里,差一点的算法差在哪里。从各种不同算法的比较中,我们可以更深刻地领会动态规划的构思技巧。

动态规划实现中的问题

应用动态规划解决问题,在有了基本的思路之后,一般来说,算法实现是比较好考虑的。但有时也会遇到一些问题,而使算法难以实现。动态规划思想设计的算法从整体上来看基本都是按照得出的递推关系式进行递推,这种递推相对于计算机来说,只要设计得当,效率往往是比较高的,这样在时间上溢出的可能性不大,而相反地,动态规划需要很大的空间以存储中间产生的结果,这样可以使包含同一个子问题的所有问题共用一个子问题解,从而体现动态规划的优越性,但这是以牺牲空间为代价的,为了有效地访问已有结果,数据也不易压缩存储,因而空间矛盾是比较突出的。另一方面,动态规划的高时效性往往要通过大的测试数据体现出来(以与搜索作比较),因而,对于大规模的问题如何在基本不影响运行速度的条件下,解决空间溢出的问题,是动态规划解决问题时一个普遍会遇到的问题。

对于这个问题,可以考虑从以下一些方面去尝试:

一个思考方向是尽可能少占用空间。如从结点的数据结构上考虑,仅仅存储必不可少的内容,以及数据存储范围上精打细算(按位存储、压缩存储等)。当然这要因问题而异,进行分析。另外,在实现动态规划时,一个我们经常采用的方法是用一个与结点数一样多的数组来存储每一步的决策,这对于倒推求得一种实现最优解的方法是十分方便的,而且处理速度也有一些提高。但是在内存空间紧张的情况下,我们就应该抓住问题的主要矛盾。省去这个存储决策的数组,而改成在从最优解逐级倒推时,再计算一次,选择某个可能达到这个值的上一阶段的状态,直到推出结果为止。这样做,在程序编写上比上一种做法稍微多花一点时间,运行的时效也可能会有一些(但往往很小)的下降,但却换来了很多的空间。因而这种思想在处理某些问题时,是很有意义的。

但有时,即使采用这样的方法也会发现空间溢出的问题。这时就要分析,这些保留下来的数据是否有必要同时存在于内存之中。因为有很多问题,动态规划递推在处理后面的内容时,前面比较远处的内容实际上是用不着的。对于这类问题,在已经确信不会再被使用的数据上覆盖数据,从而使空间得以重复利用,如果能有效地使用这一手段,对于相当大规模的问题,空间也不至于溢出(为了求出最优方案,保留每一步的决策仍是必要的,这同样需要空间)。

一般地说,这种方法可以通过两种思路来实现:一种是递推结果仅使用Data1和Data2这样两个数组,每次将Data1作为上一阶段,推得Data2数组,然后,将Data2通过复制覆盖到Data1之上,如此反复,即可推得最终结果。这种做法有一个局限性,就是对于递推与前面若干阶段相关的问题,这种做法就比较麻烦;而且,每递推一级,就需要复制很多的内容,与前面多个阶段相关的问题影响更大。另外一种实现方法是,对于一个可能与前N个阶段相关的问题,建立数组Data[0..N],其中各项为最近N各阶段的保存数据。这样不采用这种内存节约方式时对于阶段k的访问只要对应成对数组Data中下标为k mod (N+1)的单元的访问就可以了。这种处理方法对于程序修改的代码很少,速度几乎不受影响,而且需要保留不同的阶段数也都能很容易实现。

当采用以上方法仍无法解决内存问题时,也可以采用对内存的动态申请来使绝大多数情况能有效出解。而且,使用动态内存还有一点好处,就是在重复使用内存而进行交换时,可以只对指针进行交换,而不复制数据,这在实践中也是十分有效的。

动态规划与其他算法的比较

动态规划与其说是一种算法,不如说是一种算法设计的策略,他的基本思想体现于许多其它算法之中。下面我们通过比较动态规划和其他的一些算法之间的相互联系,来深入理解动态规划的基本思想。

<!-- #EndEditable -->

<script src="../../../lib/footer.js"></script>

<!-- #EndEditable --><!-- #EndEditable -->

<script src="../../../lib/footer.js"></script>

<!-- #EndEditable --><!-- #EndEditable -->

<script src="../../../lib/footer.js"></script>

<!-- #EndEditable -->

<script src="../../../lib/footer.js"></script>

<!-- #EndEditable --><!-- #EndEditable -->

<script src="../../../lib/footer.js"></script>

<!-- #EndEditable -->

<script src="../../../lib/footer.js"></script>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
样加快电脑开机时间 一、BIOS的优化设置   在BIOS设置的首页我们进入“Advanced BIOS Features”选项,将光标移到“Frist Boot Device”选项,按“PageUP”和“PageDOWN”进行选择,默认值为“Floppy”,这表示启动时系统会先从软驱里读取启动信息,这样做会加长机器的启动时间,减短软驱的寿命。所以我们要选“HDD-0”直接从硬盘启动,这样启动就快上好几秒。另外,对于BIOS设置中的“Above 1MbMemoryTest”建议选“Disabled”,对于“QuickPowerOnSelftest”建议选择Enabled。   在“Advanced Chipset Features”项中的设置对机子的加速影响非常大,请大家多加留意。将“Bank 0/1 DRAM Timing”从“8ns/10ns”改为“Fast”或“Turbo”。“Turbo”比“Fast”快,但不太稳定,建议选“Fast”。如果内存质量好可以选“Turbo”试试,不稳定可以改回“Fast”。   对于内存品质好的内存条建议在“SDRAM CAS Latency”选项中设置为“2”,这样可以加快速度哦。   较新的主板都支持AGP4X,如果你的显卡也支持AGP4X,那么就在“AGP-4XMode”处将这项激活,即选为“Enabled”,这才会更好的发挥显卡的能力,加快系统启动速度。   二、启动DMA方式,提高硬盘速度   采用UDMA/33、66、100技术的硬盘最高传输速率是33MB/s、66MB/s、100MB/s,从理论上来说是IDE硬盘(这里是指PIO MODE4 模式,其传输率是16.6MB/s)传输速率的3~6倍,但是在Windows里面缺省设置中,DMA却是被禁用的,所以我们必须将它打开。   具体方法是:打开“控制面板→系统→设备管理器”窗口,展开“磁盘驱动器”分支,双击UDMA硬盘的图标,进入“属性→设置→选项”,在“DMA”项前面“√”,然后按确定,关闭所有对话框,重启电脑即可。   三、去掉Windows的开机标志。   首先你要打开“开始”→“设置”→“文件夹选项”,从“查看”标签里的“高级设置”列表框中勾选“显示所有文件”。然后打开C盘,找到MSdos.sys这个文件,并取消它的“只读”属性,打开它,在“Option”段落下,加上一行语句:LOGO=0,这样Windows的开机图案就不会被加载运行,开机时间也可以缩短3秒钟。   四、优化“启动”组。   电脑初学者都爱试用各种软件,用不多久又将其删除,但常常会因为某些莫名其妙的原因,这些软件还会驻留在“启动”项目中(尤其是在使用一些D版软件时),Windows启动时就会为此白白浪费许多时间。要解决这个问题,其实很简单,你可以打开“开始”→“运行”,在出现的对话框的“打开”栏中选中输入“msconfig”,然后点击“确定”,就会调出“系统配置实用程序”,点击其中的“启动”标签,将不用载入启动组的程序前面的“√”去掉就可以了。如此一来,至少可以将启动时间缩短10秒。   五、整理、优化注册表。   Windows在开机启动后,系统要读取注册表里的相关资料并暂存于RAM(内存)中,Windows开机的大部分时间,都花费了在这上面。因此,整理、优化注册表显得十分必要。有关注册表的优化,可以使用Windows优化大师等软件。以Windows优化大师,点击“注册信息清理”→“扫描”(如图1),软件就会自动替你清扫注册表中的垃圾,在扫描结束后,会弹出个菜单让你选择是否备份注册表,建议选择备份,备份后再点击“清除”即可。   六、经常维护系统。   如果在系统中安装了太多的游戏、太多的应用软件、太多的旧资料,会让你的电脑运行速度越来越慢,而开机时间也越来越长。因此,最好每隔一段时间,对电脑做一次全面的维护。点击“开始”→“程序”→“附件”→“系统工具”→“维护向导”,然后点击“确定”按钮即可对电脑进行一次全面的维护,这样会使你的电脑保持在最佳状态。对于硬盘最好能每隔2个星期就做一次“磁盘碎片整理”,那样会明显加快程序启动速度的,点击“系统工具”→“磁盘碎片整理程序”即可。注意在整理磁盘碎片时系统所在的盘一定要整理,这样才能真正加快Windows的启动顺序。   七、扩大虚拟内存容量。   如果你的硬盘够大,那就请你打开”控制面板“中的“系统”,在“性能”选项中打开“虚拟内存”,选择第二项:用户自己设定虚拟内存设置,指向一个较少用的硬盘,并把最大值和最小值都设定为一个固定值,大小为物理内存的2倍左右。这样,虚拟存储器在使用硬盘时,就不用迁就其忽大忽小的差别,而将固定的空间作为虚拟内存,加快存取速度。虚拟内存的设置最好在“磁盘碎片整理”之后进行,这样虚拟内存就分不在一个连续的、无碎片文件的空间上,可以更好的发挥作用。   八、去掉“墙纸”、“屏幕保护”等花哨的设置。   这些设置占用系统资源不说,还严重影响Windows的启动顺序。去掉它们的方法是:在桌面空白处点击鼠标右键,在弹出的菜单中选择“属性”,在弹出的对话框中分别选择“背景”和“屏幕保护程序”标签,将“墙纸”和“屏幕保护程序”设置为“无”即可。   九、删除Autoexec.bat和Config.sys。   系统安装盘根目录下的Autoexec.bat和Config.sys这两个文件,Windows已经不需要它们了,可以将它们安全删除,这样可以加快Windows的启动速度。   十、精简*.ini文件,尤其是System.ini和Win.ini的内容。   在system.ini的[boot]和[386Enh]小节中加载了许多驱动程序和字体文件,是清除重点。尤其要注意的是,[boot]字段的shell=Explorer.exe是木马喜欢的隐蔽加载之所,木马们通常会将该句变为这样:shell=Explorer.exe file.exe,注意这里的file.exe就是木马服务端程序!有了木马随后加载运行不仅对系统安全造成了威胁,电脑启动也慢了许多;对Win.ini中的“Run”及“Load”后面加载的、不是每次开机必须运行的程序,可以暂时清除,等以后要用时再点击运行。这样开机时Windows调用的相关文件就会减少许多,启动速度自然就会快多了。   十一、关闭磁盘扫描。   用文本编辑器打开msdos.sys,会看到以下内容: [Options] bootmulti=1 bootgui=1 autoscan=1   如果不想非正常关机后运行磁盘扫描程序,可以把atuoscan=1改为autoscan=0,这样在非正常关机后计算机的启动速度也会快上一些(因为scandisk没有运行嘛)。   十二、让引导信息停留时间最短。   用文本编辑器打开msdos.sys,设置[Options]中的BootDelay为0即可。   十三、减少不必要的字体文件。   字体文件占用系统资源多,引导时很慢,并且占用硬盘空间也不少。因此尽量减少不必要的字体文件。但如果删错了字体文件,搞不好会使Windows不正常。因此可以采用下面这个“偷梁换柱”的方法(可以实现字体文件的安装,而不占用大量的磁盘空间):首先打开字库文件夹(如F:zk),选中全部TrueType字体文件,用鼠标的右键将它们拖动到C:WindowsFonts文件夹中,在弹出的菜单中选择“在当前位置创建快捷方式”,这样就可以在系统的字体文件夹下建立字库文件的快捷方式了。当需要使用这些字库文件时,只要插入字库光盘,不用时取出就可以了。   十四、删去多余的Dll文件。在Window操作系统的System子目录里有许多的Dll文件,这些文件可能被许多文件共享,但有的却没有没有一个文件要使用它,也就是说这些文件没用了,为了不占用硬盘空间和提高启动运行速度,完全可以将其删除。   十五、“旁门左道”的办法。如采用系统悬挂,即将当前系统状态在关机后保存,下次开机后,系统会直接进入上次关机前的桌面,用这种方法,开机时间最快可以达到4-5秒钟,但不是所有的主板BIOS都支持的,设置起来也稍显麻烦。   打开注册表,展开到HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionSharedDlls子键,在右边的有许多Dll文件,如果数据为0,则可以判定该Dll文件没有程序共享,可以删掉它。   如果按以上的方法做了电脑启动速度仍不够快,除了升级硬件(CPU、内存、硬盘等)外,另一个好办法是重装系统,这样可以明显加快电脑启动速度! 其他回答 有一个“BootVis”程序(绿色软件),是微软公司出品的专为Windows XP操作系统设计的加速程序。使用这个很小的加速程序,您会感到Windows XP启动速度明显加快。 注意:这个最新版本适用于Windows XP SP1平台,支持Intel P4 HT技术。 简易用法:(这里引用的是我自己汉化的中文版-620K无法上传,你若是下载的该是英文的) 1. 启动BootVis,单击“工具”菜单的“选项”,在“标志路径”框中键入BootVis程序所在路径(例如:D:\XP加速程序\ ),单击“保存”按钮。 2. 在“跟踪”菜单中,单击“下一引导”,再单击“确定”按钮,此时BootVis程序会引导WinXP重新启动,并记录启动进程,生成相关的BIN文件。 3. 重新启动后,BootVis仍在运行中,请在“跟踪”菜单中,单击“优化系统”命令即可。
第1章 声明和初始化 基本类型 1.1 我该如何决定使用哪种整数类型? 1.2 为什么不精确定义标准类型的大小? 1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16和int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 1.4 新的64位机上的64位类型是什么样的? 指针声明 1.5 这样的声明有什么问题?char*p1,p2;我在使用p2的时候报错了。 1.6 我想声明一个指针,并为它分配一些空间,但却不行。这样的代码有什么问题?char*p;*p=malloc(10); 声明风格 1.7 怎样声明和定义全局变量和函数最好? 1.8 如何在C中实现不透明(抽象)数据类型? 1.9 如何生成“半全局变量”,就是那种只能被部分源文件中的部分函数访问的变量? 存储类型 1.10 同一个静态(static)函数或变量的所有声明都必需包含static存储类型吗? 1.11 extern在函数声明中是什么意思? 1.12 关键字auto到底有什么用途? 类型定义(typedef) 1.13 对于用户定义类型,typedef和#define有什么区别? 1.14 我似乎不能成功定义一个链表。我试过typedefstruct{char*item;NODEPTRnext;}*NODEPTR;但是编译器报了错误信息。难道在C语言中结构不能包含指向自己的指针吗? 1.15 如何定义一对相互引用的结构? 1.16 Struct{ }x1;和typedefstruct{ }x2;这两个声明有什么区别? 1.17 “typedefint(*funcptr)();”是什么意思? const限定词 1.18 我有这样一组声明:typedefchar*charp;constcharpp;为什么是p而不是它指向的字符为const? 1.19 为什么不能像下面这样在初始式和数组维度值中使用const值?constintn=5;inta[n]; 1.20 constchar*p、charconst*p和char*constp有什么区别? 复杂的声明 1.21 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组? 1.22 如何声明返回指向同类型函数的指针的函数?我在设计一个状态机,用函数表示每种状态,每个函数都会返回一个指向下一个状态的函数的指针。可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数……,如此往复,以至无穷。 数组大小 1.23 能否声明和传入数组大小一致的局部数组,或者由其他参数指定大小的参数数组? 1.24 我在一个文件中定义了一个extern数组,然后在另一个文件中使用,为什么sizeof取不到数组的大小? 声明问题 1.25 函数只定义了一次,调用了一次,但编译器提示非法重声明了。 *1.26 main的正确定义是什么?voidmain正确吗? 1.27 我的编译器总在报函数原型不匹配的错误,可我觉得没什么问题。这是为什么? 1.28 文件中的第一个声明就报出奇怪的语法错误,可我看没什么问题。这是为什么? 1.29 为什么我的编译器不允许我定义大数组,如doublearray[256][256]? 命名空间 1.30如何判断哪些标识符可以使用,哪些被保留了? 初始化 1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 1.32 下面的代码为什么不能编译?intf(){chara[]="Hello,world!";} *1.33 下面的初始化有什么问题?编译器提示“invalidinitializers”或其他信息。char*p=malloc(10); 1.34 chara[]="stringliteral";和char*p="stringliteral";初始化有什么区别?当我向p[i]赋值的时候,我的程序崩溃了。 1.35 chara{[3]}="abc";是否合法? 1.36 我总算弄清楚函数指针的声明方法了,但怎样才能初始化呢? 1.37 能够初始化联合吗? 第2章 结构、联合和枚举 结构声明 2.1 structx1{ };和typedefstruct{ }x2;有什么不同? 2.2 这样的代码为什么不对?structx{ };xthestruct; 2.3 结构可以包含指向自己的指针吗? 2.4 在C语言中用什么方法实现抽象数据类型最好? *2.5 在C语言中是否有模拟继承等面向对象程序设计特性的好方法? 2.6 为什么声明externf(structx*p);给我报了一个晦涩难懂的警告信息? 2.7 我遇到这样声明结构的代码:structname{intnamelen;charnamestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素,namelen记录了元素个数。它是怎样工作的?这样是合法的和可移植的吗? 2.8 我听说结构可以赋给变量也可以对函数传入和传出。为什么K&R1却明确说明不能这样做? 2.9 为什么不能用内建的==和!=操作符比较结构? 2.10结构传递和返回是如何实现的? 2.11 如何向接受结构参数的函数传入常量值?怎样创建无名的中间的常量结构值? 2.12 怎样从/向数据文件读/写结构? 结构填充 2.13 为什么我的编译器在结构中留下了空洞?这导致空间浪费而且无法与外部数据文件进行“二进制”读写。能否关掉填充,或者控制结构域的对齐方式? 2.14 为什么sizeof返回的值大于结构大小的期望值,是不是尾部有填充? 2.15 如何确定域在结构中的字节偏移量? 2.16 怎样在运行时用名字访问结构中的域? 2.17 C语言中有和Pascal的with等价的语句吗? 2.18 既然数组名可以用作数组的基地址,为什么对结构不能这样? 2.19 程序运行正确,但退出时却“coredump”(核心转储)了,怎么回事? 联合 2.20 结构和联合有什么区别? 2.21 有办法初始化联合吗? 2.22 有没有一种自动方法来跟踪联合的哪个域在使用? 枚举 2.23 枚举和一组预处理的#define有什么不同? 2.24 枚举可移植吗? 2.25 有什么显示枚举值符号的容易方法吗? 位域 2.26 一些结构声明中的这些冒号和数字是什么意思? 2.27 为什么人们那么喜欢用显式的掩码和位操作而不直接声明位域? 第3章 表达式 求值顺序 3.1 为什么这样的代码不行?a[i]=i++; 3.2 使用我的编译器,下面的代码inti=7;printf("%d\n",i++*i++);打印出49。不管按什么顺序计算,难道不该是56吗? 3.3 对于代码inti=3;i=i++;不同编译器给出不同的i值,有的为3,有的为4,哪个是正确的? *3.4 有这样一个巧妙的表达式:a^=b^=a^=b;它不需要临时变量就可以交换a和b的值。 3.5 可否用显式括号来强制执行我所需要的计算顺序并控制相关的副作用?就算括号不行,操作符优先级是否能够控制计算顺序呢? 3.6 可是&&和||操作符呢?我看到过类似while((c=getchar())!=EOF&&c!='\n')的代码…… 3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个表达式的结果,则右边的表达式不会被求值? 3.8 为什么表达式printf("%d%d",f1(),f2());先调用了f2?我觉得逗号表达式应该确保从左到右的求值顺序。 3.9 怎样才能理解复杂表达式并避免写出未定义的表达式?“序列点”是什么? 3.10在a[i]=i++;中,如果不关心a[]的哪一个分量会被写入,这段代码就没有问题,i也的确会增加1,对吗? 3.11 人们总是说i=i++的行为是未定义的。可我刚刚在一个ANSI编译器上尝试过,其结果正如我所期望的。 3.12 我不想学习那些复杂的规则,怎样才能避免这些未定义的求值顺序问题呢? 其他的表达式问题 *3.13 ++i和i++有什么区别? 3.14 如果我不使用表达式的值,那我应该用i++还是++i来做自增呢? 3.15 我要检查一个数是不是在另外两个数之间,为什么if(abc)不行? 3.16 为什么如下的代码不对?inta=1000,b=1000;longintc=a*b; 3.17 为什么下面的代码总是给出0?doubledegC,degF;degC=5.0/9*(degF-32); 3.18 需要根据条件把一个复杂的表达式赋给两个变量中的一个。可以用下面这样的代码吗?((condition)?a:b)=complicated_expression; 3.19 我有些代码包含这样的表达式。a?b=c:d有些编译器可以接受,有些却不能。为什么? 保护规则 3.20 “semanticsof‘’changeinANSIC”的警告是什么意思? 3.21 “无符号保护”和“值保护”规则的区别在哪里? 第4章 指针 基本的指针应用 4.1 指针到底有什么好处? 4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char*p;*p=malloc(10); 4.3 *p++自增p还是p所指向的变量? 指针操作 4.4 我用指针操作int数组的时候遇到了麻烦。 4.5 我有一个char*型指针碰巧指向一些int型变量,我想跳过它们。为什么((int*)p)++;这样的代码不行? 4.6 为什么不能对void*指针进行算术操作? 4.7 我有些解析外部结构的代码,但是它却崩溃了,显示出了“unalignedaccess”(未对齐的访问)的信息。这是什么意思? 作为函数参数的指针 4.8 我有个函数,它应该接受并初始化一个指针:voidf(int*ip){staticintdummy=5;ip=&dummy;}但是当我如下调用时:int*ip;f(ip);调用者的指针没有任何变化。 4.9 能否用void**通用指针作为参数,使函数模拟按引用传递参数? 4.10 我有一个函数externintf(int*);,它接受指向int型的指针。我怎样用引用方式传入一个常数?调用f(&5);似乎不行。 4.11 C语言可以“按引用传参”吗? 其他指针问题 4.12 我看到了用指针调用函数的不同语法形式。到底怎么回事? 4.13 通用指针类型是什么?当我把函数指针赋向void*类型的时候,编译通不过。 4.14 怎样在整型和指针之间进行转换?能否暂时把整数放入指针变量中,或者相反? *4.15 我怎样把一个int变量转换为char*型?我试了类型转换,但是不行。 第5章 空指针 空指针和空指针常量 5.1 臭名昭著的空指针到底是什么? 5.2 怎样在程序里获得一个空指针? 5.3 用缩写的指针比较“if(p)”检查空指针是否有效?如果空指针的内部表达不是0会怎样? NULL宏 5.4 NULL是什么,它是怎么定义的? 5.5 在使用非零位模式作为空指针的内部表示的机器上,NULL是如何定义的? 5.6 如果NULL定义成#defineNULL((char*)0),不就可以向函数传入不加转换的NULL了吗? 5.7 我的编译器提供的头文件中定义的NULL为0L。为什么? 5.8 NULL可以合法地用作函数指针吗? 5.9 如果NULL和0作为空指针常量是等价的,那我到底该用哪一个呢? 5.10但是如果NULL的值改变了,比如在使用非零内部空指针的机器上,用NULL(而不是0) 不是更好吗? 5.11 我曾经使用过一个编译器,不使用NULL就不能编译。 5.12 我用预处理宏#defineNullptr(type)(type*)0帮助创建正确类型的空指针。 回顾 59 5.13 这有点奇怪:NULL可以确保是0,但空(null)指针却不一定? 5.14 为什么有那么多关于空指针的疑惑?为什么这些问题如此频繁地出现? 5.15 有没有什么简单点儿的办法理解所有这些与空指针有关的东西呢? 5.16 考虑到有关空指针的所有这些困惑,要求它们的内部表示都必须为0不是更简单吗? 5.17 说真的,真有机器用非零空指针吗,或者不同类型用不同的表示? 地址0上到底有什么? 5.18 运行时的整数值0转换为指针以后一定是空指针吗? 5.19 如何访问位于机器地址0处的中断向量?如果我将指针值设为0,编译器可能会自动将它转换为非零的空指针内部表示。 5.20运行时的“nullpointerassignment”错误是什么意思?应该怎样捕捉它? 第6章 数组和指针 数组和指针的基本关系 6.1 我在一个源文件中定义了chara[6],在另一个源文件中声明了externchar*a。为什么不行? 6.2 可是我听说chara[]和char*a是等价的。是这样的吗? 6.3 那么,在C语言中“指针和数组等价”到底是什么意思? 6.4 既然它们这么不同,那为什么作为函数形参的数组和指针声明可以互换呢? 数组不能被赋值 6.5 为什么不能这样向数组赋值?externchar*getpass();charstr[10];str=getpass("Enterpassword:"); 6.6 既然不能向数组赋值,那这段代码为什么可以呢?intf(charstr[]){if(str[0]=='\0')str="none";…} 6.7 如果你不能给它赋值,那么数组如何能成为左值呢? 回顾 6.8 现实地讲,数组和指针的区别是什么? 6.9 有人跟我讲,数组不过是常指针。这样讲准确吗? 6.10 我还是很困惑。到底指针是一种数组,还是数组是一种指针? 6.11 我看到一些“搞笑”的代码,包含5["abcdef"]这样的“表达式”。这为什么是合法的C语言表达式呢? 数组的指针 6.12 既然数组引用会退化为指针,如果array是数组,那么array和&array又有什么区别呢? 6.13 如何声明一个数组的指针? 动态数组分配 6.14 如何在运行时设定数组的大小?怎样才能避免固定大小的数组? 6.15 我如何声明大小和传入的数组一样的局部数组? 6.16 如何动态分配多维数组? 6.17 有个很好的窍门,如果我这样写:intrealarray[10];int*array=&realarray[-1];我就可以把“array”当作下标从1 开始的数组。 函数和多维数组 6.18 当我向一个接受指针的指针的函数传入二维数组的时候,编译器报错了。 6.19 我怎样编写接受编译时宽度未知的二维数组的函数? 6.20 我怎样在函数参数传递时混用静态和动态多维数组? 数组的大小 6.21 当数组是函数的参数时,为什么sizeof不能正确报告数组的大小? 6.22 如何在一个文件中判断声明为extern的数组的大小(例如,数组定义和大小在另一个文件中)?sizeof操作符似乎不行。 6.23 sizeof返回的大小是以字节计算的,怎样才能判断数组中有多少个元素呢? 第7章 内存分配 基本的内存分配问题 7.1 为什么这段代码不行?char*answer;printf("Typesomething:\n");gets(answer);printf("Youtyped\"%s\"\n",answer); 7.2 我的strcat()不行。我试了下面的代码:char*s1="Hello,";char*s2="world!";char*s3=strcat(s1,s2);但是我得到了奇怪的结果。 7.3 但是strcat的文档说它接受两个char*型参数。我怎么知道(空间)分配的事情呢? *7.4 我刚才试了这样的代码:char*p;strcpy(p,"abc");它运行正常。怎么回事?为什么它没有出错? *7.5 一个指针变量分配多少内存? 7.6 我使用fgets将文件的所有行读入一个数组,为什么读入的每一行都是最后一行的内容呢? 7.7 我有个函数,本该返回一个字符串,但当它返回调用者的时候,返回的字符串却是垃圾信息。 为什么? *7.8 那么返回字符串或其他聚集的正确方法是什么呢? 调用malloc 7.9 为什么在调用malloc()时报出了“waring:assignmentofpointerfromintegerlacksacast”? 7.10为什么有些代码小心翼翼地把malloc返回的值转换为分配的指针类型? *7.11 在调用malloc()的时候,错误“不能把void*转换为int*”是什么意思? 7.12 我看到下面这样的代码:char*p=malloc(strlen(s)+1);strcpy(p,s);难道不应该是malloc((strlen(s)+1)*sizeof(char))吗? 7.13 我为malloc写了一个小小的封装函数。它为什么不行? 7.14 我想声明一个指针并向它分配一些内存,但是不行。这样的代码有什么问题?char*p;*p=malloc(10); 7.15 我如何动态分配数组? 7.16 怎样判断还有多少内存? 7.17 malloc(0)是返回空指针还是指向0个字节的指针? 7.18 我听说有的操作系统在程序使用的时候才真正分配malloc申请的内存。这合法吗? 有关malloc的问题 7.19 为什么malloc返回了离谱的指针值?我的确读过问题7.9,而且也在调用之前包含了externvoid*malloc();声明。 7.20 我用一行这样的代码分配一个巨大的数组,用于数值运算:double*array=malloc(256 *256 *sizeof(double));malloc()并没有返回空指针,但是程序运行得有些奇怪,好像改写了某些内存,或者malloc()并没有分配我申请的那么多内存。为什么? 7.21 我的PC机有8兆内存。为什么我只能分配640K左右的内存? 7.22 我的应用程序非常依赖数据结构的节点的动态分配,而malloc/free的代价成了瓶颈。我该怎么做? 7.23 我的程序总是崩溃,显然发生在malloc内部的某个地方。但是我看不出哪里有问题。是malloc有bug吗? 释放内存 7.24 动态分配的内存一旦释放之后就不能再使用,是吧? 7.25 为什么在调用free()之后指针没有变空?使用(赋值、比较)释放之后的指针有多么不安全? 7.26 当我调用malloc()为一个函数的局部指针分配内存时,我还需要用free()显式地释放吗? 7.27 我在分配一些结构,它们包含指向其他动态分配的对象的指针。我在释放结构的时候,还需要释放每一个下级指针吗? 7.28 我必须在程序退出之前释放分配的所有内存吗? 7.29 我有个程序分配了大量的内存,然后又释放了。但是从操作系统看,内存的占用率却并没有变回去。 分配内存块的大小 7.30 free()怎么知道有多少字节需要释放? 7.31 那么我能否查询malloc包,以查明可分配的最大块是多大? 7.32 为什么sizeof不能告诉我它所指的内存块的大小? 其他分配函数 7.33 (像问题6.14中那样)动态分配数组之后,还能改变它的大小吗? 7.34 向realloc()的第一个参数传入空指针合法吗?你为什么要这样做? 7.35 calloc()和malloc()有什么区别?应该用哪一个?利用calloc的零填充功能安全吗?free()可以释放calloc()分配的内存吗,还是需要一个cfree()? 7.36 alloca是什么?为什么不提倡使用它? 第8章 字符和字符串 8.1 为什么strcat(string,'!');不行? 8.2 我想检查一个字符串是否跟某个值匹配。为什么这样不行?if(string=="value") 8.3 如果我可以写chara[]="Hello,world!";那为什么不能写chara[14];a="Hello,world!"; 8.4 为什么我的strcat不行?我试了char*s1="Hello,";char*s2="world!";char*s3 =strcat(s1,s2);可得到的结果很奇怪。 8.5 chara[]="stringliteral";和char*p="stringliteral";初始化有什么区别?当我对p[i]赋值的时候,程序崩溃了。 8.6 我怎么得到与字符相对应的数字(即ASCII或其他字符集下的)值?反过来又该怎么做? 8.7 C语言有类似其他语言的"substr"(提取子串)这样的函数吗? 8.8 我将用户键入的字符串读入数组,然后再显示出来。当用户键入\n这样的序列时,为什么不能正确处理呢? 8.9 我注意到sizeof('a')是2而不是1(即不是sizeof(char)),是不是我的编译器有问题? 8.10 我正开始考虑多语言字符集的问题。是否有必要担心sizeof(char)会被定义为2,以便表达16位的字符集呢? 第9章 布尔表达式和变量 9.1 C语言中布尔值该用什么类型?为什么它不是一个标准类型?我应该用#define或enum定义真值和假值吗? 9.2 既然在C语言中所有的非零值都被看作“真”,那是不是把TRUE定义为1很危险?如果某个内建的函数或关系操作符“返回”不是1的其他值怎么办? 9.3 当p是指针时,if(p)是合法的条件表达式吗? 9.4 我该使用像TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 9.5 我准备使用的一个第三方头文件定义了自己的TRUE和FALSE,它们跟我已经开发的部分不兼容。我该怎么办? 第10章 C预处理器 宏定义 10.1 我想定义一些函数式的宏,例如:#definesquare(x)x*x但它们并不总是正确的。为什么? 10.2 这里有一些的预处理宏,使用它们,我可以写出更像Pascal的C代码。你觉得怎么样? 10.3 怎么写一个交换两个值的通用宏? 10.4 书写多语句宏的最好方法是什么? 10.5 用typdef和预处理宏生成用户定义类型有什么区别? 头文件 10.6 我第一次把一个程序分成多个源文件,我不知道该把什么放到.c文件,把什么放到.h文件。(“.h”到底是什么意思?) 10.7 可以在一个头文件中包含另一头文件吗? 10.8 完整的头文件搜索规则是怎样的? 10.9 我在文件的第一个声明就遇到奇怪的语法错误,但是看上去没什么问题。 10.10 我使用了来自两个不同的第三方库的头文件,它们都定义了相同的宏,如TRUE、FALSE、Min()和Max()等,但是它们的定义相互冲突,而且跟我在自己的头文件中的定义也有冲突。我该怎么办? 10.11 我在编译一个程序,看起来我好像缺少需要的一个或多个头文件。谁能发给我一份? 条件编译 10.12 怎样构造比较字符串的#if预处理表达式? 10.13 sizeof操作符可以用在#if预处理指令中吗? 10.14 我可以像这样在#define行里使用#ifdef来定义两个不同的东西吗? 10.15 对typedef的类型定义有没有类似#ifdef的东西? 10.16 我如何用#if表达式来判断机器是高字节在前还是低字节在前? 10.17 为什么在我用#ifdef关掉的代码行中报出了奇怪的语法错误? 10.18 我拿到了一些代码,里边有太多的#ifdef。我不想使用预处理器把所有的#include和#ifdef都扩展开,有什么办法只保留一种条件的代码呢? 10.19 如何列出所有的预定义宏? 奇异的处理 10.20 我有些旧代码,试图用这样的宏来构造标识符:#definePaste(a,b)a/**/b但是不行了。为什么? 10.21 我有一个旧宏:#defineCTRL(c)('c'&037)不能用了。为什么? 10.22 为什么宏#defineTRACE(n)printf("TRACE:\%d\n",n)报出警告“macroreplacementwithinastringliteral”?它似乎把TRACE(count);扩展成了printf("TRACE:\%d\count",count); 10.23 如何在宏扩展的字符串字面量中使用宏参数? 10.24 我想用ANSI的“字符串化”预处理操作符#将符号常量的值放入消息中,但它总是对宏名称而不是它的值进行字符串化。这是什么原因? 10.25 我想用预处理器做某件事情,但却不知道如何下手。 可变参数列表的宏 10.26 怎样写可变参数宏?如何用预处理器“关掉”具有可变参数的函数调用? 10.27 如何在通用的调试宏中包含__FILE__和__LINE__宏? 第11章 ANSI/ISO标准C 标准 11.1 什么是“ANSIC标准”? 11.2 如何得到一份标准的副本? *11.3 我在哪里可以找到标准的更新? 函数原型 11.4 为什么我的ANSI编译器对用float声明的参数会警告类型不匹配? 11.5 能否混用旧式的和新型的函数语法? *11.6 为什么下述声明报出了一个奇怪的警告信息“StructXdeclaredinsideparameterlist”?externintf(structx*p); 11.7 有个问题一直困扰着我,它是由这一行printf("%d",n);导致的,因为n是个longint型。难道ANSI的函数原型不能检查这种函数的参数不匹配问题吗? 11.8 我听说必须在调用printf之前包含stdio.h。为什么? const限定词 11.9 为什么不能在初始化和数组维度中使用const值?例如constintn=5;inta[n]; 11.10“constchar*p”、“charconst*p”和“char*constp”有何区别? 11.11 为什么不能向接受constchar**的函数传入char**? 11.12 我这样声明:typedefchar*charp;constcharpp;为什么是p而不是它所指向的字符为const? main()函数的使用 11.13 能否通过将main声明为void来关掉“main没有返回值”的警告? 11.14 main()的第3个参数envp是怎么回事? 11.15 我觉得把main()声明为void也不会失败,因为我调用了exit()而不是return,况且我的操作系统也忽略了程序的退出/返回状态。 *11.16 那么到底会出什么问题?真的有什么系统不支持voidmain()吗? 11.17 为什么以前流行的那些C语言书总是使用voidmain()? 11.18 在main()中调用exit(status)和返回同样的status真的等价吗? 预处理功能 11.19 我试图用ANSI“字符串化”预处理操作符'#'向信息中插入符号常量的值,但它字符串化的总是宏的名字而不是它的值。为什么? 11.20 警告信息“warning:macroreplacementwithinastringliteral”是什么意思? 11.21 为什么在我用#ifdef去掉的代码里出现了奇怪的语法错误? 11.22 #pragma是什么,有什么用? 11.23 “#pragmaonce”什么意思?我在一些头文件中看到了它。 其他的ANSIC问题 11.24 chara[3]="abc";合法吗?它是什么意思? 11.25 既然对数组的引用会退化为指针,那么,如果array是数组,array和&array之间有什么区别呢? 11.26 为什么我不能对void*指针进行算术运算? 11.27 memcpy()和memmove()有什么区别? 11.28 malloc(0)有什么用?返回一个空指针还是指向0字节的指针? 11.29 为什么ANSI标准规定了外部标识符的长度和大小写限制? 11.30 noalias是怎么回事?在它身上发生了什么? 老的或非标准的编译器 11.31 为什么我的编译器对最简单的测试程序都报出了一大堆的语法错误?对这段代码的第一行就报错了:main(intargc.char**argv){return0;} 11.32 为什么有些ASNI/ISO标准库函数未定义?我明明使用的就是ANSI编译器。 11.33 谁有可以在旧的C程序和ANSIC之间相互转换的工具,或者自动生成原型的工具? 11.34 为什么声称兼容ANSI的编译器不能编译这些代码?我知道这些代码是ANSI的,因为gcc可以编译。 兼容性 11.35 人们好像有些在意实现定义的(implementation-defined)、不确定的(unspecified)和未定义的(undefined)行为的区别。它们的区别到底在哪里? *11.36 一个程序“合法(legal)”、“有效(valid)”或“符合标准的”(conforming)到底是什么意思? 11.37 我很吃惊,ANSI标准竟然有那么多未定义的东西。标准的唯一任务不就是让这些东西标准化吗? 11.38 有人说i=i++的行为是未定义的,但是我刚在一个兼容ANSI的编译器上测试,得到了我希望的结果。它真的是未定义的吗? 第12章 标准输入输出库 基本输入输出 12.1 这样的代码有什么问题?charc;while((c=getchar())!=EOF) 12.2 我有个读取直到EOF的简单程序,但是我如何才能在键盘上输入那个“\EOF”呢?我看stdio.h中定义的EOF是-1,是不是说我该输入-1? 12.3 为什么这些代码把最后一行复制了两遍?while(!feof(infp)){fgets(buf,MAXLINE,infp);fputs(buf,outfp);} 12.4 我用fgets将文件的每行内容读入指针数组。为什么结果所有的行都是最后一行的内容呢? 12.5 我的程序的屏幕提示和中间输出有时没有在屏幕上显示,尤其是当我用管道通过另一个程序输出的时候。为什么? 12.6 我怎样才能不等待回车键而一次输入一个字符? printf格式 12.7 如何在printf的格式串中输出一个'%'字符?我试过\%,但是不行。 12.8 为什么这么写不对?longintn=123456;printf("%d\n",n); 12.9 有人告诉我不能在printf中使用%lf。为什么printf()用%f输出double型,而scanf却用%lf呢? *12.10 对于size_t那样的类型定义,当我不知道它到底是long还是其他类型的时候,我应该使用什么样的printf格式呢? 12.11 如何用printf实现可变的域宽度?就是说,我想在运行时确定宽度而不是使用%8d? 12.12 如何输出在千位上用逗号隔开的数字?货币格式的数字呢? 12.13 为什么scanf("%d",i)调用不行? *12.14 为什么chars[30];scamf("%s",s);不用&也可以?我原以为传给scanf的每个变量都要带&。 12.15 为什么这些代码不行?doubled;scanf("%f",&d); 12.16 为什么这段代码不行?shortints;scanf("%d",&s); 12.17 怎样在scanf格式串中指定可变的宽度? 12.18 怎样从特定格式的数据文件中读取数据?怎样读入10个float而不用使用包含10次%f的奇怪格式?如何将一行的任意多个域读入一个数组中? scanf问题 12.19 我像这样用"%d\n"调用scanf从键盘读取数字:intn;scanf("%d\n",&n);printf("youtyped%d\n",n);好像要多输入一行才返回。为什么? 12.20 我用scanf和%d读取一个数字,然后再用gets()读取字符串,但是编译器好像跳过了gets()调用! 12.21 我发现如果坚持检查返回值以确保用户输入的是我期待的数值,则scanf的使用会安全很多。但有的时候好像会陷入无限循环。为什么? 12.22 为什么大家都说不要使用scanf?那我该用什么来代替呢? 其他stdio函数 12.23 我怎样才知道对于任意的sprintf调用需要多大的目标缓冲区?怎样才能避免sprintf目标缓冲区溢出? 12.24 sprintf的返回值是什么?是int还是char*? 12.25 为什么大家都说不要使用gets? 12.26 我觉得我应该在一长串的printf调用之后检查errno,以确定是否有失败的调用。为什么当我将输出重定向到文件的时候会输出奇怪的“printffailed:Notatypewriter”信息? 12.27 fgetops/fsetops和ftell/fseek之间有什么区别?fgetops和fsetops到底有什么用处? 12.28 如何清除用户的多余输入,以防止在下一个提示符下读入?用fflush(stdin)可以吗? 打开和操作文件 12.29 我写了一个函数用来打开文件:myfopen(char*filename,FILE*fp){fp=fopen(filename,"r");}可我这样调用的时候:FILE*infp;myfopen("filename.dat",infp);,infp指针并没有正确设置。为什么? 12.30 连一个最简单的fopen调用都不成功!这个调用有什么问题?FILE*fp=fopen(filename,'r'); 12.31 为什么我不能用完整路径名打开一个文件?这个调用总是失败:fopen("c:\newdir\file.dat","r"); 12.32 我想用fopen模式"r+"打开一个文件,读出一个字符串,修改之后再写入,从而就地更新一个文件。可是这样不行。为什么? 12.33 如何在文件中间插入或删除一行(一条记录)? 12.34 怎样从打开的流中恢复文件名? 重定向stdin和stdout 12.35 怎样在程序里把stdin或stdout重定向到文件? 12.36 一旦使用freopen之后,怎样才能恢复原来的stdout(或stdin)? 12.37 如何判断标准输入或输出是否经过了重定向,即是否在命令行上使用了“”或“”? 12.38 我想写个像"more"那样的程序。怎样才能在stdin被重定向之后再回到交互键盘? *12.39 怎样同时向两个地方输出,如同时输出到屏幕和文件? “二进制”输入输出 12.40 我希望按字节在内存和文件之间直接读写数字,而不像fprintf和fscanf进行格式化。我该怎么办? 12.41 怎样正确地读取二进制文件?有时看到0x0a和0x0d容易混淆,而且如果数据中包含0x1a的话,我好像会提前遇到EOF。 12.42 我在写一个二进制文件的“过滤器”,但是stdin和stdout却被作为文本流打开了。怎样才能把它们的模式改为二进制? 12.43 文本和二进制输入输出有什么区别? 12.44 如何在数据文件中读写结构? 12.45 怎样编写符合旧的二进制数据格式的代码? 第13章 库函数 字符串函数 13.1 怎样把数字转为字符串(与atoi相反)?有itoa函数吗? 13.2 为什么strncpy不能总在目标串放上终止符'\0'? 13.3 C语言有类似于其他语言中的“substr”(取出子串)的例程吗? 13.4 怎样把一个字符串中所有字符转换成大写或小写? 13.5 为什么有些版本的toupper对大写字符会有奇怪的反应?为什么有的代码在调用toupper前先调用islower? 13.6 怎样将字符串分割成用空白分隔的字段?怎样实现类似main处理argc和argv的过程? 13.7 哪里可以找到处理正则表达式或通配符匹配的代码? 排序 13.8 我想用strcmp作为比较函数,调用qsort对一个字符串数组排序,但是不行。为什么? 13.9 我想用qsort()对一个结构数组排序。我的比较函数接受结构指针,但是编译器认为这个函数不是qsort需要的类型。我要怎样转换这个函数指针才能避免这样的警告? 13.10 怎样对一个链表排序? 13.11 怎样对大于内存容量的数据排序? 日期和时间 13.12 怎样在C程序中取得当前日期或时间? 13.13 我知道库函数localtime可以把time_t转换成结构structtm,而ctime可以把time_t转换成为可打印的字符串。怎样才能进行反向操作,把structtm或一个字符串转换成time_t? 13.14 怎样在日期上加n天?怎样取得两个日期的时间间隔? 随机数 13.15 怎么生成一个随机数? 13.16 怎样获得某一范围内的随机整数? 13.17 每次执行程序,rand都返回相同的数字序列。为什么? 13.18 我需要随机的真/假值,所以我就直接用rand()%2,可是我得到交替的0,1,0,1,0…。为什么? 164 13.19 如何获取根本不重复的随机数? 13.20 怎样产生正态分布或高斯分布的随机数? 13.21 我在移植一个程序,里边调用了一个函数drand48 ,而我的库又没有这个。这是个什么函数? 其他库函数 13.22 exit(status)是否真的跟从main函数返回status等价? 13.23 memcpy和memmove有什么区别? 13.24 我想移植这个旧程序。为什么报出这些“undefinedexternal”错误:index?、rindex?、bcopy?、bcmp?、bzero?? 13.25 我不断得到库函数未定义错误,但是我已经包含了所有用到的头文件了。 13.26 虽然我在连接时明确地指定了正确的函数库,我还是得到库函数未定义错误。 13.27 一个最简单的程序,不过在一个窗口里打印出“Hello,World”,为什么会编译出巨大的可执行代码(数百K)?我该少包含一些头文件吗? 13.28 连接器报告_end未定义代表什么意思? *13.29 我的编译器提示printf未定义!这怎么可能? 第14章 浮点运算 14.1 一个float变量赋值为3.1时,为什么printf输出的值为3.0999999? 14.2 我想计算一些平方根,我把程序简化成这样:main(){printf("%f\h",sqrt(144.));可得到的结果却是疯狂的数字。为什么? 14.3 我想做一些简单的三角函数运算,也包含了math.h,但连接器总是提示sin、cos这样的函数未定义。为什么? 14.4 我的浮点数计算程序表现得很奇怪,在不同的机器上给出了不同的结果。为什么? 14.5 有什么好的方法来检查浮点数在“足够接近”情况下的相等? 14.6 怎样取整? 14.7 为什么C语言不提供乘幂的操作符? 14.8 为什么我机器上的math.h没有预定义常量M_PI? 14.9 怎样将变量置为IEEENaN(“NotaNumber”)或检测变量是否为NaN及其他特殊值? 14.10 如何简洁地处理浮点异常? 14.11 在C语言中如何很好地实现复数? 14.12 我要寻找一些实现以下功能的程序源代码:快速傅立叶变换(FFT)、矩阵算术(乘法、求逆等函数)、复数算术。 14.13 TurboC的程序崩溃,显示错误为“floatingpointformatsnotlinked”(浮点格式未连接)。我还缺点儿什么呢? 第15章 可变参数列表 调用变参函数 15.1 为什么调用printf前必须要包含stdio.h? 15.2 为什么%f可以在printf参数中同时表示float和double?它们难道不是不同类型吗? 15.3 我遇到了一个令人十分受挫的问题,后来发现是这行代码造成的:printf("%d",n);原来n是longint型。难道ANSI的函数原型不就是用来防止这类的参数类型不匹配吗? 15.4 怎样写一个接受可变参数的函数? 15.5 怎样写一个函数,像printf那样接受一个格式串和可变参数,然后再把参数传给printf去完成大部分工作? 15.6 怎样写类似scanf的函数,再把参数传给scanf去完成大部分工作? 15.7 我用的是ANSI前的编译器,没有stdarg.h文件。我该怎么办? 提取可变参数 15.8 怎样知道实际上有多少个参数传入函数? 15.9 为什么编译器不允许我定义一个没有固定参数项的可变参数函数? 15.10 我有个接受float型的变参函数,为什么va_arg(argp,float)却不行? 15.11 为什么va_arg不能得到类型为函数指针的参数? 困难的问题 15.12 怎样实现一个可变参数函数,它把参数再传给另一个可变参数函数? 15.13 怎样调用一个在运行时才构建参数列表的函数? 第16 章奇怪的问题 16.1 为什么这个循环只执行了一次?for(i=start;iend;i++);{printf("%d\n",i);} *16.2 遇到不可理解的不合理语法错误,似乎大段的程序没有编译。 *16.3 为什么过程调用不起作用?编译器似乎直接跳过去了。 16.4 程序在执行之前就崩溃了!(用调试器单步跟踪,在main函数的第一个语句之前就死了。)为什么? 16.5 程序执行正确,但退出时在main函数的最后一个语句之后崩溃了。为什么会这样? 16.6 程序在一台机器上运行完美,但在另一台上却得到怪异的结果。更奇怪的是,增加或去除调试的打印语句,就改变了症状…… 16.7 为什么下面的代码会崩溃?char*p="hello,world!";p[0]='H'; 16.8 我有些代码是用来解析外部结构的,但它却崩溃了,报了“unalignedaccess”(未对齐的访问)错误。这是什么意思? 16.9 “Segmentationviolation”、“Buserror”和“Generalprotectionfault”是什么意思? 第17章 风格 17.1 什么是C最好的代码布局风格? 17.2 如何在源文件中合理分配函数? 17.3 用if(!strcmp(s1,s2))比较两个字符串是否相等是个好风格吗? 17.4 为什么有的人用if(0==x)而不是if(x==0)? 17.5 为什么有些代码在每次调用printf前增加了类型转换(void)? 17.6 既然NULL和0都是空指针常量,我到底该用哪一个? 17.7 是该用TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 17.8 什么是“匈牙利表示法”(HungarianNotation)?是否值得一试? 17.9 哪里可以找到“IndianHillStyleGuide”及其他编码标准? 17.10 有人说goto是邪恶的,永远都不该用它。这是否太极端了? 17.11 人们总是说良好的风格很重要,但当他们使用良好的风格写出清晰易读的程序后,又发现程序的效率似乎降低了。既然效率那么重要,是否可以为了效率牺牲一些风格和可读性呢? 第18章 工具和资源 18.1 能否列一个常用工具列表? 18.2 怎样捕获棘手的malloc问题? 18.3 有什么免费或便宜的编译器可以使用? lint 18.4 刚刚输入完一个程序,但它表现得很奇怪。你能发现有什么错误的地方吗? 18.5 如何关掉lint对每个malloc调用报出的“warning:possiblepointeralignmentproblem”警告消息? 18.6 哪里可以找到兼容ANSI的lint? 18.7 难道ANSI函数原型说明没有使lint过时吗? 资源 18.8 网上有哪些C语言的教程或其他资源? *18.9 哪里可以找到好的源代码实例,以供研究和学习? 18.10 有什么好的学习C语言的书?有哪些高级的书和参考? 18.11 哪里能找到K&R的练习答案? 18.12 哪里能找到NumericalRecipesinC、Plauger的TheStandardCLibrary或Kernighan和Pike的TheUNIXProgrammingEnviroment等书里的源码? 18.13 哪里可以找到标准C函数库的源代码? 18.14 是否有一个在线的C参考指南? 18.15 我需要分析和评估表达式的代码。从哪里可以找到? 18.16 哪里可以找到C的BNF或YACC语法? *18.17 谁有C编译器的测试套件? *18.18 哪里有一些有用的源代码片段和例子的收集? *18.19 我需要执行多精度算术的代码。 18.20 在哪里和怎样取得这些可自由发布的程序? 第19章 系统依赖 键盘和屏幕I/O 19.1 怎样从键盘直接读入字符而不用等回车键?怎样防止字符输入时的回显? 19.2 怎样知道有未读的字符(如果有,有多少)?另外,如何在没有字符的时候不阻塞读入? 19.3 怎样显示一个在原地更新自己的百分比或“旋转棒”的进度指示器? 19.4 怎样清屏?怎样反色输出?怎样把光标移动到指定的x,y位置? 19.5 怎样读入方向键、功能键? 其他I/O 19.6 怎样读入鼠标输入? 19.7 怎样做串口(“comm”)的输入输出? 19.8 怎样直接输出到打印机? 19.9 怎样发送转义字符序列控制终端或其他设备? 19.10 怎样做图形? *19.11 怎样显示GIF和JPEG图像? 文件和目录 19.12 怎样检验一个文件是否存在?如果请求的输入文件不存在,我希望向用户提出警告。 19.13 怎样在读入文件前,知道文件大小? *19.14 怎样得到文件的修改日期和时间? 19.15 怎样原地缩短一个文件而不用清除或重写? 19.16 怎样在文件中插入或删除一行(或一条记录)? 19.17 怎样从一个打开的流或文件描述符得到文件名? 19.18 怎样删除一个文件? *19.19 怎样复制文件? 19.20 为什么用了详尽的路径还不能打开文件?下面的代码会返回错误。Fopen("c:\newdir\file.dat","r") *19.21 fopen不让我打开文件"$HOME/.profile"和"~~/.myrcfile"。 *19.22 怎样制止MS-DOS下令人恐怖的“Abort,Retry,Ignore?”信息? 19.23 遇到“Toomanyopenfiles(打开文件太多)”的错误,怎样增加同时打开文件的允许数目? 19.24 如何得到磁盘的可用空间大小? 19.25 怎样在C语言中读入目录? 19.26 如何创建目录?如何删除目录(及其内容)? 访问原始内存 19.27 怎样找出系统还有多少内存可用? 19.28 怎样分配大于64K的数组或结构? 19.29 错误信息“DGROUPdataallocationexceeds64K(DGROUP数据分配内存超过64K)”什么意思?我应该怎么做?我以为使用了大内存模型,就可以使用大于64K的数据! 19.30 怎样访问位于某特定地址的内存(内存映射的设备或图形显示内存)? 19.31 如何访问机器地址0处的中断向量?如果将指针设为0,编译器可能把它转成一个非零的内部空指针值。 “系统”命令 19.32 怎样在一个C程序中调用另一个程序(独立可执行的程序或系统命令)? 19.33 如果运行时才知道要执行的命令的参数(文件名等),应该如何调用system? 19.34 在MS-DOS上如何得到system返回的准确错误状态? 19.35 怎样调用另一个程序或命令,然后获取它的输出? 进程环境 19.36 怎样才能发现程序自己的执行文件的全路径? 19.37 怎样找出和执行文件在同一目录的配置文件? 19.38 进程如何改变它的调用者的环境变量? 19.39 如何打开命令行给出的文件并解析选项? 19.40 exit(status)是否真的和从main函数返回同样的status等价? 19.41 怎样读入一个对象文件并跳跃到其中的函数? 其他系统相关的操作 19.42 怎样以小于1秒的精度延时或计算用户响应时间? 19.43 怎样捕获或忽略control-C这样的键盘中断? 19.44 怎样简洁地处理浮点异常? 19.45 怎样使用socket?如何联网?如何写客户/服务器程序? *19.46 怎样调用BIOS函数?如何写ISR?如何创建TSR? *19.47 什么是“near”和“far”指针? 回顾 19.48 我不能使用这些非标准、依赖系统的函数,程序需要兼容ANSI! 19.49 为什么这些内容没有在C语言中进行标准化?任何现实程序都会用到这些东西。 第20章 杂项 20.1 怎样从函数返回多个值? 20.2 用什么数据结构存储文本行最好?我开始用固定大小的char型数组的数组,但是有很多局限。 20.3 怎样打开命令行提到的文件并处理参数? 20.4 如何正确地使用errno? 20.5 怎样写数据文件,使之可以在不同字大小、字节顺序或浮点格式的机器上读入? 20.6 怎样用char*指针指向的函数名调用函数? 位和字节 20.7 如何操作各个位? 20.8 怎样实现位数组或集合? 234 20.9 怎样判断机器的字节顺序是高字节在前还是低字节在前? *20.10 怎样调换字节? 20.11 怎样将整数转换到二进制或十六进制? 20.12 可以使用二进制常数(类似0b101010这样的东西)吗?printf有二进制的格式说明符吗? 效率 20.13 用什么方法计算整数中为1的位的个数最高效? 20.14 怎样提高程序的效率? 20.15 指针真的比数组快吗?函数调用会拖慢程序多少?++i比i=i+1快吗? 20.16 用移位操作符替换乘法和除法是否有价值? *20.17 人们说编译器优化得很好,我们不再需要为速度而写汇编了,但我的编译器连用移位代替i/=2都做不到。 *20.18 怎样不用临时变量而交换两个值? switch语句 20.19 switch语句和if/else链哪个更高效? 20.20 是否有根据字符串进行条件切换的方法? 20.21 是否有使用非常量case行标的方法(如范围或任意的表达式)? 各种语言功能 20.22 return语句外层的括号是否真的可选择? 20.23 为什么C语言的注释不能嵌套?怎样注释掉含有注释的代码?引号包含的字符串内的注释是否合法? 20.24 为什么C语言的操作符不设计得更全面一些?好像还缺了一些^^、&&=和-=这样的操作符。 *20.25 C语言有循环移位操作符吗? *20.26 C是个伟大的语言还是别的什么东西?哪个其他语言可以写出像a+++++b这样的代码? 20.27 如果赋值操作符是:=,是不是就不容易意外地写出if(a=b)了? 20.28 C语言有和Pascal的with等价的语句吗? 20.29 为什么C语言没有嵌套函数? *20.30 assert是什么?如何使用? 其他语言 20.31 怎样从C中调用FORTRAN(C++、BASIC、Pascal、Ada、LISP)的函数?反之如何? 20.32 有什么程序可以将Pascal或FORTRAN(或LISP、Ada、awk、“老”C)程序转化为C程序? 20.33 C++是C的超集吗?可以用C++编译器来编译C代码吗? 20.34 我需要用到“近似”的strcmp例程,比较两个字符串的近似度,并不需要完全一样。有什么好办法? 20.35 什么是散列法? 20.36 如何生成正态或高斯分布的随机数? 20.37 如何知道某个日期是星期几? 20.38 (year%4==0)是否足以判断闰年?2000年是闰年吗? 20.39 为什么tm结构中的tm_sec的范围是0到61,暗示一分钟有62秒? 琐事 20.40 一个难题:怎样写一个输出自己源代码的程序? 20.41 什么是“达夫设备”(Duff’sDevice)? 20.42 下届国际C语言混乱代码竞赛(InternationalObfuscatedCCodeContest,IOCCC)什么时候进行?哪里可以找到当前和以前的获胜代码? 20.43 K&R1提到的关键字entry是什么? 20.44 C的名字从何而来? 20.45 “char”如何发音? *20.46 “lvalue”和“rvalue”代表什么意思? 20.47 哪里可以获得本书的在线版? 术语表 参考文献

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值