Concrete Time, Abstract Time

Let's study the behavior of <#66954#><#37392#>how-many<#37392#><#66954#>, a function that we understand really well:
<#37397#>(d<#37397#><#37398#>efine<#37398#> <#37399#>(how-many<#37399#> <#37400#>a-list)<#37400#>
  <#37401#>(c<#37401#><#37402#>ond<#37402#> 
    <#37403#>[<#37403#><#37404#>(empty?<#37404#> <#37405#>a-list)<#37405#> <#37406#>0]<#37406#> 
    <#37407#>[<#37407#><#37408#>else<#37408#> <#37409#>(+<#37409#> <#37410#>(how-many<#37410#> <#37411#>(rest<#37411#> <#37412#>a-list))<#37412#> <#37413#>1)]<#37413#><#37414#>))<#37414#> 
It consumes a list and computes how many items the list contains. Here is a sample evaluation:
  <#37422#>(how-many<#37422#> <#37423#>(list<#37423#> <#37424#>'<#37424#><#37425#>a<#37425#> <#37426#>'<#37426#><#37427#>b<#37427#> <#37428#>'<#37428#><#37429#>c))<#37429#>
<#37437#>=<#37437#> <#37438#>(+<#37438#> <#37439#>(how-many<#37439#> <#37440#>(list<#37440#> <#37441#>'<#37441#><#37442#>b<#37442#> <#37443#>'<#37443#><#37444#>c))<#37444#> <#37445#>1)<#37445#>
<#37453#>=<#37453#> <#37454#>(+<#37454#> <#37455#>(+<#37455#> <#37456#>(how-many<#37456#> <#37457#>(list<#37457#> <#37458#>'<#37458#><#37459#>c))<#37459#> <#37460#>1)<#37460#> <#37461#>1)<#37461#>
<#37469#>=<#37469#> <#37470#>(+<#37470#> <#37471#>(+<#37471#> <#37472#>(+<#37472#> <#37473#>(how-many<#37473#> <#37474#>empty)<#37474#> <#37475#>1)<#37475#> <#37476#>1)<#37476#> <#37477#>1)<#37477#>
<#37485#>=<#37485#> <#37486#>3<#37486#>
It consists of only those steps that are natural recursions. The steps in between are always the same. For example, to get from the original application to the first natural recursion, we go through the following steps:
  <#37494#>(how-many<#37494#> <#37495#>(list<#37495#> <#37496#>'<#37496#><#37497#>a<#37497#> <#37498#>'<#37498#><#37499#>b<#37499#> <#37500#>'<#37500#><#37501#>c))<#37501#>
<#37509#>=<#37509#> <#37510#>(c<#37510#><#37511#>ond<#37511#>
    <#37512#>[<#37512#><#37513#>(empty?<#37513#> <#37514#>(list<#37514#> <#37515#>'<#37515#><#37516#>a<#37516#> <#37517#>'<#37517#><#37518#>b<#37518#> <#37519#>'<#37519#><#37520#>c))<#37520#> <#37521#>0]<#37521#> 
    <#37522#>[<#37522#><#37523#>else<#37523#> <#37524#>(+<#37524#> <#37525#>(how-many<#37525#> <#37526#>(rest<#37526#> <#37527#>(list<#37527#> <#37528#>'<#37528#><#37529#>a<#37529#> <#37530#>'<#37530#><#37531#>b<#37531#> <#37532#>'<#37532#><#37533#>c)))<#37533#> <#37534#>1)]<#37534#><#37535#>)<#37535#> 
<#37543#>=<#37543#> <#37544#>(c<#37544#><#37545#>ond<#37545#>
    <#37546#>[<#37546#><#37547#>false<#37547#> <#37548#>0]<#37548#> 
    <#37549#>[<#37549#><#37550#>else<#37550#> <#37551#>(+<#37551#> <#37552#>(how-many<#37552#> <#37553#>(rest<#37553#> <#37554#>(list<#37554#> <#37555#>'<#37555#><#37556#>a<#37556#> <#37557#>'<#37557#><#37558#>b<#37558#> <#37559#>'<#37559#><#37560#>c)))<#37560#> <#37561#>1)]<#37561#><#37562#>)<#37562#> 
<#37570#>=<#37570#> <#37571#>(c<#37571#><#37572#>ond<#37572#>
    <#37573#>[<#37573#><#37574#>else<#37574#> <#37575#>(+<#37575#> <#37576#>(how-many<#37576#> <#37577#>(rest<#37577#> <#37578#>(list<#37578#> <#37579#>'<#37579#><#37580#>a<#37580#> <#37581#>'<#37581#><#37582#>b<#37582#> <#37583#>'<#37583#><#37584#>c)))<#37584#> <#37585#>1)]<#37585#><#37586#>)<#37586#> 
<#37594#>=<#37594#> <#37595#>(+<#37595#> <#37596#>(how-many<#37596#> <#37597#>(rest<#37597#> <#37598#>(list<#37598#> <#37599#>'<#37599#><#37600#>a<#37600#> <#37601#>'<#37601#><#37602#>b<#37602#> <#37603#>'<#37603#><#37604#>c)))<#37604#> <#37605#>1)<#37605#>
The steps between the remaing natural recursions differ only as far as the substitution for <#66955#><#37609#>a-list<#37609#><#66955#> is concerned. If we apply <#66956#><#37610#>how-many<#37610#><#66956#> to a shorter list, we need fewer natural recursion steps:
  <#37615#>(how-many<#37615#> <#37616#>(list<#37616#> <#37617#>'<#37617#><#37618#>e))<#37618#>
<#37619#>=<#37619#> <#37620#>(+<#37620#> <#37621#>(how-many<#37621#> <#37622#>empty)<#37622#> <#37623#>1)<#37623#> 
<#37624#>=<#37624#> <#37625#>1<#37625#> 
If we apply <#66957#><#37629#>how-many<#37629#><#66957#> to a longer list, we need more natural recursion steps. The number of steps between natural recursions remains the same. The example suggests that, not surprisingly, the number of evaluation steps depends on the size of the input. More importantly, though, it also implies that the number of natural recrusions is a good measure of the size of an evaluation sequence. After all, we can reconstruct the actual number of steps from this measure and the function definition. For this reason, programmers have come to express the <#66958#><#37630#>ABSTRACT RUNNING TIME<#37630#><#66958#> of a program as a relationship between the size of the input and the number of natural recursion steps in an evaluation. In our first example, the size of the input is simply the size of the list. More specifically, if the list contains one item, the evaluation requires one natural recursion. For two items, we recur twice. For a list with <#37632#>N<#37632#> items, the evaluation requires <#37633#>N<#37633#> steps. Not all functions have such a uniform measure for their abstract running time. Take a look at our first recursive function:
<#37638#>(d<#37638#><#37639#>efine<#37639#> <#37640#>(contains-doll?<#37640#> <#37641#>a-list-of-symbols)<#37641#>
  <#37642#>(c<#37642#><#37643#>ond<#37643#> 
    <#37644#>[<#37644#><#37645#>(empty?<#37645#> <#37646#>a-list-of-symbols)<#37646#> <#37647#>false<#37647#><#37648#>]<#37648#> 
    <#37649#>[<#37649#><#37650#>else<#37650#> <#37651#>(c<#37651#><#37652#>ond<#37652#> 
            <#37653#>[<#37653#><#37654#>(symbol=?<#37654#> <#37655#>(first<#37655#> <#37656#>a-list-of-symbols)<#37656#> <#37657#>'<#37657#><#37658#>doll)<#37658#> <#37659#>true<#37659#><#37660#>]<#37660#> 
            <#37661#>[<#37661#><#37662#>else<#37662#> <#37663#>(contains-doll?<#37663#> <#37664#>(rest<#37664#> <#37665#>a-list-of-symbols))]<#37665#><#37666#>)]<#37666#><#37667#>))<#37667#> 
If we evaluate
<#37675#>(contains-doll?<#37675#> <#37676#>(list<#37676#> <#37677#>'<#37677#><#37678#>doll<#37678#> <#37679#>'<#37679#><#37680#>robot<#37680#> <#37681#>'<#37681#><#37682#>ball<#37682#> <#37683#>'<#37683#><#37684#>game-boy<#37684#> <#37685#>'<#37685#><#37686#>pokemon))<#37686#>
the application requires no natural recursion step. In contrast, for the expression
<#37694#>(contains-doll?<#37694#> <#37695#>(list<#37695#> <#37696#>'<#37696#><#37697#>robot<#37697#> <#37698#>'<#37698#><#37699#>ball<#37699#> <#37700#>'<#37700#><#37701#>game-boy<#37701#> <#37702#>'<#37702#><#37703#>pokemon<#37703#> <#37704#>'<#37704#><#37705#>doll))<#37705#>
the evaluation requires as many natural recursion steps as there are items in the list. Put differently, in the best case, the function can find the answer immediately; in the worst case, the function must search the entire input list. Programmers cannot assume that inputs are always of the best posisble shape; and they must hope that the inputs are not of the worst possible shape. Instead, they must analyze how much time their functions take <#37709#>on the average<#37709#>. For exanple, <#66959#><#37710#>contains-doll?<#37710#><#66959#> may---on the average---find <#66960#><#37711#>'<#37711#><#37712#>doll<#37712#><#66960#> somewhere in the middle of the list. Thus, we could say that if the input contains <#37713#>N<#37713#> items, the abstract running time of <#66961#><#37714#>contains-doll?<#37714#><#66961#> is (roughly)

#displaymath73828#

that is, it naturally recurs half as often as the number of items on the input. Because we already measure the running time of a function in an abstract manner, we can ignore the division by 2. More precisely, we assume that each basic step takes K units of time. If, instead, we use K/2 as the constant, we can calculate

#displaymath73834#

which shows that we can ignore other constant factors. To indicate that we are hiding such constants we say that <#66962#><#37721#>contains-doll?<#37721#><#66962#> takes ``on the order of <#37722#>N<#37722#> steps'' to find <#66963#><#37723#>'<#37723#><#37724#>doll<#37724#><#66963#> in a list of <#37725#>N<#37725#> items. Now consider our standard sorting function from figure~#figsort#37726>. Here is a hand-evaluation for a small input:

  <#37731#>(sort<#37731#> <#37732#>(list<#37732#> <#37733#>3<#37733#> <#37734#>1<#37734#> <#37735#>2))<#37735#>
<#37743#>=<#37743#> <#37744#>(insert<#37744#> <#37745#>3<#37745#> <#37746#>(sort<#37746#> <#37747#>(list<#37747#> <#37748#>1<#37748#> <#37749#>2)))<#37749#>
<#37757#>=<#37757#> <#37758#>(insert<#37758#> <#37759#>3<#37759#> <#37760#>(insert<#37760#> <#37761#>1<#37761#> <#37762#>(sort<#37762#> <#37763#>(list<#37763#> <#37764#>2))))<#37764#>
<#37772#>=<#37772#> <#37773#>(insert<#37773#> <#37774#>3<#37774#> <#37775#>(insert<#37775#> <#37776#>1<#37776#> <#37777#>(insert<#37777#> <#37778#>2<#37778#> <#37779#>(sort<#37779#> <#37780#>empty))))<#37780#>
<#37788#>=<#37788#> <#37789#>(insert<#37789#> <#37790#>3<#37790#> <#37791#>(insert<#37791#> <#37792#>1<#37792#> <#37793#>(insert<#37793#> <#37794#>2<#37794#> <#37795#>empty)))<#37795#>
<#37803#>=<#37803#> <#37804#>(insert<#37804#> <#37805#>3<#37805#> <#37806#>(insert<#37806#> <#37807#>1<#37807#> <#37808#>(list<#37808#> <#37809#>2)))<#37809#>
<#37817#>=<#37817#> <#37818#>(insert<#37818#> <#37819#>3<#37819#> <#37820#>(cons<#37820#> <#37821#>2<#37821#> <#37822#>(insert<#37822#> <#37823#>1<#37823#> <#37824#>empty)))<#37824#>
<#37832#>=<#37832#> <#37833#>(insert<#37833#> <#37834#>3<#37834#> <#37835#>(list<#37835#> <#37836#>2<#37836#> <#37837#>1))<#37837#>
<#37845#>=<#37845#> <#37846#>(insert<#37846#> <#37847#>3<#37847#> <#37848#>(list<#37848#> <#37849#>2<#37849#> <#37850#>1))<#37850#>
<#37858#>=<#37858#> <#37859#>(list<#37859#> <#37860#>3<#37860#> <#37861#>2<#37861#> <#37862#>1)<#37862#>
The evaluation is more complicated than those for <#66964#><#37866#>how-many<#37866#><#66964#> or <#66965#><#37867#>contains-doll?<#37867#><#66965#>. It also consists of two phases. During the first one, the natural recursions for <#66966#><#37868#>sort<#37868#><#66966#> set up as many applications of <#66967#><#37869#>insert<#37869#><#66967#> as there are items in the list. During the second phase, each application of <#66968#><#37870#>insert<#37870#><#66968#> traverses a list of 1, 2, 3, ...\ up to the number of items in the original list (minus one). Inserting an item is similar to finding an item, so it is not surprising that <#66969#><#37871#>insert<#37871#><#66969#> behaves like <#66970#><#37872#>contains-doll?<#37872#><#66970#>. More specifically, the applications of <#66971#><#37873#>insert<#37873#><#66971#> to a list of <#37874#>N<#37874#> items may trigger <#37875#>N<#37875#> natural recursions or none. On the average, we assume it requires N/2, which means on the order of <#37876#>N<#37876#>. Because there are <#37877#>N<#37877#> applications of <#66972#><#37878#>insert<#37878#><#66972#>, we have an average of on the order of #tex2html_wrap_inline73838# natural recursions of <#66973#><#37879#>insert<#37879#><#66973#>. In summary, if <#66974#><#37880#>l<#37880#><#66974#> contains <#37881#>N<#37881#> items, evaluating <#66975#><#37882#>(sort<#37882#>\ <#37883#>l)<#37883#><#66975#> always requires <#37884#>N<#37884#> natural recursions of <#66976#><#37885#>sort<#37885#><#66976#> and on the order of #tex2html_wrap_inline73840# natural recursions of <#66977#><#37886#>insert<#37886#><#66977#>. Taken together, we get

#displaymath73842#

steps, but we will see in exercise~#exbigo1#37887> that this is equivalent to saying that insertion sort requires on the order of #tex2html_wrap_inline73844# steps. Our final example is the function <#66978#><#37888#>max<#37888#><#66978#>:

<#71541#>;; <#66979#><#37893#>max<#37893#> <#37894#>:<#37894#> <#37895#>ne-list-of-numbers<#37895#> <#37896#><#37896#><#37897#>-;SPMgt;<#37897#><#37898#><#37898#> <#37899#>number<#37899#><#66979#><#71541#>
<#37900#>;; to determine the maximum of a non-empty list of numbers <#37900#> 
<#37901#>(d<#37901#><#37902#>efine<#37902#> <#37903#>(max<#37903#> <#37904#>alon)<#37904#> 
  <#37905#>(c<#37905#><#37906#>ond<#37906#> 
    <#37907#>[<#37907#><#37908#>(empty?<#37908#> <#37909#>(rest<#37909#> <#37910#>alon))<#37910#> <#37911#>(first<#37911#> <#37912#>alon)]<#37912#> 
    <#37913#>[<#37913#><#37914#>else<#37914#> <#37915#>(c<#37915#><#37916#>ond<#37916#> 
            <#37917#>[<#37917#><#37918#>(;SPMgt;<#37918#> <#37919#>(max<#37919#> <#37920#>(rest<#37920#> <#37921#>alon))<#37921#> <#37922#>(first<#37922#> <#37923#>alon))<#37923#> <#37924#>(max<#37924#> <#37925#>(rest<#37925#> <#37926#>alon))]<#37926#> 
            <#37927#>[<#37927#><#37928#>else<#37928#> <#37929#>(first<#37929#> <#37930#>alon)]<#37930#><#37931#>)]<#37931#><#37932#>))<#37932#> 
In exercise~#exlocalinterm2#37936>, we investigated its behavior and the behavior of an observationally equivalent function that uses <#66980#><#37937#>local<#37937#><#66980#>. Here we study its abstract running time rather than just observe some concrete running time. Let's start with a small example: <#66981#><#37938#>(max<#37938#>\ <#37939#>(list<#37939#>\ <#37940#>0<#37940#>\ <#37941#>1<#37941#>\ <#37942#>2<#37942#>\ <#37943#>3))<#37943#><#66981#>. We know that the result is <#66982#><#37944#>3<#37944#><#66982#>. Here is the first important step of a hand-evaluation:
  <#37949#>(max<#37949#> <#37950#>(list<#37950#> <#37951#>0<#37951#> <#37952#>1<#37952#> <#37953#>2<#37953#> <#37954#>3))<#37954#>
<#37955#>=<#37955#> <#37956#>(c<#37956#><#37957#>ond<#37957#> 
    <#37958#>[<#37958#><#37959#>(;SPMgt;<#37959#> <#72346#>#tex2html_wrap_inline73846#<#72346#> <#37965#>0)<#37965#> <#72347#>#tex2html_wrap_inline73848#<#72347#><#37971#>]<#37971#> 
    <#37972#>[<#37972#><#37973#>else<#37973#> <#37974#>0]<#37974#><#37975#>)<#37975#> 
From here, we must evaluate the left of the two underlined natural recursions. Because the result is <#66985#><#37979#>3<#37979#><#66985#> and the condition is thus <#66986#><#37980#>true<#37980#><#66986#>, we must evaluate the second underlined natural recursion as well. Focusing on just the natural recursion we see that its hand-evaluation begins with similar steps:
  <#37985#>(max<#37985#> <#37986#>(list<#37986#> <#37987#>1<#37987#> <#37988#>2<#37988#> <#37989#>3))<#37989#>
<#37990#>=<#37990#> <#37991#>(c<#37991#><#37992#>ond<#37992#> 
    <#37993#>[<#37993#><#37994#>(;SPMgt;<#37994#> <#37995#>(max<#37995#> <#37996#>(list<#37996#> <#37997#>2<#37997#> <#37998#>3))<#37998#> <#37999#>1)<#37999#> <#38000#>(max<#38000#> <#38001#>(list<#38001#> <#38002#>2<#38002#> <#38003#>3))]<#38003#> 
    <#38004#>[<#38004#><#38005#>else<#38005#> <#38006#>1]<#38006#><#38007#>)<#38007#> 
Again, <#66987#><#38011#>(max<#38011#>\ <#38012#>(list<#38012#>\ <#38013#>2<#38013#>\ <#38014#>3))<#38014#><#66987#> is evaluated twice because it produces the maximum. Finally, even determining the maximum of <#66988#><#38015#>(max<#38015#>\ <#38016#>(list<#38016#>\ <#38017#>2<#38017#>\ <#38018#>3))<#38018#><#66988#> requires two natural recursions:
  <#38023#>(max<#38023#> <#38024#>(list<#38024#> <#38025#>2<#38025#> <#38026#>3))<#38026#>
<#38027#>=<#38027#> <#38028#>(c<#38028#><#38029#>ond<#38029#> 
    <#38030#>[<#38030#><#38031#>(;SPMgt;<#38031#> <#38032#>(max<#38032#> <#38033#>(list<#38033#> <#38034#>3))<#38034#> <#38035#>2)<#38035#> <#38036#>(max<#38036#> <#38037#>(list<#38037#> <#38038#>3))]<#38038#> 
    <#38039#>[<#38039#><#38040#>else<#38040#> <#38041#>2]<#38041#><#38042#>)<#38042#> 
To summarize, <#66989#><#38046#>max<#38046#><#66989#> requires two natural recursion for each natural recursion. The following table counts the instances for our example:

#tabular38048#

Altogether the hand-evaluation requires eight natural recursions for a list of four items. If we add <#66996#><#38081#>4<#38081#><#66996#> (or a larger number) at the end of the list, we need to double the number of natural recursions. Thus, in general we need on the order of

#displaymath73850#

recursions for a list of <#38082#>N<#38082#> numbers when the last number is the maximum. While the scenario we considered is the worst possible case, the analysis of <#66998#><#38087#>max<#38087#><#66998#>'s abstract running time explains the phenomenon we studied in exercise~#exlocalinterm2#38088>. It also explains why a version of <#66999#><#38089#>max<#38089#><#66999#> that uses a <#67000#><#38090#>local<#38090#>-expression<#67000#> to name the result of the natural recursion is faster:

<#71544#>;; <#67001#><#38095#>max2<#38095#> <#38096#>:<#38096#> <#38097#>ne-list-of-numbers<#38097#> <#38098#><#38098#><#38099#>-;SPMgt;<#38099#><#38100#><#38100#> <#38101#>number<#38101#><#67001#><#71544#>
<#38102#>;; to determine the maximum of a list of numbers <#38102#> 
<#38103#>(d<#38103#><#38104#>efine<#38104#> <#38105#>(max2<#38105#> <#38106#>alon)<#38106#> 
  <#38107#>(c<#38107#><#38108#>ond<#38108#> 
    <#38109#>[<#38109#><#38110#>(empty?<#38110#> <#38111#>(rest<#38111#> <#38112#>alon))<#38112#> <#38113#>(first<#38113#> <#38114#>alon)]<#38114#> 
    <#38115#>[<#38115#><#38116#>else<#38116#> <#38117#>(l<#38117#><#38118#>ocal<#38118#> <#38119#>((define<#38119#> <#38120#>max-of-rest<#38120#> <#38121#>(max2<#38121#> <#38122#>(rest<#38122#> <#38123#>alon))))<#38123#> 
            <#38124#>(c<#38124#><#38125#>ond<#38125#> 
              <#38126#>[<#38126#><#38127#>(;SPMgt;<#38127#> <#38128#>max-of-rest<#38128#> <#38129#>(first<#38129#> <#38130#>alon))<#38130#> <#38131#>max-of-rest]<#38131#> 
              <#38132#>[<#38132#><#38133#>else<#38133#> <#38134#>(first<#38134#> <#38135#>alon)]<#38135#><#38136#>)]<#38136#><#38137#>)))<#38137#> 
Instead of recomputing the maximum of the rest of the list, this version just refers to the variable twice when the variable stands for the maximum of the rest of the list.
<#38143#>Exercise 29.1.1<#38143#> A number tree is either a number or a pair of number trees. Develop the function <#67002#><#38145#>sum-tree<#38145#><#67002#>, which determines the sum of the numbers in a tree. How should we measure the size of a tree? What is its abstract running time?~ external Solution<#67003#><#67003#> <#38151#>Exercise 29.1.2<#38151#> Hand-evaluate <#67004#><#38153#>(max2<#38153#>\ <#38154#>(list<#38154#>\ <#38155#>0<#38155#>\ <#38156#>1<#38156#>\ <#38157#>2<#38157#>\ <#38158#>3))<#38158#><#67004#> in a manner similar to our evaluation of <#67005#><#38159#>(max<#38159#>\ <#38160#>(list<#38160#>\ <#38161#>0<#38161#>\ <#38162#>1<#38162#>\ <#38163#>2<#38163#>\ <#38164#>3))<#38164#><#67005#>. What is the abstract running time of <#67006#><#38165#>max2<#38165#><#67006#>?~ external Solution<#67007#><#67007#>