Data Structures and Algorithms

Ordered and Sorted Ranges, Sets

As review, containers are objects that store a variable amount of data items, allowing for control and protection of the data. It's easy to copy/edit/sort/order many objects at once.

This makes it easy to create more complex data structures, by using containers within containers (databases are really just fancy containers!).

There are different categories of containers:

  • Container: supports add() and remove()
  • Searchable container: also supports find()
  • Sequential container: also has an iterator
  • Ordered container: the order is independent of the value.
    • Chapters of a book
    • List
  • Sorted container: the order is based on value.
    • Students sorted by ID

Interfaces for Sorted and Ordered Containers

Interface Sorted Ordered
addElement(val) Override Inherited
remove(val) Inherited Inherited
isMember(val) Inherited Inherited
find(val) Inherited Inherited
findPosition(val) Yes Yes
operator[]() Yes Yes
withdraw(iterator) Yes Yes
insertAfter() No Yes
insertBefore() No Yes

Complexities

Like most container structures, they can be implemented with an array<> or a list<>. Each has their own complexities, and so it is useful to compare them.

Ordered container

Interface Array Linked List
addElement(val) \(O(1)\) \(O(1)\)
remove(val) \(O(n)\) \(O(n)\)
remove(iterator) \(O(n)\) \(O(n) \text{ or } O(1)\)
isMember(val) \(O(n)\) \(O(n)\)
find(val) \(O(n)\) \(O(n)\)
findPosition(val) \(O(n)\) \(O(n)\)
iterator operator*() \(O(1)\) \(O(1)\)
operator[](unsigned) \(O(1)\) \(O(n)\)
insertAfter \(O(n)\) \(O(1)\)
insertBefore() \(O(n)\) \(O(n) \text{ or } O(1)\)

Sorted container

Interface Array Linked List
addElement(val) \(O(n)\) \(O(n)\)
remove(val) \(O(n)\) \(O(n)\)
remove(iterator) \(O(n)\) \(O(n) \text{ or } O(1)\)
isMember(val) \(O(\log n)\) \(O(n)\)
find(val) \(O(\log n)\) \(O(n)\)
findPosition(val) \(O(\log n)\) \(O(n)\)
iterator operator*() \(O(1)\) \(O(1)\)
operator[](unsigned) \(O(1)\) \(O(n)\)

Binary search is a fast type of search (typically \(O(\log n)\)), where you find an element in the middle of the sorted data structure. If what you're looking for is less than the middle element, perform binary search on the less side. If what you're looking for is greater than the middle element, perform binary search on the greater side. Else, return the position of the element.

In C++, this is done using the binary_search() function. However, this returns a bool, not the location. If you want a location, use the functions lower_bound(), upper_bound(), and equal_range().

Comparators

In searching and sorting, you need to have comparators. These are typically implemented as functors. For example, let's say you have a struct Point:

struct Point {
    int x, y;
    Point() : x(-1), y(-1) {}
    Point(int xx, int yy) : x(xx), y(yy) {}
};

You need to have some way to tell if one Point is greater or less than another Point. Here, you could either compare by x or y, so one could write two functors to do that:

struct CompareByX {
    bool operator()(const Point &p1, const Point &p2) const {
        return p1.x > p2.x;
    }
};

struct CompareByY {
    bool operator()(const Point &p1, const Point &p2) const {
        return p1.y > p2.y;
    }
};

Sets

A set is a type of mathematical structure that allows you to have a collection of elements, with no duplicates. There are well-defined operations for sets, implemented in the STL as well:

  • Union (\(A \cup B\)): in one set or the other
  • Intersection (\(A \cap B\)): in both sets
  • Set-difference (\(A - B\)): in one and not the other
  • Symmetric difference (\(A \oplus B\)): exclusive or
  • Is an element (\(x \in A\))
  • Add element

Complexity

Method Asymptotic Complexity
initialize() \(O(n)\)
clear() \(O(1) \text{ or } O(n)\)
isMember() \(O(\log n)\)
copy() \(O(n)\)
set_union() \(O(n)\)
set_intersection() \(O(n)\)

Sets of integers can be implemented using a vector<bool> or bitset<>, where the member testing takes constant time.