Skip to index filter Skip to main content
CISC 192 textbook home CISC 192 TextbookCISC 192 Textbook
  • on GitHub
Search the documentation

CISC 192 Textbook

  • on GitHub

  • Change appearance
    • Change appearance
    • Light
    • Dark
    • Automatic
  Index
Index

Legal and foreword

  • Copyright Notice
  • License
  • Foreword
  • Preface
  • 1. The way of the program
    • 1.1. The Way of the Program
    • 1.2. What is a Programming Language?
    • 1.3. What is a Program?
    • 1.4. What is Debugging?
    • 1.5. Formal and Natural Languages
    • 1.6. The First Program
    • 1.7. Glossary
    • 1.8. Multiple Choice Exercises
  • 2. Variables and types
    • 2.1. More Output
    • 2.2. Values
    • 2.3. Variables
    • 2.4. Assignment
    • 2.5. Constants
    • 2.6. Outputting Variables
    • 2.7. Keywords
    • 2.8. Operators
    • 2.9. The Modulus Operator
    • 2.10. Order of Operations
    • 2.11. Operators for Characters
    • 2.12. Compound Expressions
    • 2.13. Glossary
    • 2.14. Multiple Choice Exercises
    • 2.15. Mixed-Up Code Exercises
    • 2.16. Activecode Exercises
  • 3. Functions
    • 3.1. Floating-point
    • 3.2. Converting from double to int
    • 3.3. Math Functions
    • 3.4. Composition
    • 3.5. Adding New Functions
    • 3.6. Definitions and Uses
    • 3.7. Namespaces
    • 3.8. Prefer using declarations to using namespace std
    • 3.9. Programs with Multiple Functions
    • 3.10. Parameters and Arguments
    • 3.11. Parameters and Variables are Local
    • 3.12. Functions with Multiple Parameters
    • 3.13. Functions with Results
    • 3.14. Glossary
    • 3.15. Multiple Choice Exercises
    • 3.16. Mixed-Up Code Exercises
    • 3.17. Activecode Exercises
  • 4. Fruitful functions
    • 4.1. Fruitful functions
    • 4.2. Conditional Execution
    • 4.3. Alternative Execution
    • 4.4. Chained Conditionals
    • 4.5. Nested Conditionals
    • 4.6. The return keyword
    • 4.7. Returning early
    • 4.8. Returning from main
    • 4.9. Program Development
    • 4.10. Function Composition
    • 4.11. Overloading
    • 4.12. Boolean Values
    • 4.13. Boolean Variables
    • 4.14. Logical operators
    • 4.15. Bool Functions
    • 4.16. Glossary
    • 4.17. Multiple Choice Exercises
    • 4.18. Mixed Up Code Practice
    • 4.19. Coding Practice
  • 5. Iteration
    • 5.1. Multiple assignment
    • 5.2. Iteration
    • 5.3. The while statement
    • 5.4. Tables
    • 5.5. Two-dimensional tables
    • 5.6. The for statement
    • 5.7. Encapsulation and generalization
    • 5.8. Functions
    • 5.9. More encapsulation
    • 5.10. Local variables
    • 5.11. More generalization
    • 5.12. Glossary
    • 5.13. Multiple Choice Exercises
    • 5.14. Mixed Up Code Practice
    • 5.15. Coding Practice
  • 6. Recursion
    • 6.1. Recursion
    • 6.2. Infinite Recursion
    • 6.3. Stack Diagrams for Recursive Functions
    • 6.4. More recursion
    • 6.5. One more example
    • 6.6. Glossary
    • 6.7. Multiple Choice Exercises
    • 6.8. Mixed-Up Code Exercises
    • 6.9. Activecode Exercises
  • 7. Strings and things
    • 7.1. Containers for strings
    • 7.2. string variables
    • 7.3. Extracting characters from a string
    • 7.4. String size
    • 7.5. Traversal
    • 7.6. A run-time error
    • 7.7. The find function
    • 7.8. Our own version of find
    • 7.9. Looping and counting
    • 7.10. Increment and decrement operators
    • 7.11. String concatenation
    • 7.12. strings are mutable
    • 7.13. strings are comparable
    • 7.14. Character classification
    • 7.15. Other string functions
    • 7.16. Glossary
    • 7.17. Multiple Choice Exercises
    • 7.18. Mixed Up Code Practice
    • 7.19. Coding Practice
  • 8. Structures
    • 8.1. Compound values
    • 8.2. Point objects
    • 8.3. Accessing instance variables
    • 8.4. Operations on structures
    • 8.5. Structures as parameters
    • 8.6. Pass by value
    • 8.7. Pass by reference
    • 8.8. Rectangles
    • 8.9. Structures as return types
    • 8.10. Passing other types by reference
    • 8.11. Getting user input
    • 8.12. Glossary
    • 8.13. Multiple Choice Exercises
    • 8.14. Mixed Up Code Practice
    • 8.15. Coding Practice
  • 9. More Structures
    • 9.1. Time
    • 9.2. Functions for objects
    • 9.3. Pure functions
    • 9.4. const parameters
    • 9.5. Modifiers
    • 9.6. Fill-in functions
    • 9.7. Which is best?
    • 9.8. Incremental development versus planning
    • 9.9. Generalization
    • 9.10. Algorithms
    • 9.11. Glossary
    • 9.12. Multiple Choice Exercises
    • 9.13. Mixed Up Code Practice
    • 9.14. Coding Practice
  • 10. Vectors
    • 10.1. Vectors
    • 10.2. Accessing elements
    • 10.3. Copying vectors
    • 10.4. Vector size
    • 10.5. Vector functions
    • 10.6. Glossary
    • 10.7. Multiple Choice Exercises
    • 10.8. Mixed-Up Code Exercises
    • 10.9. Activecode Exercises
  • 11. Random numbers
    • 11.1. Random numbers
    • 11.2. Statistics
    • 11.3. Vector of random numbers
    • 11.4. Counting
    • 11.5. Checking the other values
    • 11.6. A histogram
    • 11.7. A single-pass solution
    • 11.8. Permutations
    • 11.9. Glossary
    • 11.10. Multiple Choice Exercises
    • 11.11. Mixed-Up Code Exercises
    • 11.12. Activecode Exercises
  • 12. Vectors of Objects
    • 12.1. Composition
    • 12.2. playing_card objects
    • 12.3. The print_card function
    • 12.4. The equals function
    • 12.5. The is_greater function
    • 12.6. Vectors of cards
    • 12.7. The print_deck function
    • 12.8. Searching
    • 12.9. Bisection search
    • 12.10. Decks and subdecks
    • 12.11. Glossary
    • 12.12. Multiple Choice Exercises
    • 12.13. Mixed-Up Code Exercises
    • 12.14. Coding Practice
  • 13. Objects of Vectors
    • 13.1. Enumerated types
    • 13.2. switch statement
    • 13.3. Decks
    • 13.4. Another constructor
    • 13.5. card_deck member functions
    • 13.6. Shuffling
    • 13.7. Sorting
    • 13.8. Subdecks
    • 13.9. Shuffling and dealing
    • 13.10. Mergesort
    • 13.11. Glossary
    • 13.12. Multiple Choice Exercises
    • 13.13. Mixed-Up Code Exercises
    • 13.14. Coding Practice
  • 14. Classes and invariants
    • 14.1. Private data and classes
    • 14.2. What is a class?
    • 14.3. complex_number numbers
    • 14.4. Accessor functions
    • 14.5. Output
    • 14.6. A function on complex_number numbers
    • 14.7. Another function on complex_number numbers
    • 14.8. Invariants
    • 14.9. Preconditions
    • 14.10. Private functions
    • 14.11. Glossary
    • 14.12. Multiple Choice Exercises
    • 14.13. Mixed-Up Code Exercises
    • 14.14. Coding Practice
  • 15. Files and standard-library containers
    • 15.1. From files to containers
    • 15.2. Streams
    • 15.3. File input
    • 15.4. File output
    • 15.5. Parsing quoted records
    • 15.6. Parsing numbers with error reporting
    • 15.7. Fixed-size sequences with std::array
    • 15.8. Growing sequences with std::vector
    • 15.9. Unique values with std::set
    • 15.10. Key-value associations with std::map
    • 15.11. A distance table with maps and sets
    • 15.12. Glossary
    • 15.13. Multiple Choice Exercises
    • 15.14. Mixed-Up Code Exercises
    • 15.15. Coding Practice
  1. Start
  2. 12. Vectors of Objects
  3. 12.9. Bisection search

12.9. Bisection search¶

If the cards in the deck are not in order, there is no way to search that is faster than the linear search. We have to look at every card, since otherwise there is no way to be certain the card we want is not there.

But when you look for a word in a dictionary, you don’t search linearly through every word. The reason is that the words are in alphabetical order. As a result, you probably use an algorithm that is similar to a bisection search:

  1. Start in the middle somewhere.

  2. Choose a word on the page and compare it to the word you are looking for.

  3. If you found the word you are looking for, stop.

  4. If the word you are looking for comes after the word on the page, flip to somewhere later in the dictionary and go to step 2.

  5. If the word you are looking for comes before the word on the page, flip to somewhere earlier in the dictionary and go to step 2.

If you ever get to the point where there are two adjacent words on the page and your word comes between them, you can conclude that your word is not in the dictionary. The only alternative is that your word has been misfiled somewhere, but that contradicts our assumption that the words are in alphabetical order.

In the case of a deck of cards, if we know that the cards are in order, we can write a version of find that is much faster. The best way to write a bisection search is with a recursive function. That’s because bisection is naturally recursive.

The trick is to write a function called find_bisect that takes two indices as parameters, low and high, indicating the segment of the vector that should be searched (including both low and high).

  1. To search the vector, choose an index between low and high, and call it mid. Compare the card at mid to the card you are looking for.

  2. If you found it, stop.

  3. If the card at mid is higher than your card, search in the range from low to mid-1.

  4. If the card at mid is lower than your card, search in the range from mid+1 to high.

Steps 3 and 4 look suspiciously like recursive invocations. Here’s what this all looks like translated into C++:

std::ptrdiff_t find_bisect (const playing_card& card, const std::vector<playing_card>& deck,
                std::ptrdiff_t low, std::ptrdiff_t high) {
  std::ptrdiff_t mid = low + (high - low) / 2;

  // if we found the card, return its index
  if (equals (deck[mid], card)) return mid;

  // otherwise, compare the card to the middle card
  if (deck[mid].is_greater (card)) {
    // search the first half of the deck
    return find_bisect (card, deck, low, mid-1);
  } else {
    // search the second half of the deck
    return find_bisect (card, deck, mid+1, high);
  }
}

Although this code contains the kernel of a bisection search, it is still missing a piece. As it is currently written, if the card is not in the deck, it will recurse forever. We need a way to detect this condition and deal with it properly (by returning -1).

The easiest way to tell that your card is not in the deck is if there are no cards in the deck, which is the case if high is less than low. Well, there are still cards in the deck, of course, but what I mean is that there are no cards in the segment of the deck indicated by low and high.

With that line added, the function works correctly:

std::ptrdiff_t find_bisect (const playing_card& card, const std::vector<playing_card>& deck,
                std::ptrdiff_t low, std::ptrdiff_t high) {

  std::cout << low << ", " << high << std::endl;

  if (high < low) return -1;

  std::ptrdiff_t mid = low + (high - low) / 2;

  if (equals (deck[mid], card)) return mid;

  if (deck[mid].is_greater (card)) {
    return find_bisect (card, deck, low, mid-1);
  } else {
    return find_bisect (card, deck, mid+1, high);
  }
}

I added an output statement at the beginning so I could watch the sequence of recursive calls and convince myself that it would eventually reach the base case. I tried out the following code:

std::cout << find_bisect (deck[23], deck, 0, 51);

And got the following output:

0, 51
0, 24
13, 24
19, 24
22, 24
I found the card at index = 23

Then I made up a card that is not in the deck (the 15 of Diamonds), and tried to find it. I got the following:

0, 51
0, 24
13, 24
13, 17
13, 14
13, 12
I found the card at index = -1

These tests don’t prove that this program is correct. In fact, no amount of testing can prove that a program is correct. On the other hand, by looking at a few cases and examining the code, you might be able to convince yourself.

The code below searches finds the same card from the same deck we used on the previous page. This time, it uses bisection search to locate the card.

Example c192_12_9
 1#include <iterator>
 2#include <cstddef>
 3#include <iostream>
 4#include <string>
 5#include <vector>
 6
 7struct playing_card {
 8    int suit, rank;
 9
10    playing_card ();
11    playing_card (int s, int r);
12    void print () const;
13    bool is_greater (const playing_card& c2) const;
14};
15
16std::vector<playing_card> build_deck();
17
18bool equals (const playing_card& c1, const playing_card& c2){
19    return (c1.rank == c2.rank && c1.suit == c2.suit);
20}
21
22void print_deck(const std::vector<playing_card>& deck);
23std::ptrdiff_t find (const playing_card& card, const std::vector<playing_card>& deck);
24std::ptrdiff_t find_bisect (const playing_card& card, const std::vector<playing_card>& deck, std::ptrdiff_t low, std::ptrdiff_t high);
25
26int main() {
27    std::vector<playing_card> deck = build_deck();
28    playing_card card (3, 6);
29    // We need to sort from the first card (0) to the last card (size-1)
30    std::cout << find_bisect(card, deck, 0, std::ssize(deck) - 1);
31}

The number of recursive calls is fairly small, typically 6 or 7. That means we only had to call equals and is_greater 6 or 7 times, compared to up to 52 times if we did a linear search. In general, bisection is much faster than a linear search, especially for large vectors.

Two common errors in recursive programs are forgetting to include a base case and writing the recursive call so that the base case is never reached. Either error will cause an infinite recursion, in which case C++ will (eventually) generate a run-time error.

You are given a list of spelling words where the words are not sorted in any way. What search method should you use?

Correct! No search is faster than linear search when elements are not sorted.

Incorrect! Bisection sort does not work on unsorted elements.

Incorrect! Bisection sort does not work on unsorted elements.

Incorrect! Bisection sort does not work on unsorted elements.

You are given the same list of spelling words, but this time the words are sorted alphabetically. What search method should you use this time?

Incorrect! You could use linear search, but it is not the only option.

Incorrect! You could use bisection search, but it is not the only option.

Incorrect! Both methods will work, but linear search is not the most efficient method.

Correct! When elements are sorted, bisection search is much quicker.

When writing a recursive function, which of the following will result in infinite recursion?

Incorrect! You are allowed to make multiple recursive calls inside of a function! You might do this if there is more than one condition.

Correct! You always need a base case!

Correct! If you never reach the base case, the program will never stop making recursive calls.

Incorrect! You are allowed to have multiple base cases. This is often necessary!

How many recursive calls are used to locate the King of Hearts? (Hearts = suit 2, King = rank 13).

  On this page
  • 12.9. Bisection search
  • Previous 12.8. Searching
  • Next 12.10. Decks and subdecks
CISC 192 Textbook
  • 2017-2026 Dave Parillo
Built with Sphinx 9.1.0 and Nefertiti 0.9.9