Chapter 6
Chapter 6
3
VECTOR - ADT
▪ Formally, a vector is an abstract data type (ADT) that supports:
➢ at(i)
➢ set(i,e)
➢ insert(i,e)
➢ erase(i)
➢ size()
➢ empty()
4
INSERTION & REMOVAL
▪ In an array-based implementation, where the array size is N, and vector
contains n elements:
Algorithm insert(i,e):
for j = n−1, n−2, . . . , i do
A[ j+1] ← A[ j]
A[i] ← e
n ← n+1
Algorithm erase(i):
for j = i+1, i+2, . . . ,n−1 do
A[ j−1] ← A[ j]
n←n−1
5
COMPLEXITY
▪ What is the complexity of these operations given an array-based
implementation?
6
COMPLEXITY
▪ What is the complexity of these operations given an array-based
implementation?
7
PROBLEM WITH ARRAY’S CAPACITY
▪ One of the main disadvantages of an array-based implementation of vectors
is that we have to determine the maximum capacity, N, in advance
➢ What if N was too large? i.e., what if the number of actual elements, n,
never reaches anywhere near N? We would waste memory space!
... ...
0 n N-1
➢ What if we set N was too small? We would attempt to access an element
at an index greater than N-1, and the implementation would crash!
... ... n ??
0 N-1
We can improve this by using an “extendable array”! 8
Basic idea of extendable arrays: n = No. of elements in vector
...
N = array size.
▪ Suppose the array is full, i.e., n = N
and we want to add a new element.
We can simply follow these steps.
1. Create bigger array of size N+1 ...
▪ Do you see a problem here?
2. Copy from old array to new one ...
This requires copying all elements
3. Put new element at index N ...
every time a new element is added
▪ What is the running time? 4. Delete the older (smaller) array ...
If we start with an array that fits of
size 1, and keep copying, the total
number of copy-operations needed 1. Create bigger array of size N+1 ...
to put n elements is:
2. Copy from old array to new one ...
1 + 2 + 3 + … + n, which is O(n2)
3. Put new element at index N ...
▪ Is there a more efficient design?
4. Delete the older (smaller) array ...
What if, whenever the array is full,
we double the array size? 9
copy 1 element An illustration where every row represents
copy 2 elements adding one more element to the vector.
copy 4 elements
We eventually had a vector of 32 elements,
and the total number of copies we made was
copy 8 elements
1 + 2 + 4 + 8 + 16 = 31
copy 16 elements
10
Doubling the array size Increasing the array size by 1
copy 1 copy 1 𝑛 𝑛+1
copy 2 A total of 31 copy operations copy 2 1 +2 +⋯+ 𝑛 =
copy 3 2
copy 4 copy 4 = 496 copy operations
copy 5
copy 6
copy 7
copy 8 copy 8
copy 9
copy 10
copy 11
copy 12
copy 13
copy 14
copy 15
copy 16 copy 16
copy 17
copy 18
copy 19
copy 20
copy 21
copy 22
copy 23
copy 24
copy 25
copy 26
copy 27
copy 28
copy 29
copy 30
copy 31
IMPLEMENTATION
typedef int Elem; // A user-defined element type
class ArrayVector {
public:
ArrayVector(); // constructor
int size() const; // returns the number of elements “n”
bool empty() const; // returns “true” if the vector is empty
Elem& operator[ ](int i); // overloading the “[ ]” operator
Elem& at(int i) throw(IndexOutOfBounds); // returns element at index “i”
void erase(int i); // remove element at index “i”
void insert(int i, const Elem& e); // insert “e” at index “i”
void reserve(int N); // extend array size to N (unless it’s already bigger than N)
private:
int capacity; // current array size, i.e., “N”
int n; // number of elements in the vector
Elem* A; // Array of elements of type “Elem”
};
12
IMPLEMENTATION
typedef int Elem; // A user-defined element type
class ArrayVector {
public:
ArrayVector(); // constructor
// overloading the “[ ]” operator
int size() const; // returns the number of elements “n”
bool empty() const; // returns “true” if the vector is empty Elem& ArrayVector::operator[ ](int i)
Elem& operator[ ](int i); { return A[i]; }
Elem& at(int i) throw(IndexOutOfBounds); // returns element at index “i” Now, we can write, e.g.,
void erase(int i); // remove element at index “i”
ArrayVector x = new ArrayVector();
void insert(int i, const Elem& e); // insert “e” at index “i”
...
void reserve(int N); // extend array size to N (unless it’s already bigger than N)
x[0] = x[1];
private:
cout << x[10];
int capacity; // current array size, i.e., “N”
int n; // number of elements in the vector
Elem* A; // Array of elements of type “Elem”
};
13
IMPLEMENTATION
typedef int Elem; // A user-defined element type
class ArrayVector {
public:
ArrayVector(); // constructor
int size() const; // returns the number of elements “n”
bool empty() const; // returns “true” if the vector is empty
Elem& operator[ ](int i);
Elem& at(int i) throw(IndexOutOfBounds); // returns element at index “i”
void erase(int i); // remove element at index “i”
void insert(int i, const Elem& e); // insert “e” at index “i” // extend array size to N (unless it’s already bigger than N)
void reserve(int N); void ArrayVector::reserve(int N) {
private: if (capacity >= N) return;
int capacity; // current array size, i.e., “N” Elem* B = new Elem[N]; // resize the array
int n; // number of elements in the vector for (int j = 0; j < n; j++) // copy contents to new array
Elem* A; // Array of elements of type “Elem” B[j] = A[j];
}; if (A != NULL) delete [ ] A; // discard old array
A = B; // make B the new array
capacity = N; // set new capacity 14
copy 1
IMPLEMENTATION copy 2
copy 4
...
ArrayVector(); // constructor
int size() const; // returns the number of elements “n”
bool empty() const; // returns “true” if the vector is empty
Elem& operator[ ](int i);
Elem& at(int i) throw(IndexOutOfBounds); // returns element at index “i”
void erase(int i); // remove element at index “i”
void insert(int i, const Elem& e); // insert “e” at index “i”
void reserve(int N); How would you implement the method “insert”?
private: P.S., You can use “reserve” in your implementation.
int capacity; // current array size, i.e., “N”
int n; // number of elements in the vector
Elem* A; // Array of elements of type “Elem”
};
15
copy 1
IMPLEMENTATION copy 2
copy 4
...
ArrayVector(); // constructor
int size() const; // returns the number of elements “n”
bool empty() const; // returns “true” if the vector is empty The capacity is multiplied by 2, unless
Elem& operator[ ](int i); the capacity was 0, in which case
Elem& at(int i) throw(IndexOutOfBounds); // returns element at index “i” multiplying it by 2 won't help. In this
void erase(int i); // remove element at index “i” case, we set the capacity to 1
void insert(int i, const Elem& e); // insert “e” at index “i”
void reserve(int N); void ArrayVector::insert(int i, const Elem& e) {
private: if (n >= capacity) // overflow?
int capacity; // current array size, i.e., “N” reserve( max(1, 2 * capacity) ); // double the size
int n; // number of elements in the vector for (int j = n − 1; j >= i; j−−) // shift elements up
Elem* A; // Array of elements of type “Elem” A[j+1] = A[j];
}; A[i] = e; // put “e” at index “i”
n++;
} 16
COMPLEXITY OF “PUSHING” AN ELEMENT
▪ Let us use the term “push” when inserting an element at index n in the vector
...
17
AMORTIZATION
Given a naïve implementation, every But if we "invest" by allocating memory in advance,
push operation requires copying the then our investment pays off since many subsequent
entire vector to a new one of size N+1 "push" operations would take O(1) time.
No. of operations: No. of operations:
1 1
2 2
3 3
4 1
5 5
6 1
7 1
8 1
9 9
10 1
11 1
12 1
13 1
14 1
15 1
16 1
This way of solving problems (where we execute an expensive operation, to make future
operations easier) is the main idea behind the "amortization" design pattern. 18
AMORTIZATION
The idea behind amortization is to not focus on the worst case when analyzing a given operation,
because it could be the case that a single expensive operation will make many subsequent
operations much easier.
No. of operations:
Based on this, our 1
2
complexity analysis Each of these pushes takes 3
on average 2 operations 1
should take the average 5
over the expensive Each of these pushes takes 1
on average 2 operations 1
operation and all the 1
9
subsequent, cheap 1
operations. 1
Each of these pushes takes 1
on average 2 operations 1
1
1
1
Thus, although a push takes O(n) in the worst case, it only takes O(1) time on average.
Based on this, the total time to perform a series of n pushes is just O(n) . 19
AMORTIZATION
Importantly, when we say:
the total time to perform a series of n pushes is O(n)
our analysis is taking the No. of operations:
average, rather than the 1
worst-case, time to Each of these pushes takes
2
3
perform a push, but on average 2 operations 1
5
didn’t we say that the Each of these pushes takes 1
big-Oh analysis should on average 2 operations 1
1
focus on worst case? 9
1
1
Each of these pushes takes 1
What’s going on? on average 2 operations 1
1
1
1
Our big-Oh analysis is indeed considering the worst-case input! Notice that the above
statement holds regardless of the input, i.e., regardless of the values being inserted! 20
VECTORS IN C++
▪ C++ provides a readily-available vector implemented as an
extendable array, so you don’t have to implement it yourself
#include <vector>
using std::vector; This line defines myVector as a vector
vector<int> myVector(100); in which the elements are of type int;
in this case, the “base type” is int
21
VECTORS IN C++
STL vector are similar to C++ arrays, but they provide additional features:
▪ Given a vector, x, you can access the ith element either by writing x[i] (just like
arrays), or by writing x.at(i), which generates an error exception if the index is
out of bounds, unlike x[i].
▪ New elements may be efficiently inserted at, or removed from, the end of the vector
▪ Unlike C++ arrays, when an STL vector of class objects is destroyed, it automatically
invokes the destructor for each elements
▪ STL vectors provide functions that operate on entire vectors, e.g., to copy all or part of
one vector to another, compare two vectors, and insert and erase multiple elements.
22
23
CONTAINERS, POSITIONS,
AND ITERATORS
CONTAINERS
▪ A container is a data structure that stores a collection of objects.
24
POSITIONS
▪ A position is defined to be an abstract data type that is associated with a
particular container and which supports the following function:
element() // Return a reference to the element stored at this position
25
ITERATOR
▪ An iterator is an extension of a position.
▪ An iterator, x, can access an element of a container (just like a position), but
it can also navigate forwards to the next element in the container, by
writing: x.next()
▪ This way, an iterator can be thought of as a position that can “iterate”
through the container.
▪ We overload the increment operator, ++; this way x++ becomes equivalent
to x.next()
▪ Depending on how the container is implemented, navigation may be
possible both forward and backward. In this case, we overload the
decrement operator, --; this way x-- becomes equivalent to x.prev()
26
ITERATOR
▪ We assume that each container has these methods:
➢ begin() returns an iterator that refers to the first element in the container
➢ end() returns an iterator that refers to an imaginary position that lies just
after the last element in the container
Here is an illustration
given a container, L,
which is a linked list:
29
LIST - ADT
▪ Formally, a list is an abstract data type (ADT) that supports :
➢ begin(): Return an iterator referring to the first element of L; this
is the same as end() if L is empty.
➢ end(): Return an iterator referring to an imaginary element just
after the last element of L.
➢ insertFront(e):Insert a new element e into L as the first element.
➢ insertBack(e): Insert a new element e into L as the last element.
➢ insert(p,e): Insert a new element e into L before position p in L.
➢ eraseFront(): Remove the first element of L.
➢ eraseBack(): Remove the last element of L.
➢ erase(p): Remove from L the element at position p; invalidates p.
30
LISTS IN C++
▪ C++ provides a readily-available implementation of a lists
(i.e., you do not have to implement it yourself).
#include <list>
using std::list; This line defines myList as a list in
list<float> myList; which the elements are of type float;
in this case, the “base type” is float
31
LISTS IN C++
▪ Here are the methods supported by the STL list:
33
CODE generateFibonacci
DEMONSTRATION with list
34
35 STL CONTAINERS & ITERATORS
STL CONTAINERS
▪ C++’s Standard Template Library (STL) provides a variety of
containers, many of which will be discussed later…
36
ITERATING THROUGH A CONTAINER
▪ How would you iterate through a container, say, to sum up all its elements?
▪ Let’s take vectors as an example…
int vectorSum1(const vector<int>& V) {
int sum = 0;
for (int i = 0; i < V.size(); i++)
sum += V[i];
return sum;
}
▪ Unfortunately, this method is not applicable to other types of containers
because it relies on the fact that the elements of a vector can be accessed
efficiently through indexing. This is not true for all containers, such as lists.
▪ An alternative method that works on all containers is to use an iterator!
37
ITERATING THROUGH A CONTAINER
▪ How would you iterate through a container, say, to sum up all its elements?
▪ Let’s take vectors as an example… Here is an example of how to use an iterator:
int vectorSum1(const vector<int>& V) { int vectorSum2(vector<int> V) {
typedef vector<int>::iterator Iterator;
int sum = 0; int sum = 0;
for (int i = 0; i < V.size(); i++) for (Iterator p = V.begin(); p != V.end(); ++p)
sum += V[i]; sum += *p;
return sum; return sum;
} }
38
CODE DEMONSTRATION
39
FLASH ASSIGNMENT
▪ Write a C++ program that performs the
following operations using the STL list
container:
1. Create an empty list of integers.
2. Populate the list with integers from 1 to
10 in ascending order.
3. Print the list elements in reverse order.
4. Remove the even numbers from the list.
5. Print the final list.
▪ Your code should be well-commented for
clarity.
40
41
SEQUENCES
SEQUENCES
▪ A sequence is an ADT that:
➢ Supports all the functions of the List ADT
➢ Provides functions for accessing elements by their index (as in the Vector ADT)
▪ Disadvantage: atIndex( i ) and indexOf( p ) run in O(n); they may perform n hops!
▪ Advantage: insert( p, e) and erase( p ) run in O(1); no need to shift elements!
43
CODE
DEMONSTRATION
44
SEQUENCES – ARRAY IMPLEMENTATION
▪ Recall that a sequence is an ADT that supports "indexOf(p)" and "atIndex(i)"
▪ If implemented as an array, we must have an array of positions, each of which
points to a pair consisting of an element, and the index of that element.
▪ Disadvantage: atIndex( i ) and indexOf( p ) run in O(n); they may perform n hops!
▪ Advantage: insert( p, e) and erase( p ) run in O(1); no need to shift elements!
1 PVD
p
Array: 1
After 1st pass: 5, 2, 6, 7, 3, 9 After 2nd pass: 2, 5, 6, 3, 7, 9 After 3rd pass: 2, 5, 3, 6, 7, 9 After 4th pass: 2, 3, 5, 6, 7, 9
48
BUBBLE SORT
▪ “Bubble Sort” has the following properties:
➢ In 1st pass, once the largest element is reached, it keeps on being swapped until it
gets to the last position
1st pass:
initial array: 5, 7, 2, 6, 9, 3
Swap? No! 5, 7, 2, 6, 9, 3
Swap? Yes 5, 7, 2, 6, 9, 3
Swap? Yes 5, 2, 7, 6, 9, 3
Swap? No! 5, 2, 6, 7, 9, 3
Swap? Yes 5, 2, 6, 7, 9, 3
50
BUBBLE SORT
▪ “Bubble Sort” has the following properties:
➢ In 1st pass, once the largest element is reached, it keeps on being swapped until it
gets to the last position
➢ In 2nd pass, once the second largest element is reached, it keeps on being swapped
until it gets to the second-to-last position
➢ At the end of the i th pass, the last i elements (i.e., those at indices n-i to n−1) are
already sorted!
51
BUBBLE SORT
▪ See how, at the end of the i th pass, the last i elements are sorted, implying that the i th pass
can be limited to the first n−i+1 elements.
▪ Clearly, Bubble Sort requires accessing elements at different indices. The above analysis
assumes that an element at index i can be accessed in O(1) time.
▪ Now, suppose we are sorting a sequence. Then:
➢ If we use an array-based implementation, where atIndex(i) runs in O(1), the total
runtime of BubbleSort is indeed O(n2)
➢ On the other hand, if we use a list-based implementation, where atIndex(i) runs
in O(n) time, the total runtime becomes O(n3)
▪ Thus, choosing the appropriate implementation of the ADT matters!
54