Data Structure
Data Structure
Data Structure
Review of Pointers & Dynamic memory management
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;
3
Review of Pointers & Dynamic memory
management
&x
&y
&ch
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
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
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):
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
• 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
//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;
}
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::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 traverse operation
• Each of the previous operations involves a single element in the
list, randomly accessing the list
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
struct Node {
double data; //the data identified by the key
Node *nextPtr; //a pointer that points to the next node in the linked list
};
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:
int stack::getSize(){
return size;
}
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
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.
struct Node {
double data; //the data identified by the key
Node *nextPtr; //a pointer that points to the next node in the linked list
};
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.
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
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::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
preorder(nodeleft);
preorder(noderight);
}
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(nodeleft);
displayNode(node);
inorder(noderight);
}
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(nodeleft);
postorder(noderight);
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
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