0% found this document useful (0 votes)
27 views130 pages

Data Structure

Uploaded by

eyob daggy Girma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
27 views130 pages

Data Structure

Uploaded by

eyob daggy Girma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 130

Chapter IV

Data Structure
Review of Pointers & Dynamic memory management

• Any variable in a program occupies a block of bytes in the


memory, where the block size is determined by its data type
• For example,
– int = 2bytes
– double = 4 bytes
– Array of 10 int = 20 bytes
– Etc
• The address of the first byte in the block is called the address of
the variable and it is noted using the ampersand symbol (&)

2
Review of Pointers & Dynamic memory
management
• For example: given the declarations shown below, one can refer to
their address as follows

int x;
double y;
char ch;

&x //address of the first byte occupied by x (2 bytes)


&y //address of the first byte occupied by y (4 bytes)
&ch //address of the first byte occupied by ch (1 bytes)

3
Review of Pointers & Dynamic memory
management
&x
&y
&ch

• This address information can also be stored in a variable called


pointer
• A pointer is a variable whose value is address of an object kept in
the RAM
• A pointer can be declared as
– type * pointer_variable (see the examples)

int *p_int;
double * p_double;
char * p_char;

4
Review of Pointers & Dynamic memory
management
• This pointer variables initially points to no where (NULL)
• We can assign address of a variable which has the same type to a
pointer variable int x;
double y;
char ch;
int *p_int;
double * p_double;
char * p_char;
Assignment to pointer variable

p_int= &x;
p_double = &y;
p_char = &ch;

5
Review of Pointers & Dynamic memory
management
• We can also do infer to the object whose address is kept in a
pointer variable (dereferencing) as follow
int x;
double y;
char ch;

int *p_int;
double * p_double;
char * p_char;
Dereferencing of pointer variable
x = *p_int; //this is to mean the integer object whose address is in p_int
y= *p_double ; //this is to mean the double object whose address is in p_double
ch =*p_char; //this is to mean the character object whose address is in p_char

Dereferencing on a NULL pointer variable (which doesn’t point to any object)


is illegal
6
Review of Pointers & Dynamic memory
management
• Array variable is a special type of a pointer (constant pointer)
• Consider the declaration statement below
int a[10];

• In this declaration, a[i] is an integer variable but a is not an


integer rather it is a constant pointer whose value is &a[0] that
doesn’t change throughout a

a[0]
a[1]
:
:
:
a[9]
7
Review of Pointers & Dynamic memory
management
• In the above declaration statement, the compiler allocate
sufficiently large memory space to hold the 10 integers in a block
and assign the address of the first object to the array variable a.

• Hence,
a = &a[0]
• Note also:
– *a refers to the first object
– *(a+1) refers to the second object
– *(a+i) refers to the ith + 1 object

• Question: What one can store in a pointer variable declared from a


type TYPE? 8
Review of Pointers & Dynamic memory
management
• Answer: We can store
– Address of a variable declared from that type as seen before
– NULL value
– Address of the collection of objects dynamically created using new operator
where each object has type TYPE
• For example int *int_p;
double * double_p;
char * char_p;

int_p = &x;
double_p = new double[10];
char_p = NULL;

Note: TYPE can be basic data type or used defined structure or class
9
Review of Pointers & Dynamic memory
management
Dynamically allocation of memory space
(new operator):

•The new operator request a block of memory from a contiguous memory


space where each block has the same data type and returns the address of the
first byte if the request is successful.
•Syntax of the new operator
type* new type
//This allocate a memory space for one object from the specified type
or
type* new type[size]
//This allocate a memory space for size number of objects having the specified type
10
Review of Pointers & Dynamic memory
management
• The address returned by the new operator can be assigned to a
pointer variable that pointes to the object of the same type

Examples
int *p = new int;
//allocate a memory space for an integer
int *q = new int[100];
//allocate a memory space for an array of 100 integers

• Note: type can be basic data type or user defined structure of class

11
Review of Pointers & Dynamic memory
management
• Question: What are the possible operations on a pointer

12
Review of Pointers & Dynamic memory
management
• Answer: The possible operations include
– Assignment (NULL value, array variable, address of object)
– Indexing as we did for array
int *int_p;
– Dereferencing
double * double_p;
– Arithmetic addition, subtraction char * char_p;
– Comparision Int a[100];
Int x = 55;

int_p = &x;
Int_p =NULL;
Int_p = a;
int_p[10] = x;
*(int_p + 10) = x;
p++;
if (p == NULL)//do something
13
Linear Abstract Data Type
• In this chapter, we will discuss details of both linear and non-
linear ADT

• Both linear and non-linear ADTs can be implemented as an


Array or as a linked list

• Linked list implementation usually require user defined structure


or class definition

• This section focus on linear type ADTs that is implemented using


linked list
Linear Abstract Data Type
• Linear ADT can be implemented as a general linear list or
specialized linear list ADT (such as Stack, Queue, etc)
• General linear lists are used in situations in which the elements are
accessed randomly or sequentially.
• For example, in a college a linear list can be used to store
information about students who are enrolled in each semester and
the following operations can be performed
• Insert new record any where
• Delete any of the records
• Search records
• At the ADT level, we use the list and its operations but at the
implementation level we need to choose a data structure to
implement it.
• A list ADT can be implemented using either an array or a
linked list.
12.15
Linear Abstract Data Type
• A linear ADT is an ADT in which elements are ordered in a list
structure.

• A list ADT define several operations of which six of them are:


• List
• insertion,
• deletion,
• searching,
• traverse,
• checkEmpty
• In this chapter we will discus each of them one by one
Linear Abstract Data Type
• An example of a list ADT with five items is shown below

• The figure shows how we can implement it (array and linked list).
• The linked list implementation has an extra node that has the name
of the list where the node has two fields: a counter and a pointer
that points to the first element
Array Implementation
• Array implementation require
• to declare an array with statically set maximum number of elements
• To define a variable that holds number of objects in the list
• To define a variable that holds the index of the first object
• In array implementation of a general list
• Array list is created when it is declared
• Insertion require searching the index and push some elements to
the right
• Deletion require pushing some elements to the left
• Retrieve is searching operation followed by displaying its value
• Traverse is moving from one index to another in order
• IsEmpty will check weather there is valid object in the list or not
Linked List Implementation
• Linked list is a list of nodes linked to each other via pointers
• A node structure consists of a pointer (next pointer) that points to a
node structure and data element
• The next node pointer is used to establish the links
• Linked list implementation is preferred over array in that
• Linked list avoids shifting objects during insertion
• Linked list avoids shifting objects during deletion
• Linked list don’t require to statically fix the number of objects
Linked List Implementation
• In the following slides, linked list implementation is modeled as
• a class that creates the interface and
• user defined structure Node that defines a node that forms a
linked list
• Sample list class implementation which holds the client part of the
list ADT implementation
• The actual list of objects linked to each other is defined using a
structure called node structure (Node)
• The list class is shown bellow
Linear Abstract Data Type
class list
{
private:
int size; //this defines the total number of nodes in the list
Node *head; //head is the pointer that will point to the 1 st
//object in the linked list
public:
list(); //constructor
int getSize(); //interface
void setSize(int ); //interface
void setHeadPtr( Node * );
//make nextptr to point to the list structure
Node *getHeadPtr(); //return the nextptr to the client.
void insert(Node *); //insert a new node with the given data
void insert(int, double); //insert a new node with the given data
void deleteNode(int);//delete a node with the given key
Node * retrieve(int id); //return a node with key value id
void traverse(); //traverse the list by displaying all the data
void deleteall(); //empty the linked list data
int isEmpty(); //check emptyness of the linked list
};
Linear Abstract Data Type
• Client method can instantiate object from the above class so
that
– the object will have a pointer (nextPtr) that points to the actual list of
nodes
– the object holds the total number of nodes that are linked together
The list operation
• The list operation would creates an empty list that point to no
where (no actual node exists)
• The list operation instantiate object from the list class as shown
bellow and the class constructor will be executed to make the
object

list * mylist = new list();

//constructor
public list::list()
{
size = 0;
head= NULL;
}
Some method implementation of the list class
list::list(){
size = 0;
head = NULL;
}

int list::getSize(){
return size;
}

void list::setSize(int nbSize){


size = nbSize;
}

void list::setHeadPtr(Node* headPtr){


head = headPtr;
}

Node * list::getHeadPtr(){
return head;
}
The insert operation
• Operations other that list, require to define another structure which
is called Node

• A Node structure holds the actual data structure and other related
methods and variables

• The member variable*head will point a the first Node in the linked
list
The insert operation
• The structure node is defined as follow

struct Node {
int key; //a value used to search, sort, retrieve data
double data; //the data identified by the key
Node *nextPtr; //a pointer that points to the next node in the linked list
};
The insert operation
• We assume that data in a general linear list is sorted by key
• Insertion must be done in such a way that the ordering of the
elements is maintained
• To determine where the element is to be placed, searching is
needed
• However, searching is done at the implementation level, not at the
ADT level
The insert operation
• While inserting a node sorted by key, we may need to insert in the
list at the beginning, middle or end in the linked list.

Insertion at beginning

initially

new head

Step 1

new head

Step 2

new head
The insert operation
Insertion at the middle or end

initially
head head

new new

Step 1
head head

new new

head head
Step 2

new
new
The insert operation
• Implementation
void list::insert(Node *node){
Node *current = this->getHeadPtr();
Node *prev = NULL;
while (current != NULL && current->key < node->key){
prev = current;
current = current->nextPtr;
}
if(prev == NULL){ //insert at the beginning
node->nextPtr = current;
this->setHeadPtr(node);
}
else{//insert at the middle or end
node->nextPtr = prev->nextPtr;
prev->nextPtr = node;
}
int size = getSize();
setSize(size+1);
}
The insert operation
• Implementation

void list::insert(int id, double value)


{
Node * newNode = new Node;
newNode->key =id;
newNode->data= value;
newNode->nextPtr = NULL;
insert(newNode);
}
The delete operation
• Deletion from a general list also requires that the list be searched to
locate the data to be deleted.
• After the location of the data is found, deletion can be done
• Deletion can also be made at the beginning (that require to update
head) or middle or end of the linked list
• The following shows the format:

The delete operation


The delete operation
Deletion from the beginning
•Lets assume a node pointed by head (the first node) in the linked list
needs to be deleted
•The following instruction will do that
current = head; //set current to be the node that will be deleted
head = currentnextPtr; //set the second node as the new first node
delete(current) //release the memory reserved for the node

Deleting the first node


The delete operation
Deletion from the middle or end
•Lets assume a node pointed by prev (node that points to the node to be
deleted) which can be the at the middle or end of the linked list needs to
be deleted
•The following instruction will do the deletion
current = prevnextPtr; //set current to be the node that will be
deleted
prevnextPtr= currentnextPtr; //construct the new link
delete(current ) //release the memory occupied by the deleted node
•Note: if the node to be deleted is the last node, the above instruction
makes prevnextPtr to be NULL

Deleting a node from a linked list


The delete operation
• Implementation
void list::deleteNode(int id){
Node *current = this->getHeadPtr();
Node *prev = NULL;
while(current !=NULL && current->key < id){
prev = current;
current = current->nextPtr;
}
if(current != NULL && current->key == id)
{ //current != NULL ==> current->key >= id
int size = getSize();
setSize(size-1);
if(prev == NULL){//delete the first node
this->setHeadPtr(current->nextPtr);
delete current;
}
else{//delete node from the middle or end
prev->nextPtr = current->nextPtr;
delete current;
}
}
}
The delete operation
• One may need to delete all the list in the linked list
• See the implementation

void list::deleteAll(){
Node *current = this->getHeadPtr();
Node *prev = NULL;
while (current != NULL){
prev = current;
current = current->nextPtr;
delete prev;
}
this->setHeadPtr(NULL);
setSize(0);
}
The retrieve operation
• By retrieval, we mean access of a single element (node)
• Like insertion and deletion, the general list should be first
searched, and if the data is found, it can be retrieved
• The format of the retrieve operation is

The retrieve operation


The retrieve operation
• Implementation

Node * list::retrieve(int id){


Node *current = this->getHeadPtr();
Node *prev = NULL;
while(current !=NULL && current->key < id){
prev = current;
current = current->nextPtr;
}
if(current != NULL && current->key == id) {
return current;
}
return NULL;

}
The traverse operation
• Each of the previous operations involves a single element in the
list, randomly accessing the list

• List traversal, on the other hand, involves sequential access

• It is an operation in which all elements in the list are processed one


by one
The traverse operation
• Implementation

void list::traverse(){
Node *current = this->getHeadPtr();
while(current !=NULL){
cout<<current->data<<endl;
current = current->nextPtr;
}
}
The delete operation
• One may also need to delete all the list in the linked list
• See the implementation

void list::deleteAll(){
Node *current = this->getHeadPtr();
Node *prev = NULL;
while (current != NULL){
prev = current;
current = current->nextPtr;
delete prev;
}
this->setHeadPtr(NULL);
setSize(0);
}
The isEmpty operation
• The isEmpty operation checks the status of the list.
• This operation returns true if the list is empty, false if the list is
not empty.

Implementation

int list::isEmpty(){
int count = this->getSize();
if(count == 0) return 1;
return 0;
}
Example

• Assume that a college has a general linear list that holds information about the
students and that each data element is a record with the following fields: ID,
Name and numeric Grade for a maximum of 10 courses.
• Write a menu driven program that does the following as required
• Insert a student record
• Student record can be inserted from key board or from a file
• Records must be inserted sorted based on student ID
• Delete an item
• based on student ID as a key
• based on student name as a key (what algorithm do you implement?
which one is fast?)
• Modify student grade
• after displaying the results already added, the module should facilitate
adding more grade or modify existing grade
• Search for a student and display its complete information (grades and name)
• Traverse the records and generate a report
STACK
• A stack is a restricted linear list in which all additions
and deletions are made at one end, the top
• If we insert a series of data items into a stack and then
remove them, the order of the data is reversed
• This reversing attribute is why stacks are known as Last
In, First Out (LIFO) data structures

Three representations of stacks


STACK
• At the ADT level, we use the stack and its four
operations;
1. stack,
2. push,
3. pop and
4. empty
• At the implementation level, we need to choose a data
structure to implement it.
STACK
• Stack ADTs can be implemented using either an array or
a linked list.
• The figure shows how we can implement the stack.
STACK
• In our array implementation, we need to have
• A record that has two fields: count and top where
count tells the number of objects in the stack and top
indicates the index of the last element
• Array data structure to keep the objects where the size
is difficult to guess at the design time
STACK
• The linked list implementation is similar to the array
implementation
• It has a special node containing
• the count of nodes in the linked list where each node
contains an object
• A pointer that points to the first (top) node in the
linked list (the node last added)
• We will see the linked list implementation of stack for all
the operation
STACK
• In the linked list implementation, we use the following
stack class and Node structure definition

• The Node structure

struct Node {
double data; //the data identified by the key
Node *nextPtr; //a pointer that points to the next node in the linked list
};

Note: stack Node structure don’t require key as we have restricted


operations possible on the stack
STACK
• Stack class
class stack{
private:
int size; //this defines the total number of nodes in the list
Node *head; //Node is the object holder (see next slide)
public:
stack(); //constructor
int getSize(); //interface
void setSize(int nbSize);
void setHeadPtr( Node * ); //make nextptr to point to the list structure
Node *getHeadPtr(); //return the nextptr to the client.
void push(Node *); //insert a new node with the given data
void push(double); //insert a new node with the given data
Node *pop(); //delete a node with the given key
int isEmpty(); //return true if empty
};
The stack operation
The stack operation creates an empty stack.

The following shows the implementation

stack * newStack = new stack();


Which executes the constructor

stack::stack(){
size = 0;
head = NULL;
}
The push operation
The push operation inserts an item at the top of the stack.
The following shows the format.
The push operation
• The push operation add the node to the front of the linked list only
• The following shows the implementation:

void stack::push(Node *node){


Node *head = this->getHeadPtr();
node->nextPtr = head;
this->setHeadPtr(node);
int size = this->getSize();
this->setSize(size+1);
}
void stack::push(double value){
Node * newNode = new Node;
newNode->data= value;
newNode->nextPtr = NULL;
push(newNode);
}
The push operation
• The following shows the implementation of helper functions called in push
function,

int stack::getSize(){
return size;
}

void stack::setSize(int nbSize){


size = nbSize;
}

void stack::setHeadPtr(Node* headPtr){


head = headPtr;
}

Node * stack::getHeadPtr(){
return head;
}
The pop operation
The pop operation deletes the item at the top of the stack.
The following shows the format.

Pop operation
The pop operation
• The pop operation remove the first Node in the linked list if there is
any and return it back

• The following shows the implementation of pop function:

Node* stack::pop(){
Node *current = this->getHeadPtr();
if(NULL!=current){
int size = this->getSize();
this->setHeadPtr(current->nextPtr);
this->setSize(size - 1);
}
return current;
}
The isEmpty operation
• The isEmpty operation checks the status of the stack.
• This operation returns true if the stack is empty and false
if the stack is not empty
• The following shows the implementation:

int stack::isEmpty(){
int count = this->getSize();
if(count == 0) return 1;
return 0;
}
Smaple main function
int main(){
stack * newStack = new stack();
for(int i= 0; i < 100; i++)
newStack->push(i + 0.0);
if(newStack->isEmpty()) cout<<"Stack is empty\n";
else cout<<"Stack is not emptry\n";
Node *node;
for(int i = 0; i < 100; i++){
node = newStack->pop();
if(node== NULL){
cout<<"Node is NULL\n";
break;
}
cout<<node->data<<" ";
}
return 0;
}
Stack applications
Reversing data items
• Reversing data items requires that a given set of data
items be reordered so that the first and last items are
exchanged, with all of the positions between the first and
last also being relatively exchanged.

• For example, the list (2, 4, 7, 1, 6, 8) becomes (8, 6, 1, 7,


4, 2).
Example

The famous algorithm that convert an integer (Q) from decimal


to any base (b) is to iteratively divide Q by b and keep the
remainder in a stack, modify Q to be the quotient and finally
display the content of the stack. Although the algorithm is very
simple, if we print the digits of the converted integer as they are
created, we will get the digits in reverse order. Hence, we need
Stack to reverse the order.
QUEUES
• A queue is a linear list in which data can only be inserted at one
end, called the rear, and deleted from the other end, called the
front

• These restrictions ensure that the data is processed through the


queue in the order in which it is received/arrived

• In other words, a queue is a first in, first out (FIFO) structure


Operations on queues
Although we can define many operations for a queue, four
are basic:
1. Queue,
2. Enqueue (insert a node),
3. Dequeue (delete a node) and
4. empty
Queue Implementation
• At the ADT level, we use the queue and its operations
• We need to choose a data structure to implement it
• A queue ADT can be implemented using either an array or a linked
list.
• The figure also shows how we can implement it.
Queue implementation
• In the array implementation we have a record with three
fields.
• These fields can be used to store information about the queue
(front index, rear index and count)
• Circular view the array facilitate the implementation i.e,
• every enque (if count <N) will modify the rear as
(rear = (rear + 1) mod N) and increment count by one
• every dequeue (if count >0 ) will modify the front as
(front= (front+ 1) mod N) and decrement count by one
Queue implementation
• The linked list implementation is similar but we don’t
care about the size
• We need to have an extra node that has the name of the
queue (Q)
• This node also has three fields: a count, a pointer that
points to the front element and a pointer that points to the
rear element.
Queue implementation
• In the linked list implementation, we use the following
queue class and Node structure definition

• The Node structure

struct Node {
double data; //the data identified by the key
Node *nextPtr; //a pointer that points to the next node in the linked list
};

Note: queue Node structure don’t require key as we have restricted


operations possible on the queue
Queue implementation
• Queue class
class queue{
private:
int size; //this defines the total number of nodes in the list
Node *front; //Node is the object holder (see next slide)
Node *rear;
public:
queue(); //constructor
int getSize(); //interface
void setSize(int nbSize);
void setFrontPtr( Node * ); //make nextptr to point to the list structure
void setRearPtr( Node * ); //make nextptr to point to the list structure
Node *getFrontPtr(); //return the nextptr to the client.
Node *getRearPtr(); //return the nextptr to the client.
void enque(Node *); //insert a new node with the given data

void enque(double); //insert a new node with the


The queue operation
• The queue operation creates an empty queue.

• The following shows the implementation


queue * newqueue = new queue();
Where the constructor looks like

queue::queue(){
size = 0;
front = rear = NULL;
}
The enqueue operation
• The enqueue operation inserts an item at the rear of the
queue.
• The following shows the format.

The enqueue operation


The enqueue operation
• The following shows the implementation
void queue::enque(Node *node){
Node *rear = this->getRearPtr();
if(rear == NULL)
this->setFrontPtr(node);
else
rear->nextPtr = node;
this->setRearPtr(node);
int size = this->getSize();
this->setSize(size+1);
}
void queue::enque(double value){
Node * newNode = new Node;
newNode->data= value;
newNode->nextPtr = NULL;
enque(newNode);
}
The dequeue operation
• The dequeue operation deletes the item at the front of the
queue.
• The following shows the format.
The dequeue operation
• The following shows the implementation:

Node* queue::dequeue(){
Node *current = this->getFrontPtr();
if(NULL!=current) {
int size = this->getSize();
this->setFrontPtr(current->nextPtr);
this->setSize(size - 1);
}
return current;
}
The enqueue and Dequeue operation
• The following are the implementation of functions called in the enque & dequeue
function
int queue::getSize(){
return size;
}
void queue::setSize(int nbSize){
size = nbSize;
}
void queue::setFrontPtr(Node* ptr){
front = ptr;
}
void queue::setRearPtr(Node* ptr){
rear = ptr;
}
Node * queue::getFrontPtr(){
return front;
}
Node * queue::getRearPtr(){
return rear;
}
The empty operation
• The empty operation checks the status of the queue.
• This operation returns true if the queue is empty and false if the
queue is not empty.
• The following shows the implementation:

int queue::isEmpty(){
int count = this->getSize();
if(count == 0) return 1;
return 0;
}
Queue applications
• Queues are one of the most common of all data
processing structures.
• Some of the application areas are:
• In scheduling of Operating Systems (almost all OS)
• In networking (communication of packets needs to be
queued in the network server or in the network
devices)
• in online business applications such as processing
customer requests, jobs and orders.
• In printer spooling
• etc
Example
• Implement the problem of producer and consumer. In this problem two
objects: producer and consumer are linked to each other on the basis that
• A producer produce item to be consumed by a consumer in which the
consumer will take time t to consume the object
• The producer will keep all the items into the queue immediately after
it gets produced
• The consumer will consume the item that is first enqueued into the
queue
• Assume the item produced is a randomly generated integer number
[0, 100] which takes a time t which is equals to the integer number in
miliseconds (use delay function for the consumer)

• You can consider the producer is a computer user that order printing
job and consumer is the printer that does the printing and the integer
number may be the number of characters in the document submitted
for printing
Non-Linear Abstract Data Type
• A non-linear ADT is an ADT in which elements are arranged in a
structure different from linear structure
• The most common structure of a non-linear ADT is graph/tree
• In this section, we will focus on a special non-linear ADT called
tree
• Specifically our focus is on a binary tree of which more specific
attention is given to a binary search tree
• Any binary tree ADT can be implemented as an Array or as a
linked list and we will see both type implementation of binary tree
GRAPHS

• A graph is an ADT made of a set of nodes, called vertices, and


set of lines connecting the vertices, called edges or arcs.
• Graphs may be either directed or undirected.
• In a directed graph, or digraph, each edge, which connects two
vertices, has a direction from one vertex to the other.
• In an undirected graph, there is no direction.
• Each node in a graph can have zero or more parents and children
TREES
• A tree is a non-linear ADT consists of a finite set of elements,
called nodes (or vertices) and a finite set of directed lines, called
arcs (edges), that connect pairs of the nodes with the following
constraints:
• One of the nodes is labeled as the root node
• There is one and only one path from the root to any other
node

Figure Tree representation


TREES
• According to the above definition, a tree is a constrained
directed Graph

• We can divided the vertices in a tree into three categories:


• the root, (with no incoming arc, 0 or more outgoing arcs)
• the leaves (with 1 incoming arc and 0 outgoing arcs) and
• the internal nodes (with 1 incoming arc and 1 or more
outgoing arcs)
• A tree defines a hierarchical structure in which a node can have
only one single parent with depth information (d) for each node
• The depth of the root node is 0
• The depth of any node other than the root is one plus the
depth of its parent
TREES
• Each node in a tree may have a sub tree.
• The sub tree of each node includes one of its children and all
descendants of that child.
• The figure bellow shows all sub tree for the tree shown before

Subtrees
BINARY TREES
• A binary tree is a tree in which no node can have more than two
sub trees

• In other words, a node can have zero, one or two possible sub
trees

A binary tree
Recursive definition of binary trees
• A binary tree is either empty (NULL) or consists of a node
(root) with two sub tree in which each sub tree is also a binary
tree
• The Figures below shows eight binary trees, the first of which is
an empty binary tree (sometimes called a null binary tree).
Array implementation of binary trees
• In order to implement a binary tree with maximum depth (height of the tree) of
h, we need to have an array of 2h+1-1 elements which is the maximum possible
nodes that the tree will have

• Once this array is ready, we can use the following algorithm to determine the
array index of a node as follow
• For each binary tree (in the recursive definition), we have reserved index
from min to max
• Compute mid as (min + max)/2 and keep the root of that binary tree node at
the array index mid
• The left sub tree (binary tree) will have reserved index from min to mid -1
• The right sub tree (binary tree) will have reserved index from mid + 1 to
max
• The complete binary tree will have reserved index from 1 to 2 h+1-1
• Note: some of the array element may not hold any node information (we should
keep some flag to indicate that)
Array implementation of binary trees
• For example, given the following binary tree with h = 2, we need array of 2 2+1-1
elements (which is 7)

• Prepare array of 7 elements initialized with some value (tag to indicate not
having any value
-1 -1 -1 -1 -1 -1 -1

• Keep the root node at mid index (1 + 7)/2 = 4, and use index from 1 to 3 for the
left sub tree and index from 5 to 7 to the right

-1 -1 -1 A -1 -1 -1
Array implementation of binary trees

• Keep the root node of the left sub tree at mid index (1 + 3)/2 = 2, and use index
from 1 to 1 for the left sub tree and index from 3 to 3 to the right
C B D A -1 -1 -1

• Keep the root node of the right sub tree at mid index (5 + 7)/2 = 6, and use index
from 5 to 5 for the left sub tree and index from 7 to 7 to the right

C B D A -1 E -1

• We should stop the recursive assignment of array element when min and max
index becomes the same or nodes stop growing prematurely
Linked List implementation of binary trees
• Linked list implementation of binary tree is the most natural way of
implementing a binary tree (and also for any general tree structure)

• A node structure will have two pointer that pointes to the left and
the right sub tree of the node

struct Node{
double data; //the data element
Node *leftPtr; //a pointer that points to the left sub tree
Node *rightPtr; //a pointer that points to the right sub tree
};
Operations on binary trees
• The six most common operations defined for a binary tree
are:
1. tree (creates an empty tree),
2. traversal
3. insert,
4. delete,
5. Retrieve and
6. isEmpty
The Binary Tree Class
class binary_tree{
private:
int size; //this defines the total number of nodes in the list
Node *root; //Node is the object holder (see next slide)
public:
binary_tree(); //constructor
int getSize(); //interface
void setSize(int nbSize);
void setRootPtr( Node * ); //make nextptr to point to the list structure
Node *getRootPtr(); //return the nextptr to the client.
void insert(Node *); //insert a new node with the given data
void insert(double, int); //insert a new node with the given data
int isEmpty(); //return true if empty
void traverse_preorder(Node *);
void traverse_inorder(Node *);
void traverse_postorder(Node *);
Node * searchParent(Node *node, int id);
Node * search(Node *node, int id);
void binary_tree::deleteOperation(int id);
void deletenode(Node *target, Node* targetParent);
};
Tree Operations on binary trees
• It creates an empty tree structure,
• The following shows the tree operation and the
constructor that constructs the tree

binary_tree * newbinary_tree = new binary_tree();

binary_tree::binary_tree(){
size = 0;
root = NULL;
}
Insert Operations on binary trees
• It inserts a node into the binary tree structure
• The following implementation shows sample implementation of insert operation that
inserts a number of nodes with the intention to create a balanced tree

void binary_tree::insert(double value, int id){


Node * newNode = new Node;
newNode->data= value;
newNode->key= id;
newNode->leftPtr = NULL;
newNode->rightPtr = NULL;
newNode->rightnodecount = 0;
newNode->rightnodecount = 0;
insert(newNode);
}
Insert Operations on binary trees
void binary_tree::insert(Node *node){
Node *parent, *head = this->getRootPtr();
int chooseLeft; //false
if(head == NULL)
this->setRootPtr(node);
else{
while (head != NULL){
chooseLeft = 0;
if(head->leftnodecount < head->rightnodecount) chooseLeft = 1;
parent = head;
if(chooseLeft){
head->leftnodecount++;
head = head->leftPtr;
}
else{
Insert Operations on binary trees
else{
head->rightnodecount++;
head = head->rightPtr;
}
}
if(chooseLeft)
parent->leftPtr = node;
else
parent->rightPtr = node;
}
int size = this->getSize();
this->setSize(size+1);
}
Binary tree traversals
• A binary tree traversal requires that each node of the tree be
processed once and only once in a predetermined sequence.

• The two general approaches to the traversal sequence are depth-


first and breadth-first traversal.

• We will discuss three different depth first traversal techniques:


1. Preorder traversal
2. Inorder traversal
3. Postorder traversal
Preorder tree traversals
• Print node
• Traverse left subtree
• Traverse right subtree
function preorder(Node *node)
{
if(node==null)
return;
displayNode(node);

preorder(nodeleft);

preorder(noderight);
}
Preorder tree traversals
void binary_tree::traverse_preorder(Node *node){
if(node != NULL){
cout<<node->data;
traverse_preorder(node->leftPtr);
traverse_preorder(node->rightPtr);
}
//else cout<<"node is null\n"<<endl;
}
Inorder tree traversals
• Traverse left subtree (call function again)
• Print node
• Traverse right subtree
function inorder(Node *node)
{
if(node==null)
return;
inorder(nodeleft);
displayNode(node);

inorder(noderight);
}
Inorder tree traversals
void binary_tree::traverse_inorder(Node *node){
if(node != NULL){
traverse_inorder(node->leftPtr);
cout<<node->data;
traverse_preorder(node->rightPtr);
}
}
Postorder tree traversals
• Traverse left subtree
• Traverse right subtree
• Print node
function postorder(Node *node)
{
if(node==null)
return;

postorder(nodeleft);

postorder(noderight);
displayNode(node);
}
Postorder tree traversals
void binary_tree::traverse_postorder(Node *node){
if(node != NULL){
traverse_postorder(node->leftPtr);
traverse_postorder(node->rightPtr);
cout<<node->data;
}
}
Retrieve Operations on binary trees
• This will help to retrieve a data with a given key

Node * binary_tree::search(Node *node, int id){


if(node == NULL) return NULL;
if(node->key == id) return node;
Node * left = searchParent(node->leftPtr, id);
if (left == NULL) return(searchParent(node->rightPtr, id));
else return left;
}

Node * binary_tree::searchParent(Node *node, int id){


if(node == NULL) return NULL;
if(node->leftPtr->key == id ||node->rightPtr->key == id) return node;
Node * left = searchParent(node->leftPtr, id);
if (left == NULL) return(searchParent(node->rightPtr, id));
else return left;
}
Delete Operations on binary trees
• Deletes a node from the binary tree
• Deletion first detect the node to be deleted and move the data
down to the leaf and delete the leaf node
Delete Operations on binary trees
void binary_tree::deleteOperation(int id){
Node *target, *targetParent, *node = this->getRootPtr();
if(node == NULL) return;
if(node->key ==id) {
delete node; this->setRootPtr(NULL);
}
else{
targetParent =searchParent(node, id);
if(targetParent!=NULL){
target=((targetParent->leftPtr->key==id)?targetParent->leftPtr:
targetParent->rightPtr);
deletenode(target, targetParent);
}
else return;
}
}
Delete Operations on binary trees
void binary_tree::deletenode(Node *target, Node* targetParent){
Node *grandParent;
while(target != NULL){
grandParent = targetParent;
targetParent = target;
if(target->leftnodecount > target->rightnodecount){
target->leftnodecount--;
target->key = target->leftPtr->key;
target->data = target->leftPtr->data;
target = target->leftPtr;
}
else{
Delete Operations on binary trees
else{
target->rightnodecount--;
target->key = target->rightPtr->key;
target->data = target->rightPtr->data;
target = target->rightPtr;
}
}
if(grandParent->leftPtr == targetParent) grandParent->leftPtr = NULL;
if(grandParent->rightPtr == targetParent) grandParent->rightPtr = NULL;
delete targetParent;
}
isEmptry Operations on binary trees
int binary_tree::isEmpty(){
int count = this->getSize();
if(count == 0) return 1;
return 0;
}
Binary tree applications
• Binary trees have many applications in computer science
• In this section we mention only two of them:
• Huffman coding and
• expression trees.
Huffman coding
• Huffman coding is a compression technique that uses binary trees
to generate a variable length binary code from a string of symbols.
• Huffman coding do analysis on statistical distribution of characters
and determine variable length bit sequence to compress large text
corpus
• The bit sequences will be best represented in a binary treee
Expression trees
• An arithmetic expression can be represented in three different
formats: infix, postfix and prefix.
• In an infix notation, the operator comes between the two operands.
• In postfix notation, the operator comes after its two operands, and
in prefix notation it comes before the two operands.
• These formats are shown below for addition of two operands A and
B.
Expression tree
BINARY SEARCH TREES

• A binary search tree (BST) is a binary tree with one


extra property:
• the key value of each node is greater than the key
values of all nodes in each left sub tree and
smaller than the value of all nodes in each right
sub tree.

Binary search tree (BST)


Example
The figure below shows some binary trees that are BSTs and
some that are not.
Note that a tree is a BST if all its subtrees are BSTs and the
whole tree is also a BST.
• A very interesting property of a BST is that if we apply
the inorder traversal of a binary tree, the elements that are
visited are sorted in ascending order.
• Another feature that makes a BST interesting is that we
can use a version of the binary search algorithm on BST
with logarithmic growth rate
Binary search tree ADTs

• The ADT for a binary search tree is similar to the one we


defined for a general linear list with the same operation.

• As a matter of fact, we see more BST lists than general


linear lists today.

• The reason is that searching a BST is more efficient than


searching a linear list: a general linear list uses sequential
searching, but BSTs use a version of binary search.
BST implementation
• BSTs can be implemented using either arrays or linked lists.
• However, linked list structures are more common and more
efficient.
• The implementation uses nodes with two pointers, left and right.
Implementation

Constructor
binary_tree::binary_tree()
{
size = 0;
root = NULL;
}
Implementation
Insertion
void binary_tree::insert(Node *node){
Node *parent, *head = this->getRootPtr();
if(head == NULL)
this->setRootPtr(node);
else{
while (head != NULL){
parent = head;
if(head->data > node->data)
head = head->leftPtr;
else
head = head->rightPtr;
}
if(parent->data > node->data)
parent->leftPtr = node;
else
parent->rightPtr = node;
}
int size = this->getSize();
this->setSize(size+1);
}
Implementation
Insertion
void binary_tree::insert(double value){
Node * newNode = new Node;
newNode->data= value;
newNode->leftPtr = NULL;
newNode->rightPtr = NULL;
insert(newNode);
}
Implementation

Traversals
void binary_tree::traverse_preorder(Node *node){
if(node != NULL){
cout<<node->data;
traverse_preorder(node->leftPtr);
traverse_preorder(node->rightPtr);
}
}
void binary_tree::traverse_inorder(Node *node){
if(node != NULL){
traverse_inorder(node->leftPtr);
cout<<node->data;
traverse_preorder(node->rightPtr);
}
}
Implementation
Traversals
void binary_tree::traverse_postorder(Node *node){
if(node != NULL){
traverse_postorder(node->leftPtr);
traverse_postorder(node->rightPtr);
cout<<node->data;
}
}
Implementation

Binary Search
Node * binary_tree::search(Node *node, double value){
if(node == NULL) return NULL;
else if(node->data == value) return node;
else if(node->data > value) return(search(node->leftPtr, value));
else return(search(node->rightPtr, value));
}
Implementation
Deletion
void binary_tree::deleteOperation(double value){
Node *node = this->getRootPtr();
Node *target, *targetParent;
if(node == NULL) return;
targetParent =searchParent(node, value);
if(targetParent!=NULL && node->data == value){ //delete the root
deletenode(node, targetParent);
}
else if (targetParent ==NULL){//value doesn't exist
return;
}
else{//delete either leafnode or internal node other than the root
if(targetParent->leftPtr->data == value) target = targetParent->leftPtr;
else target = targetParent->rightPtr;
deletenode(target, targetParent);
}
}
Implementation

Deletion
Node * binary_tree::searchParent(Node *node, double value){
if(node == NULL) return NULL;
if(node->leftPtr->data == value ||node->rightPtr->data == value)
return node;
Node * left = searchParent(node->leftPtr, value);
if (left == NULL)
return(searchParent(node->rightPtr, value));
else return left;
}
Implementation
Deletion
void binary_tree::deletenode(Node *target, Node* targetParent){
if(target->leftPtr == NULL) {
if(target == targetParent->leftPtr) targetParent->leftPtr = target->rightPtr;
if(target == targetParent->rightPtr) targetParent->rightPtr = target->rightPtr;
delete target;
}
else if(target->rightPtr == NULL){
if(target == targetParent->leftPtr) targetParent->leftPtr = target->leftPtr;
if(target == targetParent->rightPtr) targetParent->rightPtr = target->leftPtr;
delete target;
}
else{
Node *temp1 = target;
Node *temp2 = target->leftPtr;
while(temp2->rightPtr !=NULL)
{
Implementation

Deletion

temp1 = temp2;
temp2 = temp2->rightPtr;
}
//temp2 is the node which has the maximum value among all the node to the left of the target
//temp1->right is the parent that points to temp2
//swap data of target and temp2 and delete temp2
target->data = temp2->data;
temp1->rightPtr = temp2->leftPtr;
delete temp2;
}
}
Implementation
IsEmpty

int binary_tree::isEmpty(){
int count = this->getSize();
if(count == 0) return 1;
return 0;
}
Example

• A map of cities and the roads connecting the cities can be


represented in a computer using an undirected graph.
• The cities are vertices and the undirected edges are the roads
that connect them.
• If we want to show the distances between the cities, we can
use weighted graphs, in which each edge has a weight that
represents the distance between two cities connected by that
edge.
Example

• Another application of graphs is in computer networks.


• The vertices can represent the nodes or hubs, the edges can
represent the route.
• Each edge can have a weight that defines the cost of reaching
from one hub to an adjacent hub.
• A router can use graph algorithms to find the shortest path
between itself and the final destination of a packet.

You might also like