Data Structure and Patterns Notes PDF
Data Structure and Patterns Notes PDF
COS30008 23/10/2013
Introducing… C++
C++ Basics
Constructors
▸ All Data Types need to have constructors
▸ Constructors serve:
- They exist only for how the data type should be used in any context
- Where no default constructors exist, compiler will attempt to synthesise one for me.
Friends
▸ Not an OO concept
- Circumvents OO Encapsulation
- But in order for I/O to interact with the class, we need access to private members
▸ Friend binary operators must explicitly define who is on BOTH sides of the operator:
▸ Compare this with non-friend member operators, which implicitly take *this as the LHS
operator:
- Return *this to return the updated Book (i.e., return updated this) in this method
1 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ aIStream will return false where invalid data is provided by separated data (e.g., when
lString is provided with a float and not a string)
- If it's an incremental operator, we expect it to increment the data type it's being applied
on
Equivalence
Structural Equivalence
▸ Structurally the same value, even if not technically the same value:
- E.g., the fields inside two objects (on heap) are the same; they're structurally equivalent.
- They are not nominally equivalent; their values (pointer addresses) are different
Nominal Equivalence
▸ Two values are nominally the same if they technically the same:
Uniquely C++
Enumerators
▸ Defines a group of constants, separated by commas
2 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Typedefs
▸ Hide implementations of a given type
Const Qualifiers
Object Models
Value-Based
▸ Objects exist on the stack
▸ Accessed through object variables (object)
Reference-Based
▸ Objects exist on the heap
3 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ Objects exist on the heap—you are comparing pointer address values here
- You are working with pointers to those objects, not the objects themselves
▸ Buffer size is a reference to the address of block size (an Alias of Block Size)
▸ const addresses give readonly access to the value that the references addresses
- You cannot change the value contained where the references addresses
Pass By…
▸ Value
- Side-affect-free definition
▸ Reference
- Side-affect definition
4 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
class RefMember {
private:
SomeClass& fRefMember;
public:
RefMember(SomeClass& aRef) : fRefMember(aRef) {...}
▸ Essentially a promise that an object will eventually occupy fRefMember in the future
- There will be a SomeClass object that RefMember class can use later via this reference
▸ WARNING!
- Reference data members, i.e., fRefMember, must be initialised before the constructor
body is entered
C++ Inheritance
Access Scopes
▸ public
- Yields a "is a" relationship—causes subtyping polymorphism
▪ I can use the subtype (BankAccount) wherever I can use the supertype (Account)
- Adverse affect: You get everything—you get every single public facet of the member
▪ Get's a bit fuzzy for 500 methods on a button—most of them are just GUI inheritance
▸ protected
- Yields a "is implemented in terms of" relationship—causes subclassing
- The inheritor is not a subtype of the class from an external viewpoint—i.e., no subtyping
polymorphism will guarantee that you can use either class interchangeably
▪ Internally, I see all those inherited methods, but externally you only see my public
Constructors
▸ When a derived class is instantiated, multiple constructors will be invoked to construct the
hierarchy tree, starting from the root object down to me
5 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ Before we create a bank account, we initialise the super instance members by calling
the super constructors— It doesn't actually create super objects
Destructors
▸ Destructors in the inheritance chain will be called starting from the most recent class and
ending with the root class at the top of the inheritance chain:
Virtual Destructors
▸ When deleting a reference polymorphism subtype of a class, ensure that each class in the
inheritance chain gets to run:
BankAccount *bankAccountPtr;
Account *accountPtr;
bankAccountPtr = new BankAccount();
accountPtr = bankAccount; // Subtype polymorphism; subtype used
// anywhere supertype is used
// Ensure virtual destructor on accountPtr (i.e., bankAccontPtr) will
// destroy bank account…
delete accountPtr;
- (i.e., BankAccount is the most recent definition of the value it contains—even though
its a Account class)
- From the most recent definition, it will destruct from that class' virtual destructor
onwards
▪ i.e., we're not destructing Account—we're destructing the value within the account—
a Bank Account
▪ We need to have a virtual destructor so that Account's can be destroyed from their
polynomially defined subtypes:
delete accountPtr;
—> BankAccount() -> virtual ~Account() // virtual = destroyed virtually
6 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Data Structures
Values
Constants
▸ Constants do not have an address:
Pointers
▸ References to values; i.e., they denote value locations
- They store the address of a value (variable or function)
- You cannot change the actual address of where the pointer is pointing to; you can
change where it points to, but you can't change what the address is!
- Generally, a reference beyond two levels indicates that a composite data structure
should be used:
Sets
▸ A set is a collection of elements that are possibly empty and are unordered
{ x | P(x) = True }
- The set defined as X is defined as the property P must be true for all X values
- E.g., if X defined as a set of students, and the property P defined male, then:
7 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▹ We start with this base element that exists already in the set; 0 is a subset of S
▪ S = ∊ | aS
▹ ∊ — an empty string
▹ Hence, all strings are either empty or a subset any ASCII char
Indexed Sets
▸ To obtain an ordering relationship over our unordered sets:
- Thus:
▪ SI ={ ai | a ∈ S, i ∈ I }
▪ Thus, an Indexed Set SI is a combination of:
- SxS Check if the length of each key each is equal: "123" vs "1"
- < Check if the letter is greater than the other, i.e., "A" < "B"; "4" < "3" etc.
Arrays
8 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ Explicit declaration:
- Banana arr[10] — array of 10 Banana objects, initialised with their default constructor
▸ Multidimensional arrays:
▪ int board[][][5] — while dimensions 1 and 2 are unspecified, the last dimension is 5
- Hence this array needs to be defined with a length of 6 should the length be specified:
9 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ A × B = { (a,b) | a ∈ A and b ∈ B }
Associative
▪
▪
Array
The key is a as defined by set A — a is an index into the map
The value is b as defined by set B — b is a value being stored or retrieved
▸ Associative array
- Elements are indexed by their key not by their position
• An associate array is a map in which elements are
- Unlike indexed sets, associative arrays do not assume continuous space in memory
▪ Itindexed by ordered,
looks and feels a key but
rather
memorythan by their position.!
is non-continuously allocated
{ v, if i v in a!
!
a[i] =
!
⊥, otherwise
• Example:!
▸ Hence:
- a[ i ] is defined as:
a =if {i is
▪ v value (“u” 345),
mapped (“v”
to any value 2), (“w”
within a 39), (“x” 5) }!
▪ ⊥ ora[“w”]
undefined behaviour if there is i does not map a value in a (i.e., exception)
= 39!
Indexersa[“z”] = ⊥
▸ The i is the index in the example above
10 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
11 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Iterators
▸ Unlike indexers, iterators give us sequential access to a collection set
- Input Iterator
▪ Reads forward
▪ Provided by istream
- Output Iterator
▪ Writes forward
▪ Provided by ostream
- Forward Iterator
▪ Read and write forward (action is up to you)
- Bidirectional Iterator
▪ Read and write forwards and backwards
▸ begin() method
- Returns an iterator at the first element position (i.e., the lefthand roadblock)
▸ end() method
- Returns an iterator 1 element after the last index position (i.e., the righthand roadblock)
12 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
n + iter Returns the iterator of the nth next element Random Access
n - iter Returns the iterator of the nth previous element Random Access
iter1 - iter2 Returns the delta between two iterator positions Random Access
13 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Example
|
Note: roadblocks make us assume that we don't need to check fIndex is in bounds!
14 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
15 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Patterns
▪ BUT: Hard to recognise the applicability of the pattern to use with the problem
- While we can't change the language itself, we can introduce standard patterns to use
▪ Think Housing Codes—different houses, but same follow the same Housing Code
▸ Reduces maintenance overhead
16 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Ford Factory
▸ Send a request to the factory to create a car with 3 doors, V8 engine, color red
▸ Factory doesn't interface with the client on how the cars are made
17 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Iterator Factory
▸ Its own Iterator class is a factory to the begin() and end() roadblocks
▸ These two methods are the factory methods for the beginning/ending iterators
▸ Clients will never see the implementation of how to these specialised roadblock
iterators are actually made (i.e., I don't know what their constructors params are!)
- Expose the final aggregation of those services in the interface; all gets matched in the
implementation
- Class-Based Adapter
▪ Class-based Adapters lets two classes work together, which could not otherwise be
possible due to incompatible interfaces
18 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▪ Adaptee: Adapter’s request is forwarded from the client to me; I respond with a
specific request and the adapter will respond back in the way the client wants
- Object-Based Adapter
▪ Don’t expose the original adaptee object
( #3 ) Behavioural Patterns
▸ Isolating responsibility of aspects and distributing behaviour
- Prevent responsibility of programming aspects from being held all over the place by
different objects
19 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
- Object composition, not inheritance—how a group of objects will work together so that
all aspects are covered
- ConcreteIterators keep track of the current object in the set and can compute the next
object to be traversed
Recursion
- Decompose a larger problem into a set of smaller problems of the same set or form
▸ “If procedures contain within the body a call to itself, then recursion has been used”
▸ There must always exist a terminating rule:
- The terminating rule is one sub-problem that can be solved directly without recursion
- If there is no terminating rule, we get endless loops of recursion
- X = … X …
Examples
▸ Warning with recursion—each time we call ourself, we add ourself on top of the stack
(consider context growing above)
▸ Stack size is limited to 8KB—stack overflows are intermittent and cause recursive failure
▸ Types of Direct Recursion
- Try to use Direct Left and Direct Right Recursion to save stack space
▪ X = X T
21 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▪ Note, we’ll only perform X if T is true—T will terminate X before X occurs next!!!
Singly-Linked Lists
[ 3 ] [ 5 ] [ 9 ] [ 2] [ 4 ] [ _ ] [ _ ]
[ 3 ] [ 5 ] [ 9 ] [ 2 ] [ 4 ] [ _ ] [ _ ]
becomes shifted:
[ 3 ] [ 9 ] [ 2 ] [ 4 ] [ _ ] [ _ ]
22 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ Set of pairs
- Element The datum that you want to store
▪ The next pointer creates a Traversal order from the first pair to the sentinel or
terminator pair
- This means changes of data outside of aData the list will not affect the list
▪ Thus we avoid aliasing by using value-based containers
▸ Recursive data structure
- fNext is a pointer to itself
- For a ListNode to be declared, it has its own definition inside its definition
▹ In order to determine the size of ListNode, we need to know the size of ListNode
ad infinitum
▪ BUT if pointers are used, we just store the address of the ListNode
▪ Nil means that fNext points to nothing—we still point to it (HAS AN ADDRESS!)
23 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▪ References are aliases to objects that have a fixed and unique location as long as the
reference is in use
ListNode Iterators
▸ Since a ListNode is a generic/template, our NodeIterator must too be an iterator!
- Thus, we need to be able to define a generic NodeIterator for all generic ListNodes
24 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Node Construction
▸ Internally, we will use reference based objects to access our Nodes; it makes it easier for
us to work with the pointers without having to worry about reference types.
▸ Internally—nodes on Heap
Externally—Iterator on Stack
IntegerNode *p, *q
p = new IntegerNode(5);
q = new IntegerNode(7, p);
Node Access
▸ Dereference the pointer and access the fData member
int a = p->fData == 5
int b = q->fNext->fData == 5
// since q->fNext == p!
▸ Since we're going to disconnect p from q, it will be dangling to p still, hence step 2
IntegerNode *r;
r = new IntegerNode(6);
r->fNext = p; // STEP 1
q->fNext = r; // STEP 2
Deleting a Node, r
▸ Simple: Setting the previous node you want to delete (n-1)'s fNext to fNext's fNext
▸ Set q's fNext to it's current fNext's fNext… you set it and it deletes itself!
25 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
q->fNext = q->fNext->fNext
- Keep on iterating while the pointer to a pointer does not equal nil
- The pointer of the pointer becomes the address of the next element
▸ The pointer to the pointer refers to the last, fNext element that is nil
▸ pPtr will keep moving down until it finally reaches a nil sentinel; it will modify the fNext of
that terminator node to the new element and make that new element the nil pointer
26 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
// p is nil essentially
IntegerNode *p = (IntegerNode*)0;
// pPtr (pointer-to-a-pointer) references nil
IntegerNode **pPtr = &p;
// While pPtr is not pointing to nil
while (*pPtr != (IntegerNode*)0)
// pPtr is now equal to wherever the address (&) of the current IntegerNode
// (*pPtr)'s fNext (i.e., set pPtr to the next non-nil node)
pPtr = &((*pPtr)->fNext);
// Finally, out of loop: set wherever pPtr is pointing to to
// the new node to insert: new value is 9
*pPtr = new IntegerNode( 9 );
Inserting at the End of A list—With Aliasing: Record the last next Pointer
▸ Simply make a new pointer to the first element and a pointer to the last element
27 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
private:
DoublyLinkedList()
{
fValue = DataType(); // Set up fValue to default DataType
fNest = &NIL; // Forward of NIL is NIL
fPrevious = &NIL; // Previous of NIL is NIL
}
public:
static Node NIL; // Declare sentinel Node NIL, const above
Templates
28 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
SomeTemplate.h
template<class T1, class T2, class T3 ... class Tn>
class ATemplatedClass
{
private:
T1 fData; // fData is bound to T1's type, whatever that is...
T2 fSome; // fSome is bound to T2's type, whatever that is...
T3 fThin; // fThin is bound to T1's type, whatever that is...
};
- We don't know what this type will be at the moment, only when its used
container<T>::iterator pos;
Abstraction
▸ Abstraction:
- Fights complexity
▸ Class are a form of abstraction: they hide the data and algorithms into one ADT
29 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▪ Defines the obligations on how the client will communicate with the ADT
▪ Can use pointer programming to bypass the interface and access the implementation
▸ By State:
- Set of instance variables of an object is its state…
White Box
▸ Full exposure to both the implementation and interface
- You fully trust that the client in the calling context won't have adverse affects to your
underlying data
- E.g., Node and IteratorNode—clients will use IteratorNode, but not Node; thus have full
access to IteratorNode
▸ Pros:
- Easy to debug
▸ Con: Security!
30 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Grey Box
▸ Can view the implementation but can only modify state to interfaces that allow it
▸ Internal structure is available, but no interface methods exist to modify certain states
- Clients only care if the service they want (Interface) from the ADT is being fulfilled
▹ C++ provides us with the typeof operator for all ADTs when an ADT is compiled
Container Types
▸ Container types are Data Types that host/contain other values
- In Math: If a value is said to be part of a set, than that set will own that value
- Therefore, we can't use reference based since the set doesn't own that value
▪ It can still be modified even outside the set it's contained in!!!
▪ Not exactly the same object: structurally the same, nominally different.
Stacks
▸ A stack is a linear list (it's a container type)
▸ Items can only be added or deleted from the top element only: push or pop.
▸ Stacks are LIFO
- Last element pushed to the top is first one popped out
- Each stack frame consists of multiple elements, with a pointer to each new frame where
the frame begins and ends, accordingly
32 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ This means that fElements must be a pointer to dynamically allocated memory as an array
▸ The pointer stores the address of the first element in the array
- Whenever we point to the first element in the array, we can offset it:
- To release the memory contained within each element stored in the array
fSize: 1 2 3 4 5 6
fElements: [ abc ] [ def ] [ ghi ] [ ] [ ] [ ]
fStackPointer: ||||
33 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Dynamic Stack
▸ If we don't know the maximum size of our
stack, we need to have a dynamic array
Queue
▸ A queue is a linear list that allows us to have access to the top and the bottom of the list
▸ We queue things into the stack. They come out the other end when we dequeue them.
end[len] front[0]
->[enqueue] [ data ] [ data ] [ data ] [dequeue]->
Priority Queues
34 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ Sometimes we want to ensure that some elements move towards the end of the queue
than other elements
▸ Therefore, the elements in the queue will be prioritised (not LIFO per-se but priority LIFO)
- A new data type SortedList<T>, which will be a list sorted by a certain condition
▪ This just leaves the Insert method (the SortedList will decide where to insert the
node!)
- A new data type Pair<Key, T>, which combines an value based on an orderable key
▪ For the key to be orderable, it needs to define some sort of sorting mechanism
▸ Therefore, PriorityQueue will just use the SortedList for fElements, not List
▸ Herein lies starvation issues:
- You need to ensure that, just because a priority element is being enqueued, other
elements still get a chance to be dequeued
35 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▪ That is, age your elements by a certain age factor (to their key—i.e., mature their
priority) to ensure that all elements get a go at being dequeued
Memory
▸ Three main types of memory: Static, Heap, and Stack
Static
▸ All static memory is mapped to the read/write .data segment
- The visibility modifier of the static variable will vary where interaction can occur
▪ This will make the variable or function visible only in that compilation unit
▪ Think of it as a local global variable, local to the compilation unit (or .cpp/.h)
▪ x is initially 10 at the start of the first call, then it's += 10; @ next call it's 20
- Declare a class instance variable
▪ Think Node::NIL
someFile.cpp
int globalProgramVar = 0; // Globally available in entire prog
static int localCompUnitVar = 0; // Globally available in this .cpp
class A
{
private:
static int fMember; // Available by A::fMember
}
A::fMember = 0; // Static members must be declared
// outside the class
36 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▪ Thus, at compile time, constants are replaced with their constant values
Stack
▸ Temporary memory for the current stack frame
Heap
▸ Always allocated heap memory, even if not used
▸ Not automatically freed once the pointer is out of scope; must delete manually
- By default, automatic synthesis will not guarantee deep copies, but shallow copies
▪ Shallow Copy: Copy only the pointers; same address in pointer (same memory!)
▪ Deep Copy: Duplicate the object; different address in pointer (different memory!)
Copy Constructor
▸ Defined as a normal constructor that takes a constant reference to its own type:
- ClassName( const ClassName& instanceOfClass )
- ClassName x = y // will call copy constructor for x with arg y
▸ When implementing copy constructor, be sure to disjoin the members of aOther with this:
37 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
- This is because only the pointer values (i.e., addresses) are copied, not the heap objects
at the end of the pointe
▸ Without the deep copy (i.e. shallow), we would be doubly deleting 0x100 == deletion error
- Can't dealloc already dealloc'ed memory!
Assignment Operator (b = a NOT String b = a)
▸ An assignment operator will, by default, synthesise shallow copies to the RHS object
▪ If you deleted members of this and aOther was this, then when you try step 2, fail!!!
▸ In the above example, a shallow copy has occurred between the two reference-based obj
▸ Therefore, we will need a clone method that allows us to make a copy of the ref-based ob
▸ What is clone?
▪ Student is a person
▹ if not virtual, when I clone a student, I will call the clone on a person instead
▹ that means all the attributes of a student is now missing
class SimpleString
{
public:
// Copy Constructor for Deep Copies
SimpleString(const SimpleString& aOther);
// Virtual Destructor for any Virtual Class is C++ Protocol
virtual ~SimpleString();
// Virtual SimpleString clone method so children can override
virtual SimpleString* clone();
}
// Clone method just use Copy Constructor to make a reference to a
// new deeply copied clone of this:
SimpleString* SimpleString::clone()
{
// Newly alloc'ed memory & deep copy this
return new SimpleString(*this);
}
Why Virtual? So Children Override W/ Their Copy Constructor!
NotSoSimpleString : SimpleString…
NotSoSimpleString* SimpleString::clone()
{
// Need to use my own subclass'ed Copy Constructor…
return new NotSoSimpleString(*this)
}
39 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Remember:
*this = ClassName&
- When there is a new pointer pointing to the heap-based object, add one reference
- When that pointer is destructed (i.e., goes out of scope), then remove that reference
▸ The Handle Class is a way to synchronise reference pointers between value-based
pointers and reference-based objects
- When the reference count is zero (i.e., no pointers pointing to the heap-based object)
then the heap-based object is destructed
- We need to synchronise the reference counter itself; this will also be on the heap too!
40 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Trees
42 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ Parent vs Child
▪ To the right:
▹ Path taken has 3 nodes
▹ Path length is therefore 2 (2 links!)
- Depth and Height
▪ Depth from any given root node to any another
node
▹ D-E-H: #2 links
▹ D-F-G-K and D-F-G-L: #3 links
▹ Hence height of the three is 3
43 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
N-Ary Trees
▸ N-Ary trees are trees where all nodes have the degree
- Problem! Recursive structure (I have 3 children, each child has 3 children ad infinitum)
- Empty trees are sentinels used to denote a node that is not part of the tree set
44 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
- NIL is a black hole, meaning its children are itself: each child is an address to itself
▸ Public Constructor
- Exactly the same with NIL in that initially fill fNodes with NIL
- We will initialise it with aKey provided though, since that we do know
// Pass in aKey
NTree( const T& aKey ) : fKey( &aKey )
{
// Initially, all children are NIL—but change in attach…
for ( int i = 0; i < N; i++ )
fNodes[i] = &NIL;
}
▸ Destructor
- We only kill all of our non-NIL children
- You cannot kill the empty set since this signifies that it's empty!!
~NTree()
{
// For every node...
for ( int i = 0; i < N; i++ )
// ...that isn't a sentinel (cannot delete terminators!)...
if ( fNodes[i] != &NIL )
// ...delete the non-NIL node!
delete fNodes[i];
}
- Since not providing this NTree (i.e., the NIL NTree) with a constructor, use Default Const.
▸ Auxillaries
- Tree is empty if it is the empty set (i.e., NIL)
- Therefore, compare against NIL and allow access to Key if not NIL (otherwise ⊥)
45 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
- Note:
▪ Once injected, the tree now takes ownership of aNTree
▪ This is why you pass in the pointer, and not just a reference!
- You will only get a reference back to the tree node accessed
46 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
// Returned tree is still owned by root tree; you can't modify it!!!
const NTree& operator[]( unsigned int aIndex ) const
{
// Cannot access a NIL node
if ( this->isEmpty() )
throw std::domain_error("Cannot access any empty tree nodes");
// Cannot access outside domain (remember 0-based array!)
if (aIndex >= N)
throw std::out_of_range("Cannot access outside domain of tree!");
// Otherwise, all good! Return readonly reference to the tree
return *fNodes[aIndex];
}
// You get the entire tree back and get to own it
NTree* detachNTree( unsigned int aIndex )
{
// Cannot remove from NIL
if ( this->isEmpty() )
throw std::domain_error("Cannot remove from the empty tree!");
// Cannot remove outside domain (remember 0-based array!)
if (aIndex >= N)
throw std::out_of_range("Cannot remove outside domain of tree!");
// Take out the node (will return it)... from this index
NTree<T,N>* lRetVal = fNodes[aIndex];
// This node is now NIL'ified
fNodes[aIndex] = &NIL;
// Give back the node taken out
return lRetVal;
}
Binary Trees
▸ Binary Trees are structurally just N-Aray trees where N = 2
▸ However, we improve the semantic value of the ADT by explicating treating the interface
as if it is a binary tree—naming the nodes Left and Right etc.
▸ Therefore, this gives us a more distinguished meaning to just an N-ary node with 2 nodes
47 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ As there are just two nodes, there is really no need for an array of nodes
▸ This will mean more specialised semantics: left(), right(), attachLeft(), detachRight() etc.
Tree Traversal
▸ Trees allow us to make sense of data
▸ Systematic method of visiting all the nodes in the tree and follow their relationship
▪ We can use a Stack to implement this (i.e., add all children to stack and keep
popping until no more children)
- Breath-first Traversal (Horizontal Approach)
▪ This means that we will always go across siblings (generational search) of the tree
▹ Start with LHS child, then goto all children in 2nd generation
▪ We can use a queue to implement this (i.e., queue up all children of current node)
Traversal Methods
48 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Method
Pre-Order Traversal
1. Start at the root node
2. Go to the LHS child node
3. Continue for all child nodes to
leaf
4. Backtrack to topmost root node
5. Move to RHS sibling and repeat
Post—Order Traversal
1. Start at LHS leaf node
2. Move to next sibling leaf
3. Backtrack to root node at end
4. Move to RHS child
5. Repeat until at root
In-Order Traversal
1. Traverse from the LHS leaf
2. Keep going up until root
3. Repeat for sibling RHS leaf
FUN FACT:
Goes from smallest to largest node
This is because H < E, D < E etc
49 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Method
Breadth-First
1. Start from root
2. Move across siblings
3. Once at last sibling of same
degree go to first child of first
sibling
4. Repeat
50 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ In the accept "wiring" method, the object typically passes it's key private attributes to
VisitElementA(A)
- Overcomes encapsulation!
template<class T>
class BaseTreeVisitor
{
public:
// Since ConcreteVisitors will inherit from me, need virtual destructor
virtual ~BaseTreeVisitor() {}
// Default behaviour of my children will implement one of the following
virtual void preVisit ( const T& aKey ) const {}
virtual void postVisit ( const T& aKey ) const {}
virtual void inVisit ( const T& aKey ) const {}
// What each child will do, respectively
virtual void visit( const T& aKey ) const
{
std::cout << aKey << " ";
}
}
51 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
- But our children will override each respectively to make them usable:
▪ Preorder:
template<class T>
class PreOrderVisitor : public BaseTreeVisitor<T>
{
public:
// Override preVisit for the PreOrderVisitor iterator to call base
// visit method; now we have implemented that functionality within
// this concrete visitor!
virtual void preVisit( const T& aKey ) const
{
visit( aKey );
}
}
▸ Now in our BINARY tree, we will pass in a BaseTreeVisitor, and thanks to polymorphism,
that can be either one of PreOrderVisitor, PostOrderVisitor and InOrderVisitor:
52 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ The BINARY tree will now support traversal of each dept-first kind
- The visitor itself controls the behaviour of depth first traversal; class is just wiring it up!
Tree Traversal Visitors:
Breadth-First
▸ Rather than using recursion, we
can use a queue instead
// Since we're not using post, pre or in order visitors, we can just
// always use the base visitor instead!
void traverseBreadthFirst( const BaseTreeVisitor<T>& aVisitor ) const
{
// A queue of all the trees we're going to traverse over
Queue< BTree<T> > lTraversalQueue;
// Given that this tree is not empty
if ( !isEmpty() )
// I will let aVisitor traverse over it
lTraversalQueue.enqueue( *this );
// Keep on traversing until the queue is empty
while ( !lTraversalQueue.isEmpty() )
{
// Take off the topmost tree from the traversal queue
const BTree<T> head = lTraversalQueue.dequeue();
// Now visit it's key (i.e., make aVisitor do its job!)
// given it wasn't empty
if ( !head.isEmpty() )
aVisitor.visit( head.key() );
// Queue up each of my subtrees so that they can come in after
// I've finished with this generation—given they are not empty too
// (i.e., add children to next search...)
if ( !head.left().isEmpty() )
aVisitor.enqueue( head.left() );
if ( !head.right().isEmpty() )
aVisitor.enqueue( head.right() );
}
}
53 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
Algorithmic Patterns
- Time to implement
- The test input (i.e., A(n) is faster than B(n) where n < 10; n >= 10 it is otherwise!)
54 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▸ Computable: Any input which is processed to produce some output in a sequence of step
▸ Effectively computable: humans are finite—we want the solution to be solved in:
- finite resources
- Susceptible to:
▪ Halting Problem: We don't know if its still processing or its its broken
- Worst-Case
▪ Solution is the last element in the array, therefore O(n) for n elements in the array
- Average-Case*
▸ Varying O values:
▸ For Loops
- For a for loop with a running time of C inside the loop the
for loop has a running time of at most Cn = O(n)
55 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
▪ The more data introduced to the loop, the longer it processes, linearly
for ( … )
for ( … )
// C
▪ n process in a * n process in b = n * n = n2
▸ Consecutive Statements
- Equals the sum of all statements
- At most, the running time will be the maximum running time of the n'th statement:
▸ If-Then-Else Branch
- Equals running time of if statement and the larger of the branches
▪ Always better to overestimate than underestimate (i.e., assume for the worst)
if ( foo () ) // O(n2)
{
bar() // O(n2)
}
else
{
qux() // O(n5)
}
Algorithmic Patterns
▸ Direct Solution Strategies
▸ Greedy Algorithms
56 / 57
Data Structures And Patterns Alex Cummaudo
COS30008 23/10/2013
- Like Direct Solution approach, but does not explorer all possibilities
▸ Backtracking Strategies
▪ Walks back to cross-road and takes the alternative approach to solve the solution
▸ Top-Down Solution Strategies
- Dynamic Programming
- Solve the solution by computing the current solution to get to the next solution
- i.e., use one of the subproblems that have already been solved
- E.g., Fibonacci:
- Next was assigned to the previous solution plus the current problem…
▸ Randomised Strategies
57 / 57