416 44 19MB
English Pages [1004] Year 2018
BigC++
Cay Horstmann Late Objects 3/e
BigC++
Late Objects 3/e
Cay Horstmann San Jose State University
PUBLISHER EDITORIAL DIRECTOR DEVELOPMENTAL EDITOR ASSISTANT DEVELOPMENT EDITOR EXECUTIVE MARKETING MANAGER SENIOR PRODUCTION EDITOR SENIOR CONTENT MANAGER EDITORIAL ASSISTANT SENIOR DESIGNER SENIOR PHOTO EDITOR PRODUCTION MANAGEMENT COVER IMAGE
Laurie Rosatone Don Fowley Cindy Johnson Ryann Dannelly Dan Sayre Laura Abrams Valerie Zaborski Anna Pham Tom Nery Billy Ray Cindy Johnson © 3alexd/Getty Images
This book was set in Stempel Garamond LT Std by Publishing Services, and printed and bound by Quad/ Graphics, Versailles. The cover was printed by Quad/Graphics, Versailles. This book is printed on acid-free paper. ∞ Founded in 1807, John Wiley & Sons, Inc. has been a valued source of knowledge and understanding for more than 200 years, helping people around the world meet their needs and fulfill their aspirations. Our company is built on a foundation of principles that include responsibility to the communities we serve and where we live and work. In 2008, we launched a Corporate Citizenship Initiative, a global effort to address the environmental, social, economic, and ethical challenges we face in our business. Among the issues we are addressing are carbon impact, paper specifications and procurement, ethical conduct within our business and among our vendors, and community and charitable support. For more information, please visit our website: www.wiley.com/go/ citizenship. Copyright © 2018, 2012, 2009 John Wiley & Sons, Inc. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, scanning or otherwise, except as permitted under Sections 107 or 108 of the 1976 United States Copyright Act, without either the prior written permission of the Publisher, or authorization through payment of the appropriate per-copy fee to the Copyright Clearance Center, Inc. 222 Rosewood Drive, Danvers, MA 01923, website www.copyright.com. Requests to the Publisher for permission should be addressed to the Permissions Department, John Wiley & Sons, Inc., 111 River Street, Hoboken, NJ 07030-5774, (201) 748-6011, fax (201) 748-6008, website http://www.wiley.com/go/permissions. Evaluation copies are provided to qualified academics and professionals for review purposes only, for use in their courses during the next academic year. These copies are licensed and may not be sold or transferred to a third party. Upon completion of the review period, please return the evaluation copy to Wiley. Return instructions and a free of charge return shipping label are available at www.wiley.com/go/returnlabel. If you have chosen to adopt this textbook for use in your course, please accept this book as your complimentary desk copy. Outside of the United States, please contact your local representative.
ISBN 13: 978-1-119-40297-8 The inside back cover will contain printing identification and country of origin if omitted from this page. In addition, if the ISBN on the back cover differs from the ISBN on this page, the one on the back cover is correct. Printed in the United States of America. 10 9 8 7 6 5 4 3 2 1
P R E FA C E This book is an introduction to C++ and computer programming that focuses on the essentials—and on effective learning. The book is designed to serve a wide range of student interests and abilities and is suitable for a first course in programming for computer scientists, engineers, and students in other disciplines. No prior programming experience is required, and only a modest amount of high school algebra is needed. Here are the key features of this book: Present fundamentals first.
This book uses the C++ programming language as a vehicle for introducing computer science concepts. A substantial subset of the C++ language is covered, focusing on the modern features of standard C++ that make students productive. The book takes a traditional route, first stressing control structures, procedural decomposition, and array algorithms. Objects are used when appropriate in the early chapters. Students start designing and implementing their own classes in Chapter 9. Guidance and worked examples help students succeed.
Beginning programmers often ask “How do I start? Now what do I do?” Of course, an activity as complex as programming cannot be reduced to cookbook-style instructions. However, step-by-step guidance is immensely helpful for building confidence and providing an outline for the task at hand. “Problem Solving” sections stress the importance of design and planning. “How To” guides help students with common programming tasks. Additional Worked Examples are available in the E-Text or online. Tip: Source files for all of the program examples in the book, including the Worked Examples, are provided with the source code for this book. Download the files to your computer for easy access as you work through the chapters. Practice makes perfect.
Of course, programming students need to be able to implement nontrivial programs, but they first need to have the confidence that they can succeed. The Enhanced E-Text immerses students in activities designed to foster in-depth learning. Students don’t just watch animations and code traces, they work on generating them. The activities provide instant feedback to show students what they did right and where they need to study more. A wealth of practice opportunities, including code completion questions and skill-oriented multiple-choice questions, appear at the end of each section, and each chapter ends with well-crafted review exercises and programming projects. Problem solving strategies are made explicit.
Practical, step-by-step illustrations of techniques help students devise and evaluate solutions to programming problems. Introduced where they are most relevant, these strategies address barriers to success for many students. Strategies included are: • Algorithm Design (with pseudocode) • First Do It By Hand (doing sample calculations by hand) • Flowcharts iii
iv Preface
• • • • • • • • • • • • •
Selecting Test Cases Hand-Tracing Storyboards Solve a Simpler Problem First Reusable Functions Stepwise Refinement Adapting Algorithms Discovering Algorithms by Manipulating Physical Objects Draw a Picture (pointer diagrams) Tracing Objects (identifying state and behavior) Discovering Classes Thinking Recursively Estimating the Running Time of an Algorithm
A visual approach motivates the reader and eases navigation.
Photographs present visual analogies that explain the nature and behavior of computer concepts. Step-by-step figures illustrate complex program operations. Syntax boxes and example tables present a variety of typical and special cases in a compact format. It is easy to get the “lay of the land” by browsing the visuals, before focusing on the textual material. Focus on the essentials while being technically accurate.
© Terraxplorer/iStockphoto.
Visual features help the reader
An encyclopedic coverage is not helpful for a with navigation. beginning programmer, but neither is the opposite—reducing the material to a list of simplistic bullet points. In this book, the essentials are presented in digestible chunks, with separate notes that go deeper into good practices or language features when the reader is ready for the additional information. You will not find artificial over-simplifications that give an illusion of knowledge. Reinforce sound engineering practices.
A multitude of useful tips on software quality and common errors encourage the development of good programming habits. The focus is on test-driven development, encouraging students to test their programs systematically. Engage with optional engineering and business exercises.
End-of-chapter exercises are enhanced with problems from scientific and business domains. Designed to engage students, the exercises illustrate the value of programming in applied fields.
Preface v
New to This Edition Updated for Modern Versions of C++ A number of features of the C++ 2011 and C++ 2014 standards are described either as recommended “best practice” or as Special Topics.
New and Reorganized Topics The book now supports two pathways into object-oriented programming and inheritance. Pointers and structures can be covered before introducing classes. Alternatively, pointers can be deferred until after the implementation of classes. This edition further supports a second course in computer science by adding coverage of the implementation of common data structures and algorithms. A sequence of Worked Examples and exercises introduces “media computation,” such as generating and modifying images, sounds, and animations.
Lower-Cost, Interactive Format This third edition is published as a lower-cost Enhanced E-Text that supports active learning through a wealth of interactive activities. These activities engage and prepare students for independent programming and the Review Exercises, Practice Exercises, and Programming Projects at the end of each E-Text chapter. The Enhanced E-Text may also be bundled with an Abridged Print Companion, which is a bound book that contains the entire text for reference, but without exercises or practice material. Interactive learning solutions are expanding every day, so to learn more about these options or to explore other options to suit your needs, please contact your Wiley account manager (www.wiley.com/go/whosmyrep) or visit the product information page for this text on wiley.com (http://wiley.com/college/sc/horstmann). The Enhanced E-Text is designed to enable student practice without the instructor assigning the interactivities or recording their scores. If you are interested in assigning and grading students’ work on them, ask your Wiley Account Manager about the online course option implemented in the Engage Learning Management System. The Engage course supports the assignment and automatic grading of the interactivities. Engage access includes access to the Enhanced E-Text.
Features in the Enhanced E-Text The interactive Enhanced E-Text guides students from the basics to writing complex programs. After they read a bit, they can try all of the interactive exercises for that section. Active reading is an engaging way for students to ensure that students are prepared before going to class. There five types of interactivities: Code Walkthrough Code Walkthrough activities ask students to trace through a
segment of code, choosing which line will be executed next and entering the new values of variables changed by the code’s execution. This activity simulates the handtracing problem solving technique taught in Chapters 3 and 4—but with immediate feedback.
vi Preface Example Table Example table activities make the student the active participant in
building up tables of code examples similar to those found in the book. The tables come in many different forms. Some tables ask the student to determine the output of a line of code, or the value of an expression, or to provide code for certain tasks. This activity helps students assess their understanding of the reading—while it is easy to go back and review.
Algorithm Animation An algorithm animation shows the essential steps of an algorithm. However, instead of passively watching, students get to predict each step. When finished, students can start over with a different set of inputs. This is a surprisingly effective way of learning and remembering algorithms. Rearrange Code Rearrange code activities ask the student to arrange lines of code by dragging them from the list on the right to the area at left so that the resulting code fulfills the task described in the problem. This activity builds facility with coding structure and implementing common algorithms. Object Diagram Object diagram activities ask the student to create a memory diagram to illustrate how variables and objects are initialized and updated as sample code executes. The activity depicts variables, objects, and references in the same way as the figures in the book. After an activity is completed, pressing “Play” replays the animation. This activity goes beyond hand-tracing to illuminate what is happening in memory as code executes. Code Completion Code completion activities ask the student to finish a partiallycompleted program, then paste the solution into CodeCheck (a Wiley-based online code evaluator) to learn whether it produces the desired result. Tester classes on the CodeCheck site run and report whether the code passed the tests. This activity serves as a skill-building lab to better prepare the student for writing programs from scratch.
A Tour of the Book This book is intended for a two-semester introduction to programming that may also include algorithms and data structures. The organization of chapters offers the same flexibility as the previous edition; dependencies among the chapters are also shown in Figure 1.
Part A: Fundamentals (Chapters 1–8) The first six chapters follow a traditional approach to basic programming concepts. Students learn about control structures, stepwise refinement, and arrays. Objects are used only for input/output and string processing. Input/output is first covered in Chapter 2, which may be followed by an introduction to reading and writing text files in Section 8.1. In a course for engineers with a need for systems and embedded programming, you will want to cover Chapter 7 on pointers. Sections 7.1 and 7.4 are sufficient for using pointers with polymorphism in Chapter 10. File processing is the subject of Chapter 8. Section 8.1 can be covered sooner for an introduction to reading and writing text files. The remainder of the chapter gives additional material for practical applications.
Preface vii
Part B: Object-Oriented Design (Chapters 9–10) After students have gained a solid foundation, they are ready to tackle the implementation of classes. Chapters 9 and 10 introduce the object-oriented features of C++. Chapter 9 introduces class design and implementation. Chapter 10 covers inheritance and polymorphism. By the end of these chapters, students will be able to implement programs with multiple interacting classes.
Part C: Data Structures and Algorithms (Chapters 11–17) Chapters 11–17 cover algorithms and data structures at a level suitable for beginning students. Recursion, in Chapter 11, starts with simple examples and progresses
Fundamentals
1. Introduction
Object-Oriented Design Data Structures & Algorithms
2. Fundamental Data Types
3. Decisions
4. Loops
Section 8.1 contains the core material
7. Pointers
Sections 7.1 and 7.4 are required
8. Streams
5. Functions
A gentle introduction to recursion is optional.
6. Arrays 6. Iteration and Vectors
11. Recursion
9. Classes
10. Inheritance
13. Advanced C++ 14. Linked Lists, Stacks and Queues
Section 15.1 is required
15. Sets, Maps and Hash Tables
16. Trees
Figure 1 Chapter Dependencies
17. Priority Queues and Heaps
12. Sorting and Searching
viii Preface
to meaningful applications that would be difficult to implement iteratively. Chapter 12 covers quadratic sorting algorithms as well as merge sort, with an informal introduction to big-Oh notation. Chapter 13 introduces advanced C++ features that are required for implementing data structures, including templates and memory management. Chapters 14–17 cover linear and tree-based data structures. Students learn how to use the standard C++ library versions. They then study the implementations of these data structures and analyze their efficiency. Any subset of these chapters can be incorporated into a custom print version of this text; ask your Wiley sales representative for details, or visit customselect.wiley.com to create your custom order.
Appendices Appendices A and B summarize C++ reserved words and operators. Appendix C lists character escape sequences and ASCII character code values. Appendix D documents all of the library functions and classes used in this book. Appendix E contains a programming style guide. Using a style guide for program ming assignments benefits students by directing them toward good habits and reducing gratuitous choice. The style guide is available in electronic form on the book’s companion web site so that instructors can modify it to reflect their preferred style. Appendix F introduces common number systems used in computing.
Web Resources This book is complemented by a complete suite of online resources. Go to www.wiley. com/go/bclo3 to visit the online companion sites, which include • Source code for all example programs in the book and its Worked Examples, plus additional example programs. • Worked Examples that apply the problem-solving steps in the book to other realistic examples. • Lecture presentation slides (for instructors only). • Solutions to all review and programming exercises (for instructors only). • A test bank that focuses on skills, not just terminology (for instructors only). This extensive set of multiple-choice questions can be used with a word processor or imported into a course management system. • “CodeCheck” assignments that allow students to work on programming problems presented in an innovative online service and receive immediate feedback. Instructors can assign exercises that have already been prepared, or easily add their own. Visit http://codecheck.it to learn more.
Pointers in the print companion describe what students will find in their E-Text or online.
EXAMPLE CODE
WORKED EXAMPLE 2.1 Computing Travel Time Learn how to develop a hand calculation to compute the time that a robot requires to retrieve an item from rocky terrain. See your . E-Text or visit wiley.com/go/bclo3
See how_to_1/scores_vector in your companion code for a solution using vectors instead of arrays.
Courtesy of NASA.
Walkthrough ix
A Walkthrough of the Learning Aids The pedagogical elements in this book work together to focus on and reinforce key concepts and fundamental principles of programming, with additional tips and detail organized to support and deepen these fundamentals. In addition to traditional features, such as chapter objectives and a wealth of exercises, each chapter contains elements geared to today’s visual learner.
106
Throughout each chapter, margin notes show where new concepts are introduced and provide an outline of key ideas.
Chapter 4 Loops
4.3 The for Loop The for loop is used when a value runs from a starting point to an ending point with a constant increment or decrement.
It often happens that you want to execute a sequence of statements a given number of times. You can use a while loop that is controlled by a counter, as in the following example: counter = 1; // Initialize the counter while (counter character] = prefix; } else { fill_encoding_map(map, prefix + "0", n->left); fill_encoding_map(map, prefix + "1", n->right); } }
Building a Huffman Tree WE16-7 77 Node* HuffmanTree::remove_min(vector& nodes) const 78 { int last = nodes.size() - 1; 79 if (last == -1) { return nullptr; } 80 int min_pos = 0; 81 for (int i = 1; i frequency < nodes[min_pos]->frequency) 84 { 85 min_pos = i; 86 } 87 } 88 Node* result = nodes[min_pos]; 89 nodes[min_pos] = nodes[last]; 90 nodes.pop_back(); 91 return result; 92 93 }
worked_example_1/huffman_demo.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
#include #include #include #include
"huffman_tree.h"
using namespace std; string encode(string to_encode, const unordered_map& encoding_map) { string result = ""; for (int i = 0; i < to_encode.length(); i++) { char ch = to_encode[i]; string encoded = encoding_map.at(ch); result = result + encoded; } return result; } int main() { unordered_map frequency_map; frequency_map['A'] = 2089; frequency_map['E'] = 576; frequency_map['H'] = 357; frequency_map['I'] = 671; frequency_map['K'] = 849; frequency_map['L'] = 354; frequency_map['M'] = 259; frequency_map['N'] = 660; frequency_map['O'] = 844; frequency_map['P'] = 239; frequency_map['U'] = 472; frequency_map['W'] = 74; frequency_map['\''] = 541; HuffmanTree tree(frequency_map); unordered_map encoding_map = tree.encoding_map(); string encoded = encode("ALOHA", encoding_map);
WE16-8 Chapter 16 41 42 43 44 45 }
cout parent = nullptr; root = replacement;
}
} else if (to_be_replaced == to_be_replaced->parent->left) { to_be_replaced->parent->set_left_child(replacement); } else { to_be_replaced->parent->set_right_child(replacement); }
Insertion Insertion is handled as it is in a binary search tree. We insert a red node. Afterward, we call a function that fixes up the tree so it is a red-black tree again: void RedBlackTree::insert(string element) { Node* new_node = new Node; new_node->data = element; new_node->left = nullptr; new_node->right = nullptr; if (root == nullptr) { root = new_node; } else { root->add_node(new_node); } fix_after_add(new_node); }
If the inserted node is the root, it is turned black. Otherwise, we fix up any double-red violations: /**
Restores the tree to a red-black tree after a node has been added. the node that has been added
@param new_node
*/ void RedBlackTree::fix_after_add(Node* new_node) { if (new_node->parent == nullptr) { new_node->color = BLACK; } else { new_node->color = RED; if (new_node->parent->color == RED) { fix_double_red(new_node); } } }
The code for fixing up a double-red violation is quite long. Recall that there are four possible arrangements of the double-red nodes:
Implementing a Red-Black Tree WE16-11 n3 t4
n1 t1
n2 t2
n2
n3 t4
n2 t3
n1 t1
t3
n1
n1
t1
n3
t1
t2
t2
t3
t4
t4
n2 t2
n1 t1
n3
t3
n2 t2
n3 t3
t4
In each case, we must sort the nodes and their children. Once we have the seven references n1, n2, n3, t1, t2, t3, and t4, the remainder of the procedure is straightforward. We build the replacement tree, change the reds to black, and subtract one from the color of the grandparent (which might be a double-black node when this function is called during node removal). If we find that we introduced another double-red violation, we continue fixing it. Eventually, the violation is removed, or we reach the root, in which case the root is simply colored black: /**
Fixes a “double red” violation. the child with a red parent
@param child
*/ void RedBlackTree::fix_double_red(Node* child) {
WE16-12 Chapter 16 Node* parent = child->parent; Node* grandparent = parent->parent; if (grandparent == nullptr) { parent->color = Node* n1; Node* n2; Node* n3; Node* t1; Node* t2; Node* t3; Node* t4; if (parent == grandparent->left) { n3 = grandparent; t4 = grandparent->right; if (child == parent->left) { n1 = child; n2 = parent; t1 = child->left; t2 = child->right; t3 } else { n1 = parent; n2 = child; t1 = parent->left; t2 = child->left; t3 } } else { n1 = grandparent; t1 = grandparent->left; if (child == parent->left) { n2 = child; n3 = parent; t2 = child->left; t3 = child->right; t4 } else { n2 = parent; n3 = child; t2 = parent->left; t3 = child->left; t4 } }
BLACK; return; }
= parent->right;
= child->right;
= parent->right;
= child->right;
replace_with(grandparent, n2); n1->set_left_child(t1); n1->set_right_child(t2); n2->set_left_child(n1); n2->set_right_child(n3); n3->set_left_child(t3); n3->set_right_child(t4); n2->color = grandparent->color - 1; n1->color = BLACK; n3->color = BLACK;
}
if (n2 == root) { root->color = BLACK; } else if (n2->color == RED && n2->parent->color == RED) { fix_double_red(n2); }
Implementing a Red-Black Tree WE16-13
Removal We remove a node in the same way as in a binary search tree. However, before removing it, we want to make sure that it is colored red. There are two cases for removal: removing an element with one child and removing the successor of an element with two children. Both branches must be modified: void RedBlackTree::erase(string element) { // Find node to be removed Node* to_be_removed = root; bool found = false; while (!found && to_be_removed != nullptr) { if (element == to_be_removed->data) { found = true; } else if (element < to_be_removed->data) { to_be_removed = to_be_removed->left; } else { to_be_removed = to_be_removed->right; } } if (!found) { return; } // to_be_removed //
contains element
If one of the children is empty, use the other
if (to_be_removed->left == nullptr || to_be_removed->right == nullptr) { Node* new_child; if (to_be_removed->left == nullptr) { new_child = to_be_removed->right; } else { new_child = to_be_removed->left; }
}
fix_before_remove(to_be_removed); replace_with(to_be_removed, new_child); return;
//
Neither subtree is empty
//
Find smallest element of the right subtree
Node* smallest = to_be_removed->right; while (smallest->left != nullptr) { smallest = smallest->left; } // smallest
contains smallest child in right subtree
WE16-14 Chapter 16 //
Move contents, unlink child
}
to_be_removed->data = smallest->data; fix_before_remove(smallest); replace_with(smallest, smallest->right);
The replace_with helper function, which was shown earlier, takes care of updating the parent, child, and root links. The fix_before_remove function has three cases. Removing a red leaf is safe. If a black node has a single child, that child must be red, and we can safely swap the colors. (We don’t actually bother to color the node that is to be removed.) The case with a black leaf is the hardest. We need to initiate the “bubbling up” process: /**
Fixes the tree so that it is a red-black tree after a node has been removed. the node that is to be removed
@param to_be_removed
*/ void RedBlackTree::fix_before_remove(Node* to_be_removed) { if (to_be_removed->color == RED) { return; }
}
if (to_be_removed->left != nullptr || to_be_removed->right != nullptr) // It is not a leaf { // Color the child black if (to_be_removed->left == nullptr) { to_be_removed->right->color = BLACK; } else { to_be_removed->left->color = BLACK; } } else { bubble_up(to_be_removed->parent); }
To bubble up, we move a “toll charge” from the children to the parent. This may result in a negative-red or double-red child, which we fix. If neither fix was successful, and the parent node is still double-black, we bubble up again until we reach the root. The root color can be safely changed to black. /**
Move a charge from two children of a parent. a node with two children, or nullptr (in which case nothing is done)
@param parent
*/ void RedBlackTree::bubble_up(Node* parent) { if (parent == nullptr) { return; } parent->color++; parent->left->color--; parent->right->color--;
if (bubble_up_fix(parent->left)) { return; } if (bubble_up_fix(parent->right)) { return; }
}
if (parent->color == DOUBLE_BLACK) { if (parent->parent == nullptr) { parent->color = BLACK; } else { bubble_up(parent->parent); } }
Implementing a Red-Black Tree WE16-15 /**
Fixes a negative-red or double-red violation introduced by bubbling up. negative-red or double-red violations
@param child the child to check for @return true if the tree was fixed
*/ bool RedBlackTree::bubble_up_fix(Node* child) { if (child->color == NEGATIVE_RED) { fix_negative_red(child); return true; } else if (child->color == RED) { if (child->left != nullptr && child->left->color == RED) { fix_double_red(child->left); return true; } if (child->right != nullptr && child->right->color == RED) { fix_double_red(child->right); return true; } } return false; }
We are left with the negative red removal. In the diagram in the book, we show only one of the two possible situations. In the code, we also need to handle the mirror image.
n4 n3 t3
n2
n4
n2 n1
n3
t1 t2
n1 t1
t2
t3
May need to fix double red
n1 n2 t1
n3
n3
n1 n2 t2
n4 t3
t1
t2 t3
May need to fix double red
n4
WE16-16 Chapter 16 The implementation is not difficult, just long. /**
Fixes a “negative red” violation. the negative red node
@param neg_red
*/ void RedBlackTree::fix_negative_red(Node* neg_red) { Node* parent = neg_red->parent; Node* child; if (parent->left == neg_red) { Node* n1 = neg_red->left; Node* n2 = neg_red; Node* n3 = neg_red->right; Node* n4 = parent; Node* t1 = n3->left; Node* t2 = n3->right; Node* t3 = n4->right; n1->color = RED; n2->color = BLACK; n4->color = BLACK; replace_with(n4, n3); n3->set_left_child(n2); n3->set_right_child(n4); n2->set_left_child(n1); n2->set_right_child(t1); n4->set_left_child(t2); n4->set_right_child(t3); child = n1; } else // Mirror image { Node* n4 = neg_red->right; Node* n3 = neg_red; Node* n2 = neg_red->left; Node* n1 = parent; Node* t3 = n2->right; Node* t2 = n2->left; Node* t1 = n1->left; n4->color = RED; n3->color = BLACK; n1->color = BLACK; replace_with(n1, n2); n2->set_right_child(n3); n2->set_left_child(n1); n3->set_right_child(n4); n3->set_left_child(t3); n1->set_right_child(t2); n1->set_left_child(t1);
}
child = n4;
if (child->left != nullptr && child->left->color == RED) { fix_double_red(child->left);
Implementing a Red-Black Tree WE16-17
}
} else if (child->right != nullptr && child->right->color == RED) { fix_double_red(child->right); }
Simple Tests With such a complex implementation, it is extremely likely that some errors slipped in somewhere, and it is important to carry out thorough testing. We can start with the test case used for the binary search tree from the book: void test_from_book() { RedBlackTree t; t.insert("D"); t.insert("B"); t.insert("A"); t.insert("C"); t.insert("F"); t.insert("E"); t.insert("I"); t.insert("G"); t.insert("H"); t.insert("J"); t.erase("A"); // Removing leaf t.erase("B"); // Removing element with one child t.erase("F"); // Removing element with two children t.erase("D"); // Removing root t.print(); cout set_right_child(n[6]);
WE16-20 Chapter 16 n[2]->color = BLACK; return result;
}
Because each test changes the shape of the tree, we want to make a copy of the template in each test. The following recursive function makes a copy of a tree: /**
Copies all nodes of a red-black tree.
@param n the root of a red-black tree @return the root node of a copy of the
tree
*/ Node* copy(Node* n) { if (n == nullptr) { return nullptr; } Node* new_node = new Node; new_node->set_left_child(copy(n->left)); new_node->set_right_child(copy(n->right)); new_node->data = n->data; new_node->color = n->color; return new_node; }
To make a mirror image instead of a copy, just swap the left and right child: /**
Generates the mirror image of a red black tree.
@param n the root of the tree to reflect @return the root of the mirror image of
the tree
*/ Node* mirror(Node* n) { if (n == nullptr) { return nullptr; } Node* new_node = new Node; new_node->set_left_child(mirror(n->right)); new_node->set_right_child(mirror(n->left)); new_node->data = n->data; new_node->color = n->color; return new_node; }
We want to test all possible combinations of red and black nodes in the template. Each pattern of reds and blacks can be represented as a sequence of zeroes and ones, or a binary number between 0 and 2n – 1, where n is the number of nodes to be colored. for (int k = 0; k < pow(2, nodes_to_color); k++) { RedBlackTree rb; // The nodes to be colored if (m == 0) { rb.root = copy(t.root); } else { rb.root = mirror(t.root); } vector nodes; get_nodes(rb.root, nodes); Node* to_delete = nullptr; // Color with the bit pattern int bits = k; for (Node* n : nodes) { if (n == rb.root) { n->color = BLACK;
of k
Implementing a Red-Black Tree WE16-21
}
} else if (n->color == BLACK) { to_delete = n; } else { n->color = bits % 2; bits = bits / 2; }
}
// Now . . .
run a test with this tree
We need to have a helper function to get all nodes of a tree into a vector. Here it is: /**
Gets all nodes of a subtree and fills them into a vector.
@param n the root of the subtree @param nodes the vector into which
to place the nodes
*/ void get_nodes(Node* n, vector& nodes) { if (n == nullptr) { return; } get_nodes(n->left, nodes); nodes.push_back(n); get_nodes(n->right, nodes); }
Once the tree has been colored, we need to give it a constant black height. For each leaf, we compute the cost to the root: /**
Computes the cost from a node to a root.
@param n a node of a red-black tree @return the number of black nodes
between n and the root */ int cost_to_root(Node* n) { int c = 0; while (n != nullptr) { c = c + n->color; n = n->parent; } return c; }
If that cost is less than the black height of the node to be removed, we add a full tree of black nodes to make up the difference. This function makes these trees: /**
Makes a full tree of black nodes of a given depth.
@param depth the desired depth @return the root node of a full black
tree
*/ Node* full_tree(int depth) { if (depth color = BLACK; r->set_left_child(full_tree(depth - 1)); r->set_right_child(full_tree(depth - 1)); return r; }
WE16-22 Chapter 16 This loop adds the full trees to the nodes: int target_cost = cost_to_root(to_delete); for (Node* n : nodes) { int cost = target_cost - cost_to_root(n); if (n->left == nullptr) { n->set_left_child(full_tree(cost)); } if (n->right == nullptr) { n->set_right_child(full_tree(cost)); } }
Now we need to fill the tree with values. Because get_nodes returns the nodes in sorted order, we just populate them with A, B, C, and so on. /**
Populates a tree with the values A, B, C, ... .
@param t a red-black tree @return the number of nodes in t
*/ int populate(RedBlackTree t) { vector nodes; get_nodes(t.root, nodes); for (int i = 0; i < nodes.size(); i++) { string d = "A"; d[0] = d[0] + i; nodes[i]->data = d; } return nodes.size(); }
The resulting tree might not be a valid red-black tree. It might have some leaves with greater black height than the node to be removed, or it might have double-red violations. We will develop a function to check that a red-black tree is valid and call it before and after the removal. We also want to verify that all the parent and child links are not corrupted. Because removal introduces colors other than red or black (e.g., double-black or negative-red), we want to check that those colors are no longer present after the operation has completed. Specifically, we need to check the following for each subtree with root n: • The left and right subtree of n have the same black depth. • n must be red or black. • If n is red, its parent is not. • If n has children, then their parent pointers must equal n. • n->parent is nullptr if and only if n is the root of the tree. • The root is black. Moreover, because fixing double-red and negative-red violations reorders nodes, we will check that the tree is still a binary search tree. This can be tested by visiting the tree in order. Here are the integrity check functions. The report_errors parameter is set to false when we want to test whether a tree is valid before removing an element. It is set to true when testing that it remains valid after removal. /**
Checks whether a red-black tree is valid and reports an error if not.
Implementing a Red-Black Tree WE16-23 @param t the tree to test @param report_errors whether error messages should be printed @return true if the tree passes, false if not
*/ bool check_red_black(RedBlackTree& t, bool report_errors) { int result = check_red_black(t.root, true, report_errors); if (result == -1) { return false; }
}
// Check that it’s a BST vector nodes; get_nodes(t.root, nodes); for (int i = 0; i < nodes.size() - 1; i++) { if (nodes[i]->data > nodes[i + 1]->data > 0) { if (report_errors) { cout data right, false, report_errors); if (nleft == -1 || nright == -1) return -1; if (nleft != nright) { if (report_errors) { cout 0) { cout = 13) { actual_floor = } else if (floor >= { actual_floor = } else { cout