Sunteți pe pagina 1din 21

STACKS

A stack is an ADT that holds a collection of items where elements are always added to one end. It
is a LIFO data structure: the last item is pushed onto the stack is also that first item popped off
the stack. The stack can be implemented using either an array with a counter variable or a linked
list with a counter variable. The counter variable keeps track of the top of the stack.

class Stack
{
public:
Stack():m_top(0) {}
void push(int i)
{
if(m_top >= SIZE)
return;
m_stack[m_top] = val;
m_top++; // We post-increment our m_top variable.
}
int pop()
{
if(m_top == 0)
return;
m_top--; // We pre-decrement our m_top variable.
return m_stack[m_top];
}
bool is_empty();
int peek_top();
private:
int m_stack[100];
int m_top; // m_top represents the next open slot.
};

We can use the STL stack by including <stack> to the file. Common uses of stacks include
storing undo items for your word processor, evaluating mathematical expressions, converting
from infix expressions to postfix expressions, and solving mazes.

#include <stack>
#include <iostream>
using namespace std;
int main()
{
stack<int> intStack;
intStack.push(10);
cout << intStack.top(); // Returns top value.
intStack.pop(); // Returns no values.
intStack.empty(); // Returns a boolean value.
intStack.size(); // Returns integer value.
}

When solving a maze, the stack solution visits the cells in a depth-first order: it continues along a
path until it hits a dead end, then backtracks to the most recently visited intersection that has
unexplored branches. Because we're using a stack, the next cell to be visited will be a neighbor
of the most recently visited cell with unexplored neighbors.

QUEUE
A queue is another ADT that functions in a FIFO manner: first in first out. You enqueue items at
the rear and dequeue people from the front. Queues are implemented using linked lists or arrays.
When implementing a queue using a linked list, every time you enqueue an item, add a new node
to the end of the linked list. Every time you dequeue an item, youre essentially removing the
head node. Alternatively, a queue can be implemented as a circular queue by using an array.
There is no need to shift items with the circular queue.

class Queue
{
public:
Queue():m_head(0),m_tail(0),m_count(0) {}
void enqueue (int val)
{
m_integers[m_tail] = val; // Place item in tail position.
m_count++; // Increment the tail value.
m_tail++; // Increment the count value.
if(m_tail > 5) m_tail = 0; // Set to 0 if it passes the end.
}
int dequeue()
{
int temp_head = m_integers[m_head];
m_count--; // Decrement the count value.
m_head++; // Increment the head value.
if(m_head > 5) m_head = 0; // Set to 0 if it passes the end.
return temp_head;
}
private:
int m_integers[6];
int m_head;
int m_tail;
int m_count;
};

Like the stack, the queue also has its own STL class implementation.

#include <queue>
#include <iostream>
using namespace std;
int main()
{
queue<int> intQueue;
intQueue.push(10);
cout << intQueue.front(); // Returns front value.
intQueue.pop(); // Returns no values.
intQueue.empty(); // Returns a boolean value.
intQueue.size(); // Returns integer value.
}

When solving a maze, the queue solution visits the cells in a breadth-first order: it visits all the
cells at distance 1 from the start cell, then all those at distance 2, then all those at distance 3, etc.
Because we're using a queue, the next cell to be visited will be a neighbor of the least recently
visited cell with unexplored neighbors.

INHERITANCE
Whenever we have a relationship A is a type of B, we can apply inheritance to the situation.
Terminology includes the superclass (base class) from which subclasses (derived classes) are
derived. Subclasses can also function as superclasses for subclasses.

class Person
{
public:
Person(string nm, int age)
:m_name(nm),m_age(age){}
virtual void doSomething()
{
cout << Hello World!;
}
private:
string m_name;
int m_age;
};
/* Only use the virtual keyword
for functions you intend to
override for your subclasses.
*/

class Student: public Person


{
public:
Student(string nm, int age)
:Person(nm, age) {}
virtual void doSomething();
void study();
private:
double m_gpa;
int classes[5];
};
void Student::doSomething()
{
cout << Go Bruins!;
Person::doSomething();
}
/* Only use the virtual keyword
inside of the class definition.
Dont use it for the function
definition. */

INHERITANCE RULES:
1. If you define a function in the base class and the derived class, then the derived version of
the function will hide the base version of the function.
2. Any public functions defined in the base class, but not redefined in a derived class can be
called normally by all parts of your program.
3. If your code wants to call a function in the base class thats been redefined in the derived
class, it must use the class:: prefix. For example, a Student with variable name Yoshi
can call the original function through Yoshi.Person::doSomething().
4. Classes are constructed from superclass to subclass.
5. Classes are destructed from subclass to superclass.
6. A derived class may not access private members of the base class.

INHERITANCE AND ASSIGNMENT OPERATORS


When assigning from one derived class to the other, C++ first copies the base data and then
copies the derived data using the copy constructor and assignment operator if present. However,
if the base and derived classes have dynamically allocated member variables, then you must
define assignment operators and copy constructors for the base class and also special versions
of these functions for the derived class.

DISTINGUISHING INHERITANCE AND POLYMORPHISM


Inheritance is when we derive one or more classes from a common base class. All of the derived
classes, by definition, inherit a common set of functions from our base class. Each derived class
may redefine any function originally defined in the base class; the derived class will then have its
own specialized version of those functions.

The idea of polymorphism is invoked when C++ allows us to use a base pointer or a base
reference to access a derived object. Dynamic binding enables the appropriate version of
functions to be called during runtime when the keyword virtual is used before both base class
and derived class function declarations.

POLYMORPHISM
Use the virtual keyword in both base and derived classes when redefining a function or
writing the destructor. This, however, is unnecessary for constructors.

void PrintPrice(Shape& x)
{
cout << Cost is: $;
cout << x.getArea()*3.25;
x.setSide(10); // This function is specific to a square and
// will produce a compiler error if it is called.
}
/* As far as this function is concerned, every variable passed is
simply a Shape: it has no idea which derived class it is working
with. It follows that you can only pass member functions of Shape. */

POLYMORPHISM RULES:
1. You may never point a derived class pointer or reference to a base class variable.
2. C++ always calls the most-derived version of a function associated with a variable as long
as it is marked with the keyword virtual.
3. You cant access private members of the base class from the derived class.
4. You should always make sure that you use the virtual keyword in front of destructors.
5. Make a base class function pure virtual if you realize that the base class version of your
function doesnt or cant do anything useful.

class Person
{
public:
Person(string nm, int age)
:m_name(nm),m_age(age){}
virtual doWork = 0;
private:
string m_name;
int m_age;
};
/* If you define at least one
pure virtual function in a base
class, then the class is called
an abstract base class. */

class Student: public Person


{
public:
Student(string nm, int age)
:Person(nm, age) {}
virtual void doWork()
{
cout << zzz ...;
}
private:
double m_gpa;
int classes[5];
};

/* If your base class has a


pure virtual function, your
derived classes must define
their own versions of it
otherwise the derived class
becomes an abstract base class
itself. */

Writing pure virtual functions and abstract base classes force users to prevent common mistakes,
such as forgetting to define relevant functions in derived classes.

POLYMORPHISM AND FUNCTIONS


struct SomeBaseClass
{
virtual void VirtualFunction(){} // #1
void notVirtualFunction() {} // #2
void tricky() // #3
{
VirtualFunction();
notVirtualFunction();
}
};
struct SomeDerivedClass: public SomeBaseClass
{
virtual void VirtualFunction() {} // #4
void notVirtualFunction() {} // #5
};
int main()
{
SomeDerivedClass d;
SomeBaseClass *b = &d;
b->VirtualFunction(); // Calls #4
b->notVirtualFunction(); // Calls #2
b->tricky(); // Calls #3 which calls #4 then #2
}
/* When you use a base pointer to access a derived object, all
function calls to virtual functions will be directed to the objects
version, even if the function calling the virtual function is not
virtual itself. */

RECURSIVE STEPS TO SUCCESS


1. Figure out what argument(s) your function will take and what it needs to return.
2. Show how you would call your new function with inputs of size n and n-1.
3. Determine your base case(s) and write the code to handle them without recursion.
4. Split your input into two parts of size 1 and n-1. Have your recursive function call itself to solve
the larger part. This is known as our simplifying code.
5. Write the code that gets the result from your recursive call, processes it, and returns the
appropriate result, if any.
6. Validate the function.

DIVIDING ARRAYS FOR RECURSION


int arrayFunction(int arr[], int n) {...}
int main()
{
... // Create some array arr[SIZE].
int s1 = arrayFunction(arr, n); // Input of size n
int s2 = arrayFunction(arr+1, n-1); // Last n-1 items
int s3 = arrayFunction(arr, n-1); // First n-1 items
}

DIVIDING LINKED LISTS FOR RECURSION


int linkedListFunction(Node* h) {...}
int main()
{
... // Create some linked list and cur, a pointer to m_head.
int s1 = linkedListFunction(cur); // Input of size n
int s2 = linkedListFunction(cur->m_next); // Last n-1 items
}

10

RECURSION INVOLVING EQUALLY SIZED SUB-PROBLEMS


ProcessSomeData(SomeDataType x[], int N)
{
If x is small enough to be trivially processed
Process it directly and return the result
Otherwise
Break x into two smaller problems of equal size
Call our function to solve each sub-problem:
result1 = ProcessSomeData(x[0 to N/2],N/2)
result2 = ProcessSomeData(x[N/2 to N],N/2)
Combine result1 and result2 into a solution
Return the result
}

RECURSION INVOLVING SUB-PROBLEMS OF SIZE 1 AND N-1


ProcessSomeData(SomeDataType x[], int N)
{
If x is small enough to be trivially processed
Process it directly and return the result
Otherwise
Break x into two chunks: one of size 1, and one of size N-1
Call our function to solve the larger sub-problem:
result1 = Process x[0] without using recursion
result2 = ProcessSomeData(x[1 to N], N-1)
Combine result1 and result2 into a solution
Return the result
}

11

STACK IMPLEMENTED USING LINKED LIST


class LLStack
{
public:
LLStack():m_count(0),m_head(NULL){}
void push(int val)
{
Node* node = new Node;
node->m_value = val;
if (m_head == NULL)
m_head = node;
else
{
node->m_next = m_head;
m_head = node;
}
m_count++;
}
int pop()
{
if (m_count == 0) return 0;
int val = m_head->m_value;
Node* hnode = m_head;
m_head = hnode->m_next;
delete hnode;
m_count--;
return val;
}
int top()
{
if (m_count == 0) return 0;
return m_head->m_value;
}
private:
int m_count;
Node* m_head;
};

12

PSEUDOCODE FOR ASSIGNMENT OPERATOR AND COPY CONSTRUCTOR


class SomeClass
{
public:
SomeClass (const SomeClass& src)
{
// 1. Allocate memory for the new variable
// 2. Copy data from old variable into the new one
}
void swap(Squares& other)
{
// exchange m_n and other.m_n ints
// exchange m_sq and other.m_sq pointers
}
SomeClass& operator= (const SomeClass& src)
{
if (this != &src)
{
Squares temp(src);
swap(temp);
}
return *this;
}
};

POINTER TIDBITS
int main()
{
/* Behavior of an array of pointers to integers */
int* intPtrArray[10]; // Array of pointers to integers.
intPtrArray[0] = new int; // Use new to store an integer
// in dynamically allocated memory.
(**intPtrArray) = 3; // Access location pointer to by pointer.
int k = 3;
(*(integerArray+1)) = &k; // Determine address of k.
/* Pointer arithmetic */
int intArray[10];
// intArray is a pointer to the first position in the array.
// &intArray[3] is equivalent to (intArray+3).
int* p = intArray + 2;
// p[0] now refers to the value at intArray[2].
}

13

LINKED LIST INSERTION MECHANISM


bool SortedLinkedList::insert(const ItemType &value)
{
Node *p = m_head;
Node *q = NULL;
while (p != NULL)
{
if (value == p->m_value) return false;
if (value < p->m_value) break;
q = p;
p = p->m_next;
}
Node *newNode = new Node;
newNode->m_value = value;
newNode->m_next = p;
newNode->m_prev = q;
if (p != NULL)
p->m_prev = newNode;
else
m_tail = newNode;
if (q != NULL)
q->m_next = newNode;
else
m_head = newNode;
m_size++;
}

14

CLASS TEMPLATES
General
1. Put this in front of the class declaration:
B: class foo {...};
A: template <typename ItemType> class foo {...};
2. Update appropriate types in the class to the new ItemType
Updating internally-defined methods
1. For normal methods, just update all types to ItemType:
B: int bar(int a) {...}
A: ItemType bar(ItemType a) {...}
2. Assignment operator:
B: foo &operator=(const foo &other)
A: foo<ItemType>& operator=(const foo<ItemType>& other)
3. Copy constructor:
B: foo(const foo &other)
A: foo(const foo<ItemType>& other)
Updating externally-defined methods
1. For non-inline methods:
B: int foo::bar(int a)
A: template <typename ItemType>
ItemType foo<ItemType>::bar(ItemType a)
2. For inline methods:
B: inline int foo::bar(int a)
A: template <typename ItemType>
inline ItemType foo<ItemType>::bar(ItemType a)
3. Copy constructor:
B: foo &foo::operator=(const foo &other)
A: foo<ItemType>& foo<ItemType>::operator=(const foo<ItemType>& rhs)
4. Assignment operator:
B: foo::foo(const foo& other)
A: foo<ItemType>::foo(const foo<ItemType>& other)

15

NON-CLASS FUNCTION TEMPLATES


1. Update the function header:
B: int bar(int a)
A: template <typename ItemType> ItemType bar(ItemType a);
2. Replace appropriate types:
B: {int a; float b;}
A: {ItemType a; float b;}

TEMPLATE CLASSES WITH INTERNALLY DEFINED STRUCT


For some internally defined struct blah in a class:
class foo {... struct blah {int val;}; ...};
1. Replace appropriate internal variables in your struct (e.g., int
val;) with your ItemType (e.g., ItemType val;).
2. If an internal method in a class is trying to return an internal
struct (or a pointer to an internal struct), you dont need to
change the functions declaration at all inside the class
declaration; just update relevant variables to your ItemType.
3. If an externally-defined method in a class is trying to return an
internal struct (or a pointer to an internal struct):
a. Assuming your internal structure is called blah, update your
external function bar definitions as follows:
B: blah foo::bar(...) {...}
A: template<typename ItemType> typename foo<ItemType>::blah
foo<ItemType>::bar(...) {...}
B: blah* foo::bar(...) {...}
A: template<typename ItemType> typename foo<ItemType>::blah*
foo<ItemType>::bar(...) {...}
4. Try to pass template items by const reference if you can:
Bad: template <typename ItemType> void foo(ItemType x)
Good: template <typename ItemType> void foo(const ItemType &x)

16

THE STANDARD TEMPLATE LIBRARY


Only the vector class has the capability of being accessed using square brackets; other classes
rely on iterators. The iterator has the following properties:
1. It is much like a pointer variable, but is only used with STL containers.
2. The iterator, however, is not a pointer. It is an object that knows what element it points to, how
to find the previous element in the container, and how to find the next element in the container.
3. They are usually used by first pointing them to the first item in the container.
4. Just like a pointer, you can increment and decrement and iterator to move through each item.
5. You can also read or write each value an iterator points to.

class Nerd
{
public:
void beNerdy() const;
...
};
int main()
{
vector<int> v;
... // Adding some integers into the vector.
vector<int>::iterator it = v.begin(); // Sets to first item in v.
cout << (*it); // Values can be accessed using star operator.
it++; // Iterator now points to the next item.
it--; // Iterator now points to the previous item.
while(it != v.end()) // While the iterator doesnt point to the
// position just after the last item.
{
cout << (*it) << endl;
it++;
}
vector<Nerd> n;
... // Adding some nerds into the vector.
vector<Nerd>::const_iterator c_it = n.begin();
/* Use a const_iterator when you want to either guarantee that
items traversed using the const_iterator will not be modified, or
traverse through a const container. */
n->beNerdy(); // Arrow operator works too!
}

17

ITERATOR QUIRKS
int main()
{
vector<int> v;
v.push_back(3);
vector<int>::iterator it = v.begin();
v.erase(it); // Erasing from a vector invalidates other iterators
// The same behavior applies when you add a new item.
// This doesnt affect sets, lists, or maps.
// As with pointers, you should not access iterators
// that dont point to items.
v.push_back(4);
v.push_back(5);
it = v.begin();
it = v.erase(it); // Returns the iterator to the next element.
v.erase(v.begin(),v.end()); // Erases all elements between the
// first iterator and the position
// before the second iterator.
}
The STL also provides useful some useful functions under the <algorithm> library.

bool is_even(int n)
{...}
int main()
{
vector<int> v;
v.push_back(3);
vector<int>::iterator it = find(v.begin(),v.end(),3);
/* As is the case with the erase function, the first argument is
the first element to search, and the second argument is the
element after the last element to be searched. */
int array[5] = {0, 1, 2, 3, 4};
int* ptr = find(&array[0], &array[5], 5);
if(ptr == &array[5]) // Returns the second argument if not found.
cout << Nope, not found. << endl
int* ptr2 = find_if(&array[0], &array[5], is_even);
/* find_if takes the range and a boolean function as a pointer
that takes values of the same type as those in the container.
Callback function in the form of bool(*p1)(int). */
}

18

STL CONTAINERS
Sequence containers maintain the original ordering of inserted elements. This allows you to
specify where to insert the element in the container.
1. The list containers allows for fast insertions and deletions anywhere in the container, but you
cannot randomly access an element in the container.
2. The vector behaves like an array, but will automatically grow as required.

The defining characteristics of associative containers is that elements are inserted in a predefined order, such as sorted ascending. The associative containers can be grouped into two
subsets: maps and sets. A map consists of a key/value pair. The key is used to order the
sequence, and the value is somehow associated with that key. A set is simply an ascending
container of unique elements.

Both map and set only allow one instance of a key or element to be inserted into the container.

#include <vector>

#include <list>

int main()
{
vector<string> v;
v.push_back(Yoshi);
v.push_back(Steve);
string i = v.front();
string j = v.back();
unsigned long k = v.size();
bool v = s.empty();
v[0] = Wallace;
v.pop_back();
v.clear();
}

int main()
{
list<string> l;
l.push_front(Yoshi);
l.push_back(Steve);
string a = l.front();
string b = l.back();
unsigned long c = l.size();
bool d = l.empty();
l.pop_front();
l.pop_back();
l.clear();
}
/* In lists, you cant access
elements using square brackets.
You want to use vectors for
fast random access and lists
for fast insertion or deletion
at the head and tail of the
container. */

19

PROPERTIES OF A MAP AND SET


1. Both of these order their elements in alphabetical order.
2. For either of them to work, you must define an operator< method for the relevant classes.
3. Only unique keys and items are added to maps and sets respectively.

#include <map>

#include <set>

int main()
{
map<string, int> n_p;
n_p[Arnold] = 9293819;
n_p[Steve] = 9238104;
// [ key ]
value

int main()
{
set<int> s;
s.insert(2);
s.insert(3);
s.insert(4);
s.insert(2); // Ignored.
set<int>::iterator = it;
it = s.find(3);
cout << s.size();
s.erase(2);
s.clear();
}

map<string, int>::iterator
it = n_p.find(Steve);
cout << it->first;
cout << it->second;
n_p.clear();
}
/* This works because maps work
much like alphabetically
ordered structs with first and
second as member variables. */

UNORDERED MAP (HASH MAP)


#include <unordered_map>
#include <iostream>
#include <string>
using namespace std::tr1;
using namespace std;

// required for a hash-based map

int main()
{
unordered_map <string,int> hm;
hm["Carey"] = 10;
unordered_map <string,int>::iterator it = hm.find("Carey");
if (it != hm.end())
cout << it->first << endl;
cout << it->second << endl;
}

20

CHOOSING A SORT
The bubble sort, selection sort, insertion sort, and shell sort.
1. The first three of these sorts have a Big-O of O(n2). The shell sort has a Big-O that is at best
O(n(log2n)2) but is typically fond to be O(n3/2).
2. All of these sorts operate on constant memory.
3. Unlike the selection sort, the bubble and insertion sort is efficient on pre-sorted containers.

The merge sort, heap sort, and quick sort.


1. Quick sort is known to be significantly faster in practice than other O(nlog2n) algorithms.
However, using quick sort always warrants the possibility of stumbling upon a set of data that
produces a Big-O of O(n2) in a case where the container is pre-sorted or fully sorted in normal
or reverse order. Space used, however, is constant. Quick sort is performed on arrays.
2. Merge sort has a guaranteed Big-O of O(nlog2n). However, it is not space constant. It can be
space constant, however, at the expense of a higher constant of proportionality. It is typically
used when bad worst-case performance cannot be tolerated. It works for linked lists.
3. Like quick sort, the heap sort is performed on arrays. Although it is slower than quick sort, it is
assured a Big-O of O(nlog2n). It is performed in position. Heap sort is chosen if the container
being sorted is an array and the worst case scenario of quick sort cannot be tolerated.

CHOOSING A STL CONTAINER


1. Lists for fast insertion or deletion from the head and tail of the container.
2. Vectors for fast random access.
3. Maps when you need to associate a value to another; sorted in alphabetical order.
4. Unordered maps when you want speed but not sorting.
5. Sets when you have unique elements sorted in alphabetical order.
6. Unordered sets when you want speed but not sorting.

MISCELLANIES
1. We can preprocess arrays into hash tables for faster future searching.
2. We can merge items using an unordered set or a regular set.
3. If we want to provide a method to search, we should use a hash table.
4. If we were to choose between a sorted vector or linked list to search for an item, we should opt
for the vector where binary search can be performed.

21

S-ar putea să vă placă și