Charles river media algorithms and data structures the science of computing jun 2004 ISBN 1584502509

312 96 0
Charles river media algorithms and data structures the science of computing jun 2004 ISBN 1584502509

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

A.6 PACKAGES Programmers are free to write their own libraries of Java classes, and to share those libraries with other programmers For example, we have written a library that contains classes List, OrderedTree, LineDrawing, etc for this book As programmers all around the world write their own Java libraries, there will inevitably be name conflicts—situations in which two people use the same name to identify different entities Java's packages are a mechanism for reducing name conflicts A package is a collection of related classes Programmers place a class in a package by putting a special package statement at the beginning of the file that defines the class All members of a package have names that, technically, include the package name For example, the classes that accompany this book are all in a package named geneseo.cs.sc The full name of the List class that accompanies this book is therefore geneseo.cs.sc.List.[5] A.6.1 Using Classes from a Package In order to use classes from a package, those classes must be installed on your computer Unfortunately, exactly what "installed" means depends on your Java programming environment: in some environments, "installed" means that files containing the compiled classes have to be present in specific directories, in other environments it means that either the compiled or source files must be listed in "project files" that describe your program, etc Consult your instructor, or the documentation for your programming environment, if you want to install packages for yourself Once classes defined in a package are installed on your computer, you may use them by writing out their full names in your own programs For example, you could declare an instance of this book's List class by writing code such as: geneseo.cs.sc.List aList = new geneseo.cs.sc.List(); Such code quickly becomes painful to write and hard to read Java therefore provides a way to import some or all of a package's definitions into a source file After importing a definition, code in that file can refer to the imported entity by its short name For example, after importing geneseo.cs.sc.List via the statement: import geneseo.cs.sc.List; you could write the above declaration as just: List aList = new List(); To import a single class into a file, you write a statement of the form: import ; at the beginning of the file In this template, is a package name, and is the name of the class you want to import For example: import geneseo.cs.sc.List; To import every class in a package, use an import statement of the form: import * ; where is the name of the package For example: import geneseo.cs.sc.*; A file can contain multiple import statements, if you need to import multiple classes, or need to import from multiple packages [5]Packages only solve the problem of name conflicts if package names themselves don't give rise to conflicts Fortunately there are published conventions for naming packages that make such conflicts unlikely A.5 IMPRECISE CLASSES Sometimes a program needs a way of specifying an object's class imprecisely For example, suppose you want to define a data structure in which you can store arbitrary kinds of data—String objects, Counter objects, Point objects, anything that someone might someday represent as an object The message that stores a piece of data into this structure cannot declare that data to be an instance of any one of these classes, because doing so would preclude storing instances of other classes in the structure Nor can you declare different messages to store different kinds of data, because you can't know what classes future clients will define and want to store in your data structure Similar problems arise in declaring messages that retrieve data from the structure, declaring the structure's member variables, etc What you need is a way to declare parameters, results, and member variables that can be objects of any class, not just one class Java provides several features that help programmers write such declarations A.5.1 Class "Object" Java programmers use the class hierarchy (i.e., superclass/subclass relationships between classes) to specify classes imprecisely In particular, something declared to be an instance of a superclass can actually be an instance of any of that superclass's subclasses (or subclasses of the subclasses, etc.) Therefore, if you don't know exactly what class some object will have, but you do know that it will be one of the subclasses of some general superclass, you can simply declare the object to be an instance of the superclass The most general superclass in Java is Object, which is the superclass of all other classes Any class that doesn't extend something else is automatically a subclass of Object, so every class descends, either directly or indirectly, from Object For example, consider a simple data structure of the sort suggested in the introduction to this section This structure holds data in an array, provides a store message to add an item to the array, and provides a retrieve message to fetch an item, given its position in the array You could use Object to declare these features, as outlined in Listing A.6 (for the complete class definition and sample client code, see file SimpleStructure.java at this book's Web site) LISTING A.6: An Outline of a Simple Generic Data Structure in Java private Object[] data; // The array of items public void store(Object newData) { // The store method } public Object retrieve(int index) { // The retrieve method } The actual values represented by something declared as Object will always really be instances of some subclass of Object For example, here is a statement that adds a String to a simple data structure named container: container.store("This is a string"); Note that this statement passes an ordinary String to store This is a legal use of store, because store can receive any instance of Object as its parameter, and String is a subclass of Object—so all String objects are, indirectly, also instances of Object Class Object is never used to create the value stored in the structure A.5.2 Casts Imprecise classes sometimes leave a programmer knowing an object's class more precisely than is evident from a program For example, suppose you are a client using Listing A.6's simple data structure to store strings Your application will thus retrieve only strings from the data structure Unfortunately, however, a Java compiler only knows that the retrieve message is declared as returning Object, it doesn't know how your application uses retrieve Therefore, if you try to send string messages to the objects you retrieve, or assign those objects to string variables, the compiler will report an error and refuse to compile your program Java's solution to this problem is a cast Casts let programmers assert that a value has a particular type The syntax for writing a cast is: ( ) where is an expression whose result is apparently of one type, and is another type that really has For example, a client of the simple data structure who knew that every object stored in the structure was a string, and so wanted to assign objects retrieved from the structure to a string variable, could write: String item = (String) container.retrieve(i); Although you can sometimes use casts to convert one simple type to another (e.g., you can cast a char to an int, an int to a double), you should generally treat a cast as a promise by the programmer that an expression produces a result of a different type than the one the expression's operators or operands imply You should not use casts to convert values of one type into values of a different type Trying to do so can cause a program to abort with an error called a "class cast exception." A.5.3 Interfaces As you use either the standard Java libraries, or the classes provided with this text, you will encounter Java data types known as "interfaces." Like a class, an interface specifies messages that certain sets of objects handle Unlike a class, however, an interface cannot define methods with which to handle these messages Instead, other classes have to implement the interface, meaning that they provide methods for handling its messages You can use the names of interfaces as you would use the names of classes to declare objects whose exact class is unknown Objects so declared can actually be instances of any class that implements the interface You probably won't need to define your own interfaces while using this book, but you may need to declare classes that implement other programmers' interfaces (recall that "implement" in this context is a technical term that means "provides methods for handling the interface's messages") A class that implements an interface has a declaration that looks like: class implements { } or class extends implements { } In these templates, , , and are the class's name, the name of its superclass, and definitions of its member variables and methods, exactly as in other class declarations is a comma-separated list of names of interfaces A class can implement many interfaces, whereas it can only be a subclass of one class For example: class InterfaceExample implements Comparable, Serializable { } (Comparable and Serializable are interfaces from the Java library Comparable represents objects with a "less than" relation Serializable represents objects that can be written to and read from files.) A.7 JAVA'S IMPLEMENTATION OF OBJECTS Java's internal representation of objects has some consequences that can unpleasantly surprise programmers Fortunately, knowing something about Java's implementation of objects reduces these surprises, and leads to more effective use of the language in general A.7.1 Object Variables are Pointers Object variables in Java are more complicated, with more subtle behaviors, than simple variables A simple variable is basically a place in the computer's memory to store the variable's value For example, the statement int i = 1; sets aside enough memory to hold one integer, notes that henceforth the name i means that location in memory, and places the value 1 into that location Figure A.1 diagrams these effects Image from book Figure A.1: A simple variable is a name for a memory location In contrast, Figure A.2 illustrates an object variable A declaration such as SomeClass obj = new SomeClass(); reserves two regions of memory First, the new operation sets aside enough memory to hold all the members of one SomeClass object This memory includes locations that store the values of the object's member variables, and locations that indicate where to find the code for the object's methods Second, the declaration sets aside one memory location, named obj, that holds information about where in memory to find the members Such information is called a pointer Properly speaking, the object is the memory that holds the members; the variable is only a pointer to the object Imagine stapling two grocery lists together, or coupling a train with a whole series of cars already linked together, generically shown in Figure 11.4 Combining two lists to make one large list goes by many names, including join (usually applied to unordered collections or sets), concatenate (usually applied to sequences of characters or words), and append But the basic idea is the same no matter what the specific features of the list: start with two separate lists and end up with one long list containing all the elements of both lists Figure 11.4: Combining two lists Visit the Elements Any number of related actions, collectively called visiting or traversing, require accessing every element in the list individually, perhaps to print them out, perhaps to make a change to each item, or perhaps simply to find one particular value within the list It is not the specific action, such as printing, that is important here Rather, we need a general means of traversal through the list: a tool that enables us to access each element one at a time In fact, this may be the most important of all the capabilities described here As we develop the concept of list further and ultimately create a List class, we will want to make sure that the resulting class facilitates these basic operations 11.2.2 Visiting the Subparts Traversal of a list is especially important because it is the means for accessing the elements even though their number or order may change Perhaps the best way to assure that every element is reachable is to provide a way to visit each of them in a specific sequence This suggests a path through the list: starting with some first element, each element is linked or connected to a next, as in Figure 11.5 Image from book Figure 11.5: Links in a list Every element in the list should be on the path once—and no more than once This requirement assures that we can visit every element by following the path from the first element to the last Performing some operation on every element in the list can be accomplished by processing each element and then following the path to the next So far, this says nothing about the actual construction of a link, which might be accomplished in any of several ways In a grocery list, items are linked simply by their position on the page; train cars are connected by a physical coupling device Such links are probably not a new concept to most readers If you have ever clicked on a link on a Web page you have followed a link If you have ever followed directions that included something like "go to step 3," you have followed a link In the figure and most other graphical representations, a link is represented as an arrow Humans can intuitively follow the arrows with no special instructions, but it may not be immediately clear how an arrow should best be represented in a computer Any representation of a list class will require some computer equivalent to "arrow", some mechanism that says, "When looking at a given element, this is how to find the next in the sequence." Thus, every element in a list has an associated aspect or property: the link to the next element These two items, element and link, taken together are often thought of as a single unit, called a node But we will soon see an alternative representation that is more amenable to an object-oriented environment Find the Next Element We need a specific tool that effectively says "follow the link." It must somehow explain how to get from "Alpha" in Figure 11.5 to "Beta" This single tool is the enabling tool for any algorithm that requires visiting every element of a list Using Lists Given a tool for getting to the next element, most list-processing algorithms are very simple Perhaps more importantly, they have remarkably similar structures, and with a little experience, can be built almost intuitively A simple example might be something like: Algorithm to find the total cost of groceries: Initialize the total cost to zero Start at the first element While there are more groceries on the list: Set total = total plus cost of current element Find (visit) the next element Recursive versions of most list algorithms are even simpler and look something like: Algorithm totalCost (groceryList): If there are no more elements in the list Set total cost to 0 otherwise set total cost = cost of this element + cost of all following elem This algorithm contains the question, "Are there any elements remaining to be visited?" Hidden within that question are at least two assumptions: that a list may not have any elements at all and that we can recognize when it doesn't With those assumptions, the general structure of almost any traversing algorithm is almost identical: Algorithm genericProcess: If the list has more elements to be visited then Process this element, genericProcess the remaining elements Only the details of the specific action performed during the visit will change In fact, the simplicity of recursive algorithms for list processing is one of the features that make both lists and recursion popular tools For example, one algorithm to print out all the items on a grocery list looks like: Algorithm to print all element names: If there are elements remaining to print then Print the current name, Print the names of the remaining elements 11.2.3 A Unifying Observation Before we actually attempt a formal definition of a list, let's consider the question: What follows an element in a list? There are two possible answers: The next element A series of elements starting with the next element and running to the last element in the list This second answer, while perhaps less intuitive, is more interesting, because it can be stated as: each element is followed by another list (possibly empty) Consider the train example yet again: each car within a train has a coupling device capable of hanging onto—or letting go of—the following car By closing this coupler at the right moment, the car can attach itself to another car But if that additional car happens to be the first car of a train, attaching it also adds that entire train Each car in a train is followed not just by the next car but all the remaining cars in the train Those remaining cars can collectively be thought of as a train The first answer to the question seems to correspond directly to the original concept of list The second answer leads to an alternative description of list, that while consistent with the previous use, enables a much more powerful development of a list class Just as each car in a train can be thought of as the first car of another smaller train, a list can be thought of as composed of a series of lists, each of which is an element followed by another list (rather than simply by an element), as illustrated in Figure 11.6 Notice that just as the list ovals in the figure are simply super-imposed on the original list of elements (recall Figure 11.5), this vision of list can be superimposed on the original The sublist provides the mechanism for visiting the remaining elements; each sublist of any given list contains all of the elements that follow the first element Instead of representing a list as a series of individual elements, we will think of it as a nested series of lists Image from book Figure 11.6: A recursive vision of list By tradition, the two parts of a list are called the head and the tail.[1] The head is the first element of the list, while the tail—the rest of the list—is actually another list In this view, every element is the head of some list, as in Figure 11.7 "Alpha" is the head of the list, "Beta" is the head of the tail of that first list "Delta" is the head of the next list In general, we can refer to any element of the original series as the head of some tail within the list On occasion we may continue to visualize the physical list as a sequence of elements In fact, we may even refer to "the next element of the list," but you should remember that by "the next element" we mean the head of the next sublist (or, "the head of the tail") following the current element Image from book Figure 11.7: The list as sequence of lists 11.2.4 The Empty List The new definition is a recursive definition, and any recursive definition must terminate somehow A list, like so many objects we have discussed in this text, may be empty—a list with no elements A train with no cars is one that hasn't been put together yet (but perhaps is scheduled, in which case the switching yard needs to know about it); a character string with no characters is the null string (represented in most compilers as " "); a folder with no files in it yet is an empty list of files; and hopefully at the end of a shopping trip the shopping list is empty (all items have been crossed off) If we require that the last (innermost) list within a list be the empty or null list, we can avoid any infinite regression In general a list may be: an element—followed by a list or the empty (null) list Requiring that every list terminate with the empty list provides an easy way to recognize the end of a path through the list: if the current list is the empty list, we must be at the end A list made up of lists lends itself perfectly to recursive algorithms The printList algorithm nears its final form as: Algorithm printList: If the list is not empty then Print the current name (the head), printList the next list (the tail) Exercises For each of the previous real-world examples of lists (train, shopping list, computer program, table of contents, words in a word processor, and 11.4 letters on a screen), give an example of each of the basic list operations Repeat for the computer-specific lists 11.5 Draw schematic representations of three of the lists from Section 11.1 Be sure to include the list ovals superimposed on the sequence 11.6 Describe, as a recursive algorithm, the process of buying all the items on a grocery list Describe each element of that list in terms of heads and tails [1]The names are suggestive of the old question: Where does a snake's tail begin? Answer: right behind the head 12.4 FURTHER READING Stacks have been a staple of computing systems for almost a half century Bauer and Samelson first described a scheme using a pushdown stack for the translation and interpretation of arithmetic expressions, which first appeared in German, but was translated into English as: F.L Bauer and K Samelson "Sequential Formula Translation," in Communications of The Association for Computing Machinery, vol.3 (1960), pp.76–83 It is interesting to note that that is the same year as the publication of the "The Algol Report," which may have been the first language specification that explicitly called for a stack For more detailed information specific to the two data structures of this chapter, see any good data structures book such as: Richard Gilberg and Behrouz Forouzan Data Structures: A Pseudocode Approach With C Brooks/Cole, 1998 For the relationship of stacks to the complexity of problems, see any theory of computation text, such as: J Hopcroft, R Motwani, J Ullman & Rotwani Introduction to Automata Theory, Languages, and Computation Addison-Wesley, 2000 For the application of stacks to compiler design, see any compiler text such as the classic: Alfred V Aho, Ravi Sethi, Jeffrey D Ullman Compilers, Addison-Wesley, 1986 or the newer and Java-specific: Andrew Appel and Jens Palsberg Modern Compiler Implementation in Java Cambridge University Press, 2003 The mathematical analysis of queues and the systems that employ them is called queuing theory A good introduction may be found in: Frederick Hillier and Gerald Lieberman Introduction to Operations Research McGraw-Hill, 2002.p 13.2 FORMALIZING THE CONCEPT OF TREE We can define the notion of "tree" in much the way we defined "list." While a tree is certainly a more complex structure than a list, the two formalisms share many of the same features 13.2.1 Trees as Data Structures We have seen several important properties of trees that we want to capture: Every tree has exactly one root Every node other than the root has exactly one parent No node can be its own descendant (or ancestor) No two nodes can have the same child Among other things, these properties mean that there are no cycles, that is, no series of branches or edges that lead from one node through other nodes and back to the original node Figure 13.9 illustrates some way these properties may fail to be met In general, any attempt to define tree formally will want to capture these properties Image from book Figure 13.9: Some structures that are not trees Binary Trees In general, a node can have any number of children, and a tree with an upper bound of n on the number of possible children for any one node is called an nary tree The binary tree (a tree with no more than two children for any node, such as the sports tournaments, genealogy, or decision tree) is especially important in computer science While n-ary trees can be cumbersome, binary trees are easy to write and reason about And as we will see in Section 13.7.2, any n-ary tree has an equivalent binary tree For these reasons, the next several sections reason about binary trees exclusively 13.2.2 Visualizing a Formalism The tree structure can be envisioned much like the list structure—especially when restricted to binary trees Just as a list is conceptually a series of lists, each with a nested, or next list, a tree should be envisioned as a collection of trees, each composed of a root value and two children, each of which is itself a tree Any class definition for a treelike data structure would start just as the list class does, with member variables corresponding to these three pieces: root: any object left: a tree right: another tree Just as a list could be empty, so can a tree Notice that just as each node in a list is actually another list, each node in a tree is another (sub)tree Generally we will say child, subtree, or node depending on which aspect we wish to emphasize Figure 13.10 shows this recursive view of a tree, with empty trees represented by "(empty)" Even from this small tree, it may be obvious that the total number of these empty subtrees may be very large (Section 13.7.1 explores just how large) For that reason, we may often simplify the graphical representations as in Figure 13.11, omitting the explicit ovals representing subtrees, and empty trees along with the links to them However, as we reason about trees, it will be important to take care to recognize whether we are talking about all nodes (subtrees) or just the nonempty ones Similarly, when we say leaf, we need to be clear as to whether we mean the empty trees at the end of each path, or the last node with content "Leaf" will usually mean the last nonempty node However, many of the algorithms will actually visit the empty trees, and that fact will need to be considered when evaluating execution times Image from book Figure 13.10: Schematic representation of a tree structure Image from book Figure 13.11: Simplified graphic representation of a tree The uniformity of the tree structure is key to most proofs involving trees For example, the following lemma is used implicitly in most proofs in this chapter Lemma: The height of a tree is strictly greater than that of either of its children Proof: Let T be a tree and let T' be the taller of its subtrees (if they have the same height, select the left subtree) and let h be the height of T' Let P be the longest path in T' By definition, P has pathlength h - 1 The path formed by joining P and the branch from the T to T' must be one longer than h - 1 So the height of T must be greater than that of T', which by definition is at least as high as the other subtree Since every node is actually a tree, proofs involving nodes can usually be stated in terms of the subtree that has the node of interest at its root For example, one particular place this change of reference is useful is where any two nodes in a tree have a common ancestor That common ancestor is the root of the smallest tree that contains both nodes In particular, for any two nodes in a tree, there is a unique smallest (shortest) subtree containing both nodes To see this, start at the two nodes, A and B in Figure 13.12, and trace upwards until a common ancestor is located Within that smallest tree, the two nodes cannot share any common path (otherwise they could share in an even smaller tree) Thus, they must either be in opposite sides of their common subtree, or one must be its root Image from book Figure 13.12: Reasoning about relationships between nodes ... Subclasses in Java ordinarily inherit all of the features of their superclass In other words, instances of the subclass will have all of the member variables and methods of the superclass (although code in the subclass does not have direct... { } In these templates, , , and are the class's name, the name of its superclass, and definitions of its member variables and methods, exactly as in other class declarations... other parts of a program can directly access the variable The syntax for a member variable declaration is: ; where is the access specifier, and and are the type and name of the variable

Ngày đăng: 26/03/2019, 17:13

Từ khóa liên quan

Mục lục

  • Chapter 2: Abstraction: An Introduction to Design We begin our presentation of computer science's methods of inquiry by considering some fundamental ideas in algorithm design. The most important of these ideas is abstraction. We illustrate these ideas by us ing a running example involving a simulated robot that can move about and spray paint onto the floor, and we design an algorithm that makes this robot paint squares. Although this problem is simple, the concepts introduced while solving it are used throughout computer science and are powerful enough to apply to even the most complex problems. 2.1 ABSTRACTION WITH OBJECTS Abstraction means deliberately ignoring some details of something in order to concentrate on features that are essential to the job at hand. For example, abstract art is "abstract" because it ignores many details that make an image physically realistic and emphasizes just those details that are important to the message the artist wants to convey. Abstraction is impo

  • Chapter 2: Abstraction: An Introduction to Design

  • 2.5 ENCAPSULATING VALUES Most of the preceding examples of value-producing methods calculate their results from parameters supplied with a message. Often, however, this is not a very convenient way to provide inputs to a method. For instance, the heading method within robots must determine a robot's orientation for itself rather than being told it via a parameter. This problem can be solved by allowing objects to contain pieces of data that methods can use. For example, a robot could contain data about its orientation, which the heading method could retrieve, and which the turnLeft and turnRight methods would presumably alter. The mechanism for doing such things is member variables. Like other variables, member variables are named containers in which to store data. Unlike other variables, however, member variables are contained within objects. Think of a member variable as a pocket inside an object. The object can place a piece of data into this pocket, and examine the data in it. The

  • 3.4 CORRECTNESS OF CLASSES Imagine that you have defined a new class, and you want to show that it is correct. What does this entail? Partly, it requires proving that the individual methods within the class are correct. But correctness of a class requires more than just that the methods are correct in isolation. The methods must also interact with each other in the proper ways. 3.4.1 Class Invariants Whether interactions between methods are "proper" is often determined by conditions called class invariants. Informally, a class invariant is a condition that is true whenever an object is "at rest," that is, not in the midst of executing a method. Formally, a class invariant is a postcondition of every method in a class, and a precondition of every method in the class except ones that initialize new objects. Because they are universal postconditions, and nearly universal preconditions, class invariants govern all of a class's behaviors, regardless of when those behaviors are invoked, how

  • 2.4 ALGORITHMS THAT PRODUCE VALUES So far, all of our example algorithms have produced their results in the form of side effects. However, there is another way for algorithms to produce results: they can calculate some sort of value, or answer. Such algorithms are called value-producing algorithms, and you have probably seen examples before (for instance, an algorithm that calculates the average of a set of numbers). All of the forms of abstraction that we introduced in connection with side effects (encapsulating algorithms in methods, preconditions and postconditions, etc.) remain important when designing value-producing algorithms. 2.4.1 Expressions Consider an algorithm that calculates the area of a square that is n meters on a side. In English, this algorithm might be stated as: Take the value n, and multiply it by itself. In Java, this algorithm corresponds to the expression: n*n Although this expression looks different from the algorithms seen so far, it nonetheless describes the

  • 2.3 ALGORITHMS THAT PRODUCE EFFECTS Many algorithms produce their results by changing something—changing the contents of a file or the image displayed on a monitor, changing a robot's position, etc. The things that are changed are external to the algorithms (that is, not defined within the algorithms themselves), and the changes persist after the algorithms finish. Such changes are called side effects. Note that this use of the term "side effect" differs somewhat from its use in everyday speech—computer scientists use the term to mean any change that an algorithm causes to its environment, without the colloquial connotations of the change being accidental or even undesirable. Indeed, many algorithms deliver useful results through side effects. In this section, we examine how to use object-oriented programming, preconditions, and postconditions to design side-effect-producing algorithms. 2.3.1 Design from Preconditions and Postconditions A problem's preconditions and postconditions prov

  • 1.4 FURTHER READING For more on the meaning and history of the word "algorithm," see Section 1.1 of: Donald Knuth, Fundamental Algorithms (The Art of Computer Programming, Vol. 1), Addison-Wesley, 1973. For more on how the field (or, as some would have it, fields) of computer science defines itself, see: Peter Denning et al., "Computing as a Discipline," Communications of the ACM, Jan. 1989. The three methods of inquiry that we described in this chapter are essentially the three "paradigms" of computer science from this report.

  • 2.2 PRECONDITIONS AND POSTCONDITIONS Now that you know what the robot can do, you could probably design an algorithm to make it draw squares—but would it draw the right squares? Of course, you have no way to answer this question yet, because we haven't told you what we mean by "right": whether we require the squares to have a particular size or color, where they should be relative to the robot's initial position, whether it matters where the robot ends up relative to a square it has just drawn, etc. These are all examples of what computer scientists call the preconditions and postconditions of the problem. As these examples suggest, you can't know exactly what constitutes a correct solution to a problem until you know exactly what the problem is. Preconditions and postconditions help describe problems precisely. A precondition is a requirement that must be met before you start solving a problem. For example, "I know the traffic laws" is a precondition for receiving a driver's license.

  • 2.6 CONCLUDING REMARKS Abstraction, selectively concentrating on some aspects of a thing while ignoring others, is a powerful theme throughout computer science. This chapter introduced you to abstraction in algorithm design, in particular, to the way in which designers focus on different aspects of a problem or solution at different times. Objectoriented design helps designers do this by letting them think of the overall solution to a problem in terms of the actions of one or more objects that each solve a subproblem. The details of how each object solves its subproblem can be dealt with at a different time or even by a different person. Preconditions and postconditions, both those of the overall problem and those of the various subproblems, connect the different pieces of the large design: they provide a "contract" through which implementors and clients agree on exactly what the interface to an algorithm should be, without concern for how that algorithm will be implemented or used. Al

  • 3.3 CORRECTNESS OF SIDE EFFECTS Correctness proofs for side-effect-producing algorithms are almost exactly like proofs for value-producing algorithms. The only difference is that the postconditions to prove for a side-effect-producing algorithm describe the changes the algorithm makes to its environment rather than the value it returns. 3.3.1 The Square-drawing Problem Revisited Recall Chapter 2's square-drawing problem. It requires a robot to draw a red square of a client-specified size. The heart of the solution is a drawSquare method, which receives the size of the square as a parameter. This method coordinates the overall drawing of squares but uses a secondary algorithm, drawLine, to draw the individual sides of the square. The preconditions and postconditions for drawing a square are as follows: Preconditions: The requested length of each side of the square is an integer number of tiles, and is at least one tile. The future square is to the right and forward of the robot. There a

  • 3.5 APPLYING THE PROOF TECHNIQUES This chapter opened with the algorithm MakeChange, which supposedly makes change between 1 and 99 cents using the fewest coins. We deliberately raised doubts about whether this algorithm is correct. It turns out that it is, and you now know enough about correctness proofs to follow one for MakeChange. Furthermore, you also know that a variant on MakeChange is not correct for a different currency. This will offer an opportunity to see what proofs can tell you about incorrect algorithms. 3.5.1 The Correctness of MakeChange Our correctness proof for MakeChange is subtler than the other proofs in this chapter. However, it is an important proof, since it isn't obvious that MakeChange is correct, and you should be able to follow it even if you might not be able to invent it by yourself yet. Formally, this proof uses a technique called proof by contradiction, which amounts to showing that it is impossible for a theorem not to be true. In particular, we will s

  • Chapter 3: Proof: An Introduction to Theory OVERVIEW This chapter introduces theoretical techniques for reasoning about correctness and other aspects of algorithm behavior. You have surely written at least one program that looked right but didn't deliver correct results, so you realize that an algorithm may seem correct without actually being so. In fact, the correctness of any algorithm should be suspect until proven. For example, consider the following algorithm for making any amount of change between 1 and 99 cents in U.S. currency, using as few coins as possible: Algorithm MakeChange Give as many quarters as you can without exceeding the amount owed. Give as many dimes as you can without exceeding the amount still owed after the quarters. Give as many nickels as you can without exceeding the amount still owed after the dimes. Give whatever is left in pennies. Most people accept that this algorithm uses as few coins as possible. But suppose the currency were slightly different—suppo

  • Chapter 3: Proof: An Introduction to Theory

  • Chapter 15: Exponential Growth Figure 15.1 is a picture of a tree, created entirely by a computer program. The algorithm that generated this picture is about 20 lines of pseudocode. The picture, on the other hand, consists of over 72,000 line segments, and took over six seconds of computer time to create. It probably comes as no surprise to you by now that the trick to getting such a complex result from so little code is recursion. This chapter explores ways to use recursion to design very concise algorithms for some complicated problems, and the serious consequences for execution time that can ensue. Figure 15.1: An algorithmically generated tree. 15.1 WARM-UP: THE TOWERS OF HANOI We begin discussing recursive solutions to complicated problems with a puzzle called the Towers of Hanoi. The puzzle is easy to describe, but its solution is not at all obvious to most people. Thinking recursively, however, reveals a straightforward solution that can be phrased as an almost embarrassingly sh

  • Chapter 15: Exponential Growth

  • Chapter 11: Lists We all have an intuitive notion of "list," as in a grocery list or a "to do" list. This chapter uses that simple notion as the starting point for creating a more formal or abstract concept of list, one of the basic building blocks for data structures in computer science. As formal objects, lists share several properties that make them both interesting and conceptually different from objects you have dealt with previously. Each list has subparts: the series of items that make up the list. That property, by itself, is not new; many objects can be described as a whole, and those same objects can be described in terms of their subparts. What is new is that relationships among the subparts of a list or between those subparts and the list as a whole also can be described or even manipulated. 11.1 EXAMPLES OF LISTS OR SEQUENCES Examples of listlike structures can be found everywhere and include some entities that do not initially seem like lists. This section first looks to

  • Chapter 11: Lists

  • Chapter 13: Binary Trees The data structures queue and stack expand the concept of list in straightforward ways, and therefore necessarily share the essential characteristics of lists: linearity, access only via the end points, etc. Some data collections have characteristics or interrelationships that are not linear and cannot readily be represented in a list. Even when the data collection is linear, more efficient search strategies than those possible with lists may be needed. The tree structure or hierarchical organization often meets these needs. As a result, it is perhaps the most ubiquitous and useful of all data structures (also the most studied). 13.1 HIERARCHICAL ORGANIZATION This section starts our investigation of trees by examining several examples from everyday life. It then develops some nomenclature before looking at examples that are specifically useful in computer science. 13.1.1 Real World Trees Tree structures occur almost everywhere one looks. Consider just a few exa

  • Chapter 13: Binary Trees

  • 1.3 CONCLUDING REMARKS Computer science is the science that studies algorithms. An algorithm is a process for solving a problem. To be an algorithm, a process must: Be unambiguous. Solve the problem in a finite number of steps. In order to be solved by an algorithm, a problem must be defined precisely enough for a person to be able to tell whether a proposed answer is right or wrong. In order for it to be worthwhile solving a problem with an algorithm, the problem usually has to be general enough to have a number of different instances. Computer scientists use three methods of inquiry to study algorithms: design, theory, and empirical analysis. Figure 1.1 illustrates the relationships between algorithms, design, theory, and empirical analysis. Algorithms are the field's central concern—they are the reason computer scientists engage in any of the methods of inquiry. Design creates algorithms. Theory predicts how algorithms will behave under ideal circumstances. Empirical analysis measur

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan