You are on page 1of 18

Data Structures & Algorithms 1st Year Sheet #7

Cairo University
Faculty of Engineering
Computer Engineering Department
Data Structures and Algorithms
Sheet #7
Trees Commented [E1]:
[Sheet Structure]:
Some differences in trees terminology and which terminology you must Part I: Exercises (simple questions without code):
use in any exam in the course: A – General Trees
B – Binary Trees
C – Binary Search Trees (BST)
D – Heap
As instructed by the doctor, in the reference we use: Part II: Problems (code)
- The node “Levels”, starts with 1 (NOT: 0). A – Binary Trees
B – Binary Search Trees
- The binary tree that has its maximum number of nodes in each level is called:
“Full” Tree (Not: complete).
- The binary tree that has minimum height (all the levels except last contain max
num of nodes) and the nodes in the last level are consecutive (no gaps) is
called: “Complete” Tree (Not: nearly complete).
- In BST, if you want to insert a node that already existing, Do NOT make another node
for it in the tree. Instead, increment the count of this node (the count is a data
member NOT the node’s value).

Part I: Exercises

A. General Trees

1. For the following tree, get the following:

Solution
[1] Tree nodes {A, B, F, C, G, D, E, H, I, J, K, L}
[2] Tree Branches {AB, AF, BC, CD, CE, FG, GH, GI, GJ, IK, KL}
[3] Root Nodes that don’t have parent = {A}
[4] Leaves Nodes that don’t have children {D, E, H, L, J}
[5] Internal nodes = all nodes – root – leaves
= {B, F, C, G, I, K}
[6] Parents Nodes that have children
= all nodes – leaves
= {A, B, F, C, G, I, K}

CMP 102 & CMP N102 1/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

[7] Children Nodes that have parent


= all nodes – root
= {B, F, C, G, D, E, H, I, J, K, L}
[8] Parent of G {F}
[9] Children of G {H, I, J}
[10] Ancestors of H Its parents and grandparents and so on
= {G, F, A}
some references include the node itself as an
ancestor
[11] Descendants of F Its children and grandchildren and so on
= {G, H, I, J, K, L}
some references include the node itself as an
descendants
[12] Siblings of G No siblings
[13] Siblings of I {H, J}
[14] Sub-trees of node A 1-Tree rooted in B
2-Tree rooted in F
[15] Sub-trees of node G 1-Tree rooted in H
2-Tree rooted in I: {I, K, L}
3- Tree rooted in J
[16] Sub-trees of node H NULL, no sub-trees because leaf
[17] In-degree of G one
[18] In-degree of A zero (root)
[19] Out-degree of G three
[20] Out-degree of H zero (leaf)
[21] Degree of G = In-degree + Out-degree
= 1+3 = 4
[22] Level of node A Level 1
(As instructed by the doctor, Levels in the
reference we use starts with 1)
[23] Level of node K Level 5
[24] The height of the tree 6
Height of a tree is the level of the leaf in the
longest path from the root and (Or the number
of tree levels). It is sometimes referred to as
“depth”.
[25] The height of the sub-tree G (rooted at G) 4
[26] The indented list representation A
B
C
D
E
F
G
H
I
K
L
J

CMP 102 & CMP N102 2/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

2. Convert the following trees into binary trees.

(a) (b)

Solution of problem A-2-b

CMP 102 & CMP N102 3/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

2. Binary Trees

1. For a binary tree of N nodes (from 1 to N):


a. What is the max height? Draw its tree. Commented [E2]: = N (tree that contains 1 child from each
b. What is the min height? Draw its tree. node)
Commented [E3]: = Floor(Log2 N) + 1
2. For a binary tree of height H: Full and Complete tree are considered a min height tree –
Draw N=7  H=3
a. What is the max number of nodes? Draw its tree.
b. What is the min number of nodes? Draw its tree. Commented [E4]: = 2^H – 1
Full tree – Draw H=3  N=7

3. For each of the following binary trees: Commented [E5]: = H


a. is it full or complete tree? Commented [E6]:
[ Question 3 (a) : Is it full or complete tree? ]
b. What is its balance factor? a.full, b.complete, c.none,
d.none, e.complete, f. none,
c. is it a balanced binary tree? g.none, h.full, i.none

Full: has its max number of nodes in each level …. The last
level is full
Complete: has minimum height (all the levels except last
contain its max num of nodes) and the nodes in the last
level are consecutive (no gaps) i.e. the left of the first parent
is inserted then the right of it then the left of the second parent
and so on.
(a) (b) (c)
As instructed by the doctor, our reference use the “Full”
and the “Complete” terminology as described above. You
MUST use this terminology in any exam in the course.

Some other references call the “Full” tree  Complete and the
“Complete” tree  Nearly Complete but we will NOT use
this terminology in the course.

Commented [E7]:
[ Question 3 (b) : The balance factor ]

The balance factor of a binary tree is the difference in height


between its left and right subtrees.
B = H(left) – H(right)

(d) (e) (f) a.0, b.1, c.-1,


d.-3, e.1, f.0,
g.0, h.0, i.-1
Commented [E8]:
[ Question 3 (c) : Balanced Binary Trees ]

In a balanced binary tree, the height of its subtrees differs by


no more than one
(its balance factor is –1, 0, or +1), AND its subtrees are also
balanced.

a.yes, b.yes, c. yes,


d.no, e.yes, f.yes,
g.yes, h.yes, i.no (not balanced subtrees)

(h) (i)
(g)

4. Draw all “complete” trees of height 3.

5. Draw “full” trees of the following heights: 2, 3 and 4.

CMP 102 & CMP N102 4/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

6. For the following binary trees, write:


a. The depth-first traversals:
i. Pre-order Commented [E9]:
Depth-First – [ Pre-Order ]:
ii. In-order
iii. Post-order No. a:
ABD GH EI C F JK L M
b. The breadth-first traversal No. b:
ABC DE F G HI J K

Root is at the beginning.


Commented [E10]:
Depth-First – [ In-Order ]:

No. a:
GDH B IE A C JF K ML
No. b:
DCE B A F H G I J K
Commented [E11]:
Depth-First – [ Post-Order ]:

No. a:
GHD IE B J M L K F C A
No. b:
DEC B H K J I G F A

(b) Root is at last.


(a)
Commented [E12]:
Breadth-First:
7. Find the root of each of the following binary trees:
a. tree with post-order traversal: FCBDG No. a:
b. tree with pre-order traversal: IBCDFEN ABCDEFGHIJKLM
No. b:
ABFCGDEHIJK
Commented [E13]: G (post-order -> last node)
3. Binary Search Trees (BST)
Commented [E14]: I (pre-order -> first node)
1. Create a BST using the following data entered as a sequential set: 14 23 7 10 33 56 80 66 70 Commented [E15]: In BST, we always insert at leaf.

We start the comparison from the root then go left or right


depending of the value smaller or greater then insert at the
correct leaf location.

Note: when we insert a value,


we don’t change the location of the previously inserted nodes.

Note: the order of insertion matters in the resultant tree.

Note:
As instructed by the doctor, if you want to insert a node
that already existing in the BST, Do NOT make another
node for it in the tree. Instead, increment the count of this
node (the count is a data member in node NOT its value).

Some other references allow to insert the duplicates but we


will NOT use this in the course.
2. Draw the BST that results when you insert items with keys: E A S Y Q U E S T I O N
Commented [M. Ismail16]:
Created by
3. Traverse the BST no. (a) in the figure below using an inorder traversal. http://visualgo.net/bst.html

Commented [E17]:
Remember the note of inserting duplicates mentioned in the
previous comment.

According to what’s mentioned in the previous comment,


the second “E” will NOT be inserted, however, it’s count
of node “E” will be 2. Same for the second “S” node.
Commented [EHA18]:
In-order traversal of binary search tree is a sorted list.
[Important Note]

CMP 102 & CMP N102 5/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

4. For the BST no. (b) in the figure below:

(a) (b)

a. Search for the value 56 (show the compared nodes in order) Commented [E19]: Tree a:
b. Get the smallest value 25 40 54 -> not found
Tree b:
c. Get the largest value 70 60 50 55 -> not found
d. Insert 44, 66, and 77 into the binary search tree
Commented [E20]: Left-most = 45
Commented [E21]: Right-most = 90
Commented [M. Ismail22]:
Created by
http://visualgo.net/bst.html

e. Delete the node containing 60 from the binary search tree


Solution Steps:

CMP 102 & CMP N102 6/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

f. Delete the node containing 85 from the binary search tree Commented [M. Ismail23]:
Simpler than first case
1-Find node to be delete (85)
2- As 85 has no left, then make its pointer points to its right
4. Heap This will make 80 pointer to 90 at its right

1. Define AVL trees and Heap trees. Draw an example for each.

2. Show which of the following figures are heaps and which are not. Commented [M. Ismail24]:
Heap should be
1- Full or complete BT
2- Parent ≥ both subtrees

a Not heap (not complete)


c Not heap, 13>12
b,d Heap

CMP 102 & CMP N102 7/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

3. Make a heap out of the following data read from the keyboard: Commented [M. Ismail25]:
Steps in general
1-Insert at 1st available leaf
Insertion here does not follow BST algo. Students usually
confuse this part with insert in BST
2-Reheap up
Answer:

I will show only insertions that will cause re-heaping

Commented [M. Ismail26]: Insert 23,7,92


Commented [M. Ismail27]: Reheap up after inserting 92

Commented [M. Ismail28]:


Insert 12 then rehaep up

Commented [M. Ismail29]:


Insertion of 7 and 14 will not cause reheaping
Insertion of 40 needs reheaping up

Commented [M. Ismail30]:


Reheap up after inserting 40

Commented [M. Ismail31]:


Insert 44

CMP 102 & CMP N102 8/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

Commented [M. Ismail32]:


Reheap up (exchange 44 with 6)

Commented [M. Ismail33]:


Reheap up continued
Exchange 44&12

Commented [M. Ismail34]:


Insert 20

Commented [M. Ismail35]:


Reheap up (20&12)

Commented [M. Ismail36]:


Insert 21

Commented [M. Ismail37]:


Reheap up (21&7)

CMP 102 & CMP N102 9/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

4. Apply the re-heap up algorithm to the non-heap structure shown in following figure.

Answer:
The node 45 will be exchanged with 13, then with 23, then with 44
Final heap will be:

5. Apply the re-heap down algorithm to the partial heap structure shown in the following figure.

Answer:
Node 15 will be exchanged with the larger of its children  exchange 15,32 then exchange 15,25
Final Heap:

6. For the following figure:


a. Write the array representation of the heap
b. Apply the delete operation to the heap. Repair the heap after the deletion.
c. Insert 38 into the heap. Repair the heap after the insertion.

CMP 102 & CMP N102 10/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

Answer:
a.
40 23 32 13 11 25 21 8 10

b. Delete operation,
i. Deletes root node  delete 40

ii. Moves the last node data (10) to the root

iii. Reheaps down (as in problem 5 above)  exchange 10 with 32 then with 25
Final heap

c. Insert 38
Inserts 38 at 1st available leaf (right child of node 13)
Then reheaps up: exchange 38 and 13, then 38 and 23, then 38 and 32
Final Heap:

CMP 102 & CMP N102 11/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

Part II: Problems

A. Binary Trees Commented [E38]:


If implemented as linked list:

1. Write a member function that counts the number of nodes in a binary tree. class Node
{
int data;
Node* left;
// Private (Utility) Node* right;
// Recursive function public:
// to be used inside the public CountN() function ...
int BinTree::RecCount (BinaryNode * subRoot) const };
{ class BinTree
if ( ! subRoot) return 0; {
return 1 + RecCount (subRoot -> getleft()) + RecCount (subRoot -> getright()); Node * root;
} public:
};

// Public Function
int BinTree::CountN ()
{
return RecCount(root);
}

2. Write a member function that returns the sum of all the nodes in a binary tree.

// Private (Utility)
// Recursive function
// to be used inside the public function
int BinTree::RecSum (BinaryNode * subRoot) const
{
if( ! subRoot ) return 0;
return subRoot -> getdata() + RecSum (subRoot -> getleft()) + RecSum (subRoot -> getright());
}

// Public Function
int BinTree::Sum ()
{
return RecSum (root);
}

3. Write a member function that counts the number of leaves in a binary tree.

// Private (Utility) Recursive function


int BinTree::RecCountLeaves (BinaryNode * subRoot) const
{
if( ! subRoot ) return 0;
if( ! subRoot -> getleft() && ! subRoot ->getright() ) return 1;
return RecCountLeaves (subRoot -> getleft()) + RecCountLeaves (subRoot -> getright());
}

// Public Function
Same way as shown in previous questions.

4. Write a member function that counts the number of leaves that contain even values in a binary tree.

CMP 102 & CMP N102 12/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

// Private (Utility) Recursive function


int BinTree::RecCountEvenLeaves (BinaryNode * subRoot) const
{
if( ! subRoot ) return 0;
if( ! subRoot -> getleft() && ! subRoot ->getright() )
return (subRoot -> getdata() % 2 == 0); // returns 1 if even, 0 otherwise
return RecCountEvenLeaves (subRoot -> getleft()) + RecCountEvenLeaves (subRoot -> getright());
}

// Public Function
Same way as shown in previous questions.

5. Write a member function that returns the maximum value of all the nodes in a binary tree. Assume
all values are non-negative; return -1 if the tree is empty.

// Private (Utility) Recursive function


int BinTree::RecMaxVal (BinaryNode * subRoot) const
{
if ( ! subRoot ) return -1;
int max = subRoot -> getdata();
int Lmax = RecMaxVal (subRoot -> getleft());
int Rmax = RecMaxVal (subRoot -> getright());
if ( Lmax > max ) max = Lmax;
if ( Rmax > max ) max = Rmax;
return max;
}

// Public Function
Same way as shown in previous questions.

6. Write a member function that prints all the keys that are less than a given value, V, in a binary tree.

// Private (Utility) Recursive function


void BinTree::RecPrintSmaller (BinaryNode * subRoot, int V) const
{
if ( ! subRoot ) return; //
if (subRoot -> getdata() < V)
cout << subRoot -> getdata() <<" " ;
RecPrintSmaller (subRoot -> getleft(), V);
RecPrintSmaller (subRoot -> getright(),V);
}

// Public Function
Same way as shown in previous questions.

7. Write a member function that returns true if the sum of all the nodes in a binary tree equal to a given
value, V.

8. Write a member function is_mirror that returns true if the two passed tree are mirrors; every left
child in the first tree is a right second tree and vice versa.

CMP 102 & CMP N102 13/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

9. Write a member function that creates a mirror image of a binary tree. All left children become right
children and vice versa. The input tree must remain the same.

That’s an Insertion Example.

// Private (Utility) Recursive function


BinaryNode * BinTree::RecMirror (BinaryNode * subRoot) const
{
if ( ! subRoot ) return NULL;
BinaryNode * root2 = new BinaryNode;
root2 -> setdata ( subRoot -> getdata());
root2 -> setleft ( RecMirror ( subRoot -> getright() ) );
root2 -> setright ( RecMirror ( subRoot -> getleft() ) );
}

// Public Function
Same way as shown in previous questions.

10. Write a member function double_tree that, for each node in a binary tree, it creates a new duplicate
node and insert it as the left child.
So the tree...

5
/ \
1 3
Is changed to...
5
/ \
5 3
/ /
1 3
/
1

That’s an Insertion Example.

// Private (Utility) Recursive function


void BinTree::RecDouble (BinaryNode * subRoot) const
{
if ( ! subRoot ) return;
BinaryNode * N = new BinaryNode;
N -> setdata (subRoot -> getdata() );
N -> setleft ( subRoot -> getleft() );
N -> setright ( NULL );
subRoot -> setleft ( N );
RecDouble ( N -> getleft() );
RecDouble (subRoot -> getright() );
}

// Public Function
Same way as shown in previous questions.

CMP 102 & CMP N102 14/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

11. Write a member function to delete all the leaves from a binary tree, leaving the root and intermediate
nodes in place. (Hint: Use a preorder traversal.)

That’s an Deletion Example.

// Private (Utility) Recursive function


// Note that subRoot should be sent by ref
// because fucntion may make it point to NULL if it’s a leaf
// and you want this change to be reflected on the passed pointer after return
void BinTree::RecDeleteLeaves (BinaryNode * & subRoot) const
{
if ( ! subRoot ) return;
If( ! subRoot -> getleft() && ! subRoot -> getright() )
{
delete subRoot;
subRoot = NULL;
return;
}
BinaryNode * left = subRoot -> getleft();
BinaryNode * right = subRoot -> getright();
RecDeleteLeaves ( left );
RecDeleteLeaves ( right );
subRoot -> setleft( left );
subRoot -> setright ( right ); Commented [E39]: In these 6 lines, we simply want to write:
}
RecDeleteLeaves ( subRoot -> getleft());
RecDeleteLeaves ( subRoot -> getright ());
// Public Function
Same way as shown in previous questions. But the problem is that:
subRoot -> getleft() and subRoot -> getright()
are rvalues cannot be sent directly by reference (Error).

12. Write a member function same_structure that returns true if the two passed tree are structurally the In a simpler way, you can think of it as follows.
same whatever the data inside them.
In this function the “subRoot” parameter is sent by reference
because it may be changed inside the function (be equal to
NULL if leaf).
// Private (Utility) Recursive function
So when we send “ subRoot -> getleft(); ” in the recursive
bool BinTree::RecIsSameStructure (BinaryNode * rootA, BinaryNode * rootB) const
function call, that may change the root of the left subtree to
{ NULL if leaf, so we must use setleft() on subRoot (the setter
if ( ! rootA && ! rootB) return true; of its left) to change its left to be the new root of the left
if ( rootA && ! rootB || rootB && ! rootA) return false; subtree (NULL).
return RecIsSameStructure (rootA -> getleft() , rootB -> getleft() ) &&
That’s why we used a temp variable for left, “left”, and send it
RecIsSameStructure (rootA -> getright() , rootB -> getright() ); in the function call. Then, we called subRoot -> setleft( left );
} to set the left of the subRoot with the “left” after change.

If you want an example tree,


// Public Function
try using a tree full of height 2.
Same way as shown in previous questions.
You will notice that you will delete the 2 leaves (all the nodes
of the second level) and make them NULL
but if you don’t call the setters of left and right of the root node
13. Write a member function has_path_sum that returns true if the tree contains any path from the root to be equal the left and right after change to NULL, the root
to one of the leaves with the given sum. node’s left and right will be still pointing to the deleted nodes.

// Private (Utility) Recursive function


bool BinTree::RecHasPathSum (BinaryNode * subRoot, int sum) const
{
if ( ! subRoot ) return ( sum == 0 ); // returns true if sum == 0, false otherwise
int newSum = sum - subRoot -> getdata();
if ( RecHasPathSum ( subRoot -> getleft(), newSum) ||
RecHasPathSum ( subRoot -> getright(), newSum)) return true;
else return false;
}

CMP 102 & CMP N102 15/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

// Public Function
Same way as shown in previous questions.

14. Given a binary tree, write a member function that prints out all of its root-to-leaf paths, one per line.

Not so easy !
Idea:
▪ Each time you visit a node, add it to the path to be printed (shared variable "pass by ref")
▪ Once you reached a leaf, print flush the stored path (root-to-leaf) on the screen
▪ Remove only the leaf from the printed path as the remaining of the path may be needed to
print another branch
▪ For non-leaf nodes, Print all left paths, then print all right paths, then remove this node from
the path to print as all paths passing through it have been printed

The recursive function takes more parameters and print the paths (parameters: subRoot, integer array,
last item index).

// Private (Utility) Recursive function


// note that “lastitem” is sent by ref.
void BinTree::RecPrintPaths (BinaryNode * subRoot, int path[ ], int & lastitem) const
{
if ( ! subRoot ) return;
path[ ++ lastitem ] = subRoot -> getdata(); //Add current node to the path

if( ! subRoot -> getleft() && ! subRoot -> getright() ) //This is a leaf ==> path finished , print it
{
for(int i=0; i<=lastitem; i++)
cout<<path[i]<<" ";
cout<<endl;
lastitem --; // decrement lastitem to remove this leaf from the path
return;
}
if(subRoot -> getleft()) RecPrintPaths ( subRoot -> getleft() , path , lastitem);
if(subRoot -> getright()) RecPrintPaths ( subRoot -> getright() , path , lastitem);
lastitem --; //remove this node as all paths from it have been printed
}

// Public Function  Note how we call the Recursive function above


void BinTree::PrintPaths ( )
{
int path[100]; // array to store current path being explored
int lastitem = -1; // index of last item added to the path
RecPrintPaths (root, path, lastitem);
}

15. The height of a tree is the maximum number of nodes on a path from the root to a leaf node. Write a
member function that returns the height of a binary tree.

// Private (Utility) Recursive function


void BinTree::RecHeight (BinaryNode * subRoot) const
{
if ( ! subRoot ) return 0;
int hL = RecHeight ( subRoot -> getleft() );
int hR = RecHeight ( subRoot -> getright() );
if (hL >= hR ) return hR + 1;
else return hL + 1;

CMP 102 & CMP N102 16/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

// Public Function
Same way as shown in previous questions.

16. A balanced binary tree is a tree in which all the following conditions are satisfied:
a. the left and right sub-trees heights differ by at most one level
b. the left sub-tree is balanced
c. the right sub-tree is balanced
Write a member function that checks if a given tree is balanced or not.

We will use 2 recursive functions.

// Private (Utility) Recursive function  to get the Tree Height


int BinTree::RecTreeHeight (BinaryNode * subRoot) const
{
if ( ! subRoot ) return 0;
int LeftHi = 1 + RecTreeHeight ( subRoot -> getleft() );
int RightHi = 1 + RecTreeHeight ( subRoot -> getright() );
return (LeftHi > RightHi) ? LeftHi : RightHi;
}

// Private (Utility) Recursive function  to check if the Tree is Balanced


bool BinTree::RecIsBalanced (BinaryNode * subRoot) const
{
if ( ! subRoot ) return true;
int hL = RecTreeHeight ( subRoot -> getleft() ); // use the first recursive function
int hR = RecTreeHeight ( subRoot -> getright() ); // use the first recursive function
if ( abs (hL - hR) > 1 ) return false;
return RecIsBalanced ( subRoot -> getleft() ) && RecIsBalanced ( subRoot -> getright() );
}

// Public Function
bool BinTree::IsBalanced ()
{ RecIsBalanced (root); }

17. A full binary tree is a tree in which every node other than the leaves has two children.
Write a member function that checks whether a given tree is full or not.

18. Write a member function that determines whether a binary tree is complete.

19. Rewrite the binary tree pre-order, in-order and post-order traversal member functions using a
stack instead of recursion.

CMP 102 & CMP N102 17/18 Spring 2018


Data Structures & Algorithms 1st Year Sheet #7

B. Binary Search Trees (BST) Commented [M. Ismail40]:


We assumed here that if there is a duplicate value in BST, it
will be on the right subtree.
1. Write a member function that inserts a specific value in BST tree. (definition:
All_left_subtree_nodes < parent
&& ALL_right_subtree_nodes >= parent)
2. Write a member function that searches for a specific value in BST tree.

3. Write a member function that returns the minimum data value found in BST tree.

4. Write a member function that returns number of nodes less than a specific value in BST tree

// Private (Utility) Recursive function


int BST::RecCountLess (BinaryNode * subRoot, int X) const
{
if ( ! subRoot ) return 0;
if( X <= subRoot -> getdata() ) //all values<x are on left only
return RecCountLess ( subRoot -> getleft() , X);
else
return 1 + RecCountLess ( subRoot -> getleft() , X) + RecCountLess (subRoot -> getright() , X);
}

// Public Function
Same way as shown in previous questions.

5. Write a member function that returns a sub-tree that has nodes sum equal to an input value.

CMP 102 & CMP N102 18/18 Spring 2018

You might also like