18
Vectors Introduction When we learn how to program we learn about arrays, their uses, and their benefits. An array is a simple collection of elements of the same data type. Its syntax allows us to address and access a specific element using an offset (commonly known as an index), which is a number which specifies the position the element occupies in the array. The capacity of the array is fixed either when declaring it if it is a static array (e.g., int a[10];) or when allocating it in the memory heap if it is a dynamic array (e.g., int* a = new int[10]; or int* a = (int*) malloc(10 * sizeof(int);). A vector is an array-based data structure which allows a programmer to add, remove, and fetch elements from it very much like an array, but unlike an array, a vector has an internal implementation that keeps count of how many elements are inside it (variable count), as well as makes sure to maintain the integrity and order of the data structure. Also, depending on the implementation, a vector may increase (or even decrease) the capacity of the underlying array to fit more elements (or trim unused slots). When a vector increases its internal array capacity we say it grows, when it decreases its internal array capacity we say it shrinks. In a vector, elements are stored contiguously. That means that even though the underlying array may allow storing elements in whichever slot is available, a vector implementation makes sure that slots in the array are filled in order and with no empty slots in between. This property, the property of elements been stored contiguously, affects all operations which must make sure the property is kept at all times. Vectors are data structures with a vast range of uses. They may be used to represent bounded or unbounded queues, lists, or stacks, which in turn may be used to store items when we do not know the number of items beforehand or we know the number of items will increase eventually. Operations Unlike an array, a vector does not allow us to insert, remove, or fetch elements in or from whichever position we want directly , but rather it provides a set of methods which allow us to insert, remove, or fetch an element at the beginning (or head) of the vector, or at the end (or tail) of the vector. Optionally, an implementation may include an operation to insert, remove, or fetch an element in the i th position (or at the middle), or an operation that allows us to replace (set) an element at a given position rather than insert a new one. This document discusses the insert, remove and fetch operations, as well as the growth operation; some other operations are left as exercises.

Reference #1 - Vectors

Embed Size (px)

DESCRIPTION

Vectors explanation pdf

Citation preview

Page 1: Reference #1 - Vectors

Vectors

Introduction When we learn how to program we learn about arrays, their uses, and their benefits. An array is a

simple collection of elements of the same data type. Its syntax allows us to address and access a specific

element using an offset (commonly known as an index), which is a number which specifies the position

the element occupies in the array. The capacity of the array is fixed either when declaring it if it is a

static array (e.g., int a[10];) or when allocating it in the memory heap if it is a dynamic array (e.g.,

int* a = new int[10]; or int* a = (int*) malloc(10 * sizeof(int);).

A vector is an array-based data structure which allows a

programmer to add, remove, and fetch elements from

it very much like an array, but unlike an array, a vector

has an internal implementation that keeps count of how

many elements are inside it (variable count), as well as

makes sure to maintain the integrity and order of the

data structure. Also, depending on the implementation,

a vector may increase (or even decrease) the capacity of

the underlying array to fit more elements (or trim

unused slots). When a vector increases its internal array

capacity we say it grows, when it decreases its internal

array capacity we say it shrinks.

In a vector, elements are stored contiguously. That means that even though the underlying array may

allow storing elements in whichever slot is available, a vector implementation makes sure that slots in

the array are filled in order and with no empty slots in between. This property, the property of elements

been stored contiguously, affects all operations which must make sure the property is kept at all times.

Vectors are data structures with a vast range of uses. They may be used to represent bounded or

unbounded queues, lists, or stacks, which in turn may be used to store items when we do not know the

number of items beforehand or we know the number of items will increase eventually.

Operations Unlike an array, a vector does not allow us to insert, remove, or fetch elements in or from whichever

position we want directly , but rather it provides a set of methods which allow us to insert, remove, or

fetch an element at the beginning (or head) of the vector, or at the end (or tail) of the vector.

Optionally, an implementation may include an operation to insert, remove, or fetch an element in the ith

position (or at the middle), or an operation that allows us to replace (set) an element at a given position

rather than insert a new one. This document discusses the insert, remove and fetch operations, as well

as the growth operation; some other operations are left as exercises.

Page 2: Reference #1 - Vectors

Insertion Insertion in a vector may occur at any valid position. A valid position is any position between the index 0

and the next empty position. For example, for a vector with three (3) elements the valid range of

positions is [0, 3] which reads: from index 0 to index 3 including the index 0 and the index 3. Note that if

the vector has three elements there is an element at index 0, another at index 1, and another one at

index 2. Even though empty, the slot at index 3 is valid position because we may add a new element at

that position without breaking the property that elements should be stored contiguously. Thus, for a

completely empty vector (i.e., a vector without elements), the only valid position to insert an element is

at index 0.

When inserting an element in a vector we must check if the given index is a valid position, but also, we

must check whether the array is already full or not. If the position is valid and the array is not full, we

may proceed to insert the element, but if the array is already full, we either disallow the insertion

operation to proceed further or we increase the underlying array capacity to make more space available

for the new element. Typically when this happens, the underlying array capacity is doubled or grown by

a constant factor.

After successfully inserting an element we should increment the variable count by one to indicate that a

new element is stored in the vector. This also helps us keep record of the next empty position in the

array (you will see why soon).

Inserting at the Head

The head is a term used to mean the beginning of the data structure. For the insert operation, the head

is the first position or index 0. Inserting an element at the head of the vector requires shifting all

elements (n) to the right to “make up” space for the new element. If a vector has 20 elements it means

that the operation requires 20 shifts, if it has 100 elements, the operation requires 100 shifts, and so on.

Due to this fact, inserting an element at the head of the vector is a O(n) or linear-time operation on

the average case. (Read Appendix A to learn more about the Big-O notation.)

Suppose that we have a vector with three (3) elements in it. Due to the property of contiguity all slots in the underlying array are filled up from position zero (0) upwards without skipping or leaving an empty slot in between, thus, the positions 0, 1, and 2 are occupied.

Imagine we want to insert the value ‘E’ at the head of the vector (index 0). To do that we need to shift all elements to the right to “make up” space for the value ‘E’.

Page 3: Reference #1 - Vectors

Notice that we began shifting the elements from the last element down to the first. The order in which we do this is important because otherwise we would overwrite all slots with the value ‘D’.

Once all elements are shifted, the first slot becomes available. NOTE: Although it is not shown for simplicity, the position 0 is not truly empty; it has a “ghost” copy of the value ‘D’.

After moving all elements we copy the value ‘E’ at the head of the vector as we intended. The element count is incremented by one to indicate that the vector holds one more element than it did before. NOTE: The “ghost” copy of ‘D’ is overwriten by ‘E’.

Inserting at the Tail

The tail is a term used to mean the end of the data structure. For the insert operation, the tail is the last

position or rather the first empty slot after the last element. Fortunately, this number is the same as the

number of elements in the vector (count). How come? Notice that a vector with two (2) elements has

an element at position 0, and another one at position 1. Thus, the next empty slot would be at index 2.

Unlike inserting at the head, inserting at the tail requires no shifting of elements. As you may have

already noticed the last position has no elements to the right, so there are no elements to shift. Due to

this fact, inserting an element at the end of the vector requires (n – n) or zero (0) shifts, and thus, is a

O(1) or constant-time operation on the average case. (Read Appendix A to learn more about the Big-O

notation.) When inserting elements into a vector this is the best and more efficient operation.

Suppose that we have a vector with three (3) elements in it (same as before). Imagine this time we want to insert the value ‘U’ at the tail of the vector. The element count (3) marks the next available position which is just after the last element or the tail.

Page 4: Reference #1 - Vectors

Since the position at count is already empty we do not need to shift any elements we just insert the value ‘U’ into it and increase the element count by one to indicate that the vector holds one more element than it did before.

Inserting at the Middle

The middle is a term used to mean whatever position other than the first and last position. It is often

referred as inserting the element at the ith position, and is a rarely used operation. Inserting an element

at the ith position of the vector requires shifting all elements beyond it (i.e., all elements from the ith

position to the last position) to the right to “make up” space for the new element. Due to this fact,

inserting an element at the ith position in a vector requires (n – i) shifts which is a O(n) or linear-time

operation on the average case. (Read Appendix A to learn more about the Big-O notation.)

Suppose that we have a vector with three (3) elements in it (same as before). Imagine this time we want to insert the value ‘E’ at index 1 (middle) of the vector.

To do this we shift all elements to the right beginning from the last element and working our way down to the element at position 1. The order in which we do this is important because otherwise we would overwrite all slots at the right of index 1with the value ‘H’.

Once all elements from index 1 are shifted, the slot at position 1 becomes available. NOTE: Although it is not shown for simplicity, the position 1 is not truly empty; it has a “ghost” copy of the value ‘H’.

After moving all elements we copy the value ‘E’ at index 1 of the vector as we intended. The element count is incremented by one to indicate that the vector holds one more element than it did before. NOTE: The “ghost” copy of ‘H’ is overwriten by ‘E’.

Page 5: Reference #1 - Vectors

Special Case: Growing

When inserting either at the beginning, the end, or the middle, we must make sure that the underlying

array capacity is large enough to hold the new element. For bounded or limited-capacity vectors, when

the array is full, no new element may be inserted, the insert operation either fails throwing an exception

or returns false depending on the implementation.

For unbounded or unlimited-capacity vectors, when the array is full, the insertion process is

“interrupted” so the underlying array capacity may be “grown”. The way the underlying array is

“grown” is by allocating a new empty array with double the capacity or a new empty array with a fixed

number of additional slots, and copying all elements from the original array to the newly created one.

On the first case, the array capacity is doubled each time the array becomes full. For example, if the

original capacity is 12, the capacity of the new array will be 24, and the next one will be 48, and so on.

On the second case, the array capacity increases by a fixed amount each time it becomes full. For

example, if the original capacity is 12 and the capacity increment is 4, the next time the array becomes

full the capacity of the new array will be 16 (12 + 4), the next time will be 20 (16 + 4), the next time 24

(20 + 4), and so on.

On a computer the growth operation may fail if there is no enough memory available to allocate the

new array. When this happens, the insert operation may throw an exception or return false to indicate

that the element could not be inserted.

Suppose we have a vector with four (4) elements and an underlying array with a capacity of 5 slots.

If we add another value, the vector will have become full. For bounded vectors this would mean that further inserts will fail, but for an unbounded vector this means that the next insert will trigger a growth in the underlying array capacity.

Imagine we want to add a new value ‘Z’ at the tail of the vector. As you can see from the last diagram, adding another value would put it over the limit of the array. To avoid going over the limit and cause a buffer overflow error, the insert operation is put on hold and a growth operation is initiated over the vector. As part of the growth process, we allocate a new dynamic array with double the capacity. In this example, the capacity is doubled, but the actual capacity of the new array can be tailored differently depending on the implementation.

Page 6: Reference #1 - Vectors

After verifying that the new array is created (which may not be the case if there was not enough memory available) we begin copying all elements from the old array to the new one, one-by-one. If the new array could not be created for whatever reason, the growth operation either throws an exception or returns false to indicate that it failed.

Page 7: Reference #1 - Vectors

Once all elements from the old array have been copied to the new array we delete the old array to recover memory and we set the new array as the underlying array of the vector. After this, we resume the insert operation which will put the value ‘Z’ on the newly available position 5 and will increase the element count to indicate that the vector holds one more element than it did before.

In the past example, the growth operation was triggered by the insert at the tail operation and thus

when it finished, the resumed insert operation requiered no shifting of elements, but you should know

that any of the insert operations could have triggered the growth operation, and if that would have

been the case, none, some or all elements would have been shifted as it would be normally required

depending on the operation. That said, when the vector is full all insert operations become O(n)

including inserting at the tail since the growth operation requires to copy all elements from one array to

the other. Due to this reason, when a vector is full, and thus it needs to grow, it is considered a special

worst-case scenario for the insert operation.

Page 8: Reference #1 - Vectors

The growth operation is a O(n) or linear-time operation, thus:

O(n) + O(n) O(n + n) = O(n) for “Insert at the Head” and “Insert at the Middle” operations.

O(n) + O(1) O(n + 1) = O(n) for “Insert at the Tail” operation.

Insert Implementations

The following implementation makes use of the generic nature of the algorithm to insert an element

into the underlying array at any given position. The algorithm, regardless of position, shifts all elements

from the desired position to the right to “make up” space for the new element. This algorithm holds

true for all valid cases including adding at the beginning (position 0) in which all elements are shifted to

the right, and including adding to the end (position count) in which no elements are shifted.

Algorithm: Verify that the index given is a valid position, and if it is, then check if the vector is full. If

there is at least one slot available in the array shift to the right all the elements from the 'index' position

to the last position to make space for the new value. To do that, begin moving the values from the last

element and work your way down to the element at position 'index'. The order in which we shift the

elements is important. If the position is not valid throw an exception or return false to indicate the

operation could not be completed. The following implementation does the latter.

Inserting at the Middle

bool addMiddle(int index, T value)

{

if(index >= 0 && index <= count) // Includes count

{

if(ensureCapacity())

{

for(int i=count ; i>index ; i--)

{

elements[i] = elements[i-1] ;

}

elements[index] = value ;

count++ ;

return true ;

}

}

return false ;

}

Page 9: Reference #1 - Vectors

Inserting an element at the head of the vector is the same as inserting it at position 0, the

implementation is:

Inserting at the Head

bool addFirst(T value)

{

return addMiddle(0, value) ;

}

Adding an element at the tail is a similar case:

Inserting at the Tail

bool addLast(T value)

{

return addMiddle(count, value) ;

}

The following method (below) makes sure that the underlying array capacity is large enough to store at

least one more element. If it is not large enough, this method:

1. If the ‘capacityIncrement’ variable is zero (0) returns false to indicate that the vector is a

bounded or limited-capacity vector and that it will not grow.

2. Otherwise it creates a new array with the capacity:

a. incremented by 'capacityIncrement' if it is positive or

b. doubled otherwise

3. copies every element of the old array to the new array

4. deletes the old array

5. sets the new array as the underlying array

This method should be called from all insertion-related methods to ensure that enough space is

allocated before inserting a new value. If this method fails to increase the capacity (i.e. it fails to

allocate a new array with enough capacity) it returns false and the new value is not added.

When this method actually has to increment the underlying array capacity, and hence, move all

elements from the old array to the new one, it causes the execution time for all insert operations to be

O(n) since it takes time proportional to the number of elements to complete.

A special note should be made for C/C++ programming language implementations which have the

realloc() function in the cstdlib.h library. This function may simplify the process by not having to allocate

another (larger) array nor move the elements into it (the function does it for you if necessary).

Page 10: Reference #1 - Vectors

Ensuring the Capacity of the Underlying Array

bool ensureCapacity()

{

if(count >= capacity)

{

if(capacityIncrement == 0) { return false ; }

int newCapacity = (capacityIncrement > 0) ?

capacity + capacityIncrement : capacity * 2 ;

T* tmp = new (nothrow) T[newCapacity] ;

if(tmp == NULL) { return false ; }

for(int i=0 ; i<count ; i++)

{

tmp[i] = elements[i] ;

}

delete[] elements ;

elements = tmp ;

capacity = newCapacity ;

}

return true ;

}

Removal Removal in a vector may occur at any valid position. A valid position is any position between the index 0

and the last element position. For example, for a vector with three elements the valid range of positions

is [0, 3) which reads: from index 0 to index 3 including the index 0 and excluding the index 3. Note that

if the vector has three elements there is an element at index 0, another at index 1, and another one at

index 2, so no element at index 3 (we cannot remove something that does not exist).

When removing an element in a vector we must check if the given index is a valid position. If the

position is valid, we may proceed to remove the element. Typically, the underlying array capacity is left

unmodified even if we end up removing all elements, although special implementations may decrease

the underlying array capacity if the number of elements in the array is less than a specific threshold to

reduce memory consumption. This reduction is called shrinking or trimming the vector.

After successfully removing an element we should decrease the variable count by one to indicate that

the vector holds one less element than before. This also helps us keep record of the next empty

position in the array.

Page 11: Reference #1 - Vectors

Removing at the Head

The head is a term used to mean the beginning of the data structure. For the remove operation, the

head is the first position or index 0. Removing an element at the head of the vector requires shifting all

elements (n – 1) to the left to “fill up” the hole left by the removed element. If a vector has 20 elements

that means that the operation requires 19 shifts, if it has 100 elements, the operation requires 99 shifts,

and so on. Due to this fact, removing an element at the head of the vector is a O(n) or linear-time

operation on the average case. (Read Appendix A to learn more about the Big-O notation.)

Suppose that we have a vector with four (4) elements in it. Due to the property of contiguity all slots in the underlying array are filled up from position zero (0) upwards without skipping or leaving an empty slot in between, thus, the positions 0, 1, 2, and 3 are occupied.

Imagine we want to remove the value at the head of the vector (index 0). To do that we need to shift all elements to the left to “fill up” the hole left by the removal of value ‘E’. NOTE: In truth, the value ‘E’ is not removed but rather it is overwritten by the value ‘D’.

Notice that we began shifting the elements from index 1 down to the last element. The order in which we do this is important because otherwise we would overwrite all slots with the value ‘W’.

Once all elements are shifted we decrease the element count by one to indicate that the vector holds one less element than it did before. The last slot is now empty. NOTE: Although it is not shown for simplicity, the position 3 is not truly empty; it has a “ghost” copy of the value ‘W’.

Removing at the Tail

The tail is a term used to mean the end of the data structure. For the remove operations, the tail is the

last position or rather the position of the last element. Fortunately, this number is the same as the

number of elements in the vector minus one (count-1). How come? Notice that a vector with three (3)

Page 12: Reference #1 - Vectors

elements has an element at position 0, another at position 1, and another one at position 2. Thus, the

last element is at index 2 (3 – 1 = 2).

Unlike removing at the head, removing at the tail requires no shifting of elements. As you may have

already noticed the last position has no elements to the right, so there are no elements to shift. Due to

this fact, removing an element at the end of the vector requires (n – n) or zero (0) shifts, and thus, is a

O(1) or constant-time operation on the average case. (Read Appendix A to learn more about the Big-O

notation.) When removing elements from a vector this is the best and more efficient operation.

Suppose that we have a vector with four (4) elements in it (same as before). Imagine this time we want to remove the value at the tail of the vector. The element count (4) marks the next available position which is just after the last element or the tail.

Since the position at count-1 is already the last position we do not need to shift any elements we just “delete” the last element and decrease the element count by one to indicate that the vector holds one less element than it did before. NOTE: The last element ‘U’ does not truly gets “deleted” per se, but by decreasing the element count by one, the index 3 becomes an invalid position and thus, inaccessible for all operations except for the insert operation. If and when the insert operation is invoked the “inaccessible” value ‘U’ at index 3 becomes overwritten.

Removing at the Middle

The middle is a term used to mean whatever position other than the first and last position. It is often

referred as removing the element at the ith position, and is a rarely used operation. Removing an

element at the ith position of the vector requires shifting all elements beyond it (i.e., all elements from

the ith position to the last position) to the left to “fill up” the hole left by the removed element. Due to

this fact, removing an element at the ith position in a vector requires (n – i–1) shifts which is a O(n) or

linear-time operation on the average case. (Read Appendix A to learn more about the Big-O notation.)

Page 13: Reference #1 - Vectors

Suppose that we have a vector with four (4) elements in it (same as before). Imagine this time we want to remove the value at index 1 (middle) of the vector.

To do this we shift all elements to the left beginning from the element at index 2 and working our way down to the last element to “fill up” the hole left by the removal of the value ‘D’. The order in which we do this is important because otherwise we would overwrite all slots up to index 1 with the value ‘W’.

Once all elements are shifted we decrease the element count by one to indicate that the vector holds one less element than it did before. The last slot is now empty. NOTE: Although it is not shown for simplicity, the position 3 is not truly empty; it has a “ghost” copy of the value ‘W’. Also, the value ‘D’ at position 1 was not “deleted” but rather it was overwritten by the value ‘H’ when shifting.

Special Case: Shrinking

When removing either at the beginning, the end, or the middle, sometimes we reach a threshold or load

ratio (count:capacity) in which the current capacity of the underlying array is too large for the few

elements left in it. For bounded or limited-capacity vectors, this is not a problem since the capacity is

fixed and should stay fixed, but for unbounded or unlimited-capacity vectors, the underlying array could

have reached a disproportionate size in relation with its current load, and thus, wasting a lot of memory.

The shrinking process, for unbounded or unlimited-capacity vectors, is triggered when the array reaches

a certain threshold after removing an element. The way the underlying array is “reduced” is by

allocating a new empty array with fewer slots more than the current count or the exact element count

(as for a perfect trim). On the first case for example, if the original capacity is 256, and the element

count is 10, the new array capacity may be 128 or even less. On the second case, the new array capacity

would be exactly 10.

On a computer with relatively limited resources the shrink operation will result in better performance

and resource utilization.

Page 14: Reference #1 - Vectors

Remove Implementations

The following implementation makes use of the generic nature of the algorithm to remove an element

from the underlying array at any given position. The algorithm, regardless of position, shifts left all

elements from the desired position to “fill up” the hole left by the removed element. This algorithm

holds true for all cases including removing from the beginning (position 0) in which all elements are

shifted to the left, and including removing from the end (position count-1) in which no elements are

shifted.

Algorithm: Verify that the index given is a valid position. If it is, shift to the left all the elements from the

'index' position and forward to “fill up” the hole left by the removed element. To do that, begin moving

them from the element at position 'index' and work your way down to the last element (count-1). The

order in which we shift the elements is important. If the position is not valid throw an exception or

return false to indicate the operation could not be completed. The following implementation does the

latter.

Removing from the Middle

bool removeMiddle(int index)

{

if(index >= 0 && index < count) // Does NOT include count

{

for(int i=index ; i<count-1 ; i++)

{

elements[i] = elements[i+1] ;

}

count-- ;

return true ;

}

return false ;

}

Removing an element at the head of the vector is the same as removing the element at position 0, the

implementation is:

Removing from the Head

bool removeFirst()

{

return removeMiddle(0) ;

}

Page 15: Reference #1 - Vectors

Removing an element at the tail is a similar case:

Removing from the Tail

bool removeLast()

{

return removeMiddle(count-1) ;

}

The implementation of the shrinking operation is left as an exercise. Notice that it should behave similar

to the ensureCapacity() implementation, but by decreasing the capacity rather than increase it. A

special note should be made for C/C++ programming language implementations which have the realloc()

function in the cstdlib.h library. This function may simplify the process by not having to allocate another

(smaller) array nor move the elements into it (the function does it for you if necessary).

Fetching You may access an element at any valid position in a vector. A valid position is any position between the

index 0 and the last element position. For example, for a vector with three elements the valid range of

positions is [0, 3) which reads: from index 0 to index 3 including the index 0 and excluding the index 3.

Note that if the vector has three elements there is an element at index 0, another at index 1, and

another one at index 2, so no element at index 3 (we cannot fetch something that does not exists).

When fetching an element in a vector we must check if the given index is a valid position. If the position

is valid, we may proceed to fetch the element. Accessing any element within a vector whether at the

head, at the tail, or at the middle is a O(1) or constant-time operation, because arrays are random-

access memory constructs. In other words, fetching an element at any index of the array occurs

immediately so it is a O(1) or constant-time operation on all cases. (Read Appendix A to learn more

about the Big-O notation.) There is no shifting of elements nor changes involved in the internal structure

when fetching since fetching is a read-only operation. If the index given is illegal (i.e., not valid) a NULL

value is returned or an exception is thrown depending on the implementation.

Fetch Implementations

The algorithm of all three operations is straight forward: verify index, then fetch element. Notice that

though the elements array may have a large capacity, the fetch operation (like all the others operations

discussed) restricts access to only those positions in the array that are valid. For example, an array that

has a capacity for 100 elements, but it only has 12 elements in it, may only access the positions [0 12),

that is, from index 0 to index 11. Implementations may return a sentinel value (e.g., NULL) to indicate

that the index was illegal or thrown an exception. The following implementation does the former.

Page 16: Reference #1 - Vectors

Fetching at the Middle

T getMiddle(int index)

{

return (index >= 0 && index < count) ? elements[index] : NULL ;

}

Fetching at the Head

T getFirst()

{

return getMiddle(0) ;

}

Fetching at the Tail

T getLast()

{

return getMiddle(count-1) ;

}

Advanced Topics Generic Types

When creating an instance of a vector with a generic template implementation like the one shown in

this document is better to create it so it can store pointers to instances of the type rather than directly

to instances of the type. That is, if you want to create vector that will store elements of type class or

struct.

Concurrency

Using semaphores for read and write access for thread. Create a wrapper-like class implementation that

invokes the semaphore methods on each overwritten method.

Exercises 1. When a vector is full the insert operation will cause the vector to try to increase its underlying array

capacity and then to insert the element at the indicated position. This process consists of creating a

new and larger array and copying all elements from the old array to the new array, and then to

Page 17: Reference #1 - Vectors

proceed normally and shift the elements to make space for the new element. In the worst case,

(i.e., inserting at the head when the array is full) this means that n copies and n shifts take place.

Can you think of a way to optimize the insert operation to avoid having to copy all elements from

the old array to the new array and then shift all elements? Explain your answer and write the

optimized code if you thought of a way to do it.

2. When looking at the Vector.h implementation, we can see that removing an element from the tail or

end of the vector — the last occupied slot in the array — is a constant-time operation. In contrast,

removing an element from the head or beginning of the vector — the first slot in the array — is a

linear-time operation, because all elements in the array have to be shifted to fill up the hole left by

the removed element.

Do you think is possible to remove an element from the beginning of the vector in O(1) or constant-

time? If no, why do you think so? Explain your answer and write the modified code if you thought of

a way to do it.

3. Implement a shrinkCapacity() method that reduces the underlying array capacity only if the element

count is less than 25% of the total capacity. Remember to insert the necessary code in the

corresponding places throughout the class too.

4. Implement a removeAll() method that empties the vector by removing all elements. Can you think

of a way to make this method a O(1) or constant-time operation? Explain your answer and write the

modified code if you thought of a way to do it.

References D.S. Malik. C++ Programming: Program Design Including Data Structures, 5th Edition. Course

Technology, 2010. ISBN-10: 0538798092 ISBN-13: 978-0538798099

R. Lafore, Data Structures and Algorithms in Java, 2nd Edition. Sams, 2002 [Classic]. ISBN-10:

0672324539, ISBN-13: 978-0672324536

T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein, Introduction to Algorithms, 3rd Edition. The

MIT Press, 2009. ISBN-10: 0262033844, ISBN-13: 978-0262033848

From Wikipedia, the free encyclopedia:

o http://en.wikipedia.org/wiki/Vector_(C%2B%2B)

o http://en.wikipedia.org/wiki/Array_data_structure

o http://en.wikipedia.org/wiki/Queue_(data_structure)

o http://en.wikipedia.org/wiki/Stack_(data_structure)

Created by: Henry F. Bruckman Vargas

Last Revised: 2011/09/24

Page 18: Reference #1 - Vectors