Free Essay

Submitted By khatera44

Words 30587

Pages 123

Words 30587

Pages 123

Data Structures and Algorithm

Analysis

Third Edition (Java)

Clifford A. Shaffer

Department of Computer Science

Virginia Tech

Blacksburg, VA 24061

April 16, 2009

Copyright c 2008 by Clifford A. Shaffer.

This document is the draft of a book to be published by Prentice Hall and may not be duplicated without the express written consent of either the author or a representative of the publisher.

Contents

Preface

xiii

I

Preliminaries

1

1

Data Structures and Algorithms

1.1 A Philosophy of Data Structures

1.1.1 The Need for Data Structures

1.1.2 Costs and Beneﬁts

1.2 Abstract Data Types and Data Structures

1.3 Design Patterns

1.3.1 Flyweight

1.3.2 Visitor

1.3.3 Composite

1.3.4 Strategy

1.4 Problems, Algorithms, and Programs

1.5 Further Reading

1.6 Exercises

3

4

4

6

8

12

13

14

15

16

17

19

21

2

Mathematical Preliminaries

2.1 Sets and Relations

2.2 Miscellaneous Notation

2.3 Logarithms

2.4 Summations and Recurrences

25

25

29

31

33 iii iv

Contents

2.5

2.6

2.7

2.8

2.9

3

II

4

Recursion

Mathematical Proof Techniques

2.6.1 Direct Proof

2.6.2 Proof by Contradiction

2.6.3 Proof by Mathematical Induction

Estimating

Further Reading

Exercises

Algorithm Analysis

3.1 Introduction

3.2 Best, Worst, and Average Cases

3.3 A Faster Computer, or a Faster Algorithm?

3.4 Asymptotic Analysis

3.4.1 Upper Bounds

3.4.2 Lower Bounds

3.4.3 Θ Notation

3.4.4 Simplifying Rules

3.4.5 Classifying Functions

3.5 Calculating the Running Time for a Program

3.6 Analyzing Problems

3.7 Common Misunderstandings

3.8 Multiple Parameters

3.9 Space Bounds

3.10 Speeding Up Your Programs

3.11 Empirical Analysis

3.12 Further Reading

3.13 Exercises

3.14 Projects

Fundamental Data Structures

Lists, Stacks, and Queues

36

39

40

40

41

47

49

50

57

57

63

65

67

68

70

71

72

73

74

79

81

83

84

86

89

90

91

95

97

99

v

Contents

4.1

4.2

4.3

4.4

4.5

4.6

4.7

5

Lists

4.1.1 Array-Based List Implementation

4.1.2 Linked Lists

4.1.3 Comparison of List Implementations

4.1.4 Element Implementations

4.1.5 Doubly Linked Lists

Stacks

4.2.1 Array-Based Stacks

4.2.2 Linked Stacks

4.2.3 Comparison of Array-Based and Linked Stacks

4.2.4 Implementing Recursion

Queues

4.3.1 Array-Based Queues

4.3.2 Linked Queues

4.3.3 Comparison of Array-Based and Linked Queues

Dictionaries and Comparators

Further Reading

Exercises

Projects

Binary Trees

5.1 Deﬁnitions and Properties

5.1.1 The Full Binary Tree Theorem

5.1.2 A Binary Tree Node ADT

5.2 Binary Tree Traversals

5.3 Binary Tree Node Implementations

5.3.1 Pointer-Based Node Implementations

5.3.2 Space Requirements

5.3.3 Array Implementation for Complete Binary Trees

5.4 Binary Search Trees

5.5 Heaps and Priority Queues

5.6 Huffman Coding Trees

5.6.1 Building Huffman Coding Trees

100

103

106

117

119

120

125

125

128

129

129

133

134

137

140

140

147

147

150

153

153

156

157

158

162

163

169

170

171

180

187

189

vi

Contents

5.7

5.8

5.9

6

III

7

5.6.2 Assigning and Using Huffman Codes

Further Reading

Exercises

Projects

Non-Binary Trees

6.1 General Tree Deﬁnitions and Terminology

6.1.1 An ADT for General Tree Nodes

6.1.2 General Tree Traversals

6.2 The Parent Pointer Implementation

6.3 General Tree Implementations

6.3.1 List of Children

6.3.2 The Left-Child/Right-Sibling Implementation

6.3.3 Dynamic Node Implementations

6.3.4 Dynamic “Left-Child/Right-Sibling” Implementation

6.4 K-ary Trees

6.5 Sequential Tree Implementations

6.6 Further Reading

6.7 Exercises

6.8 Projects

Sorting and Searching

Internal Sorting

7.1 Sorting Terminology and Notation

7.2 Three Θ(n2 ) Sorting Algorithms

7.2.1 Insertion Sort

7.2.2 Bubble Sort

7.2.3 Selection Sort

7.2.4 The Cost of Exchange Sorting

7.3 Shellsort

7.4 Mergesort

7.5 Quicksort

195

198

198

202

205

205

206

207

208

216

217

218

218

220

221

223

226

226

230

233

235

236

237

238

240

241

243

244

246

249

vii

Contents

7.6

7.7

7.8

7.9

7.10

7.11

7.12

Heapsort

Binsort and Radix Sort

An Empirical Comparison of Sorting Algorithms

Lower Bounds for Sorting

Further Reading

Exercises

Projects

256

259

265

267

271

272

275

8

File Processing and External Sorting

8.1 Primary versus Secondary Storage

8.2 Disk Drives

8.2.1 Disk Drive Architecture

8.2.2 Disk Access Costs

8.3 Buffers and Buffer Pools

8.4 The Programmer’s View of Files

8.5 External Sorting

8.5.1 Simple Approaches to External Sorting

8.5.2 Replacement Selection

8.5.3 Multiway Merging

8.6 Further Reading

8.7 Exercises

8.8 Projects

279

280

282

283

286

289

297

298

301

304

307

310

311

315

9

Searching

9.1 Searching Unsorted and Sorted Arrays

9.2 Self-Organizing Lists

9.3 Bit Vectors for Representing Sets

9.4 Hashing

9.4.1 Hash Functions

9.4.2 Open Hashing

9.4.3 Closed Hashing

9.4.4 Analysis of Closed Hashing

9.4.5 Deletion

317

318

324

329

330

331

336

337

346

350

viii

Contents

9.5

9.6

9.7

Further Reading

Exercises

Projects

351

352

355

10 Indexing

10.1 Linear Indexing

10.2 ISAM

10.3 Tree-based Indexing

10.4 2-3 Trees

10.5 B-Trees

10.5.1 B+ -Trees

10.5.2 B-Tree Analysis

10.6 Further Reading

10.7 Exercises

10.8 Projects

357

359

361

364

366

372

375

381

382

382

384

IV

387

Advanced Data Structures

11 Graphs

11.1 Terminology and Representations

11.2 Graph Implementations

11.3 Graph Traversals

11.3.1 Depth-First Search

11.3.2 Breadth-First Search

11.3.3 Topological Sort

11.4 Shortest-Paths Problems

11.4.1 Single-Source Shortest Paths

11.5 Minimum-Cost Spanning Trees

11.5.1 Prim’s Algorithm

11.5.2 Kruskal’s Algorithm

11.6 Further Reading

11.7 Exercises

11.8 Projects

389

390

394

397

400

401

405

407

407

411

412

415

416

416

420

Contents

ix

12 Lists and Arrays Revisited

12.1 Multilists

12.2 Matrix Representations

12.3 Memory Management

12.3.1 Dynamic Storage Allocation

12.3.2 Failure Policies and Garbage Collection

12.4 Further Reading

12.5 Exercises

12.6 Projects

423

423

427

430

431

438

443

444

445

13 Advanced Tree Structures

13.1 Tries

13.2 Balanced Trees

13.2.1 The AVL Tree

13.2.2 The Splay Tree

13.3 Spatial Data Structures

13.3.1 The K-D Tree

13.3.2 The PR quadtree

13.3.3 Other Point Data Structures

13.3.4 Other Spatial Data Structures

13.4 Further Reading

13.5 Exercises

13.6 Projects

447

447

452

453

455

459

461

466

471

471

473

473

475

V

479

Theory of Algorithms

14 Analysis Techniques

14.1 Summation Techniques

14.2 Recurrence Relations

14.2.1 Estimating Upper and Lower Bounds

14.2.2 Expanding Recurrences

14.2.3 Divide and Conquer Recurrences

14.2.4 Average-Case Analysis of Quicksort

481

482

487

487

491

492

495

x

Contents

14.3

14.4

14.5

14.6

Amortized Analysis

Further Reading

Exercises

Projects

496

499

500

504

15 Lower Bounds

15.1 Introduction to Lower Bounds Proofs

15.2 Lower Bounds on Searching Lists

15.2.1 Searching in Unsorted Lists

15.2.2 Searching in Sorted Lists

15.3 Finding the Maximum Value

15.4 Adversarial Lower Bounds Proofs

15.5 State Space Lower Bounds Proofs

15.6 Finding the ith Best Element

15.7 Optimal Sorting

15.8 Further Reading

15.9 Exercises

15.10Projects

505

506

508

508

510

511

513

516

519

522

524

525

527

16 Patterns of Algorithms

16.1 Greedy Algorithms

16.2 Dynamic Programming

16.2.1 Knapsack Problem

16.2.2 All-Pairs Shortest Paths

16.3 Randomized Algorithms

16.3.1 Skip Lists

16.4 Numerical Algorithms

16.4.1 Exponentiation

16.4.2 Largest Common Factor

16.4.3 Matrix Multiplication

16.4.4 Random Numbers

16.4.5 Fast Fourier Transform

16.5 Further Reading

529

529

530

531

532

534

536

541

542

543

543

546

546

551

Contents

16.6 Exercises

16.7 Projects

xi

551

552

17 Limits to Computation

17.1 Reductions

17.2 Hard Problems

17.2.1 The Theory of N P-Completeness

17.2.2 N P-Completeness Proofs

17.2.3 Coping with N P-Complete Problems

17.3 Impossible Problems

17.3.1 Uncountability

17.3.2 The Halting Problem Is Unsolvable

17.4 Further Reading

17.5 Exercises

17.6 Projects

553

554

559

560

565

569

573

574

577

581

581

584

Bibliography

585

Index

591

Preface

We study data structures so that we can learn to write more efﬁcient programs. But why must programs be efﬁcient when new computers are faster every year? The reason is that our ambitions grow with our capabilities. Instead of rendering efﬁciency needs obsolete, the modern revolution in computing power and storage capability merely raises the efﬁciency stakes as we computerize more complex tasks.

The quest for program efﬁciency need not and should not conﬂict with sound design and clear coding. Creating efﬁcient programs has little to do with “programming tricks” but rather is based on good organization of information and good algorithms. A programmer who has not mastered the basic principles of clear design is not likely to write efﬁcient programs. Conversely, “software engineering” cannot be used as an excuse to justify inefﬁcient performance. Generality in design can and should be achieved without sacriﬁcing performance, but this can only be done if the designer understands how to measure performance and does so as an integral part of the design and implementation process. Most computer science curricula recognize that good programming skills begin with a strong emphasis on fundamental software engineering principles. Then, once a programmer has learned the principles of clear program design and implementation, the next step is to study the effects of data organization and algorithms on program efﬁciency.

Approach: This book describes many techniques for representing data. These techniques are presented within the context of the following principles:

1. Each data structure and each algorithm has costs and beneﬁts. Practitioners need a thorough understanding of how to assess costs and beneﬁts to be able to adapt to new design challenges. This requires an understanding of the principles of algorithm analysis, and also an appreciation for the signiﬁcant effects of the physical medium employed (e.g., data stored on disk versus main memory). xiii xiv

Preface

2. Related to costs and beneﬁts is the notion of tradeoffs. For example, it is quite common to reduce time requirements at the expense of an increase in space requirements, or vice versa. Programmers face tradeoff issues regularly in all phases of software design and implementation, so the concept must become deeply ingrained.

3. Programmers should know enough about common practice to avoid reinventing the wheel. Thus, programmers need to learn the commonly used data structures, their related algorithms, and the most frequently encountered design patterns found in programming.

4. Data structures follow needs. Programmers must learn to assess application needs ﬁrst, then ﬁnd a data structure with matching capabilities. To do this requires competence in principles 1, 2, and 3.

As I have taught data structures through the years, I have found that design issues have played an ever greater role in my courses. This can be traced through the various editions of this textbook by the increasing coverage for design patterns and generic interfaces. The ﬁrst edition had no mention of design patterns. The second edition had limited coverage of a few example patterns, and introduced the dictionary ADT and comparator classes. With the third edition, there is explicit coverage of some design patterns that are encountered when programming the basic data structures and algorithms covered in the book.

Using the Book in Class: Data structures and algorithms textbooks tend to fall into one of two categories: teaching texts or encyclopedias. Books that attempt to do both usually fail at both. This book is intended as a teaching text. I believe it is more important for a practitioner to understand the principles required to select or design the data structure that will best solve some problem than it is to memorize a lot of textbook implementations. Hence, I have designed this as a teaching text that covers most standard data structures, but not all. A few data structures that are not widely adopted are included to illustrate important principles. Some relatively new data structures that should become widely used in the future are included.

Within an undergraduate program, this textbook is designed for use in either an advanced lower division (sophomore or junior level) data structures course, or for a senior level algorithms course. New material has been added in the third edition to support its use in an algorithms course. Normally, this text would be used in a course beyond the standard freshman level “CS2” course that often serves as the initial introduction to data structures. Readers of this book should have programming experience, typically two semesters or the equivalent of a structured programming language such as Pascal or C, and including at least some exposure to Java. Readers who are already familiar with recursion will have an advantage. Students of

Preface

xv

data structures will also beneﬁt from having ﬁrst completed a good course in Discrete Mathematics. Nonetheless, Chapter 2 attempts to give a reasonably complete survey of the prerequisite mathematical topics at the level necessary to understand their use in this book. Readers may wish to refer back to the appropriate sections as needed when encountering unfamiliar mathematical material.

A sophomore-level class where students have only a little background in basic data structures or analysis (that is, background equivalent to what would be had from a traditional CS2 course) might cover Chapters 1-11 in detail, as well as selected topics from Chapter 13. That is how I use the book for my own sophomorelevel class. Students with greater background might cover Chapter 1, skip most of Chapter 2 except for reference, brieﬂy cover Chapters 3 and 4, and then cover chapters 5-12 in detail. Again, only certain topics from Chapter 13 might be covered, depending on the programming assignments selected by the instructor. A senior-level algorithms course would focus on Chapters 11 and 14-17.

Chapter 13 is intended in part as a source for larger programming exercises.

I recommend that all students taking a data structures course be required to implement some advanced tree structure, or another dynamic structure of comparable difﬁculty such as the skip list or sparse matrix representations of Chapter 12. None of these data structures are signiﬁcantly more difﬁcult to implement than the binary search tree, and any of them should be within a student’s ability after completing

Chapter 5.

While I have attempted to arrange the presentation in an order that makes sense, instructors should feel free to rearrange the topics as they see ﬁt. The book has been written so that once the reader has mastered Chapters 1-6, the remaining material has relatively few dependencies. Clearly, external sorting depends on understanding internal sorting and disk ﬁles. Section 6.2 on the UNION/FIND algorithm is used in Kruskal’s Minimum-Cost Spanning Tree algorithm. Section 9.2 on selforganizing lists mentions the buffer replacement schemes covered in Section 8.3.

Chapter 14 draws on examples from throughout the book. Section 17.2 relies on knowledge of graphs. Otherwise, most topics depend only on material presented earlier within the same chapter.

Most chapters end with a section entitled “Further Reading.” These sections are not comprehensive lists of references on the topics presented. Rather, I include books and articles that, in my opinion, may prove exceptionally informative or entertaining to the reader. In some cases I include references to works that should become familiar to any well-rounded computer scientist.

Use of Java: The programming examples are written in JavaTM . As with any programming language, Java has both advantages and disadvantages. Java is a

xvi

Preface

small language. There usually is only one way to do something, and this has the happy tendency of encouraging a programmer toward clarity when used correctly.

In this respect, it is superior to C or C++ . Java serves nicely for deﬁning and using most traditional data structures such as lists and trees. On the other hand,

Java is quite poor when used to do ﬁle processing, being both cumbersome and inefﬁcient. It is also a poor language when ﬁne control of memory is required. As an example, applications requiring memory management, such as those discussed in Section 12.3, are difﬁcult to write in Java. Since I wish to stick to a single language throughout the text, like any programmer I must take the bad along with the good. The most important issue is to get the ideas across, whether or not those ideas are natural to a particular language of discourse. Most programmers will use a variety of programming languages throughout their career, and the concepts described in this book should prove useful in a variety of circumstances.

I do not wish to discourage those unfamiliar with Java from reading this book.

I have attempted to make the examples as clear as possible while maintaining the advantages of Java. Java is used here strictly as a tool to illustrate data structures concepts. Fortunately, Java is an easy language for C or Pascal programmers to read with a minimal amount of study of the syntax related to object-oriented programming. In particular, I make use of Java’s support for hiding implementation details, including features such as classes, private class members, and interfaces. These features of the language support the crucial concept of separating logical design, as embodied in the abstract data type, from physical implementation as embodied in the data structure.

I make no attempt to teach Java within the text. An Appendix is provided that describes the Java syntax and concepts necessary to understand the program examples. I also provide the actual Java code used in the text through anonymous

FTP.

Inheritance, a key feature of object-oriented programming, is used only sparingly in the code examples. Inheritance is an important tool that helps programmers avoid duplication, and thus minimize bugs. From a pedagogical standpoint, however, inheritance often makes code examples harder to understand since it tends to spread the description for one logical unit among several classes. Thus, some of my class deﬁnitions for objects such as tree or list nodes do not take full advantage of possible inheritance from earlier code examples. This does not mean that a programmer should do likewise. Avoiding code duplication and minimizing errors are important goals. Treat the programming examples as illustrations of data structure principles, but do not copy them directly into your own programs.

Preface

xvii

My Java implementations serve to provide concrete illustrations of data structure principles. They are not meant to be a series of commercial-quality Java class implementations. The code examples provide less parameter checking than is sound programming practice for commercial programmers. Some parameter checking is included in the form of calls to methods in class Assert. These methods are modeled after the C standard library function assert. Method

Assert.notFalse takes a Boolean expression. If this expression evaluates to false, then the program terminates immediately. Method Assert.notNull takes a reference to class Object, and terminates the program if the value of the reference is null. (To be precise, these functions throw an IllegalArgumentException, which typically results in terminating the program unless the programmer takes action to handle the exception.) Terminating a program when a function receives a bad parameter is generally considered undesirable in real programs, but is quite adequate for understanding how a data structure is meant to operate. In real programming applications, Java’s exception handling features should be used to deal with input data errors.

I make a distinction in the text between “Java implementations” and “pseudocode.” Code labeled as a Java implementation has actually been compiled and tested on one or more Java compilers. Pseudocode examples often conform closely to Java syntax, but typically contain one or more lines of higher level description.

Pseudocode is used where I perceived a greater pedagogical advantage to a simpler, but less precise, description.

Exercises and Projects: Proper implementation and anaysis of data structures cannot be learned simply by reading a book. You must practice by implementing real programs, constantly comparing different techniques to see what really works best in a given situation.

One of the most important aspects of a course in data structures is that it is where students really learn to program using pointers and dynamic memory allocation, by implementing data structures such as linked lists and trees. Its also where students truly learn recursion. In our curriculum, this is the ﬁrst course where students do signiﬁcant design, because it often requires real data structures to motivate signiﬁcant design exercises. Finally, the fundamental differences between memory-based and disk-based data access cannot be appreciated without practical programming experience. For all of these reasons, a data structures course cannot succeed without a signiﬁcant programming component. In our department, the data structures course is arguably the most difﬁcult programming course in the curriculum.

xviii

Preface

Students should also work problems to develop their analytical abilities. I provide over 400 exercises and suggestions for programming projects. I urge readers to take advantage of them.

Contacting the Author and Supplementary Materials: A book such as this is sure to contain errors and have room for improvement. I welcome bug reports and constructive criticism. I can be reached by electronic mail via the Internet at shaffer@vt.edu. Alternatively, comments can be mailed to

Cliff Shaffer

Department of Computer Science

Virginia Tech

Blacksburg, VA 24061

A set of transparency masters for use in conjunction with this book can be obtained via the WWW at http://www.cs.vt.edu/∼shaffer/book.html.

The Java code examples are also available this site. Online Web pages for Virginia

Tech’s sophomore-level data structures class can be found at URL http://courses.cs.vt.edu/∼cs3114 A

This book was originally typeset by the author with L TEX. The bibliography was prepared using BIBTEX. The index was prepared using makeindex. The ﬁgures were mostly drawn with Xfig. Figures 3.1 and 9.8 were partially created using Mathematica.

Acknowledgments: It takes a lot of help from a lot of people to make a book.

I wish to acknowledge a few of those who helped to make this book possible. I apologize for the inevitable omissions.

Virginia Tech helped make this whole thing possible through sabbatical research leave during Fall 1994, enabling me to get the project off the ground. My department heads during the time I have written the various editions of this book, Dennis Kafura and Jack Carroll, provided unwavering moral support for this project.

Mike Keenan, Lenny Heath, and Jeff Shaffer provided valuable input on early versions of the chapters. I also wish to thank Lenny Heath for many years of stimulating discussions about algorithms and analysis (and how to teach both to students).

Steve Edwards deserves special thanks for spending so much time helping me on various redesigns of the C++ and Java code versions for the second and third editions, and many hours of discussion on the principles of program design. Thanks to Layne Watson for his help with Mathematica, and to Bo Begole, Philip Isenhour,

Jeff Nielsen, and Craig Struble for much technical assistance. Thanks to Bill McQuain, Mark Abrams and Dennis Kafura for answering lots of silly questions about

C++ and Java.

Preface

xix

I am truly indebted to the many reviewers of the various editions of this manuscript. For the ﬁrst edition these reviewers included J. David Bezek (University of

Evansville), Douglas Campbell (Brigham Young University), Karen Davis (University of Cincinnati), Vijay Kumar Garg (University of Texas – Austin), Jim Miller

(University of Kansas), Bruce Maxim (University of Michigan – Dearborn), Jeff

Parker (Agile Networks/Harvard), Dana Richards (George Mason University), Jack

Tan (University of Houston), and Lixin Tao (Concordia University). Without their help, this book would contain many more technical errors and many fewer insights.

For the second edition, I wish to thank these reviewers: Gurdip Singh (Kansas

State University), Peter Allen (Columbia University), Robin Hill (University of

Wyoming), Norman Jacobson (University of California – Irvine), Ben Keller (Eastern Michigan University), and Ken Bosworth (Idaho State University). In addition,

I wish to thank Neil Stewart and Frank J. Thesen for their comments and ideas for improvement. Third edition reviewers included Randall Lechlitner (University of Houstin,

Clear Lake) and Brian C. Hipp (York Technical College). I thank them for their comments. Without the hard work of many people at Prentice Hall, none of this would be possible. Authors simply do not create printer-ready books on their own. Foremost thanks go to Kate Hargett, Petra Rector, Laura Steele, and Alan Apt, my editors over the years. My production editors, Irwin Zucker for the second edition, Kathleen Caren for the original C++ version, and Ed DeFelippis for the Java version, kept everything moving smoothly during that horrible rush at the end. Thanks to

Bill Zobrist and Bruce Gregory (I think) for getting me into this in the ﬁrst place.

Others at Prentice Hall who helped me along the way include Truly Donovan, Linda

Behrens, and Phyllis Bregman. I am sure I owe thanks to many others at Prentice

Hall for their help in ways that I am not even aware of.

I wish to express my appreciation to Hanan Samet for teaching me about data structures. I learned much of the philosophy presented here from him as well, though he is not responsible for any problems with the result. Thanks to my wife

Terry, for her love and support, and to my daughters Irena and Kate for pleasant diversions from working too hard. Finally, and most importantly, to all of the data structures students over the years who have taught me what is important and what should be skipped in a data structures course, and the many new insights they have provided. This book is dedicated to them.

Clifford A. Shaffer

Blacksburg, Virginia

PART I

Preliminaries

1

1

Data Structures and Algorithms

How many cities with more than 250,000 people lie within 500 miles of Dallas,

Texas? How many people in my company make over $100,000 per year? Can we connect all of our telephone customers with less than 1,000 miles of cable? To answer questions like these, it is not enough to have the necessary information. We must organize that information in a way that allows us to ﬁnd the answers in time to satisfy our needs.

Representing information is fundamental to computer science. The primary purpose of most computer programs is not to perform calculations, but to store and retrieve information — usually as fast as possible. For this reason, the study of data structures and the algorithms that manipulate them is at the heart of computer science. And that is what this book is about — helping you to understand how to structure information to support efﬁcient processing.

This book has three primary goals. The ﬁrst is to present the commonly used data structures. These form a programmer’s basic data structure “toolkit.” For many problems, some data structure in the toolkit provides a good solution.

The second goal is to introduce the idea of tradeoffs and reinforce the concept that there are costs and beneﬁts associated with every data structure. This is done by describing, for each data structure, the amount of space and time required for typical operations.

The third goal is to teach how to measure the effectiveness of a data structure or algorithm. Only through such measurement can you determine which data structure in your toolkit is most appropriate for a new problem. The techniques presented also allow you to judge the merits of new data structures that you or others might invent. There are often many approaches to solving a problem. How do we choose between them? At the heart of computer program design are two (sometimes conﬂicting) goals:

3

4

Chap. 1 Data Structures and Algorithms

1. To design an algorithm that is easy to understand, code, and debug.

2. To design an algorithm that makes efﬁcient use of the computer’s resources.

Ideally, the resulting program is true to both of these goals. We might say that such a program is “elegant.” While the algorithms and program code examples presented here attempt to be elegant in this sense, it is not the purpose of this book to explicitly treat issues related to goal (1). These are primarily concerns of the discipline of Software Engineering. Rather, this book is mostly about issues relating to goal (2).

How do we measure efﬁciency? Chapter 3 describes a method for evaluating the efﬁciency of an algorithm or computer program, called asymptotic analysis.

Asymptotic analysis also allows you to measure the inherent difﬁculty of a problem.

The remaining chapters use asymptotic analysis techniques for every algorithm presented. This allows you to see how each algorithm compares to other algorithms for solving the same problem in terms of its efﬁciency.

This ﬁrst chapter sets the stage for what is to follow, by presenting some higherorder issues related to the selection and use of data structures. We ﬁrst examine the process by which a designer selects a data structure appropriate to the task at hand.

We then consider the role of abstraction in program design. We brieﬂy consider the concept of a design pattern and see some examples. The chapter ends with an exploration of the relationship between problems, algorithms, and programs.

1.1

A Philosophy of Data Structures

1.1.1

The Need for Data Structures

You might think that with ever more powerful computers, program efﬁciency is becoming less important. After all, processor speed and memory size still seem to double every couple of years. Won’t any efﬁciency problem we might have today be solved by tomorrow’s hardware?

As we develop more powerful computers, our history so far has always been to use additional computing power to tackle more complex problems, be it in the form of more sophisticated user interfaces, bigger problem sizes, or new problems previously deemed computationally infeasible. More complex problems demand more computation, making the need for efﬁcient programs even greater. Worse yet, as tasks become more complex, they become less like our everyday experience.

Today’s computer scientists must be trained to have a thorough understanding of the principles behind efﬁcient program design, because their ordinary life experiences often do not apply when designing computer programs.

Sec. 1.1 A Philosophy of Data Structures

5

In the most general sense, a data structure is any data representation and its associated operations. Even an integer or ﬂoating point number stored on the computer can be viewed as a simple data structure. More typically, a data structure is meant to be an organization or structuring for a collection of data items. A sorted list of integers stored in an array is an example of such a structuring.

Given sufﬁcient space to store a collection of data items, it is always possible to search for speciﬁed items within the collection, print or otherwise process the data items in any desired order, or modify the value of any particular data item. Thus, it is possible to perform all necessary operations on any data structure. However, using the proper data structure can make the difference between a program running in a few seconds and one requiring many days.

A solution is said to be efﬁcient if it solves the problem within the required resource constraints. Examples of resource constraints include the total space available to store the data — possibly divided into separate main memory and disk space constraints — and the time allowed to perform each subtask. A solution is sometimes said to be efﬁcient if it requires fewer resources than known alternatives, regardless of whether it meets any particular requirements. The cost of a solution is the amount of resources that the solution consumes. Most often, cost is measured in terms of one key resource such as time, with the implied assumption that the solution meets the other resource constraints.

It should go without saying that people write programs to solve problems. However, it is crucial to keep this truism in mind when selecting a data structure to solve a particular problem. Only by ﬁrst analyzing the problem to determine the performance goals that must be achieved can there be any hope of selecting the right data structure for the job. Poor program designers ignore this analysis step and apply a data structure that they are familiar with but which is inappropriate to the problem.

The result is typically a slow program. Conversely, there is no sense in adopting a complex representation to “improve” a program that can meet its performance goals when implemented using a simpler design.

When selecting a data structure to solve a problem, you should follow these steps. 1. Analyze your problem to determine the basic operations that must be supported. Examples of basic operations include inserting a data item into the data structure, deleting a data item from the data structure, and ﬁnding a speciﬁed data item.

2. Quantify the resource constraints for each operation.

3. Select the data structure that best meets these requirements.

6

Chap. 1 Data Structures and Algorithms

This three-step approach to selecting a data structure operationalizes a datacentered view of the design process. The ﬁrst concern is for the data and the operations to be performed on them, the next concern is the representation for those data, and the ﬁnal concern is the implementation of that representation.

Resource constraints on certain key operations, such as search, inserting data records, and deleting data records, normally drive the data structure selection process. Many issues relating to the relative importance of these operations are addressed by the following three questions, which you should ask yourself whenever you must choose a data structure:

• Are all data items inserted into the data structure at the beginning, or are insertions interspersed with other operations?

• Can data items be deleted?

• Are all data items processed in some well-deﬁned order, or is search for speciﬁc data items allowed?

Typically, interspersing insertions with other operations, allowing deletion, and supporting search for data items all require more complex representations.

1.1.2

Costs and Beneﬁts

Each data structure has associated costs and beneﬁts. In practice, it is hardly ever true that one data structure is better than another for use in all situations. If one data structure or algorithm is superior to another in all respects, the inferior one will usually have long been forgotten. For nearly every data structure and algorithm presented in this book, you will see examples of where it is the best choice. Some of the examples might surprise you.

A data structure requires a certain amount of space for each data item it stores, a certain amount of time to perform a single basic operation, and a certain amount of programming effort. Each problem has constraints on available space and time.

Each solution to a problem makes use of the basic operations in some relative proportion, and the data structure selection process must account for this. Only after a careful analysis of your problem’s characteristics can you determine the best data structure for the task.

Example 1.1 A bank must support many types of transactions with its customers, but we will examine a simple model where customers wish to open accounts, close accounts, and add money or withdraw money from accounts. We can consider this problem at two distinct levels: (1) the requirements for the physical infrastructure and workﬂow process that the

Sec. 1.1 A Philosophy of Data Structures

bank uses in its interactions with its customers, and (2) the requirements for the database system that manages the accounts.

The typical customer opens and closes accounts far less often than he or she accesses the account. Customers are willing to wait many minutes while accounts are created or deleted but are typically not willing to wait more than a brief time for individual account transactions such as a deposit or withdrawal. These observations can be considered as informal speciﬁcations for the time constraints on the problem.

It is common practice for banks to provide two tiers of service. Human tellers or automated teller machines (ATMs) support customer access to account balances and updates such as deposits and withdrawals. Special service representatives are typically provided (during restricted hours) to handle opening and closing accounts. Teller and ATM transactions are expected to take little time. Opening or closing an account can take much longer (perhaps up to an hour from the customer’s perspective).

From a database perspective, we see that ATM transactions do not modify the database signiﬁcantly. For simplicity, assume that if money is added or removed, this transaction simply changes the value stored in an account record. Adding a new account to the database is allowed to take several minutes. Deleting an account need have no time constraint, because from the customer’s point of view all that matters is that all the money be returned (equivalent to a withdrawal). From the bank’s point of view, the account record might be removed from the database system after business hours, or at the end of the monthly account cycle.

When considering the choice of data structure to use in the database system that manages customer accounts, we see that a data structure that has little concern for the cost of deletion, but is highly efﬁcient for search and moderately efﬁcient for insertion, should meet the resource constraints imposed by this problem. Records are accessible by unique account number

(sometimes called an exact-match query). One data structure that meets these requirements is the hash table described in Chapter 9.4. Hash tables allow for extremely fast exact-match search. A record can be modiﬁed quickly when the modiﬁcation does not affect its space requirements. Hash tables also support efﬁcient insertion of new records. While deletions can also be supported efﬁciently, too many deletions lead to some degradation in performance for the remaining operations. However, the hash table can be reorganized periodically to restore the system to peak efﬁciency. Such reorganization can occur ofﬂine so as not to affect ATM transactions.

7

8

Chap. 1 Data Structures and Algorithms

Example 1.2 A company is developing a database system containing information about cities and towns in the United States. There are many thousands of cities and towns, and the database program should allow users to ﬁnd information about a particular place by name (another example of an exact-match query). Users should also be able to ﬁnd all places that match a particular value or range of values for attributes such as location or population size. This is known as a range query.

A reasonable database system must answer queries quickly enough to satisfy the patience of a typical user. For an exact-match query, a few seconds is satisfactory. If the database is meant to support range queries that can return many cities that match the query speciﬁcation, the entire operation may be allowed to take longer, perhaps on the order of a minute. To meet this requirement, it will be necessary to support operations that process range queries efﬁciently by processing all cities in the range as a batch, rather than as a series of operations on individual cities.

The hash table suggested in the previous example is inappropriate for implementing our city database, because it cannot perform efﬁcient range queries. The B+ -tree of Section 10.5.1 supports large databases, insertion and deletion of data records, and range queries. However, a simple linear index as described in Section 10.1 would be more appropriate if the database is created once, and then never changed, such as an atlas distributed on a

CD-ROM.

1.2

Abstract Data Types and Data Structures

The previous section used the terms “data item” and “data structure” without properly deﬁning them. This section presents terminology and motivates the design process embodied in the three-step approach to selecting a data structure. This motivation stems from the need to manage the tremendous complexity of computer programs. A type is a collection of values. For example, the Boolean type consists of the values true and false. The integers also form a type. An integer is a simple type because its values contain no subparts. A bank account record will typically contain several pieces of information such as name, address, account number, and account balance. Such a record is an example of an aggregate type or composite type. A data item is a piece of information or a record whose value is drawn from a type. A data item is said to be a member of a type.

Sec. 1.2 Abstract Data Types and Data Structures

9

A data type is a type together with a collection of operations to manipulate the type. For example, an integer variable is a member of the integer data type.

Addition is an example of an operation on the integer data type.

A distinction should be made between the logical concept of a data type and its physical implementation in a computer program. For example, there are two traditional implementations for the list data type: the linked list and the array-based list. The list data type can therefore be implemented using a linked list or an array. Even the term “array” is ambiguous in that it can refer either to a data type or an implementation. “Array” is commonly used in computer programming to mean a contiguous block of memory locations, where each memory location stores one ﬁxed-length data item. By this meaning, an array is a physical data structure.

However, array can also mean a logical data type composed of a (typically homogeneous) collection of data items, with each data item identiﬁed by an index number. It is possible to implement arrays in many different ways. For example, Section 12.2 describes the data structure used to implement a sparse matrix, a large two-dimensional array that stores only a relatively few non-zero values. This implementation is quite different from the physical representation of an array as contiguous memory locations.

An abstract data type (ADT) is the realization of a data type as a software component. The interface of the ADT is deﬁned in terms of a type and a set of operations on that type. The behavior of each operation is determined by its inputs and outputs. An ADT does not specify how the data type is implemented. These implementation details are hidden from the user of the ADT and protected from outside access, a concept referred to as encapsulation.

A data structure is the implementation for an ADT. In an object-oriented language such as Java, an ADT and its implementation together make up a class.

Each operation associated with the ADT is implemented by a member function or method. The variables that deﬁne the space required by a data item are referred to as data members. An object is an instance of a class, that is, something that is created and takes up storage during the execution of a computer program.

The term “data structure” often refers to data stored in a computer’s main memory. The related term ﬁle structure often refers to the organization of data on peripheral storage, such as a disk drive or CD-ROM.

Example 1.3 The mathematical concept of an integer, along with operations that manipulate integers, form a data type. The Java int variable type is a physical representation of the abstract integer. The int variable type, along with the operations that act on an int variable, form an ADT. Un-

10

Chap. 1 Data Structures and Algorithms

fortunately, the int implementation is not completely true to the abstract integer, as there are limitations on the range of values an int variable can store. If these limitations prove unacceptable, then some other representation for the ADT “integer” must be devised, and a new implementation must be used for the associated operations.

Example 1.4 An ADT for a list of integers might specify the following operations: • Insert a new integer at a particular position in the list.

• Return true if the list is empty.

• Reinitialize the list.

• Return the number of integers currently in the list.

• Delete the integer at a particular position in the list.

From this description, the input and output of each operation should be clear, but the implementation for lists has not been speciﬁed.

One application that makes use of some ADT might use particular member functions of that ADT more than a second application, or the two applications might have different time requirements for the various operations. These differences in the requirements of applications are the reason why a given ADT might be supported by more than one implementation.

Example 1.5 Two popular implementations for large disk-based database applications are hashing (Section 9.4) and the B+ -tree (Section 10.5). Both support efﬁcient insertion and deletion of records, and both support exactmatch queries. However, hashing is more efﬁcient than the B+ -tree for exact-match queries. On the other hand, the B+ -tree can perform range queries efﬁciently, while hashing is hopelessly inefﬁcient for range queries.

Thus, if the database application limits searches to exact-match queries, hashing is preferred. On the other hand, if the application requires support for range queries, the B+ -tree is preferred. Despite these performance issues, both implementations solve versions of the same problem: updating and searching a large collection of records.

The concept of an ADT can help us to focus on key issues even in non-com-uting applications.

Sec. 1.2 Abstract Data Types and Data Structures

11

Example 1.6 When operating a car, the primary activities are steering, accelerating, and braking. On nearly all passenger cars, you steer by turning the steering wheel, accelerate by pushing the gas pedal, and brake by pushing the brake pedal. This design for cars can be viewed as an ADT with operations “steer,” “accelerate,” and “brake.” Two cars might implement these operations in radically different ways, say with different types of engine, or front- versus rear-wheel drive. Yet, most drivers can operate many different cars because the ADT presents a uniform method of operation that does not require the driver to understand the speciﬁcs of any particular engine or drive design. These differences are deliberately hidden.

The concept of an ADT is one instance of an important principle that must be understood by any successful computer scientist: managing complexity through abstraction. A central theme of computer science is complexity and techniques for handling it. Humans deal with complexity by assigning a label to an assembly of objects or concepts and then manipulating the label in place of the assembly.

Cognitive psychologists call such a label a metaphor. A particular label might be related to other pieces of information or other labels. This collection can in turn be given a label, forming a hierarchy of concepts and labels. This hierarchy of labels allows us to focus on important issues while ignoring unnecessary details.

Example 1.7 We apply the label “hard drive” to a collection of hardware that manipulates data on a particular type of storage device, and we apply the label “CPU” to the hardware that controls execution of computer instructions. These and other labels are gathered together under the label

“computer.” Because even small home computers have millions of components, some form of abstraction is necessary to comprehend how a computer operates.

Consider how you might go about the process of designing a complex computer program that implements and manipulates an ADT. The ADT is implemented in one part of the program by a particular data structure. While designing those parts of the program that use the ADT, you can think in terms of operations on the data type without concern for the data structure’s implementation. Without this ability to simplify your thinking about a complex program, you would have no hope of understanding or implementing it.

12

Chap. 1 Data Structures and Algorithms

Example 1.8 Consider the design for a relatively simple database system stored on disk. Typically, records on disk in such a program are accessed through a buffer pool (see Section 8.3) rather than directly. Variable length records might use a memory manager (see Section 12.3) to ﬁnd an appropriate location within the disk ﬁle to place the record. Multiple index structures (see Chapter 10) will typically be used to access records in various ways. Thus, we have a chain of classes, each with its own responsibilities and access privileges. A database query from a user is implemented by searching an index structure. This index requests access to the record by means of a request to the buffer pool. If a record is being inserted or deleted, such a request goes through the memory manager, which in turn interacts with the buffer pool to gain access to the disk ﬁle. A program such as this is far too complex for nearly any human programmer to keep all of the details in his or her head at once. The only way to design and implement such a program is through proper use of abstraction and metaphors.

In object-oriented programming, such abstraction is handled using classes.

Data types have both a logical and a physical form. The deﬁnition of the data type in terms of an ADT is its logical form. The implementation of the data type as a data structure is its physical form. Figure 1.1 illustrates this relationship between logical and physical forms for data types. When you implement an ADT, you are dealing with the physical form of the associated data type. When you use an

ADT elsewhere in your program, you are concerned with the associated data type’s logical form. Some sections of this book focus on physical implementations for a given data structure. Other sections use the logical ADT for the data type in the context of a higher-level task.

Example 1.9 A particular Java environment might provide a library that includes a list class. The logical form of the list is deﬁned by the public functions, their inputs, and their outputs that deﬁne the class. This might be all that you know about the list class implementation, and this should be all you need to know. Within the class, a variety of physical implementations for lists is possible. Several are described in Section 4.1.

1.3

Design Patterns

At a higher level of abstraction than ADTs are abstractions for describing the design of programs — that is, the interactions of objects and classes. Experienced software

13

Sec. 1.3 Design Patterns

Data Type

ADT:

• Type

• Operations

Data Items:

Logical Form

Data Structure:

• Storage Space Data Items:

Physical Form

• Subroutines

Figure 1.1 The relationship between data items, abstract data types, and data structures. The ADT deﬁnes the logical form of the data type. The data structure implements the physical form of the data type.

designers learn and reuse various techniques for combining software components.

Such techniques are sometimes referred to as design patterns.

A design pattern embodies and generalizes important design concepts for a recurring problem. A primary goal of design patterns is to quickly transfer the knowledge gained by expert designers to newer programmers. Another goal is to allow for efﬁcient communication between programmers. Its much easier to discuss a design issue when you share a vocabulary relevant to the topic.

Speciﬁc design patterns emerge from the discovery that a particular design problem appears repeatedly in many contexts. They are meant to solve real problems. Design patterns are a bit like generics: They describe the structure for a design solution, with the details ﬁlled in for any given problem. Design patterns are a bit like data structures: Each one provides costs and beneﬁts, which implies that tradeoffs are possible. Therefore, a given design pattern might have variations on its application to match the various tradeoffs inherent in a given situation.

The rest of this section introduces a few simple design patterns that are used later in the book.

1.3.1

Flyweight

The Flyweight design pattern is meant to solve the following problem. You have an application with many objects. Some of these objects are identical in the information that they contain, and the role that they play. But they must be reached from various places, and conceptually they really are distinct objects. Because so much information is shared, we would like to take advantage of the opportunity to reduce memory cost by sharing space. An example comes from representing the

14

Chap. 1 Data Structures and Algorithms

layout for a document. The letter “C” might reasonably be represented by an object that describes that character’s strokes and bounding box. However, we don’t want to create a separate “C” object everywhere in the document that a “C” appears.

The solution is to allocate a single copy of the shared representation for “C” object. Then, every place in the document that needs a “C” in a given font, size, and typeface will reference this single copy. The various instances of references to “C” are called ﬂyweights. A ﬂyweight includes the reference to the shared information, and might include additional information speciﬁc to that instance.

We could imagine describing the layout of text on a page by using a tree structure. The root of the tree is a node representing the page. The page has multiple child nodes, one for each column. The column nodes have child nodes for each row. And the rows have child nodes for each character. These representations for characters are the ﬂyweights. The ﬂyweight includes the reference to the shared shape information, and might contain additional information speciﬁc to that instance. For example, each instance for “C” will contain a reference to the shared information about strokes and shapes, and it might also contain the exact location for that instance of the character on the page.

Flyweights are used in the implementation for the PR quadtree data structure for storing collections of point objects, described in Section 13.3. In a PR quadtree, we again have a tree with leaf nodes. Many of these leaf nodes (the ones that represent empty areas) contain the same information. These identical nodes can be implemented using the Flyweight design pattern for better memory efﬁciency.

1.3.2

Visitor

Given a tree of objects to describe a page layout, we might wish to perform some activity on every node in the tree. Section 5.2 discusses tree traversal, which is the process of visiting every node in the tree in a deﬁned order. A simple example for our text composition application might be to count the number of nodes in the tree that represents the page. At another time, we might wish to print a listing of all the nodes for debugging purposes.

We could write a separate traversal function for each such activity that we intend to perform on the tree. A better approach would be to write a generic traversal function, and pass in the activity to be performed at each node. This organization constitutes the visitor design pattern. The visitor design pattern is used in Sections 5.2 (tree traversal) and 11.3 (graph traversal).

Sec. 1.3 Design Patterns

1.3.3

15

Composite

There are two fundamental approaches to dealing with the relationship between a collection of actions and a hierarchy of object types. First consider the typical procedural approach. Say we have a base class for page layout entities, with a subclass hierarchy to deﬁne speciﬁc subtypes (page, columns, rows, ﬁgures, characters, etc.). And say there are actions to be performed on a collection of such objects (such as rendering the objects to the screen). The procedural design approach is for each action to be implemented as a method that takes as a parameter a pointer to the base class type. Each such method will traverse through the collection of objects, visiting each object in turn. Each method contains something like a case statement that deﬁnes the details of the action for each subclass in the collection (e.g., page, column, row, character). We can cut the code down some by using the visitor design pattern so that we only need to write the traversal once, and then write a visitor subroutine for each action that might be applied to the collection of objects. But each such visitor subroutine must still contain logic for dealing with each of the possible subclasses.

In our page composition application, there are only a few activities that we would like to perform on the page representation. We might render the objects in full detail. Or we might want a “rough draft” rendering that prints only the bounding boxes of the objects. If we come up with a new activity to apply to the collection of objects, we do not need to change any of the code that implements the existing activities. But adding new activities won’t happen often for this application. In contrast, there could be many object types, and we might frequently add new object types to our implementation. Unfortunately, adding a new object type requires that we modify each activity, and the subroutines implementing the activities get rather long case statements to distinguish the behavior of the many subclasses.

An alternative design is to have each object subclass in the hierarchy embody the action for each of the various activities that might be performed. Each subclass will have code to perform each activity (such as full rendering or bounding box rendering). Then, if we wish to apply the activity to the collection, we simply call the ﬁrst object in the collection and specify the action (as a method call on that object). In the case of our page layout and its hierarchical collection of objects, those objects that contain other objects (such as a row objects that contains letters) will call the appropriate method for each child. If we want to add a new activity with this organization, we have to change the code for every subclass. But this is relatively rare for our text compositing application. In contrast, adding a new object into the subclass hierarchy (which for this application is far more likely than adding a new rendering function) is easy. Adding a new subclass does not require changing

16

Chap. 1 Data Structures and Algorithms

any of the existing subclasses. It merely requires that we deﬁne the behavior of each activity that can be performed on that subclass.

This second design approach of burying the functional activity in the subclasses is called the Composite design pattern. A detailed example for using the Composite design pattern is presented in Section 5.3.1.

1.3.4

Strategy

Our ﬁnal example of a design pattern lets us encapsulate and make interchangeable a set of alternative actions that might be performed as part of some larger activity.

Again continuing our text compositing example, each output device that we wish to render to will require its own function for doing the actual rendering. That is, the objects will be broken down into constituent pixels or strokes, but the actual mechanics of rendering a pixel or stroke will depend on the output device. We don’t want to build this rendering functionality into the object subclasses. Instead, we want to pass to the subroutine performing the rendering action a method or class that does the appropriate rendering details for that output device. That is, we wish to hand to the object the appropriate “strategy” for accomplishing the details of the rendering task. Thus, we call this approach the Strategy design pattern.

The Strategy design pattern will be discussed further in Chapter 7. There, a sorting function is given a class (called a comparator) that understands how to extract and compare the key values for records to be sorted. In this way, the sorting function does not need to know any details of how its record type is implemented.

One of the biggest challenges to understanding design patterns is that many of them appear to be pretty much the same. For example, you might be confused about the difference between the composite pattern and the visitor pattern. The distinction is that the composite design pattern is about whether to give control of the traversal process to the nodes of the tree or to the tree itself. Both approaches can make use of the visitor design pattern to avoid rewriting the traversal function many times, by encapsulating the activity performed at each node.

But isn’t the strategy design pattern doing the same thing? The difference between the visitor pattern and the strategy pattern is more subtle. Here the difference is primarily one of intent and focus. In both the strategy design pattern and the visitor design pattern, an activity is being passed in as a parameter. The strategy design pattern is focused on encapsulating an activity that is part of a larger process, so that different ways of performing that activity can be substituted. The visitor design pattern is focused on encapsulating an activity that will be performed on all members of a collection so that completely different activities can be substituted within a generic method that accesses all of the collection members.

Sec. 1.4 Problems, Algorithms, and Programs

1.4

17

Problems, Algorithms, and Programs

Programmers commonly deal with problems, algorithms, and computer programs.

These are three distinct concepts.

Problems: As your intuition would suggest, a problem is a task to be performed.

It is best thought of in terms of inputs and matching outputs. A problem deﬁnition should not include any constraints on how the problem is to be solved. The solution method should be developed only after the problem is precisely deﬁned and thoroughly understood. However, a problem deﬁnition should include constraints on the resources that may be consumed by any acceptable solution. For any problem to be solved by a computer, there are always such constraints, whether stated or implied. For example, any computer program may use only the main memory and disk space available, and it must run in a “reasonable” amount of time.

Problems can be viewed as functions in the mathematical sense. A function is a matching between inputs (the domain) and outputs (the range). An input to a function might be a single value or a collection of information. The values making up an input are called the parameters of the function. A speciﬁc selection of values for the parameters is called an instance of the problem. For example, the input parameter to a sorting function might be an array of integers. A particular array of integers, with a given size and speciﬁc values for each position in the array, would be an instance of the sorting problem. Different instances might generate the same output. However, any problem instance must always result in the same output every time the function is computed using that particular input.

This concept of all problems behaving like mathematical functions might not match your intuition for the behavior of computer programs. You might know of programs to which you can give the same input value on two separate occasions, and two different outputs will result. For example, if you type “date” to a typical

UNIX command line prompt, you will get the current date. Naturally the date will be different on different days, even though the same command is given. However, there is obviously more to the input for the date program than the command that you type to run the program. The date program computes a function. In other words, on any particular day there can only be a single answer returned by a properly running date program on a completely speciﬁed input. For all computer programs, the output is completely determined by the program’s full set of inputs. Even a

“random number generator” is completely determined by its inputs (although some random number generating systems appear to get around this by accepting a random input from a physical process beyond the user’s control). The relationship between programs and functions is explored further in Section 17.3.

18

Chap. 1 Data Structures and Algorithms

Algorithms: An algorithm is a method or a process followed to solve a problem.

If the problem is viewed as a function, then an algorithm is an implementation for the function that transforms an input to the corresponding output. A problem can be solved by many different algorithms. A given algorithm solves only one problem

(i.e., computes a particular function). This book covers many problems, and for several of these problems I present more than one algorithm. For the important problem of sorting I present nearly a dozen algorithms!

The advantage of knowing several solutions to a problem is that solution A might be more efﬁcient than solution B for a speciﬁc variation of the problem, or for a speciﬁc class of inputs to the problem, while solution B might be more efﬁcient than A for another variation or class of inputs. For example, one sorting algorithm might be the best for sorting a small collection of integers, another might be the best for sorting a large collection of integers, and a third might be the best for sorting a collection of variable-length strings.

By deﬁnition, an algorithm possesses several properties. Something can only be called an algorithm to solve a particular problem if it has all of the following properties. 1. It must be correct. In other words, it must compute the desired function, converting each input to the correct output. Note that every algorithm implements some function Because every algorithm maps every input to some output (even if that output is a system crash). At issue here is whether a given algorithm implements the intended function.

2. It is composed of a series of concrete steps. Concrete means that the action described by that step is completely understood — and doable — by the person or machine that must perform the algorithm. Each step must also be doable in a ﬁnite amount of time. Thus, the algorithm gives us a “recipe” for solving the problem by performing a series of steps, where each such step is within our capacity to perform. The ability to perform a step can depend on who or what is intended to execute the recipe. For example, the steps of a cookie recipe in a cookbook might be considered sufﬁciently concrete for instructing a human cook, but not for programming an automated cookiemaking factory.

3. There can be no ambiguity as to which step will be performed next. Often it is the next step of the algorithm description. Selection (e.g., the if statements in Java) is normally a part of any language for describing algorithms. Selection allows a choice for which step will be performed next, but the selection process is unambiguous at the time when the choice is made.

Sec. 1.5 Further Reading

19

4. It must be composed of a ﬁnite number of steps. If the description for the algorithm were made up of an inﬁnite number of steps, we could never hope to write it down, nor implement it as a computer program. Most languages for describing algorithms (including English and “pseudocode”) provide some way to perform repeated actions, known as iteration. Examples of iteration in programming languages include the while and for loop constructs of

Java. Iteration allows for short descriptions, with the number of steps actually performed controlled by the input.

5. It must terminate. In other words, it may not go into an inﬁnite loop.

Programs: We often think of a computer program as an instance, or concrete representation, of an algorithm in some programming language. In this book, nearly all of the algorithms are presented in terms of programs, or parts of programs. Naturally, there are many programs that are instances of the same algorithm, because any modern computer programming language can be used to implement the same collection of algorithms (although some programming languages can make life easier for the programmer). To simplify presentation throughout the remainder of the text, I often use the terms “algorithm” and “program” interchangeably, despite the fact that they are really separate concepts. By deﬁnition, an algorithm must provide sufﬁcient detail that it can be converted into a program when needed.

The requirement that an algorithm must terminate means that not all computer programs meet the technical deﬁnition of an algorithm. Your operating system is one such program. However, you can think of the various tasks for an operating system (each with associated inputs and outputs) as individual problems, each solved by speciﬁc algorithms implemented by a part of the operating system program, and each one of which terminates once its output is produced.

To summarize: A problem is a function or a mapping of inputs to outputs.

An algorithm is a recipe for solving a problem whose steps are concrete and unambiguous. The algorithm must be correct, of ﬁnite length, and must terminate for all inputs. A program is an instantiation of an algorithm in a computer programming language.

1.5

Further Reading

The ﬁrst authoritative work on data structures and algorithms was the series of books The Art of Computer Programming by Donald E. Knuth, with Volumes 1 and 3 being most relevant to the study of data structures [Knu97, Knu98]. A modern encyclopedic approach to data structures and algorithms that should be easy

20

Chap. 1 Data Structures and Algorithms

to understand once you have mastered this book is Algorithms by Robert Sedgewick [Sed03]. For an excellent and highly readable (but more advanced) teaching introduction to algorithms, their design, and their analysis, see Introduction to Algorithms: A Creative Approach by Udi Manber [Man89]. For an advanced, encyclopedic approach, see Introduction to Algorithms by Cormen, Leiserson, and

Rivest [CLRS01]. Steven S. Skiena’s The Algorithm Design Manual [Ski98] provides pointers to many implementations for data structures and algorithms that are available on the Web.

For a gentle introduction to ADTs and program speciﬁcation, see Abstract Data

Types: Their Speciﬁcation, Representation, and Use by Thomas, Robinson, and

Emms [TRE88].

The claim that all modern programming languages can implement the same algorithms (stated more precisely, any function that is computable by one programming language is computable by any programming language with certain standard capabilities) is a key result from computability theory. For an easy introduction to this ﬁeld see James L. Hein, Discrete Structures, Logic, and Computability [Hei03].

Much of computer science is devoted to problem solving. Indeed, this is what attracts many people to the ﬁeld. How to Solve It by George P´ lya [P´ l57] is cono o sidered to be the classic work on how to improve your problem-solving abilities. If you want to be a better student (as well as a better problem solver in general), see

Strategies for Creative Problem Solving by Folger and LeBlanc [FL95], Effective

Problem Solving by Marvin Levine [Lev94], and Problem Solving & Comprehension by Arthur Whimbey and Jack Lochhead [WL99].

See The Origin of Consciousness in the Breakdown of the Bicameral Mind by

Julian Jaynes [Jay90] for a good discussion on how humans use the concept of metaphor to handle complexity. More directly related to computer science education and programming, see “Cogito, Ergo Sum! Cognitive Processes of Students

Dealing with Data Structures” by Dan Aharoni [Aha00] for a discussion on moving from programming-context thinking to higher-level (and more design-oriented) programming-free thinking.

On a more pragmatic level, most people study data structures to write better programs. If you expect your program to work correctly and efﬁciently, it must ﬁrst be understandable to yourself and your co-workers. Kernighan and Pike’s The

Practice of Programming [KP99] discusses a number of practical issues related to programming, including good coding and documentation style. For an excellent

(and entertaining!) introduction to the difﬁculties involved with writing large programs, read the classic The Mythical Man-Month: Essays on Software Engineering by Frederick P. Brooks [Bro95].

Sec. 1.6 Exercises

21

If you want to be a successful Java programmer, you need good reference manuals close at hand. David Flanagan’s Java in a Nutshell [Fla05] provides a good reference for those familiar with the basics of the language.

After gaining proﬁciency in the mechanics of program writing, the next step is to become proﬁcient in program design. Good design is difﬁcult to learn in any discipline, and good design for object-oriented software is one of the most difﬁcult of arts. The novice designer can jump-start the learning process by studying wellknown and well-used design patterns. The classic reference on design patterns is Design Patterns: Elements of Reusable Object-Oriented Software by Gamma,

Helm, Johnson, and Vlissides [GHJV95] (this is commonly referred to as the “gang of four” book). Unfortunately, this is an extremely difﬁcult book to understand, in part because the concepts are inherently difﬁcult. A number of Web sites are available that discuss design patterns, and which provide study guides for the Design Patterns book. Two other books that discuss object-oriented software design are Object-Oriented Software Design and Construction with C ++ by Dennis Kafura [Kaf98], and Object-Oriented Design Heuristics by Arthur J. Riel [Rie96].

1.6

Exercises

The exercises for this chapter are different from those in the rest of the book. Most of these exercises are answered in the following chapters. However, you should not look up the answers in other parts of the book. These exercises are intended to make you think about some of the issues to be covered later on. Answer them to the best of your ability with your current knowledge.

1.1 Think of a program you have used that is unacceptably slow. Identify the speciﬁc operations that make the program slow. Identify other basic operations that the program performs quickly enough.

1.2 Most programming languages have a built-in integer data type. Normally this representation has a ﬁxed size, thus placing a limit on how large a value can be stored in an integer variable. Describe a representation for integers that has no size restriction (other than the limits of the computer’s available main memory), and thus no practical limit on how large an integer can be stored. Brieﬂy show how your representation can be used to implement the operations of addition, multiplication, and exponentiation.

1.3 Deﬁne an ADT for character strings. Your ADT should consist of typical functions that can be performed on strings, with each function deﬁned in

22

1.4

1.5

1.6

1.7

1.8

1.9

1.10

1.11

Chap. 1 Data Structures and Algorithms

terms of its input and output. Then deﬁne two different physical representations for strings.

Deﬁne an ADT for a list of integers. First, decide what functionality your

ADT should provide. Example 1.4 should give you some ideas. Then, specify your ADT in Java in the form of an abstract class declaration, showing the functions, their parameters, and their return types.

Brieﬂy describe how integer variables are typically represented on a computer. (Look up one’s complement and two’s complement arithmetic in an introductory computer science textbook if you are not familiar with these.)

Why does this representation for integers qualify as a data structure as deﬁned in Section 1.2?

Deﬁne an ADT for a two-dimensional array of integers. Specify precisely the basic operations that can be performed on such arrays. Next, imagine an application that stores an array with 1000 rows and 1000 columns, where less than 10,000 of the array values are non-zero. Describe two different implementations for such arrays that would be more space efﬁcient than a standard two-dimensional array implementation requiring one million positions.

You have been assigned to implement a sorting program. The goal is to make this program general purpose, in that you don’t want to deﬁne in advance what record or key types are used. Describe ways to generalize a simple sorting algorithm (such as insertion sort, or any other sort you are familiar with) to support this generalization.

You have been assigned to implement a simple seqential search on an array.

The problem is that you want the search to be as general as possible. This means that you need to support arbitrary record and key types. Describe ways to generalize the search function to support this goal. Consider the possibility that the function will be used multiple times in the same program, on differing record types. Consider the possibility that the function will need to be used on different keys (possibly with the same or different types) of the same record. For example, a student data record might be searched by zip code, by name, by salary, or by GPA.

Does every problem have an algorithm?

Does every algorithm have a Java program?

Consider the design for a spelling checker program meant to run on a home computer. The spelling checker should be able to handle quickly a document of less than twenty pages. Assume that the spelling checker comes with a dictionary of about 20,000 words. What primitive operations must be implemented on the dictionary, and what is a reasonable time constraint for each operation? Sec. 1.6 Exercises

23

1.12 Imagine that you have been hired to design a database service containing information about cities and towns in the United States, as described in Example 1.2. Suggest two possible implementations for the database.

1.13 Imagine that you are given an array of records that is sorted with respect to some key ﬁeld contained in each record. Give two different algorithms for searching the array to ﬁnd the record with a speciﬁed key value. Which one do you consider “better” and why?

1.14 How would you go about comparing two proposed algorithms for sorting an array of integers? In particular,

(a) What would be appropriate measures of cost to use as a basis for comparing the two sorting algorithms?

(b) What tests or analysis would you conduct to determine how the two algorithms perform under these cost measures?

1.15 A common problem for compilers and text editors is to determine if the parentheses (or other brackets) in a string are balanced and properly nested.

For example, the string “((())())()” contains properly nested pairs of parentheses, but the string “)()(” does not; and the string “())” does not contain properly matching parentheses.

(a) Give an algorithm that returns true if a string contains properly nested and balanced parentheses, and false if otherwise. Hint: At no time while scanning a legal string from left to right will you have encountered more right parentheses than left parentheses.

(b) Give an algorithm that returns the position in the string of the ﬁrst offending parenthesis if the string is not properly nested and balanced.

That is, if an excess right parenthesis is found, return its position; if there are too many left parentheses, return the position of the ﬁrst excess left parenthesis. Return −1 if the string is properly balanced and nested. 1.16 A graph consists of a set of objects (called vertices) and a set of edges, where each edge connects two vertices. Any given pair of vertices can be connected by only one edge. Describe at least two different ways to represent the connections deﬁned by the vertices and edges of a graph.

1.17 Imagine that you are a shipping clerk for a large company. You have just been handed about 1000 invoices, each of which is a single sheet of paper with a large number in the upper right corner. The invoices must be sorted by this number, in order from lowest to highest. Write down as many different approaches to sorting the invoices as you can think of.

24

Chap. 1 Data Structures and Algorithms

1.18 Imagine that you are a programmer who must write a function to sort an array of about 1000 integers from lowest value to highest value. Write down at least ﬁve approaches to sorting the array. Do not write algorithms in Java or pseudocode. Just write a sentence or two for each approach to describe how it would work.

1.19 Think of an algorithm to ﬁnd the maximum value in an (unsorted) array.

Now, think of an algorithm to ﬁnd the second largest value in the array.

Which is harder to implement? Which takes more time to run (as measured by the number of comparisons performed)? Now, think of an algorithm to ﬁnd the third largest value. Finally, think of an algorithm to ﬁnd the middle value. Which is the most difﬁcult of these problems to solve?

1.20 An unsorted list of integers allows for constant-time insert simply by adding a new integer at the end of the list. Unfortunately, searching for the integer with key value X requires a sequential search through the unsorted list until you ﬁnd X, which on average requires looking at half the list. On the other hand, a sorted array-based list of n integers can be searched in log n time by using a binary search. Unfortunately, inserting a new integer requires a lot of time because many integers might be shifted in the array if we want to keep it sorted. How might data be organized to support both insertion and search in log n time?

2

Mathematical Preliminaries

This chapter presents mathematical notation, background, and techniques used throughout the book. This material is provided primarily for review and reference.

You might wish to return to the relevant sections when you encounter unfamiliar notation or mathematical techniques in later chapters.

Section 2.7 on estimating might be unfamiliar to many readers. Estimating is not a mathematical technique, but rather a general engineering skill. It is enormously useful to computer scientists doing design work, because any proposed solution whose estimated resource requirements fall well outside the problem’s resource constraints can be discarded immediately.

2.1

Sets and Relations

The concept of a set in the mathematical sense has wide application in computer science. The notations and techniques of set theory are commonly used when describing and implementing algorithms because the abstractions associated with sets often help to clarify and simplify algorithm design.

A set is a collection of distinguishable members or elements. The members are typically drawn from some larger population known as the base type. Each member of a set is either a primitive element of the base type or is a set itself.

There is no concept of duplication in a set. Each value from the base type is either in the set or not in the set. For example, a set named P might be the three integers 7,

11, and 42. In this case, P’s members are 7, 11, and 42, and the base type is integer.

Figure 2.1 shows the symbols commonly used to express sets and their relationships. Here are some examples of this notation in use. First deﬁne two sets, P and Q.

P = {2, 3, 5},

Q = {5, 10}.

25

26

Chap. 2 Mathematical Preliminaries

{1, 4}

{x | x is a positive integer} x∈P x∈P

/

∅

|P|

P ⊆ Q, Q ⊇ P

P∪Q

P∩Q

P−Q

A set composed of the members 1 and 4

A set deﬁnition using a set former

Example: the set of all positive integers x is a member of set P x is not a member of set P

The null or empty set

Cardinality: size of set P or number of members for set P

Set P is included in set Q, set P is a subset of set Q, set Q is a superset of set P

Set Union: all elements appearing in P OR Q

Set Intersection: all elements appearing in P AND Q

Set difference: all elements of set P NOT in set Q

Figure 2.1 Set notation.

|P| = 3 (because P has three members) and |Q| = 2 (because Q has two members).

The union of P and Q, written P ∪ Q, is the set of elements in either P or Q, which is {2, 3, 5, 10}. The intersection of P and Q, written P ∩ Q, is the set of elements that appear in both P and Q, which is {5}. The set difference of P and Q, written

P − Q, is the set of elements that occur in P but not in Q, which is {2, 3}. Note that P ∪ Q = Q ∪ P and that P ∩ Q = Q ∩ P, but in general P − Q = Q − P.

In this example, Q − P = {10}. Note that the set {4, 3, 5} is indistinguishable from set P, because sets have no concept of order. Likewise, set {4, 3, 4, 5} is also indistinguishable from P, because sets have no concept of duplicate elements.

The powerset of a set S is the set of all possible subsets for S. Consider the set

S = {a, b, c}. The powerset of S is

{∅, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}.

Sometimes we wish to deﬁne a collection of elements with no order (like a set), but with duplicate-valued elements. Such a collection is called a bag.1 To distinguish bags from sets, I use square brackets [] around a bag’s elements. For

1

The object referred to here as a bag is sometimes called a multilist. But, I reserve the term multilist for a list that may contain sublists (see Section 12.1).

27

Sec. 2.1 Sets and Relations

example, bag [3, 4, 5, 4] is distinct from bag [3, 4, 5], while set {3, 4, 5, 4} is indistinguishable from set {3, 4, 5}. However, bag [3, 4, 5, 4] is indistinguishable from bag [3, 4, 4, 5].

A sequence is a collection of elements with an order, and which may contain duplicate-valued elements. A sequence is also sometimes called a tuple or a vector. In a sequence, there is a 0th element, a 1st element, 2nd element, and so on. I indicate a sequence by using angle brackets to enclose its elements. For example, 3, 4, 5, 4 is a sequence. Note that sequence 3, 5, 4, 4 is distinct from sequence 3, 4, 5, 4 , and both are distinct from sequence 3, 4, 5 .

A relation R over set S is a set of ordered pairs from S. As an example of a relation, if S is {a, b, c}, then

{ a, c , b, c , c, b } is a relation, and

{ a, a , a, c , b, b , b, c , c, c } is a different relation. If tuple x, y is in relation R, we may use the inﬁx notation xRy. We often use relations such as the less than operator ( 0; i--) // for each i swap(A, i-1, DSutil.random(i)); // swap A[i-1] with

}

// a random element

Boolean variables: A Boolean variable is a variable (of type boolean in Java) that takes on one of the two values true and false. These two values are often associated with the values 1 and 0, respectively, although there is no reason why this needs to be the case. It is poor programming practice to rely on the correspondence between 0 and false, because these are logically distinct objects of different types.

Floor and ceiling: The ﬂoor of x (written x ) takes real value x and returns the greatest integer ≤ x. For example, 3.4 = 3, as does 3.0 , while −3.4 = −4 and −3.0 = −3. The ceiling of x (written x ) takes real value x and returns the least integer ≥ x. For example, 3.4 = 4, as does 4.0 , while −3.4 =

−3.0 = −3.

Modulus operator: The modulus (or mod) function returns the remainder of an integer division. Sometimes written n mod m in mathematical expressions, the syntax for the Java modulus operator is n % m. From the deﬁnition of remainder, n mod m is the integer r such that n = qm + r for q an integer, and |r| < |m|.

Therefore, the result of n mod m must be between 0 and m − 1 when n and m are

Sec. 2.3 Logarithms

31

positive integers. For example, 5 mod 3 = 2; 25 mod 3 = 1, 5 mod 7 = 5, and

5 mod 5 = 0.

Unfortunately, there is more than one way to assign values to q and r, depending on how integer division is interpreted. The most common mathematical deﬁnition computes the mod function as n mod m = n − m n/m . In this case,

−3 mod 5 = 2. However, Java and C++ compilers typically use the underlying processor’s machine instruction for computing integer arithmetic. On many computers this is done by truncating the resulting fraction, meaning n mod m = n − m(trunc(n/m)). Under this deﬁnition, −3 mod 5 = −3.

Unfortunately, for many applications this is not what the user wants or expects.

For example, many hash systems will perform some computation on a record’s key value and then take the result modulo the hash table size. The expectation here would be that the result is a legal index into the hash table, not a negative number.

Implementors of hash functions must either insure that the result of the computation is always postive, or else add the hash table size to the result of the modulo function when that result is negative.

2.3

Logarithms

A logarithm of base b for value y is the power to which b is raised to get y. Normally, this is written as logb y = x. Thus, if logb y = x then bx = y, and blogb y = y.

Logarithms are used frequently by programmers. Here are two typical uses.

Example 2.6 Many programs require an encoding for a collection of objects. What is the minimum number of bits needed to represent n distinct code values? The answer is log2 n bits. For example, if you have 1000 codes to store, you will require at least log2 1000 = 10 bits to have 1000 different codes (10 bits provide 1024 distinct code values).

Example 2.7 Consider the binary search algorithm for ﬁnding a given value within an array sorted by value from lowest to highest. Binary search ﬁrst looks at the middle element and determines if the value being searched for is in the upper half or the lower half of the array. The algorithm then continues splitting the appropriate subarray in half until the desired value is found. (Binary search is described in more detail in Section 3.5.) How many times can an array of size n be split in half until only one element remains in the ﬁnal subarray? The answer is log2 n times.

32

Chap. 2 Mathematical Preliminaries

In this book, nearly all logarithms used have a base of two. This is because data structures and algorithms most often divide things in half, or store codes with binary bits. Whenever you see the notation log n in this book, either log2 n is meant or else the term is being used asymptotically and the actual base does not matter. If any base for the logarithm other than two is intended, then the base will be shown explicitly. Logarithms have the following properties, for any positive values of m, n, and r, and any positive integers a and b.

1. log(nm) = log n + log m.

2. log(n/m) = log n − log m.

3. log(nr ) = r log n.

4. loga n = logb n/ logb a.

The ﬁrst two properties state that the logarithm of two numbers multiplied (or divided) can be found by adding (or subtracting) the logarithms of the two numbers.4 Property (3) is simply an extension of property (1). Property (4) tells us that, for variable n and any two integer constants a and b, loga n and logb n differ by the constant factor logb a, regardless of the value of n. Most runtime analyses in this book are of a type that ignores constant factors in costs. Property (4) says that such analyses need not be concerned with the base of the logarithm, because this can change the total cost only by a constant factor. Note that 2log n = n.

When discussing logarithms, exponents often lead to confusion. Property (3) tells us that log n2 = 2 log n. How do we indicate the square of the logarithm

(as opposed to the logarithm of n2 )? This could be written as (log n)2 , but it is traditional to use log2 n. On the other hand, we might want to take the logarithm of the logarithm of n. This is written log log n.

A special notation is used in the rare case where we would like to know how many times we must take the log of a number before we reach a value ≤ 1. This quantity is written log∗ n. For example, log∗ 1024 = 4 because log 1024 = 10, log 10 ≈ 3.33, log 3.33 ≈ 1.74, and log 1.74 < 1, which is a total of 4 log operations.

4

These properties are the idea behind the slide rule. Adding two numbers can be viewed as joining two lengths together and measuring their combined length. Multiplication is not so easily done.

However, if the numbers are ﬁrst converted to the lengths of their logarithms, then those lengths can be added and the inverse logarithm of the resulting length gives the answer for the multiplication (this is simply logarithm property (1)). A slide rule measures the length of the logarithm for the numbers, lets you slide bars representing these lengths to add up the total length, and ﬁnally converts this total length to the correct numeric answer by taking the inverse of the logarithm for the result.

33

Sec. 2.4 Summations and Recurrences

2.4

Summations and Recurrences

Most programs contain loop constructs. When analyzing running time costs for programs with loops, we need to add up the costs for each time the loop is executed.

This is an example of a summation. Summations are simply the sum of costs for some function applied to a range of parameter values. Summations are typically written with the following “Sigma” notation: n f (i). i=1 This notation indicates that we are summing the value of f (i) over some range of

(integer) values. The parameter to the expression and its initial value are indicated below the symbol. Here, the notation i = 1 indicates that the parameter is i and that it begins with the value 1. At the top of the symbol is the expression n. This indicates the maximum value for the parameter i. Thus, this notation means to sum the values of f (i) as i ranges from 1 through n. This can also be written f (1) + f (2) + · · · + f (n − 1) + f (n).

Within a sentence, Sigma notation is typeset as n f (i). i=1 Given a summation, you often wish to replace it with a direct equation with the same value as the summation. This is known as a closed-form solution, and the process of replacing the summation with its closed-form solution is known as solving the summation. For example, the summation n 1 is simply the expression i=1 “1” summed n times (remember that i ranges from 1 to n). Because the sum of n

1s is n, the closed-form solution is n. The following is a list of useful summations, along with their closed-form solutions. n i = i=1 n

i2 = i=1 n(n + 1)

.

2

(2.1)

2n3 + 3n2 + n n(2n + 1)(n + 1)

=

.

6

6

(2.2)

log n

n = n log n. i=1 ∞

ai = i=0 1 for 0 < a < 1.

1−a

(2.3)

(2.4)

34

Chap. 2 Mathematical Preliminaries n ai = i=0 an+1 − 1 for a = 1. a−1 (2.5)

As special cases to Equation 2.5, n 1

1

= 1 − n, i 2

2

(2.6)

i=1

and

n

2i = 2n+1 − 1.

(2.7)

i=0

As a corollary to Equation 2.7, log n

2i = 2log n+1 − 1 = 2n − 1.

(2.8)

i

2i

(2.9)

i=0

Finally, n i=1

= 2−

n+2

.

2n

The sum of reciprocals from 1 to n, called the Harmonic Series and written

Hn , has a value between loge n and loge n + 1. To be more precise, as n grows, the summation grows closer to

1

,

(2.10)

2n where γ is Euler’s constant and has the value 0.5772...

Most of these equalities can be proved easily by mathematical induction (see

Section 2.6.3). Unfortunately, induction does not help us derive a closed-form solution. It only conﬁrms when a proposed closed-form solution is correct. Techniques for deriving closed-form solutions are discussed in Section 14.1.

The running time for a recursive algorithm is most easily expressed by a recursive expression because the total time for the recursive algorithm includes the time to run the recursive call(s). A recurrence relation deﬁnes a function by means of an expression that includes one or more (smaller) instances of itself. A classic example is the recursive deﬁnition for the factorial function:

Hn ≈ loge n + γ +

n! = (n − 1)! · n for n > 1;

1! = 0! = 1.

Another standard example of a recurrence is the Fibonacci sequence:

Fib(n) = Fib(n − 1) + Fib(n − 2) for n > 2;

Fib(1) = Fib(2) = 1.

35

Sec. 2.4 Summations and Recurrences

From this deﬁnition we see that the ﬁrst seven numbers of the Fibonacci sequence are 1, 1, 2, 3, 5, 8, and 13.

Notice that this deﬁnition contains two parts: the general deﬁnition for Fib(n) and the base cases for Fib(1) and Fib(2). Likewise, the deﬁnition for factorial contains a recursive part and base cases.

Recurrence relations are often used to model the cost of recursive functions. For example, the number of multiplications required by function fact of Section 2.5 for an input of size n will be zero when n = 0 or n = 1 (the base cases), and it will be one plus the cost of calling fact on a value of n − 1. This can be deﬁned using the following recurrence:

T(n) = T(n − 1) + 1 for n > 1;

T(0) = T(1) = 0.

As with summations, we typically wish to replace the recurrence relation with a closed-form solution. One approach is to expand the recurrence by replacing any occurrences of T on the right-hand side with its deﬁnition.

Example 2.8 If we expand the recurrence T(n) = T(n − 1) + 1, we get

T(n) = T(n − 1) + 1

= (T(n − 2) + 1) + 1.

We can expand the recurrence as many steps as we like, but the goal is to detect some pattern that will permit us to rewrite the recurrence in terms of a summation. In this example, we might notice that

(T(n − 2) + 1) + 1 = T(n − 2) + 2 and if we expand the recurrence again, we get

T(n) = T(n − 2) + 2 = T(n − 3) + 1 + 2 = T(n − 3) + 3 which generalizes to the pattern T(n) = T(n − i) + i. We might conclude that T(n) = T(n − (n − 1)) + (n − 1)

= T(1) + n − 1

= n − 1.

36

Chap. 2 Mathematical Preliminaries

Because we have merely guessed at a pattern and not actually proved that this is the correct closed form solution, we can use an induction proof to complete the process (see Example 2.13).

Example 2.9 A slightly more complicated recurrence is

T(n) = T(n − 1) + n;

T (1) = 1.

Expanding this recurrence a few steps, we get

T(n) = T(n − 1) + n

= T(n − 2) + (n − 1) + n

= T(n − 3) + (n − 2) + (n − 1) + n.

We should then observe that this recurrence appears to have a pattern that leads to

T(n) = T(n − (n − 1)) + (n − (n − 2)) + · · · + (n − 1) + n

= 1 + 2 + · · · + (n − 1) + n.

This is equivalent to the summation closed-form solution.

n i=1 i,

for which we already know the

Techniques to ﬁnd closed-form solutions for recurrence relations are discussed in Section 14.2. Prior to Chapter 14, recurrence relations are used infrequently in this book, and the corresponding closed-form solution and an explanation for how it was derived will be supplied at the time of use.

2.5

Recursion

An algorithm is recursive if it calls itself to do part of its work. For this approach to be successful, the “call to itself” must be on a smaller problem then the one originally attempted. In general, a recursive algorithm must have two parts: the base case, which handles a simple input that can be solved without resorting to a recursive call, and the recursive part which contains one or more recursive calls to the algorithm where the parameters are in some sense “closer” to the base case than those of the original call. Here is a recursive Java function to compute the

Sec. 2.5 Recursion

37

factorial of n. A trace of fact’s execution for a small value of n is presented in

Section 4.2.4. static long fact(int n) { // Compute n! recursively

// fact(20) is the largest value that fits in a long assert (n >= 0) && (n 1, then fact calls a function that knows how to ﬁnd the factorial of n − 1. Of course, the function that knows how to compute the factorial of n − 1 happens to be fact itself. But we should not think too hard about this while writing the algorithm. The design for recursive algorithms can always be approached in this way. First write the base cases. Then think about solving the problem by combining the results of one or more smaller — but similar — subproblems. If the algorithm you write is correct, then certainly you can rely on it (recursively) to solve the smaller subproblems.

The secret to success is: Do not worry about how the recursive call solves the subproblem. Simply accept that it will solve it correctly, and use this result to in turn correctly solve the original problem. What could be simpler?

Recursion has no counterpart in everyday problem solving. The concept can be difﬁcult to grasp because it requires you to think about problems in a new way.

To use recursion effectively, it is necessary to train yourself to stop analyzing the recursive process beyond the recursive call. The subproblems will take care of themselves. You just worry about the base cases and how to recombine the subproblems.

The recursive version of the factorial function might seem unnecessarily complicated to you because the same effect can be achieved by using a while loop.

Here is another example of recursion, based on a famous puzzle called “Towers of

Hanoi.” The natural algorithm to solve this problem has multiple recursive calls. It cannot be rewritten easily using while loops.

The Towers of Hanoi puzzle begins with three poles and n rings, where all rings start on the leftmost pole (labeled Pole 1). The rings each have a different size, and are stacked in order of decreasing size with the largest ring at the bottom, as shown in Figure 2.2.a. The problem is to move the rings from the leftmost pole to the rightmost pole (labeled Pole 3) in a series of steps. At each step the top ring on some pole is moved to another pole. There is one limitation on where rings may be moved: A ring can never be moved on top of a smaller ring.

38

Chap. 2 Mathematical Preliminaries

(a)

(b)

Figure 2.2 Towers of Hanoi example. (a) The initial conditions for a problem with six rings. (b) A necessary intermediate step on the road to a solution.

How can you solve this problem? It is easy if you don’t think too hard about the details. Instead, consider that all rings are to be moved from Pole 1 to Pole 3.

It is not possible to do this without ﬁrst moving the bottom (largest) ring to Pole 3.

To do that, Pole 3 must be empty, and only the bottom ring can be on Pole 1.

The remaining n − 1 rings must be stacked up in order on Pole 2, as shown in

Figure 2.2.b. How can you do this? Assume that a function X is available to solve the problem of moving the top n − 1 rings from Pole 1 to Pole 2. Then move the bottom ring from Pole 1 to Pole 3. Finally, again use function X to move the remaining n − 1 rings from Pole 2 to Pole 3. In both cases, “function X” is simply the Towers of Hanoi function called on a smaller version of the problem.

The secret to success is relying on the Towers of Hanoi algorithm to do the work for you. You need not be concerned about the gory details of how the Towers of Hanoi subproblem will be solved. That will take care of itself provided that two things are done. First, there must be a base case (what to do if there is only one ring) so that the recursive process will not go on forever. Second, the recursive call to Towers of Hanoi can only be used to solve a smaller problem, and then only one of the proper form (one that meets the original deﬁnition for the Towers of Hanoi problem, assuming appropriate renaming of the poles).

Here is a Java implementation for the recursive Towers of Hanoi algorithm.

Function move(start, goal) takes the top ring from Pole start and moves it to Pole goal. If the move function were to print the values of its parameters, then the result of calling TOH would be a list of ring-moving instructions that solves the problem.

Sec. 2.6 Mathematical Proof Techniques

39

static void TOH(int n, Pole start, Pole goal, Pole temp) { if (n == 0) return;

// Base case

TOH(n-1, start, temp, goal); // Recursive call: n-1 rings move(start, goal);

// Move bottom disk to goal

TOH(n-1, temp, goal, start); // Recursive call: n-1 rings

}

Those who are unfamiliar with recursion might ﬁnd it hard to accept that it is used primarily as a tool for simplifying the design and description of algorithms.

A recursive algorithm usually does not yield the most efﬁcient computer program for solving the problem because recursion involves function calls, which are typically more expensive than other alternatives such as a while loop. However, the recursive approach usually provides an algorithm that is reasonably efﬁcient in the sense discussed in Chapter 3. (But not always! See Exercise 2.11.) If necessary, the clear, recursive solution can later be modiﬁed to yield a faster implementation, as described in Section 4.2.4.

Many data structures are naturally recursive, in that they can be deﬁned as being made up of self-similar parts. Tree structures are an example of this. Thus, the algorithms to manipulate such data structures are often presented recursively.

Many searching and sorting algorithms are based on a strategy of divide and conquer. That is, a solution is found by breaking the problem into smaller (similar) subproblems, solving the subproblems, then combining the subproblem solutions to form the solution to the original problem. This process is often implemented using recursion. Thus, recursion plays an important role throughout this book, and many more examples of recursive functions will be given.

2.6

Mathematical Proof Techniques

Solving any problem has two distinct parts: the investigation and the argument.

Students are too used to seeing only the argument in their textbooks and lectures.

But to be successful in school (and in life after school), one needs to be good at both, and to understand the differences between these two phases of the process.

To solve the problem, you must investigate successfully. That means engaging the problem, and working through until you ﬁnd a solution. Then, to give the answer to your client (whether that “client” be your instructor when writing answers on a homework assignment or exam, or a written report to your boss), you need to be able to make the argument in a way that gets the solution across clearly and succinctly. The argument phase involves good technical writing skills — the ability to make a clear, logical argument.

Being conversant with standard proof techniques can help you in this process.

Knowing how to write a good proof helps in many ways. First, it clariﬁes your

40

Chap. 2 Mathematical Preliminaries

thought process, which in turn clariﬁes your explanations. Second, if you use one of the standard proof structures such as proof by contradiction or an induction proof, then both you and your reader are working from a shared understanding of that structure. That makes for less complexity to your reader to understand your proof, because the reader need not decode the structure of your argument from scratch.

This section brieﬂy introduces three commonly used proof techniques: (i) deduction, or direct proof; (ii) proof by contradiction, and (iii) proof by mathematical induction. 2.6.1

Direct Proof

In general, a direct proof is just a “logical explanation.” A direct proof is sometimes referred to as an argument by deduction. This is simply an argument in terms of logic. Often written in English with words such as “if ... then,” it could also be written with logic notation such as “P ⇒ Q.” Even if we don’t wish to use symbolic logic notation, we can still take advantage of fundamental theorems of logic to structure our arguments. For example, if we want to prove that P and Q are equivalent, we can ﬁrst prove P ⇒ Q and then prove Q ⇒ P .

In some domains, proofs are essentially a series of state changes from a start state to an end state. Formal predicate logic can be viewed in this way, with the various “rules of logic” being used to make the changes from one formula or combining a couple of formulas to make a new formula on the route to the destination. Symbolic manipulations to solve integration problems in introductory calculus classes are similar in spirit, as are high school geometry proofs.

2.6.2

Proof by Contradiction

The simplest way to disprove a theorem or statement is to ﬁnd a counterexample to the theorem. Unfortunately, no number of examples supporting a theorem is sufﬁcient to prove that the theorem is correct. However, there is an approach that is vaguely similar to disproving by counterexample, called Proof by Contradiction.

To prove a theorem by contradiction, we ﬁrst assume that the theorem is false. We then ﬁnd a logical contradiction stemming from this assumption. If the logic used to ﬁnd the contradiction is correct, then the only way to resolve the contradiction is to recognize that the assumption that the theorem is false must be incorrect. That is, we conclude that the theorem must be true.

Example 2.10 Here is a simple proof by contradiction.

Theorem 2.1 There is no largest integer.

Sec. 2.6 Mathematical Proof Techniques

41

Proof: Proof by contradiction.

Step 1. Contrary assumption: Assume that there is a largest integer.

Call it B (for “biggest”).

Step 2. Show this assumption leads to a contradiction: Consider

C = B + 1. C is an integer because it is the sum of two integers. Also,

C > B, which means that B is not the largest integer after all. Thus, we have reached a contradiction. The only ﬂaw in our reasoning is the initial assumption that the theorem is false. Thus, we conclude that the theorem is correct. 2

A related proof technique is proving the contrapositive. We can prove that

P ⇒ Q by proving (not Q) ⇒ (not P ).

2.6.3

Proof by Mathematical Induction

Mathematical induction is much like recursion. It is applicable to a wide variety of theorems. Induction also provides a useful way to think about algorithm design, because it encourages you to think about solving a problem by building up from simple subproblems. Induction can help to prove that a recursive function produces the correct result.

Within the context of algorithm analysis, one of the most important uses for mathematical induction is as a method to test a hypothesis. As explained in Section 2.4, when seeking a closed-form solution for a summation or recurrence we might ﬁrst guess or otherwise acquire evidence that a particular formula is the correct solution. If the formula is indeed correct, it is often an easy matter to prove that fact with an induction proof.

Let Thrm be a theorem to prove, and express Thrm in terms of a positive integer parameter n. Mathematical induction states that Thrm is true for any value of parameter n (for n ≥ c, where c is some constant) if the following two conditions are true:

1. Base Case: Thrm holds for n = c, and

2. Induction Step: If Thrm holds for n − 1, then Thrm holds for n.

Proving the base case is usually easy, typically requiring that some small value such as 1 be substituted for n in the theorem and applying simple algebra or logic as necessary to verify the theorem. Proving the induction step is sometimes easy, and sometimes difﬁcult. An alternative formulation of the induction step is known as strong induction. The induction step for strong induction is:

2a. Induction Step: If Thrm holds for all k, c ≤ k < n, then Thrm holds for n.

42

Chap. 2 Mathematical Preliminaries

Proving either variant of the induction step (in conjunction with verifying the base case) yields a satisfactory proof by mathematical induction.

The two conditions that make up the induction proof combine to demonstrate that Thrm holds for n = 2 as an extension of the fact that Thrm holds for n = 1.

This fact, combined again with condition (2) or (2a), indicates that Thrm also holds for n = 3, and so on. Thus, Thrm holds for all values of n (larger than the base cases) once the two conditions have been proved.

What makes mathematical induction so powerful (and so mystifying to most people at ﬁrst) is that we can take advantage of the assumption that Thrm holds for all values less than n to help us prove that Thrm holds for n. This is known as the induction hypothesis. Having this assumption to work with makes the induction step easier to prove than tackling the original theorem itself. Being able to rely on the induction hypothesis provides extra information that we can bring to bear on the problem.

There are important similarities between recursion and induction. Both are anchored on one or more base cases. A recursive function relies on the ability to call itself to get the answer for smaller instances of the problem. Likewise, induction proofs rely on the truth of the induction hypothesis to prove the theorem.

The induction hypothesis does not come out of thin air. It is true if and only if the theorem itself is true, and therefore is reliable within the proof context. Using the induction hypothesis it do work is exactly the same as using a recursive call to do work. Example 2.11 Here is a sample proof by mathematical induction. Call the sum of the ﬁrst n positive integers S(n).

Theorem 2.2 S(n) = n(n + 1)/2.

Proof: The proof is by mathematical induction.

1. Check the base case. For n = 1, verify that S(1) = 1(1 + 1)/2. S(1) is simply the sum of the ﬁrst positive number, which is 1. Because

1(1 + 1)/2 = 1, the formula is correct for the base case.

2. State the induction hypothesis. The induction hypothesis is n−1 S(n − 1) =

i= i=1 (n − 1)((n − 1) + 1)

(n − 1)(n)

=

.

2

2

3. Use the assumption from the induction hypothesis for n − 1 to show that the result is true for n. The induction hypothesis states

43

Sec. 2.6 Mathematical Proof Techniques

that S(n − 1) = (n − 1)(n)/2, and because S(n) = S(n − 1) + n, we can substitute for S(n − 1) to get n−1 n

i

i = i=1 =

i=1

2−n

n

+n=

+ 2n

2

=

(n − 1)(n)

+n

2 n(n + 1)

.

2

Thus, by mathematical induction, n S(n) =

i = n(n + 1)/2. i=1 2

Note carefully what took place in this example. First we cast S(n) in terms of a smaller occurrence of the problem: S(n) = S(n − 1) + n. This is important because once S(n − 1) comes into the picture, we can use the induction hypothesis to replace S(n − 1)) with (n − 1)(n)/2. From here, it is simple algebra to prove that S(n − 1) + n equals the right-hand side of the original theorem.

Example 2.12 Here is another simple proof by induction that illustrates choosing the proper variable for induction. We wish to prove by induction that the sum of the ﬁrst n positive odd numbers is n2 . First we need a way to describe the nth odd number, which is simply 2n − 1. This also allows us to cast the theorem as a summation. n 2

Theorem 2.3 i=1 (2i − 1) = n .

Proof: The base case of n = 1 yields 1 = 12 , which is true. The induction hypothesis is n−1 (2i − 1) = (n − 1)2 . i=1 We now use the induction hypothesis to show that the theorem holds true for n. The sum of the ﬁrst n odd numbers is simply the sum of the ﬁrst n − 1 odd numbers plus the nth odd number. In the second line below, we will use the induction hypothesis to replace the partial summation (shown in brackets in the ﬁrst line) with its closed-form solution. After that, algebra

44

Chap. 2 Mathematical Preliminaries

takes care of the rest. n−1 n

(2i − 1) + 2n − 1

(2i − 1) = i=1 i=1

= (n − 1)2 + 2n − 1

= n2 − 2n + 1 + 2n − 1

= n2 .

Thus, by mathematical induction,

n i=1 (2i

− 1) = n2 .

2

Example 2.13 This example shows how we can use induction to prove that a proposed closed-form solution for a recurrence relation is correct.

Theorem 2.4 The recurrence relation T(n) = T(n−1)+1; T(1) = 0 has closed-form solution T(n) = n − 1.

Proof: To prove the base case, we observe that T(1) = 1 − 1 = 0. The induction hypothesis is that T(n − 1) = n − 2. Combining the deﬁnition of the recurrence with the induction hypothesis, we see immediately that

T(n) = T(n − 1) + 1 = n − 2 + 1 = n − 1 for n > 1. Thus, we have proved the theorem correct by mathematical induction. 2

Example 2.14 This example uses induction without involving summations or other equations. It also illustrates a more ﬂexible use of base cases.

Theorem 2.5 2c and 5c stamps can be used to form any value (for values

/

/

≥ 4).

Proof: The theorem deﬁnes the problem for values ≥ 4 because it does not hold for the values 1 and 3. Using 4 as the base case, a value of 4c can be

/

made from two 2c stamps. The induction hypothesis is that a value of n − 1

/

can be made from some combination of 2c and 5c stamps. We now use the

/

/ induction hypothesis to show how to get the value n from 2c and 5c stamps.

/

/

Either the makeup for value n − 1 includes a 5c stamp, or it does not. If so,

/

Sec. 2.6 Mathematical Proof Techniques

45

then replace a 5c stamp with three 2c stamps. If not, then the makeup must

/

/ have included at least two 2c stamps (because it is at least of size 4 and

/

contains only 2c stamps). In this case, replace two of the 2c stamps with a

/

/ single 5c stamp. In either case, we now have a value of n made up of 2c

/

/ and 5c stamps. Thus, by mathematical induction, the theorem is correct. 2

/

Example 2.15 Here is an example using strong induction.

Theorem 2.6 For n > 1, n is divisible by some prime number.

Proof: For the base case, choose n = 2. 2 is divisible by the prime number 2. The induction hypothesis is that any value a, 2 ≤ a < n, is divisible by some prime number. There are now two cases to consider when proving the theorem for n. If n is a prime number, then n is divisible by itself. If n is not a prime number, then n = a × b for a and b, both integers less than n but greater than 1. The induction hypothesis tells us that a is divisible by some prime number. That same prime number must also divide n. Thus, by mathematical induction, the theorem is correct.

2

Our next example of mathematical induction proves a theorem from geometry.

It also illustrates a standard technique of induction proof where we take n objects and remove some object to use the induction hypothesis.

Example 2.16 Deﬁne a two-coloring for a set of regions as a way of assigning one of two colors to each region such that no two regions sharing a side have the same color. For example, a chessboard is two-colored. Figure 2.3 shows a two-coloring for the plane with three lines. We will assume that the two colors to be used are black and white.

Theorem 2.7 The set of regions formed by n inﬁnite lines in the plane can be two-colored.

Proof: Consider the base case of a single inﬁnite line in the plane. This line splits the plane into two regions. One region can be colored black and the other white to get a valid two-coloring. The induction hypothesis is that the set of regions formed by n − 1 inﬁnite lines can be two-colored. To prove the theorem for n, consider the set of regions formed by the n − 1 lines remaining when any one of the n lines is removed. By the induction hypothesis, this set of regions can be two-colored. Now, put the nth line back.

This splits the plane into two half-planes, each of which (independently) has a valid two-coloring inherited from the two-coloring of the plane with

46

Chap. 2 Mathematical Preliminaries

Figure 2.3 A two-coloring for the regions formed by three lines in the plane.

n − 1 lines. Unfortunately, the regions newly split by the nth line violate the rule for a two-coloring. Take all regions on one side of the nth line and reverse their coloring (after doing so, this half-plane is still two-colored).

Those regions split by the nth line are now properly two-colored, Because the part of the region to one side of the line is now black and the region to the other side is now white. Thus, by mathematical induction, the entire plane is two-colored.

2

Compare the proof of Theorem 2.7 with that of Theorem 2.5. For Theorem 2.5, we took a collection of stamps of size n − 1 (which, by the induction hypothesis, must have the desired property) and from that “built” a collection of size n that has the desired property. We therefore proved the existence of some collection of stamps of size n with the desired property.

For Theorem 2.7 we must prove that any collection of n lines has the desired property. Thus, our strategy is to take an arbitrary collection of n lines, and “reduce” it so that we have a set of lines that must have the desired property because it matches the induction hypothesis. From there, we merely need to show that reversing the original reduction process preserves the desired property.

In contrast, consider what would be required if we attempted to “build” from a set of lines of size n − 1 to one of size n. We would have great difﬁculty justifying that all possible collections of n lines are covered by our building process. By reducing from an arbitrary collection of n lines to something less, we avoid this problem. This section’s ﬁnal example shows how induction can be used to prove that a recursive function produces the correct result.

Sec. 2.7 Estimating

47

Example 2.17 We would like to prove that function fact does indeed compute the factorial function. There are two distinct steps to such a proof.

The ﬁrst is to prove that the function always terminates. The second is to prove that the function returns the correct value.

Theorem 2.8 Function fact will terminate for any value of n.

Proof: For the base case, we observe that fact will terminate directly whenever n ≤ 0. The induction hypothesis is that fact will terminate for n − 1. For n, we have two possibilities. One possibility is that n ≥ 12.

In that case, fact will terminate directly because it will fail its assertion test. Otherwise, fact will make a recursive call to fact(n-1). By the induction hypothesis, fact(n-1) must terminate.

2

Theorem 2.9 Function fact does compute the factorial function for any value in the range 0 to 12.

Proof: To prove the base case, observe that when n = 0 or n = 1, fact(n) returns the correct value of 1. The induction hypothesis is that fact(n-1) returns the correct value of (n − 1)!. For any value n within the legal range, fact(n) returns n ∗ fact(n-1). By the induction hypothesis, fact(n-1) = (n − 1)!, and because n ∗ (n − 1)! = n!, we have proved that fact(n) produces the correct result.

2

We can use a similar process to prove many recursive programs correct. The general form is to show that the base cases perform correctly, and then to use the induction hypothesis to show that the recursive step also produces the correct result.

Prior to this, we must prove that the function always terminates, which might also be done using an induction proof.

2.7

Estimating

One of the most useful life skills that you can gain from your computer science training is knowing how to perform quick estimates. This is sometimes known as

“back of the napkin” or “back of the envelope” calculation. Both nicknames suggest that only a rough estimate is produced. Estimation techniques are a standard part of engineering curricula but are often neglected in computer science. Estimation is no substitute for rigorous, detailed analysis of a problem, but it can serve to indicate when a rigorous analysis is warranted: If the initial estimate indicates that the solution is unworkable, then further analysis is probably unnecessary.

Estimating can be formalized by the following three-step process:

1. Determine the major parameters that affect the problem.

48

Chap. 2 Mathematical Preliminaries

2. Derive an equation that relates the parameters to the problem.

3. Select values for the parameters, and apply the equation to yield an estimated solution. When doing estimations, a good way to reassure yourself that the estimate is reasonable is to do it in two different ways. In general, if you want to know what comes out of a system, you can either try to estimate that directly, or you can estimate what goes into the system (assuming that what goes in must later come out). If both approaches (independently) give similar answers, then this should build conﬁdence in the estimate.

When calculating, be sure that your units match. For example, do not add feet and pounds. Verify that the result is in the correct units. Always keep in mind that the output of a calculation is only as good as its input. The more uncertain your valuation for the input parameters in Step 3, the more uncertain the output value.

However, back of the envelope calculations are often meant only to get an answer within an order of magnitude, or perhaps within a factor of two. Before doing an estimate, you should decide on acceptable error bounds, such as within 10%, within a factor of two, and so forth. Once you are conﬁdent that an estimate falls within your error bounds, leave it alone! Do not try to get a more precise estimate than necessary for your purpose.

Example 2.18 How many library bookcases does it take to store books containing one million pages? I estimate that a 500-page book requires one inch on the library shelf (for example, look at the size of this book), yielding about 200 feet of shelf space for one million pages. If a shelf is 4 feet wide, then 50 shelves are required. If a bookcase contains 5 shelves, this yields about 10 library bookcases. To reach this conclusion, I estimated the number of pages per inch, the width of a shelf, and the number of shelves in a bookcase. None of my estimates are likely to be precise, but I feel conﬁdent that my answer is correct to within a factor of two. (After writing this, I went to Virginia Tech’s library and looked at some real bookcases.

They were only about 3 feet wide, but typically had 7 shelves for a total of 21 shelf-feet. So I was correct to within 10% on bookcase capacity, far better than I expected or needed. One of my selected values was too high, and the other too low, which canceled out the errors.)

Example 2.19 Is it more economical to buy a car that gets 20 miles per gallon, or one that gets 30 miles per gallon but costs $2000 more? The

Sec. 2.8 Further Reading

49

typical car is driven about 12,000 miles per year. If gasoline costs $2/gallon, then the yearly gas bill is $1200 for the less efﬁcient car and $800 for the more efﬁcient car. If we ignore issues such as the payback that would be received if we invested $2000 in a bank, it would take 5 years to make up the difference in price. At this point, the buyer must decide if price is the only criterion and if a 5-year payback time is acceptable. Naturally, a person who drives more will make up the difference more quickly, and changes in gasoline prices will also greatly affect the outcome.

Example 2.20 When at the supermarket doing the week’s shopping, can you estimate about how much you will have to pay at the checkout? One simple way is to round the price of each item to the nearest dollar, and add this value to a mental running total as you put the item in your shopping cart. This will likely give an answer within a couple of dollars of the true total. 2.8

Further Reading

Most of the topics covered in this chapter are considered part of Discrete Mathematics. An introduction to this ﬁeld is Discrete Mathematics with Applications by Susanna S. Epp [Epp04]. An advanced treatment of many mathematical topics useful to computer scientists is Concrete Mathematics: A Foundation for Computer

Science by Graham, Knuth, and Patashnik [GKP94].

See “Technically Speaking” from the February 1995 issue of IEEE Spectrum

[Sel95] for a discussion on the standard for indicating units of computer storage used in this book.

Introduction to Algorithms by Udi Manber [Man89] makes extensive use of mathematical induction as a technique for developing algorithms.

For more information on recursion, see Thinking Recursively by Eric S. Roberts

[Rob86]. To learn recursion properly, it is worth your while to learn the programming language LISP, even if you never intend to write a LISP program. In particular, Friedman and Felleisen’s The Little LISPer [FF89] is designed to teach you how to think recursively as well as teach you LISP. This book is entertaining reading as well. A good book on writing mathematical proofs is Daniel Solow’s How to Read and Do Proofs [Sol90]. To improve your general mathematical problem-solving abilities, see The Art and Craft of Problem Solving by Paul Zeitz [Zei07]. Zeitz

50

Chap. 2 Mathematical Preliminaries

also discusses the three proof techniques presented in Section 2.6, and the roles of investigation and argument in problem solving.

For more about estimating techniques, see two Programming Pearls by John

Louis Bentley entitled The Back of the Envelope and The Envelope is Back [Ben84,

Ben00, Ben86, Ben88]. Genius: The Life and Science of Richard Feynman by

James Gleick [Gle92] gives insight into how important back of the envelope calculation was to the developers of the atomic bomb, and to modern theoretical physics in general.

2.9

Exercises

2.1 For each relation below, explain why the relation does or does not satisfy each of the properties reﬂexive, symmetric, antisymmetric, and transitive.

(a) “isBrotherOf” on the set of people.

(b) “isFatherOf” on the set of people.

(c) The relation R = { x, y | x2 + y 2 = 1} for real numbers x and y.

(d) The relation R = { x, y | x2 = y 2 } for real numbers x and y.

(e) The relation R = { x, y | x mod y = 0} for x, y ∈ {1, 2, 3, 4}.

(f) The empty relation ∅ (i.e., the relation with no ordered pairs for which it is true) on the set of integers.

(g) The empty relation ∅ (i.e., the relation with no ordered pairs for which it is true) on the empty set.

2.2 For each of the following relations, either prove that it is an equivalence relation or prove that it is not an equivalence relation.

(a) For integers a and b, a ≡ b if and only if a + b is even.

(b) For integers a and b, a ≡ b if and only if a + b is odd.

(c) For nonzero rational numbers a and b, a ≡ b if and only if a × b > 0.

(d) For nonzero rational numbers a and b, a ≡ b if and only if a/b is an integer. (e) For rational numbers a and b, a ≡ b if and only if a − b is an integer.

(f) For rational numbers a and b, a ≡ b if and only if |a − b| ≤ 2.

2.3 State whether each of the following relations is a partial ordering, and explain why or why not.

(a) “isFatherOf” on the set of people.

(b) “isAncestorOf” on the set of people.

(c) “isOlderThan” on the set of people.

(d) “isSisterOf” on the set of people.

(e) { a, b , a, a , b, a } on the set {a, b}.

Sec. 2.9 Exercises

51

(f) { 2, 1 , 1, 3 , 2, 3 } on the set {1, 2, 3}.

2.4 How many total orderings can be deﬁned on a set with n elements? Explain your answer.

2.5 Deﬁne an ADT for a set of integers (remember that a set has no concept of duplicate elements, and has no concept of order). Your ADT should consist of the functions that can be performed on a set to control its membership, check the size, check if a given element is in the set, and so on. Each function should be deﬁned in terms of its input and output.

2.6 Deﬁne an ADT for a bag of integers (remember that a bag may contain duplicates, and has no concept of order). Your ADT should consist of the functions that can be performed on a bag to control its membership, check the size, check if a given element is in the set, and so on. Each function should be deﬁned in terms of its input and output.

2.7 Deﬁne an ADT for a sequence of integers (remember that a sequence may contain duplicates, and supports the concept of position for its elements).

Your ADT should consist of the functions that can be performed on a sequence to control its membership, check the size, check if a given element is in the set, and so on. Each function should be deﬁned in terms of its input and output.

2.8 An investor places $30,000 into a stock fund. 10 years later the account has a value of $69,000. Using logarithms and anti-logarithms, present a formula for calculating the average annual rate of increase. Then use your formula to determine the average annual growth rate for this fund.

2.9 Rewrite the factorial function of Section 2.5 without using recursion.

2.10 Rewrite the for loop for the random permutation generator of Section 2.2 as a recursive function.

2.11 Here is a simple recursive function to compute the Fibonacci sequence:

// Recursive Fibonacci generator static long fibr(int n) {

// fibr(91) is the largest value that fits in a long assert (n > 0) && (n 0) && (n 0; T(0) = 1.

2.34 Assume that an n-bit integer (represented by standard binary notation) takes any value in the range 0 to 2n − 1 with equal probability.

(a) For each bit position, what is the probability of its value being 1 and what is the probability of its value being 0?

(b) What is the average number of “1” bits for an n-bit random number?

(c) What is the expected value for the position of the leftmost “1” bit? In other words, how many positions on average must we examine when moving from left to right before encountering a “1” bit? Show the appropriate summation.

2.35 What is the total volume of your body in liters (or, if you prefer, gallons)?

2.36 An art historian has a database of 20,000 full-screen color images.

(a) About how much space will this require? How many CD-ROMs would be required to store the database? (A CD-ROM holds about 600MB of data). Be sure to explain all assumptions you made to derive your answer. (b) Now, assume that you have access to a good image compression technique that can store the images in only 1/10 of the space required for an uncompressed image. Will the entire database ﬁt onto a single CDROM if the images are compressed?

2.37 How many cubic miles of water ﬂow out of the mouth of the Mississippi

River each day? DO NOT look up the answer or any supplemental facts. Be sure to describe all assumptions made in arriving at your answer.

Sec. 2.9 Exercises

55

2.38 When buying a home mortgage, you often have the option of paying some money in advance (called “discount points”) to get a lower interest rate. Assume that you have the choice between two 15-year mortgages: one at 8%,

3

and the other at 7 4 % with an up-front charge of 1% of the mortgage value.

How long would it take to recover the 1% charge when you take the mortgage at the lower rate? As a second, more precise estimate, how long would it take to recover the charge plus the interest you would have received if you had invested the equivalent of the 1% charge in the bank at 5% interest while paying the higher rate? DO NOT use a calculator to help you answer this question. 2.39 When you build a new house, you sometimes get a “construction loan” which is a temporary line of credit out of which you pay construction costs as they occur. At the end of the construction period, you then replace the construction loan with a regular mortgage on the house. During the construction loan, you only pay each month for the interest charged against the actual amount borrowed so far. Assume that your house construction project starts at the beginning of April, and is complete at the end of six months. Assume that the total construction cost will be $300,000 with the costs occurring at the beginning of each month in $50,000 increments. The construction loan charges

6% interest. Estimate the total interest payments that must be paid over the life of the construction loan.

2.40 Here are some questions that test your working knowledge of how fast computers operate. Is disk drive access time normally measured in milliseconds

(thousandths of a second) or microseconds (millionths of a second)? Does your RAM memory access a word in more or less than one microsecond?

How many instructions can your CPU execute in one year if the machine is left running at full speed all the time? DO NOT use paper or a calculator to derive your answers.

2.41 Does your home contain enough books to total one million pages? How many total pages are stored in your school library building?

2.42 How many words are in this book?

2.43 How many hours are one million seconds? How many days? Answer these questions doing all arithmetic in your head.

2.44 How many cities and towns are there in the United States?

2.45 How many steps would it take to walk from Boston to San Francisco?

2.46 A man begins a car trip to visit his in-laws. The total distance is 60 miles, and he starts off at a speed of 60 miles per hour. After driving exactly 1 mile, he loses some of his enthusiasm for the journey, and (instantaneously) slows

56

Chap. 2 Mathematical Preliminaries

down to 59 miles per hour. After traveling another mile, he again slows to

58 miles per hour. This continues, progressively slowing by 1 mile per hour for each mile traveled until the trip is complete.

(a) How long does it take the man to reach his in-laws?

(b) How long would the trip take in the continuous case where the speed smoothly diminishes with the distance yet to travel?

3

Algorithm Analysis

How long will it take to process the company payroll once we complete our planned merger? Should I buy a new payroll program from vendor X or vendor Y? If a particular program is slow, is it badly implemented or is it solving a hard problem?

Questions like these ask us to consider the difﬁculty of a problem, or the relative efﬁciency of two or more approaches to solving a problem.

This chapter introduces the motivation, basic notation, and fundamental techniques of algorithm analysis. We focus on a methodology known as asymptotic algorithm analysis, or simply asymptotic analysis. Asymptotic analysis attempts to estimate the resource consumption of an algorithm. It allows us to compare the relative costs of two or more algorithms for solving the same problem. Asymptotic analysis also gives algorithm designers a tool for estimating whether a proposed solution is likely to meet the resource constraints for a problem before they implement an actual program. After reading this chapter, you should understand

• the concept of a growth rate, the rate at which the cost of an algorithm grows as the size of its input grows;

• the concept of upper and lower bounds for a growth rate, and how to estimate these bounds for a simple program, algorithm, or problem; and

• the difference between the cost of an algorithm (or program) and the cost of a problem.

The chapter concludes with a brief discussion of the practical difﬁculties encountered when empirically measuring the cost of a program, and some principles for code tuning to improve program efﬁciency.

3.1

Introduction

How do you compare two algorithms for solving some problem in terms of efﬁciency? One way is to implement both algorithms as computer programs and then

57

58

Chap. 3 Algorithm Analysis

run them on a suitable range of inputs, measuring how much of the resources in question each program uses. This approach is often unsatisfactory for four reasons.

First, there is the effort involved in programming and testing two algorithms when at best you want to keep only one. Second, when empirically comparing two algorithms there is always the chance that one of the programs was “better written” than the other, and that the relative qualities of the underlying algorithms are not truly represented by their implementations. This is especially likely to occur when the programmer has a bias regarding the algorithms. Third, the choice of empirical test cases might unfairly favor one algorithm. Fourth, you could ﬁnd that even the better of the two algorithms does not fall within your resource budget. In that case you must begin the entire process again with yet another program implementing a new algorithm. But, how would you know if any algorithm can meet the resource budget? Perhaps the problem is simply too difﬁcult for any implementation to be within budget.

These problems can often be avoided by using asymptotic analysis. Asymptotic analysis measures the efﬁciency of an algorithm, or its implementation as a program, as the input size becomes large. It is actually an estimating technique and does not tell us anything about the relative merits of two programs where one is always “slightly faster” than the other. However, asymptotic analysis has proved useful to computer scientists who must determine if a particular algorithm is worth considering for implementation.

The critical resource for a program is most often its running time. However, you cannot pay attention to running time alone. You must also be concerned with other factors such as the space required to run the program (both main memory and disk space). Typically you will analyze the time required for an algorithm (or the instantiation of an algorithm in the form of a program), and the space required for a data structure.

Many factors affect the running time of a program. Some relate to the environment in which the program is compiled and run. Such factors include the speed of the computer’s CPU, bus, and peripheral hardware. Competition with other users for the computer’s resources can make a program slow to a crawl. The programming language and the quality of code generated by a particular compiler can have a signiﬁcant effect. The “coding efﬁciency” of the programmer who converts the algorithm to a program can have a tremendous impact as well.

If you need to get a program working within time and space constraints on a particular computer, all of these factors can be relevant. Yet, none of these factors address the differences between two algorithms or data structures. To be fair, programs derived from two algorithms for solving the same problem should both be

Sec. 3.1 Introduction

59

compiled with the same compiler and run on the same computer under the same conditions. As much as possible, the same amount of care should be taken in the programming effort devoted to each program to make the implementations “equally efﬁcient.” In this sense, all of the factors mentioned above should cancel out of the comparison because they apply to both algorithms equally.

If you truly wish to understand the running time of an algorithm, there are other factors that are more appropriate to consider than machine speed, programming language, compiler, and so forth. Ideally we would measure the running time of the algorithm under standard benchmark conditions. However, we have no way to calculate the running time reliably other than to run an implementation of the algorithm on some computer. The only alternative is to use some other measure as a surrogate for running time.

Of primary consideration when estimating an algorithm’s performance is the number of basic operations required by the algorithm to process an input of a certain size. The terms “basic operations” and “size” are both rather vague and depend on the algorithm being analyzed. Size is often the number of inputs processed. For example, when comparing sorting algorithms, the size of the problem is typically measured by the number of records to be sorted. A basic operation must have the property that its time to complete does not depend on the particular values of its operands. Adding or comparing two integer variables are examples of basic operations in most programming languages. Summing the contents of an array containing n integers is not, because the cost depends on the value of n (i.e., the size of the input).

Example 3.1 Consider a simple algorithm to solve the problem of ﬁnding the largest value in an array of n integers. The algorithm looks at each integer in turn, saving the position of the largest value seen so far. This algorithm is called the largest-value sequential search and is illustrated by the following Java function:

// Return position of largest value in "A" static int largest(int[] A) { int currlarge = 0; // Holds largest element position for (int i=1; i 1, na grows faster than either logb n or log nb . Finally, algorithms with cost T(n) = 2n or

T(n) = n! are prohibitively expensive for even modest values of n. Note that for constants a, b ≥ 1, an grows faster than nb .

62

Chap. 3 Algorithm Analysis

n!

1400

2n2

2n

5n log n

1200

20n

1000

800

600

10n

400

200

0

0

10

20

n!

30

40

50

2n2

2n

400

20n

300

5n log n

200

10n

100

0

0

5

10

15

Input size n

Figure 3.1 Two views of a graph illustrating the growth rates for six equations.

The bottom view shows in detail the lower-left portion of the top view. The horizontal axis represents input size. The vertical axis can represent time, space, or any other measure of cost.

63

Sec. 3.2 Best, Worst, and Average Cases

n

16

256

1024

64K

1M

1G

log log n log n

2

3

≈ 3.3

4

≈ 4.3

≈ 4.9

4

8

10

16

20

30

n

n log n

n2

n3

2n

24

28

210

216

220

230

2 · 24 = 25

8 · 28 = 211

10 · 210 ≈ 213

16 · 216 = 220

20 · 220 ≈ 224

30 · 230 ≈ 235

28

216

220

232

240

260

212 216

224 2256

230 21024

248 264K

260 21M

290 21G

Figure 3.2 Costs for growth rates representative of most computer algorithms.

We can get some further insight into relative growth rates for various algorithms from Figure 3.2. Most of the growth rates that appear in typical algorithms are shown, along with some representative input sizes. Once again, we see that the growth rate has a tremendous effect on the resources consumed by an algorithm.

3.2

Best, Worst, and Average Cases

Consider the problem of ﬁnding the factorial of n. For this problem, there is only one input of a given “size” (that is, there is only a single instance of size n for each value of n). Now consider our largest-value sequential search algorithm of

Example 3.1, which always examines every array value. This algorithm works on many inputs of a given size n. That is, there are many possible arrays of any given size. However, no matter what array the algorithm looks at, its cost will always be the same in that it always looks at every element in the array one time.

For some algorithms, different inputs of a given size require different amounts of time. For example, consider the problem of searching an array containing n integers to ﬁnd the one with a particular value K (assume that K appears exactly once in the array). The sequential search algorithm begins at the ﬁrst position in the array and looks at each value in turn until K is found. Once K is found, the algorithm stops. This is different from the largest-value sequential search algorithm of Example 3.1, which always examines every array value.

There is a wide range of possible running times for the sequential search algorithm. The ﬁrst integer in the array could have value K, and so only one integer is examined. In this case the running time is short. This is the best case for this algorithm, because it is not possible for sequential search to look at less than one value. Alternatively, if the last position in the array contains K, then the running time is relatively long, because the algorithm must examine n values. This is the worst case for this algorithm, because sequential search never looks at more than

64

Chap. 3 Algorithm Analysis

n values. If we implement sequential search as a program and run it many times on many different arrays of size n, or search for many different values of K within the same array, we expect the algorithm on average to go halfway through the array before ﬁnding the value we seek. On average, the algorithm examines about n/2 values. We call this the average case for this algorithm.

When analyzing an algorithm, should we study the best, worst, or average case?

Normally we are not interested in the best case, because this might happen only rarely and generally is too optimistic for a fair characterization of the algorithm’s running time. In other words, analysis based on the best case is not likely to be representative of the behavior of the algorithm. However, there are rare instances where a best-case analysis is useful — in particular, when the best case has high probability of occurring. In Chapter 7 you will see some examples where taking advantage of the best-case running time for one sorting algorithm makes a second more efﬁcient.

How about the worst case? The advantage to analyzing the worst case is that you know for certain that the algorithm must perform at least that well. This is especially important for real-time applications, such as for the computers that monitor an air trafﬁc control system. Here, it would not be acceptable to use an algorithm that can handle n airplanes quickly enough most of the time, but which fails to perform quickly enough when all n airplanes are coming from the same direction.

For other applications — particularly when we wish to aggregate the cost of running the program many times on many different inputs — worst-case analysis might not be a representative measure of the algorithm’s performance. Often we prefer to know the average-case running time. This means that we would like to know the typical behavior of the algorithm on inputs of size n. Unfortunately, average-case analysis is not always possible. Average-case analysis ﬁrst requires that we understand how the actual inputs to the program (and their costs) are distributed with respect to the set of all possible inputs to the program. For example, it was stated previously that the sequential search algorithm on average examines half of the array values. This is only true if the element with value K is equally likely to appear in any position in the array. If this assumption is not correct, then the algorithm does not necessarily examine half of the array values in the average case.

See Section 9.2 for further discussion regarding the effects of data distribution on the sequential search algorithm.

The characteristics of a data distribution have a signiﬁcant effect on many search algorithms, such as those based on hashing (Section 9.4) and search trees

(e.g., see Section 5.4). Incorrect assumptions about data distribution can have dis-

Sec. 3.3 A Faster Computer, or a Faster Algorithm?

65

astrous consequences on a program’s space or time performance. Unusual data distributions can also be used to advantage, as shown in Section 9.2.

In summary, for real-time applications we are likely to prefer a worst-case analysis of an algorithm. Otherwise, we often desire an average-case analysis if we know enough about the distribution of our input to compute the average case. If not, then we must resort to worst-case analysis.

3.3

A Faster Computer, or a Faster Algorithm?

Imagine that you have a problem to solve, and you know of an algorithm whose running time is proportional to n2 . Unfortunately, the resulting program takes ten times too long to run. If you replace your current computer with a new one that is ten times faster, will the n2 algorithm become acceptable? If the problem size remains the same, then perhaps the faster computer will allow you to get your work done quickly enough even with an algorithm having a high growth rate. But a funny thing happens to most people who get a faster computer. They don’t run the same problem faster. They run a bigger problem! Say that on your old computer you were content to sort 10,000 records because that could be done by the computer during your lunch break. On your new computer you might hope to sort 100,000 records in the same time. You won’t be back from lunch any sooner, so you are better off solving a larger problem. And because the new machine is ten times faster, you would like to sort ten times as many records.

If your algorithm’s growth rate is linear (i.e., if the equation that describes the running time on input size n is T(n) = cn for some constant c), then 100,000 records on the new machine will be sorted in the same time as 10,000 records on the old machine. If the algorithm’s growth rate is greater than cn, such as c1 n2 , then you will not be able to do a problem ten times the size in the same amount of time on a machine that is ten times faster.

How much larger a problem can be solved in a given amount of time by a faster computer? Assume that the new machine is ten times faster than the old. Say that the old machine could solve a problem of size n in an hour. What is the largest problem that the new machine can solve in one hour? Figure 3.3 shows how large a problem can be solved on the two machines for the ﬁve running-time functions from Figure 3.1.

This table illustrates many important points. The ﬁrst two equations are both linear; only the value of the constant factor has changed. In both cases, the machine that is ten times faster gives an increase in problem size by a factor of ten. In other words, while the value of the constant does affect the absolute size of the problem that can be solved in a ﬁxed amount of time, it does not affect the improvement in

66

Chap. 3 Algorithm Analysis

f(n) n n

Change

n /n

10n

1000 10, 000 n = 10n

10

20n

500

5000 n = 10n

10

√

5n log n 250

1842

10n < n < 10n 7.37

√

70

223 n = 10n

3.16

2n2

2n

13

16 n = n + 3

−−

Figure 3.3 The increase in problem size that can be run in a ﬁxed period of time on a computer that is ten times faster. The ﬁrst column lists the right-hand sides for each of the ﬁve growth rate equations of Figure 3.1. For the purpose of this example, arbitrarily assume that the old machine can run 10,000 basic operations in one hour. The second column shows the maximum value for n that can be run in 10,000 basic operations on the old machine. The third column shows the value for n , the new maximum size for the problem that can be run in the same time on the new machine that is ten times faster. Variable n is the greatest size for the problem that can run in 100,000 basic operations. The fourth column shows how the size of n changed to become n on the new machine. The ﬁfth column shows the increase in the problem size as the ratio of n to n.

problem size (as a proportion to the original size) gained by a faster computer. This relationship holds true regardless of the algorithm’s growth rate: Constant factors never affect the relative improvement gained by a faster computer.

An algorithm with time equation T(n) = 2n2 does not receive nearly as great an improvement from the faster machine as an algorithm with linear growth rate.

Instead of an √ improvement by a factor of ten, the improvement is only the square root of that: 10 ≈ 3.16. Thus, the algorithm with higher growth rate not only solves a smaller problem in a given time in the ﬁrst place, it also receives less of a speedup from a faster computer. As computers get ever faster, the disparity in problem sizes becomes ever greater.

The algorithm with growth rate T(n) = 5n log n improves by a greater amount than the one with quadratic growth rate, but not by as great an amount as the algorithms with linear growth rates.

Note that something special happens in the case of the algorithm whose running time grows exponentially. In Figure 3.1, the curve for the algorithm whose time is proportional to 2n goes up very quickly. In Figure 3.3, the increase in problem size on the machine ten times as fast is shown to be about n + 3 (to be precise, it is n + log2 10). The increase in problem size for an algorithm with exponential growth rate is by a constant addition, not by a multiplicative factor. Because the old value of n was 13, the new problem size is 16. If next year you buy another

Sec. 3.4 Asymptotic Analysis

67

computer ten times faster yet, then the new computer (100 times faster than the original computer) will only run a problem of size 19. If you had a second program whose growth rate is 2n and for which the original computer could run a problem of size 1000 in an hour, than a machine ten times faster can run a problem only of size 1003 in an hour! Thus, an exponential growth rate is radically different than the other growth rates shown in Figure 3.3. The signiﬁcance of this difference is explored in Chapter 17.

Instead of buying a faster computer, consider what happens if you replace an algorithm whose running time is proportional to n2 with a new algorithm whose running time is proportional to n log n. In the graph of Figure 3.1, a ﬁxed amount of time would appear as a horizontal line. If the line for the amount of time available to solve your problem is above the point at which the curves for the two growth rates in question meet, then the algorithm whose running time grows less quickly is faster. An algorithm with running time T(n) = n2 requires 1024 × 1024 =

1, 048, 576 time steps for an input of size n = 1024. An algorithm with running time T(n) = n log n requires 1024 × 10 = 10, 240 time steps for an input of size n = 1024, which is an improvement of much more than a factor of ten when compared to the algorithm with running time T(n) = n2 . Because n2 > 10n log n whenever n > 58, if the typical problem size is larger than 58 for this example, then you would be much better off changing algorithms instead of buying a computer ten times faster. Furthermore, when you do buy a faster computer, an algorithm with a slower growth rate provides a greater beneﬁt in terms of larger problem size that can run in a certain time on the new computer.

3.4

Asymptotic Analysis

Despite the larger constant for the curve labeled 10n in Figure 3.1, the curve labeled

2n2 crosses it at the relatively small value of n = 5. What if we double the value of the constant in front of the linear equation? As shown in the graph, the curve labeled 20n is surpassed by the curve labeled 2n2 once n = 10. The additional factor of two for the linear growth rate does not much matter; it only doubles the x-coordinate for the intersection point. In general, changes to a constant factor in either equation only shift where the two curves cross, not whether the two curves cross. When you buy a faster computer or a faster compiler, the new problem size that can be run in a given amount of time for a given growth rate is larger by the same factor, regardless of the constant on the running-time equation. The time curves for two algorithms with different growth rates still cross, regardless of their running-time equation constants. For these reasons, we usually ignore the constants

68

Chap. 3 Algorithm Analysis

when we want an estimate of the running time or other resource requirements of an algorithm. This simpliﬁes the analysis and keeps us thinking about the most important aspect: the growth rate. This is called asymptotic algorithm analysis.

To be precise, asymptotic analysis refers to the study of an algorithm as the input size “gets big” or reaches a limit (in the calculus sense). However, it has proved to be so useful to ignore all constant factors that asymptotic analysis is used for most algorithm comparisons.

It is not always reasonable to ignore the constants. When comparing algorithms meant to run on small values of n, the constant can have a large effect. For example, if the problem is to sort a collection of exactly ﬁve records, then an algorithm designed for sorting thousands of records is probably not appropriate, even if its asymptotic analysis indicates good performance. There are rare cases where the constants for two algorithms under comparison can differ by a factor of 1000 or more, making the one with lower growth rate impractical for most purposes due to its large constant. Asymptotic analysis is a form of “back of the envelope” estimation for algorithm resource consumption. It provides a simpliﬁed model of the running time or other resource needs of an algorithm. This simpliﬁcation usually helps you understand the behavior of your algorithms. Just be aware of the limitations to asymptotic analysis in the rare situation where the constant is important.

3.4.1

Upper Bounds

Several terms are used to describe the running-time equation for an algorithm.

These terms — and their associated symbols — indicate precisely what aspect of the algorithm’s behavior is being described. One is the upper bound for the growth of the algorithm’s running time. It indicates the upper or highest growth rate that the algorithm can have.

To make any statement about the upper bound of an algorithm, we must be making it about some class of inputs of size n. We measure this upper bound nearly always on the best-case, average-case, or worst-case inputs. Thus, we cannot say, “this algorithm has an upper bound to its growth rate of n2 .” We must say something like, “this algorithm has an upper bound to its growth rate of n2 in the average case.”

Because the phrase “has an upper bound to its growth rate of f (n)” is long and often used when discussing algorithms, we adopt a special notation, called big-Oh notation. If the upper bound for an algorithm’s growth rate (for, say, the worst case) is f (n), then we would write that this algorithm is “in the set O(f (n))in the worst case” (or just “in O(f (n))in the worst case”). For example, if n2 grows as

Sec. 3.4 Asymptotic Analysis

69

fast as T(n) (the running time of our algorithm) for the worst-case input, we would say the algorithm is “in O(n2 ) in the worst case.”

The following is a precise deﬁnition for an upper bound. T(n) represents the true running time of the algorithm. f (n) is some expression for the upper bound.

For T(n) a non-negatively valued function, T(n) is in set O(f (n)) if there exist two positive constants c and n0 such that T(n) ≤ cf (n) for all n > n0 .

Constant n0 is the smallest value of n for which the claim of an upper bound holds true. Usually n0 is small, such as 1, but does not need to be. You must also be able to pick some constant c, but it is irrelevant what the value for c actually is.

In other words, the deﬁnition says that for all inputs of the type in question (such as the worst case for all inputs of size n) that are large enough (i.e., n > n0 ), the algorithm always executes in less than cf (n) steps for some constant c.

Example 3.4 Consider the sequential search algorithm for ﬁnding a speciﬁed value in an array of integers. If visiting and examining one value in the array requires cs steps where cs is a positive number, and if the value we search for has equal probability of appearing in any position in the array, then in the average case T(n) = cs n/2. For all values of n > 1, cs n/2 ≤ cs n. Therefore, by the deﬁnition, T(n) is in O(n) for n0 = 1 and c = cs .

Example 3.5 For a particular algorithm, T(n) = c1 n2 + c2 n in the average case where c1 and c2 are positive numbers. Then, c1 n2 + c2 n ≤ c1 n2 + c2 n2 ≤ (c1 + c2 )n2 for all n > 1. So, T(n) ≤ cn2 for c = c1 + c2 , and n0 = 1. Therefore, T(n) is in O(n2 ) by the deﬁnition.

Example 3.6 Assigning the value from the ﬁrst position of an array to a variable takes constant time regardless of the size of the array. Thus,

T(n) = c (for the best, worst, and average cases). We could say in this case that T(n) is in O(c). However, it is traditional to say that an algorithm whose running time has a constant upper bound is in O(1).

Just knowing that something is in O(f (n)) says only how bad things can get.

Perhaps things are not nearly so bad. Because we know sequential search is in

O(n) in the worst case, it is also true to say that sequential search is in O(n2 ). But

70

Chap. 3 Algorithm Analysis

sequential search is practical for large n, in a way that is not true for some other algorithms in O(n2 ). We always seek to deﬁne the running time of an algorithm with the tightest (lowest) possible upper bound. Thus, we prefer to say that sequential search is in O(n). This also explains why the phrase “is in O(f (n))” or the notation “∈ O(f (n))” is used instead of “is O(f (n))” or “= O(f (n)).” There is no strict equality to the use of big-Oh notation. O(n) is in O(n2 ), but O(n2 ) is not in O(n).

3.4.2

Lower Bounds

Big-Oh notation describes an upper bound. In other words, big-Oh notation states a claim about the greatest amount of some resource (usually time) that is required by an algorithm for some class of inputs of size n (typically the worst such input, the average of all possible inputs, or the best such input).

Similar notation is used to describe the least amount of a resource that an algorithm needs for some class of input. Like big-Oh notation, this is a measure of the algorithm’s growth rate. Like big-Oh notation, it works for any resource, but we most often measure the least amount of time required. And again, like big-Oh notation, we are measuring the resource required for some particular class of inputs: the worst-, average-, or best-case input of size n.

The lower bound for an algorithm (or a problem, as explained later) is denoted by the symbol Ω, pronounced “big-Omega” or just “Omega.” The following deﬁnition for Ω is symmetric with the deﬁnition of big-Oh.

For T(n) a non-negatively valued function, T(n) is in set Ω(g(n)) if there exist two positive constants c and n0 such that T(n) ≥ cg(n) for all n > n0 .1

1

An alternate (non-equivalent) deﬁnition for Ω is

T(n) is in the set Ω(g(n)) if there exists a positive constant c such that T(n) ≥ cg(n) for an inﬁnite number of values for n.

This deﬁnition says that for an “interesting” number of cases, the algorithm takes at least cg(n) time. Note that this deﬁnition is not symmetric with the deﬁnition of big-Oh. For g(n) to be a lower bound, this deﬁnition does not require that T(n) ≥ cg(n) for all values of n greater than some constant. It only requires that this happen often enough, in particular that it happen for an inﬁnite number of values for n. Motivation for this alternate deﬁnition can be found in the following example.

Assume a particular algorithm has the following behavior:

n for all odd n ≥ 1

T(n) = n2 /100 for all even n ≥ 0

1

From this deﬁnition, n2 /100 ≥ 100 n2 for all even n ≥ 0. So, T(n) ≥ cn2 for an inﬁnite number of values of n (i.e., for all even n) for c = 1/100. Therefore, T(n) is in Ω(n2 ) by the deﬁnition.

71

Sec. 3.4 Asymptotic Analysis

Example 3.7 Assume T(n) = c1 n2 + c2 n for c1 and c2 > 0. Then, c1 n2 + c2 n ≥ c1 n2 for all n > 1. So, T(n) ≥ cn2 for c = c1 and n0 = 1. Therefore, T(n) is in Ω(n2 ) by the deﬁnition.

It is also true that the equation of Example 3.7 is in Ω(n). However, as with big-Oh notation, we wish to get the “tightest” (for Ω notation, the largest) bound possible. Thus, we prefer to say that this running time is in Ω(n2 ).

Recall the sequential search algorithm to ﬁnd a value K within an array of integers. In the average and worst cases this algorithm is in Ω(n), because in both the average and worst cases we must examine at least cn values (where c is 1/2 in the average case and 1 in the worst case).

3.4.3

Θ Notation

The deﬁnitions for big-Oh and Ω give us ways to describe the upper bound for an algorithm (if we can ﬁnd an equation for the maximum cost of a particular class of inputs of size n) and the lower bound for an algorithm (if we can ﬁnd an equation for the minimum cost for a particular class of inputs of size n). When the upper and lower bounds are the same within a constant factor, we indicate this by using

Θ (big-Theta) notation. An algorithm is said to be Θ(h(n)) if it is in O(h(n)) and it is in Ω(h(n)). Note that we drop the word “in” for Θ notation, because there is a strict equality for two equations with the same Θ. In other words, if f (n) is

Θ(g(n)), then g(n) is Θ(f (n)).

Because the sequential search algorithm is both in O(n) and in Ω(n) in the average case, we say it is Θ(n) in the average case.

Given an algebraic equation describing the time requirement for an algorithm, the upper and lower bounds always meet. That is because in some sense we have a perfect analysis for the algorithm, embodied by the running-time equation. For

For this equation for T(n), it is true that all inputs of size n take at least cn time. But an inﬁnite number of inputs of size n take cn2 time, so we would like to say that the algorithm is in Ω(n2 ).

Unfortunately, using our ﬁrst deﬁnition will yield a lower bound of Ω(n) because it is not possible to pick constants c and n0 such that T(n) ≥ cn2 for all n > n0 . The alternative deﬁnition does result in a lower bound of Ω(n2 ) for this algorithm, which seems to ﬁt common sense more closely. Fortunately, few real algorithms or computer programs display the pathological behavior of this example.

Our ﬁrst deﬁnition for Ω generally yields the expected result.

As you can see from this discussion, asymptotic bounds notation is not a law of nature. It is merely a powerful modeling tool used to describe the behavior of algorithms.

72

Chap. 3 Algorithm Analysis

many algorithms (or their instantiations as programs), it is easy to come up with the equation that deﬁnes their runtime behavior. Most algorithms presented in this book are well understood and we can almost always give a Θ analysis for them.

However, Chapter 17 discusses a whole class of algorithms for which we have no

Θ analysis, just some unsatisfying big-Oh and Ω analyses. Exercise 3.14 presents a short, simple program fragment for which nobody currently knows the true upper or lower bounds.

While some textbooks and programmers will casually say that an algorithm is

“order of” or “big-Oh” of some cost function, it is generally better to use Θ notation rather than big-Oh notation whenever we have sufﬁcient knowledge about an algorithm to be sure that the upper and lower bounds indeed match. Throughout this book, Θ notation will be used in preference to big-Oh notation whenever our state of knowledge makes that possible. Limitations on our ability to analyze certain algorithms may require use of big-Oh or Ω notations. In rare occasions when the discussion is explicitly about the upper or lower bound of a problem or algorithm, the corresponding notation will be used in preference to Θ notation.

3.4.4

Simplifying Rules

Once you determine the running-time equation for an algorithm, it really is a simple matter to derive the big-Oh, Ω, and Θ expressions from the equation. You do not need to resort to the formal deﬁnitions of asymptotic analysis. Instead, you can use the following rules to determine the simplest form.

1. If f (n) is in O(g(n)) and g(n) is in O(h(n)), then f (n) is in O(h(n)).

2. If f (n) is in O(kg(n)) for any constant k > 0, then f (n) is in O(g(n)).

3. If f1 (n) is in O(g1 (n)) and f2 (n) is in O(g2 (n)), then f1 (n) + f2 (n) is in

O(max(g1 (n), g2 (n))).

4. If f1 (n) is in O(g1 (n)) and f2 (n) is in O(g2 (n)), then f1 (n)f2 (n) is in

O(g1 (n)g2 (n)).

The ﬁrst rule says that if some function g(n) is an upper bound for your cost function, then any upper bound for g(n) is also an upper bound for your cost function. A similar property holds true for Ω notation: If g(n) is a lower bound for your cost function, then any lower bound for g(n) is also a lower bound for your cost function. Likewise for Θ notation.

The signiﬁcance of rule (2) is that you can ignore any multiplicative constants in your equations when using big-Oh notation. This rule also holds true for Ω and

Θ notations.

73

Sec. 3.4 Asymptotic Analysis

Rule (3) says that given two parts of a program run in sequence (whether two statements or two sections of code), you need consider only the more expensive part. This rule applies to Ω and Θ notations as well: For both, you need consider only the more expensive part.

Rule (4) is used to analyze simple loops in programs. If some action is repeated some number of times, and each repetition has the same cost, then the total cost is the cost of the action multiplied by the number of times that the action takes place.

This rule applies to Ω and Θ notations as well.

Taking the ﬁrst three rules collectively, you can ignore all constants and all lower-order terms to determine the asymptotic growth rate for any cost function.

The advantages and dangers of ignoring constants were discussed near the beginning of this section. Ignoring lower-order terms is reasonable when performing an asymptotic analysis. The higher-order terms soon swamp the lower-order terms in their contribution to the total cost as n becomes larger. Thus, if T(n) = 3n4 + 5n2 , then T(n) is in O(n4 ). The n2 term contributes relatively little to the total cost.

Throughout the rest of this book, these simplifying rules are used when discussing the cost for a program or algorithm.

3.4.5

Classifying Functions

Given functions f (n) and g(n) whose growth rates are expressed as algebraic equations, we might like to determine if one grows faster than the other. The best way to do this is to take the limit of the two functions as n grows towards inﬁnity, f (n)

.

n→∞ g(n) lim If the limit goes to ∞, then f (n) is in Ω(g(n)) because f (n) grows faster. If the limit goes to zero, then f (n) is in O(g(n)) because g(n) grows faster. If the limit goes to some constant other than zero, then f (n) = Θ(g(n)) because both grow at the same rate.

Example 3.8 If f (n) = 2n log n and g(n) = n2 , is f (n) in O(g(n)),

Ω(g(n)), or Θ(g(n))? Because n2 n

=

,

2n log n

2 log n we easily see that n2 =∞ n→∞ 2n log n lim 74

Chap. 3 Algorithm Analysis

because n grows faster than 2 log n. Thus, n2 is in Ω(2n log n).

3.5

Calculating the Running Time for a Program

This section presents the analysis for several simple code fragments.

Example 3.9 We begin with an analysis of a simple assignment statement to an integer variable. a = b;

Because the assignment statement takes constant time, it is Θ(1).

Example 3.10 Consider a simple for loop. sum = 0; for (i=1; i…...

Premium Essay

...that has made his work popular with programmers for many years. Michael Schidlowsky and Sedgewick have developed concise new Java implementations that both express the methods in a natural and direct manner and also can be used in real applications. Algorithms in Java, Third Edition, Part 5: Graph Algorithms is the second book in Sedgewick's thoroughly revised and rewritten series. The first book, Parts 1-4, addresses fundamental algorithms, data structures, sorting, and searching. A forthcoming third book will focus on strings, geometry, and a range of advanced algorithms. Each book's expanded coverage features new algorithms and implementations, enhanced descriptions and diagrams, and a wealth of new exercises for polishing skills. The natural match between Java classes and abstract data type (ADT) implementations makes the code more broadly useful and relevant for the modern object-oriented programming environment. The Web site for this book (www.cs.princeton.edu/~rs/) provides additional source code for programmers along with a variety of academic support materials for educators. Coverage includes: A complete overview of graph properties and types Diagraphs and DAGs Minimum spanning trees Shortest paths Network flows Diagrams, sample Java code, and detailed algorithm descriptions A landmark revision, Algorithms in Java, Third Edition, Part 5 provides a complete tool set for programmers to implement, debug, and use graph algorithms......

Words: 281 - Pages: 2

Premium Essay

...tutorial explains the installation and usage of the Java programming language. It also contains examples for standard programming tasks. 1. Introduction to Java 1.1. History Java is a programming language created by James Gosling from Sun Microsystems in 1991. The first publicly available version of Java (Java 1.0) was released in 1995. Over time new enhanced versions of Java have been released. The current version of Java is Java 1.7 which is also known as Java 7. From the Java programming language the Java platform evolved. The Java platform allows that the program code is written in other languages than the Java programming language and still runs on the Java virtual machine. 1.2. Java Virtual machine The Java virtual machine (JVM) is a software implementation of a computer that executes programs like a real machine. The Java virtual machine is written specifically for a specific operating system, e.g. for Linux a special implementation is required as well as for Windows. Java programs are compiled by the Java compiler into so-called bytecode. The Java virtual machine interprets this bytecode and executes the Java program. 1.3. Java Runtime Environment vs. Java Development Kit Java comes in two flavors, the Java Runtime Environment (JRE) and the Java Development Kit (JDK). The Java runtime environment (JRE) consists of the JVM and the Java class libraries and contains the necessary functionality to start Java programs. The JDK contains in......

Words: 662 - Pages: 3

Free Essay

...A Java applet is an applet delivered to the users in the form of Java bytecode. Java applets can run in a web browser using a Java Virtual Machine or Sun’s Applet Viewer, a stand-alone tool for testing applets. What this means is that is that it is own program language that is different than JavaScript, but we will get into that later on. Java applet is program language that is written in different types of bytecode, other than just the normal Java language, it is also written in Jython, JRuby, or Eiffel. Now the Java applet that I found is http://www.schubart.net/rc / which is a simple Rubik’s Cube Applet that allows you to play with the Rubik’s Cube. The way that it allows you to play is by at first scrambling the cube, then you gets to try and rebuild it back. Now it keeps track of how many moves that you do in order to try and figure it out. The controls for the Rubik’s Cube are very simple in the fact that the mouse is how you make the cube move. Now you can see that as you play with it the Rubik’s Cube moves very fast to the point that you almost don’t see it change. This is due to the fact that the Java applet is much faster than normal JavaScript by about 2011 times. Now this is a big deal if you are going to be running 3D graphics for the fact that they will run in real time and not slow down in order to load. Now with that last part in mind I believe that Java applet enhances a website for the fact that it allows for much more to go on in your website. What I......

Words: 393 - Pages: 2

Free Essay

...© Prentice Hall and Sun Microsystems. Personal use only; do not redistribute. JSP Scripting Chapter Elements Topics in This Chapter • The purpose of JSP • How JSP pages are invoked • Using JSP expressions to insert dynamic results directly into the output page • Using JSP scriptlets to insert Java code into the method that handles requests for the page • Using JSP declarations to add methods and field declarations to the servlet that corresponds to the JSP page • Predefined variables that can be used within expressions and scriptlets Online version of this first edition of Core Servlets and JavaServer Pages is free for personal use. For more information, please see: • • • Second edition of the book: http://www.coreservlets.com. Sequel: http://www.moreservlets.com. Servlet and JSP training courses from the author: http://courses.coreservlets.com. © Prentice Hall and Sun Microsystems. Personal use only; do not redistribute. Chapter avaServer Pages (JSP) technology enables you to mix regular, static HTML with dynamically generated content from servlets. You simply write the regular HTML in the normal manner, using familiar Web-page-building tools. You then enclose the code for the dynamic parts in special tags, most of which start with . For example, here is a section of a JSP page that results in “Thanks for ordering Core Web Programming” for a URL of http://host/OrderConfirmation.jsp?title=Core+Web+Programming: Thanks for ordering J Separating......

Words: 1383 - Pages: 6

Premium Essay

...we're continuing our Java security research series by analyzing other plug-ins, browser extensions and rich internet applications that are commonly exploited. Our previous research indicated that the current state of Java affairs isn't pretty. At that time, ninety-three percent of enterprises were vulnerable to known Java exploits. Nearly 50 percent of enterprise traffic used a Java version that was more than two years out of date. Through Websense ThreatSeeker Intelligence Cloud analysis we now discover: Only 19 percent of enterprise Windows-based computers ran the latest version of Java (7u25) between August 1-29, 2013. More than 40 percent of enterprise Java requests are from browsers still using outdated Java 6. As a result, more than 80 percent of Java requests are susceptible to two popular new Java exploits: CVE-2013-2473 and CVE-2013-2463. 83.86 percent of enterprise browsers have Java enabled. Nearly 40 percent of users are not running the most up-to-date versions of Flash. In fact, nearly 25 percent of Flash installations are more than six months old, close to 20 percent are outdated by a year and nearly 11 percent are two years old. Our in-depth analysis ran for one month, across multiple verticals and industries. We surveyed millions of real-world web requests for Java usage through our global Websense ThreatSeeker Intelligence Cloud. New Java Exploits and the Neutrino Exploit Kit New Java exploits......

Words: 1745 - Pages: 7

Free Essay

...JAVA: Cost cutting may not be the long-term EPS driver Sun Microsystems, Inc. (JAVA) continues with its resurrection of earnings by treading into positive turf and achieving an operating profit in FY07, the first time since FY01. JAVA recorded three consecutive quarters of positive EPS in FY07 and is now targeting an operating margin of at least 10% in FY09. However, underlying this growth is a scenario that bristles with complexities, and not everything is as rosy as it seems. For instance, growth appears anemic—sales are expected to rise in low single digits this quarter. And it’s not yet clear whether JAVA is making new money through open source or simply finding new ways to save money. All eyes are now on Jonathan I. Schwartz, who took over as CEO of JAVA from Scott McNealy in Apr-06; justifiably so, because Schwartz is not merely seeking to turn around operations by paring payrolls, reducing headcount, and restructuring. He has set out to redefine the way a company can do business in the new Web age. If Schwartz can reestablish Java as a credible trendsetter, it would make for one of the Valley’s more spectacular comebacks. We analyze whether Schwartz’s pronouncements of growth objectives match JAVA’s performance across sectors and whether the targets can be achieved by cost containment rather than revenue growth. We set out to gauge whether this turnaround is a temporary blip or a true resurgence. Management change and new objectives When Schwartz took over...

Words: 1427 - Pages: 6

Premium Essay

...Mohammad Doush Mr. Matthew Robert English 103 13 April 2013 Java the Programming Language Computer is very important in our live, we use computer in everywhere on our live. The doctor uses the computer to see file or pictures of his patients. Also, each engineer uses it in many ways of his work. The teacher in the classroom, employees in the offices and student in their study all of them use computer in them daily live. They are not using the mouse, the keyboard or the scream. They are using the applications by them these applications in the computer are like the soul in the body. The only way to build these applications is programming. To program we need to know one of the programming languages which are very similar each other. If you are professional in one of these languages you can be professional in the other language in a short period of time. It is acceptable if you have the same application written with Java once and with C++ or C sharp at the same time. So for this reason you cannot say that a programming language is better than others. There are three types of programming languages procedural, functional and object-oriented languages. The most uses of these languages are object-oriented and one of these languages is Java you can write any application you need using it. Also you can translate any application to its word. The message of the High-Level programming languages such as Algol and Pascal in first programming revolution was...

Words: 2352 - Pages: 10

Premium Essay

...deliver this program, Accenture is partnering with Udacity, an online university focused on bridging the gap between real-world skills, relevant education, and employment. Participants are awarded scholarships for the Accenture Veteran Technology Training Program, which grants access to Udacity’s Intro to Java Programming online program. Accenture is offering the premium version of the course, where coaches actively support students and students earn a Verified Certificate. In this introductory course, you'll learn and practice essential computer science concepts using the Java programming language. You'll learn about Object Oriented Programming, a technique that allows you to use code written by other programmers in your own programs. You'll put your new Java programming skills to the test by solving real-world problems faced by software engineers. Upon successful completion of the training course, participants will be granted an interview with Accenture for the Entry Level Software Engineering Associate position, which is part of our global network of technology experts. If ultimately hired for this full-time position, you will apply your military background and new Java skills to assist with the development, delivery and management of technology-based business solutions. You will continue to develop new skills, through Accenture’s robust training curriculum....

Words: 264 - Pages: 2

Free Essay

...Eclipse and Java for Total Beginners Tutorial Companion Document Eclipse And Java For Total Beginners Companion Tutorial Document By Mark Dexter Table of Contents Introduction........................................................................................................... .............................2 . Tutorial Target Audience.....................................................................................................................2 Tutorial Objectives..............................................................................................................................2 Why learn Java with Eclipse?.............................................................................................................3 Topics Covered...................................................................................................................................3 Tutorial Approach............................................................................................................... ................3 . Getting The Most From This Tutorial..................................................................................................3 Sample Java Application – Personal Lending Library........................................................................4 Downloading and Installing Eclipse ...................................................................................................4 Playing the Lessons...........................

Words: 7556 - Pages: 31

Free Essay

...Object Oriented System Software Engineering with JAVA Coursework The coursework is 50% of the assessment for the module. Learning Outcomes Have a through knowledge of one object orientated method down to detailed design. Have the experience to implement an object oriented design in an object oriented language. Assessment Criteria IMPORTANT: This is an individual assignment, all submitted components must be your own work or appropriately accredited. The assignment is scenario based (see attached scenario). You are encouraged to make any assumptions you deem necessary when analyzing he requirements outlined in the scenario, however, these must be clearly stated in the report. Your report should address the following three tasks: Task 1: Design the system required using the UML method. Task 2: Implement the system (or part of , see Grading below) using JAVA. Task 3: Objectively evaluate your solution including an appraisal of the suitability of UML and JAVA as tools for implementing object oriented solutions. Deliverable • You should submit a hard-copy and an electronic copy of the assignment • The submission should not be longer that 40 pages including diagrams but excluding the Appendices. • Instructions on how to install and run the program should be provided in an Appendix. Grading Distinction-Grade A (70%) To achieve a Distinction, you must successfully meet all of the criteria for a Merit and Task 1 • The design is comprehensive......

Words: 1035 - Pages: 5

Premium Essay

...A Comparison between Java and .Net Languages Introduction Java and .Net provide technologies that enable skilled developers to build quality enterprise applications. These technologies are rarely picked based on performance alone. There are many factors to consider when choosing Java or .Net. These considerations are often the deciding factor when choosing one or both of these platforms. Java Java is kenned as both a programming language and a development platform. It was first developed by Sun Microsystems in 1991 and subsequently relinquished in 1995. To help to make the language more accepted and accessible, Sun Microsystems developed it as an object oriented language with a syntax that is very similar to C++. (Java vs. .NET, 2007) Sun Microsystems decided to create this new platform out of a desire to be able to write programs only once that could be run on any system. (James) The Java 2 platform was launched in December 1998. This was a major amelioration of the platform, and included incipient graphics, user interface, and enterprise capabilities. This upgrade was over seven times as large as the initial Java 1.0 release and marked the maturity of the Java platform. (What is java?) Within the Java 2 platform there are 3 editions: • The Java 2 Standard Edition (J2SE) Provides the essential compiler, tools, runtimes, and APIs for writing, deploying, and running applets and applications. • The Java 2 Enterprise Edition (J2EE) Defines a standard for developing......

Words: 2279 - Pages: 10

Premium Essay

...Software Design Introduction to the Java Programming Language Material drawn from [JDK99,Sun96,Mitchell99,Mancoridis00] Software Design (Java Tutorial) © SERG Java Features • “Write Once, Run Anywhere.” • Portability is possible because of Java virtual machine technology: – Interpreted – JIT Compilers • Similar to C++, but “cleaner”: – No pointers, typedef, preprocessor, structs, unions, multiple inheritance, goto, operator overloading, automatic coercions, free. Software Design (Java Tutorial) © SERG Java Subset for this Course • We will focus on a subset of the language that will allow us to develop a distributed application using CORBA. • Input and output will be character (terminal) based. • For detailed treatment of Java visit: – http://java.sun.com/docs/books/tutorial/index.html Software Design (Java Tutorial) © SERG Java Virtual Machine • Java programs run on a Java Virtual Machine. • Features: – – – – – Security Portability Superior dynamic resource management Resource location transparency Automatic garbage collection Software Design (Java Tutorial) © SERG The Java Environment Java Source File (*.java) Java Compiler (javac) Java Bytecode File (*.class) Java Virtual Machine (java) Software Design (Java Tutorial) © SERG Program Organization Source Files (.java) Running Application Running Applet JAVA BYTECODE COMPILER Class Files (.class) JAVA VIRTUAL MACHINE WEB BROWSER Software Design (Java Tutorial) © SERG Program Organization Standards •...

Words: 5230 - Pages: 21

Premium Essay

...Release Team[oR] 2001 [x] java Java 2: The Complete Reference by Patrick Naughton and Herbert Schildt Osborne/McGraw-Hill © 1999, 1108 pages ISBN: 0072119764 This thorough reference reads like a helpful friend. Includes servlets, Swing, and more. Table of Contents Back Cover Synopsis by Rebecca Rohan Java 2: The Complete Reference blends the expertise found in Java 1: The Complete Reference with Java 2 topics such as "servlets" and "Swing." As before, there's help with Java Beans and migrating from C++ to Java. A special chapter gives networking basics and breaks out networking-related classes. This book helps you master techniques by doing as well as reading. Projects include a multi-player word game with attention paid to network security. The book is updated where appropriate throughout, and the rhythm of text, code, tables, and illustrations is superb. It's a valuable resource for the developer who is elbow-deep in demanding projects. Table of Contents Java 2 Preface - 7 Part l The Java Language - The Complete Reference - 4 Chapter 1 Chapter 2 Chapter 3 Chapter 4 Chapter 5 Chapter 6 Chapter 7 Chapter 8 Chapter 9 hapter 10 - The Genesis of Java - 9 - An Overview of Java - 20 - Data Types, Variables, and Arrays - 36 - Operators - 57 - Control Statements - 75 - Introducing Classes - 94 - A Closer Look at Methods and Classes - 111 - Inheritance - 134 - Packages and Interfaces - 156 - Exception Handling - 174 Chapter 11 - Multithreaded......

Words: 78285 - Pages: 314

Premium Essay

...November 30, 2011 Java Security Jessica Shaw: 628 Robert Grimsley: 596 Java is a programming language developed by Sun Microsystems in 1995, which is now called Oracle. The language itself is derived from the languages C and C++. Java is a simple language compared to C and C++; however they are all object-oriented languages. The language was designed to help minimize the amount of space and take up as little of your computer’s hardware resources as possible. The language was designed upon five key goals, and they are as followed: * It should be "simple, object-oriented and familiar" * It should be "robust and secure" * It should be "architecture-neutral and portable" * It should execute with "high performance" * It should be "interpreted, threaded, and dynamic" Java is used for a multitude of things. Java allows one to play virtual video games, view and design 3D photos, and many other interactive topics on the internet. Java is the main programming language for mobile devices as well as smart phones such as Android by google. Java is a very secure and reliable language that is used by over 800 billion people. Without the Java languages there are millions of applets and interactive applications on websites that wouldn’t preform properly without it. Java is compiled by byte code that allows for the program to be run through the Java Virtual Machine, or JVM. The compiling method allows......

Words: 2557 - Pages: 11

Premium Essay

...JMaster list of Java interview questions - 115 questions By admin | July 18, 2005 115 questions total, not for the weak. Covers everything from basics to JDBC connectivity, AWT and JSP. 1. What is the difference between procedural and object-oriented programs?- a) In procedural program, programming logic follows certain procedures and the instructions are executed one after another. In OOP program, unit of program is object, which is nothing but combination of data and code. b) In procedural program, data is exposed to the whole program whereas in OOPs program, it is accessible with in the object and which in turn assures the security of the code. 2. What are Encapsulation, Inheritance and Polymorphism?- Encapsulation is the mechanism that binds together code and data it manipulates and keeps both safe from outside interference and misuse. Inheritance is the process by which one object acquires the properties of another object. Polymorphism is the feature that allows one interface to be used for general class actions. 3. What is the difference between Assignment and Initialization?- Assignment can be done as many times as desired whereas initialization can be done only once. 4. What is OOPs?- Object oriented programming organizes a program around its data, i. e. , objects and a set of well defined interfaces to that data. An object-oriented program can be characterized as data controlling 5. access to code. What are Class,......

Words: 6762 - Pages: 28