Data Structures Lec PDF

Download as pdf or txt
Download as pdf or txt
You are on page 1of 156

Data Structures

Lecture 1: Introduction and


Background

Dr. Ashwaq Talib Hashim


Data Structures
A data structure is a scheme for organizing data in the memory
of a computer and the set of operations we need to access this
data.

- The way in which the data is organized affects the performance


of a program for different tasks.

- Computer programmers decide which data structures to use


based on the nature of the data and the processes that need to
be performed on that data.
Some of the more commonly used data
structures include
There are two types of data structure
 Linear Data Structure
A data structure is said to be linear if its elements form a sequence, o rin other words a linear list.

 Array
 Stack
 Queue
 Linked List

 Non- Linear Data Structure


A non-linear structure is mainly used to represent data containing a hierarchical relationship between elements.

 Tree
 Graph

Data Structure Philosophy
 Each data structure has costs and benefits.
 Rarely is one data structure better than another in all situations.
 A data structure requires:
 space for each data item it stores,
 time to perform each basic operation,
 programming effort.

Some Questions to Ask


 Are all data inserted into the data structure at the beginning, or are
insertions interspersed with other operations?
 Can data be deleted?
 Are all data processed in some well-defined order, or is random
access allowed?
Data Structure Operations

 Traversing
 Accessing each data element exactly once so that
certain items in the data may be processed
 Searching
 Finding the location of the data element (key) in the
structure
 Insertion
 Adding a new data element to the structure
Data Structure Operations (cont.)
 Deletion
 Removing a data element from the structure
 Sorting
 Arrange the data elements in a logical order
(ascending/descending)
 Merging
 Combining data elements from two or more data
structures into one
Algorithm
An algorithm is a well-defined list of steps for solving computational
problem

 Complexity of Algorithm

Efficiency or complexity of an algorithm is stated as a function


relating the length to the number of steps (time complexity) or
storage location (space complexity).
f (n)
 In simple words complexity of an algorithm is the time and space
it uses.
What is a good algorithm?

 It must be correct
 It must be finite (in terms of time and size)
 It must terminate
 It must be unambiguous
 Which step is next?
 It must be space and time efficient

A program is an instance of an algorithm, written in


some specific programming language
A simple algorithm
 Problem: Find maximum of a, b, c
 Algorithm
 Input = a, b, c
 Output = max
 Process
o Let max = a
o If b > max then
max = b
o If c > max then
max = c
o Display max

Order is very important!!!


Atomic Variables
 Atomic variables can only store one value at a time.
int num;
float s;
 A value stored in an atomic variable cannot be subdivided.

 A data structure is a data type that can hold multiple values at the
same time. (Synonyms: complex data type, composite data type)
 The array is one kind of data structure.

 Abstract Data Type (ADT) - A data object and a set of operations for
manipulating it
 List ADT with operations insert and delete
 Stack ADT with operations push and pop
Arrays
Array: a set of pairs (index and value)

data structure
For each index, there is a value associated with
that index.

representation (possible)
implemented by using consecutive memory.
Arrays in C++
int list[5];

list[5]: five integers


list[0], list[1], list[2], list[3], list[4]

implementation of 1-D array


list[0] base address = 
list[1]  + sizeof(int)
list[2]  + 2*sizeof(int)
list[3]  + 3*sizeof(int)
list[4]  + 4*size(int)
Array of an element of an array say “A[ I ]” is calculated using the following
formula:
• Address of A [ I ] = B + W * ( I – LB )
Where,
B = Base address
W = Storage Size of one element stored in the array (in byte)
I = Subscript of element whose address is to be found
LB = Lower limit / Lower Bound of subscript, if not specified assume 0
(zero)
Example:
• Given the base address of an array B[1300…..1900] as 1020 and size of
each element is 2 bytes in the memory. Find the address of B[1700].

Solution:
The given values are: B = 1020, LB = 1300, W = 2, I = 1700
Address of A [ I ] = B + W * ( I – LB )
= 1020 + 2 * (1700 – 1300)
= 1020 + 2 * 400
= 1020 + 800
= 1820 [Ans]
Address of an element of any array say “A[ I ][ J ]” is calculated in two forms as given: (1)
Row Major System (2) Column Major System
Row Major System:
The address of a location in Row Major System is calculated using the following formula:
Address of A [ I ][ J ] = B + W * [ N * ( I – Lr ) + ( J – Lc ) ]
Column Major System:
The address of a location in Column Major System is calculated using the following formula:
Address of A [ I ][ J ] Column Major Wise = B + W * [( I – Lr ) + M * ( J – Lc )]
Where,
B = Base address
I = Row subscript of element whose address is to be found
J = Column subscript of element whose address is to be found
W = Storage Size of one element stored in the array (in byte)
Lr = Lower limit of row/start row index of matrix, if not given assume 0 (zero)
Lc = Lower limit of column/start column index of matrix, if not given assume 0 (zero)
M = Number of row of the given matrix
N = Number of column of the given matrix

Data Structures
Lecture 2: C++ Overview

Dr. Ashwaq Talib Hashim


Function
A function is a group of statements that is executed when it is called from
some point of the program. The following is its format:
type name ( parameter1, parameter2, ...)
{ statements }
where:
type is the data type specifier of the data returned by the function.
name is the identifier by which it will be possible to call the function.
parameters (as many as needed): Each parameter consists of a data type
specifier followed by an identifier, like any regular variable declaration (for
example: int x) and which acts within the function as a regular local variable.
They allow to pass arguments to the function when it is called. The different
parameters are separated by commas.
statements are the function's body. It is a block of statements surrounded by
braces { }.
Function
// function example
#include <iostream.h>
int addition (int a, int b)
{ int r;
r=a+b;
return (r);}
int main ()
{ int z; z = addition (5,3);
cout << "The result is " << z;
Return 0;}

Result: The result is 8


// function example
Function
#include <iostream.h>
int subtraction (int a, int b)
{ int r; r=a-b; return (r);}
int main (){
int x=5, y=3, z;
z = subtraction (7,2);
cout << "The first result is " << z << '\n';
cout << "The second result is " << subtraction (7,2) << '\n';
cout << "The third result is " << subtraction (x,y) << '\n';
z= 4 +subtraction (x,y);
cout << "The fourth result is " << z << '\n';
return 0;}
Result:
The first result is 5
The second result is 5
The third result is 2
The fourth result is 6
Function
 Arguments passed by value and by reference
But there might be some cases where you need to manipulate from inside a function the value of an
external variable. For that purpose we can use arguments passed by reference, as in the function
duplicate of the following example:
// passing parameters by reference
#include <iostream.h>
void duplicate (int& a, int& b, int& c)
{
a*=2;
b*=2;
c*=2; }
int main ()
{
int x=1, y=3, z=7;
duplicate (x, y, z);
cout << "x=" << x << ", y=" << y << ", z=" << z;
return 0;
}
Result:
x=2, y=6, z=14
Function
 Recursivity.
Recursivity is the property that functions have to be called by themselves. It is
useful for many tasks, like sorting or calculate the factorial of numbers. For
example, to obtain the factorial of a number (n!) the mathematical formula
would be:

n! = n * (n-1) * (n-2) * (n-3) ... * 1

more concretely, 5! (factorial of 5) would be:


5! = 5 * 4 * 3 * 2 * 1 = 120
and a recursive function to calculate this in C++ could be:
Function
// factorial calculator
#include <iostream.h>
long factorial (long a)
{
if (a > 1)
return (a * factorial (a-1));
else
return (1);
}
int main ()
{ long number;
cout << "Please type a number: ";
cin >> number;
cout << number << "! = " << factorial (number);
return 0; }
Pointers and dynamic objects
Computer Memory

• Each variable is assigned a memory slot (the


size depends on the data type) and the
variable’s data is stored there
Memory address: 1020 1024 1032

… … 100
a
… 1024 …
Variable a’s value, i.e., 100, is
int a = 100; stored at memory location 1024
Pointer Types
Pointer •
C++ has pointer types for each type of object –
• Pointers to int objects
• Pointers to char objects
• Pointers to user-defined objects
(e.g., RationalNumber)
– Even pointers to pointers
• Pointers to pointers to int objects
Pointer Variable
• Declaration of Pointer variables
type* pointer_name;
//or
type *pointer_name;

where type is the type of data pointed to (e.g. int, char,


double)
Examples:
int *n;
RationalNumber *r;
int **p; // pointer to pointer
Address Operator &
• The "address of " operator (&) gives the memory
address of the variable
– Usage: &variable_name

Memory address: 1020 1024

… … 100 … … …
a
int a = 100;
//get the value,
cout << a;
//prints 100
//get the memory address
cout << &a;
//prints 1024
Pointer Variables
Memory address: 1020 1024 1032

… 88 100 … 1024 …
a p
int a = 100; Result is:
int *p = &a; 100 1024
cout << a << " " << &a <<endl; 1024 1032
cout << p << " " << &p <<endl;

The value of pointer p is the address of variable a •


A pointer is also a variable, so it has its own memory address •
Dereferencing Operator *
We can access to the value stored in the variable pointed •
to by using the dereferencing operator (*),

Memory address: 1020 1024 1032

… 88 100 … 1024 …
a p
int a = 100;
int *p = &a; Result is:
cout << a << endl; 100
cout << &a << endl; 1024
cout << p << " " << *p << endl; 1024 100
cout << &p << endl; 1032
Reference Variables
A reference is an additional name to
an existing memory location
Pointer: Reference:

x 9 x
9
ref

ref

int x = 9;
int x=9;
int &ref = x;
int *ref;
ref = &x;
Reference Variables
• A reference variable serves as an alternative name for an
object
int m = 10;
int &j = m; // j is a reference variable
cout << “value of m = “ << m << endl;
//print 10
j = 18;
cout << “value of m = “ << m << endl;
// print 18

• Reference variables are commonly used for parameter passing


to a function
Traditional Pointer Usage
void IndirectSwap(char *Ptr1, char *Ptr2){
char temp = *Ptr1;
*Ptr1 = *Ptr2;
*Ptr2 = temp;
}
int main() {
char a = 'y';
char b = 'n';
IndirectSwap(&a, &b);
cout << a << b << endl;
return 0;
}
Pass by Reference
void IndirectSwap(char& y, char& z) {
char temp = y;
y = z;
z = temp;
}
int main() {
char a = 'y';
char b = 'n';
IndirectSwap(a, b);
cout << a << b << endl;
return 0;
}
Array Name is a pointer constant

#include <iostream>
void main (){
int a[5];
cout << "Address of a[0]: " << &a[0] << endl
<< "Name as pointer: " << a << endl;
}

Result:
Address of a[0]: 0x0065FDE4
Name as pointer: 0x0065FDE4
Array Names as Pointers
To access an array, any pointer to the first element can
be used instead of the name of the array.
We could replace *p by *a

p #include <iostream>
a
void main(){
a[0] 2 int a[5] = {2,4,6,8,22}; 22
int *p = a;
a[1] 4
cout << a[0] << " "
a[2] 6 << *p;
a[3] 8 }
a[4] 22
a
Pointer Arithmetic
Given a pointer p, p+n refers to the element that is
offset from p by n positions.

a 2 p - 1

a + 1 4 p
a + 2 6 p + 1
a + 3 8 p + 2
a + 4 22 p + 3
Array of Pointers & Pointers to Array
a
p
b

An array of Pointers A pointer to an array


int a = 1, b = 2, c = 3; int list[5] = {9, 8, 7, 6, 5};
int *p[5]; int *p;
p[0] = &a; P = list;//points to 1st entry
p[1] = &b; P = &list[0];//points to 1st entry
p[2] = &c; P = &list[1];//points to 2nd entry
P = list + 1; //points to 2nd entry
NULL pointer
• NULL is a special value that indicates an empty pointer
• If you try to access a NULL pointer, you will get an error
int *p;
p = 0;
cout << p << endl; //prints 0
cout << &p << endl;//prints address of p
cout << *p << endl;//Error!
Memory Management
• Static Memory Allocation
– Memory is allocated at compilation time
• Dynamic Memory
– Memory is allocated at running time
Static vs. Dynamic Objects
• Static object • Dynamic object
– Memory is acquired by
(variables as declared program with an
in function calls) allocation request
– Memory is acquired
• new operation
automatically
– Dynamic objects can
– Memory is returned exist beyond the
automatically when object function in which they
goes out of scope were allocated
– Object memory is
returned by a
deallocation request
• delete operation
Object (variable) creation: New
Syntax
ptr = new SomeType;

where ptr is a pointer of type SomeType

Example
int* p = new int;

Uninitialized int variable

p
Object (variable) destruction: Delete
Syntax
delete p;
storage pointed to by p is returned to free store and p is now undefined

Example
int* p = new int;
*p = 10;
delete p;

p 10
Data Structures
Lecture 3: Stack and its Applications

Dr. Ashwaq Talib Hashim


Stack
Stack An abstract data type in which elements are added and removed from
only end; a Last in, First out" (FILO) structure

Operation on Stacks
• Push
 The operation to place a new item at the top of the stack
• Pop
 The operation to remove the next item from the top of the stack
• IsEmpty
 Determines whether the stack is empty
• IsFull
 Determines whether the stack is empty
Stack ADT Specification
Structure: Elements are added to and removed from the top of the stack.
Definitions (provided by user)
MAX_ITEMS: Maximum number of items that might be on the stack.
ItemType: Datatype of the items on the stack.
Operations (provided by the ADT):
MakeEmpty
Function: Sets stack to an empty state.
Precondition: None
Postcondition: Stack is empty.
Implementation of Stack
#include<iostream.h>
#include<stdlib.h>
typedef int ItemType;
const int MAX_ITEMS=10;
class StackType
{
public:
StackType();
void push(ItemType newitem);
ItemType pop();
bool IsEmpty();
bool IsFull();
private:
int top;
ItemType items[MAX_ITEMS];
};
StackType::StackType()
{ top=-1; }
bool StackType::IsEmpty()
{ return(top==-1);}
bool StackType::IsFull(void)
{ return(top==MAX_ITEMS);}
void StackType::push(ItemType newitem)
{
if(IsFull())
{ cout<<"empty"; exit(0);}
top++;
items[top]=newitem;
}
ItemType StackType::pop()
{
if(IsEmpty())
{ cout<<"empty"; exit(0);}
ItemType x=items[top];
top--;
return x;
}
void main()
{ ItemType symbol;
StackType stack;
for(int i=0;i<5;i++)
{ cin>>symbol;
stack.push(symbol);
}
for( i=0;i<5;i++)
cout<<stack.pop();}
Applications of Stack
• Stacks are useful for any application requiring
LIFO storage. There are many, many of these:

 Function call management


 Parsing context-free languages
 Evaluating arithmetic expressions
 Conversion of infix to postfix
 Palindromes
Data Structures
Lecture 4: Queue and its Aapplications

Dr. Ashwaq Talib Hashim


What is a queue?
• It is an ordered group of homogeneous
items of elements.
• Queues have two ends:
– Elements are added at one end.
– Elements are removed from the other end.
• The element added first is also removed
first (FIFO: First In, First Out).
Applications of Queue
• Operating systems use queues to
– Track tasks waiting for a scarce resource
– Ensure tasks carried out in order generated
• Print queue:
– Printing slower than selecting pages to print
– So use a queue, for fairness
Queue Specification
• Definitions: (provided by the user)
– MAX_ITEMS: Max number of items that might be on
the queue
– ItemType: Data type of the items on the queue

Operations •
MakeEmpty –
Boolean IsEmpty –
Boolean IsFull –
Enqueue (ItemType newItem) –
Dequeue (ItemType& item) –
Enqueue (ItemType newItem)
• Function: Adds newItem to the rear of the queue.
• Preconditions: Queue has been initialized and is not full.
• Postconditions: newItem is at rear of queue.

Dequeue (ItemType& item)


• Function: Removes front item from queue and returns it in
item.
• Preconditions: Queue has been initialized and is not empty.
• Postconditions: Front element has been removed from
queue and item is a copy of removed element.
Queue Implementation
#include<stdlib.h>
const int MAX_ITEMS=5;
typedef int ItemType;

class QueueType { private:


public: int front;
int rear;
QueueType()
ItemType items[MAX_ITEMS]; };
{ rear=front=-1;}
bool IsEmpty() ;
bool IsFull() ;
void Enqueue(ItemType);
void Dequeue(ItemType&);
Queue Implementation (cont.)
bool QueueType::IsEmpty()
{
return ( front == -1);
}
bool QueueType::IsFull()
{
return ( (rear== MAX_ITEMS -1));
}

void QueueType<::Enqueue (ItemType newItem)


{
if (IsFull())
{
cout<<"error.......the Queue is full";
exit(0); }

if(front == -1)
front=front+1;
rear=rear+1;
items[r]=item;
} }
Queue Implementation (cont.)
ItemType QueueType::Dequeue()
{
int item;
if( IsEmpty())
{
cout<<"Error......the is empty";
exit(0); }

item=items[front];
front=front+1;
if(front== MAX_ITEMS)
{ front =-1; rear =-1; }
return item;}
Queue Implementation (cont.)
void main()
{
QueueType queue;
ItemType x;
for( int i=0;i< MAX_ITEMS;i++)
{
cin>>x;
queue.Enqueue(x);
}
for( i=0;i< MAX_ITEMS;i++)
cout<<" "<<queue.Dequeue(); }
Circular Queue
Circular Queue Implementation
class Cqueue
{
int a[MAX],
front,
rear;
public:
cqueue()
{ front=rear=-1; }
void insert(int );
int deletion();
void display();
};
Queue Implementation (cont.)
void cqueue :: insert(int val)
{ if((front==0 && rear==MAX-1) || (rear+1==front))
cout<<" Circular Queue is Full";
else
{ if(rear==MAX-1)
rear=0; (rear + 1) % MAX== front);
else
rear++;
a[rear]=val; }
if(front==-1) (rear = (rear + 1) % MAX
front=0; }
Queue Implementation (cont.)
int cqueue :: deletion()
{ int k;
if(front==-1)
cout<<"Circular Queue is Empty";
else {
k=a[front];
if(front==rear)
front=rear=-1;
else {
if(front==MAX-1)
front=0;
else
front++; }
}
return k; }
front = (front + 1) % MAX;
Queue Implementation (cont.)
void cqueue :: display()
{ int i;
if(front==-1)
cout<<"Circular Queue is Empty";
else
{ if(rear < front)
{ for(i=front; i<=MAX-1;i++)
cout<<a[i]<<" ";
for(i=0;i<=rear;i++)
cout<<a[i]<<" ";
}
Data Structures
Lecture 5: Linked Lists

Dr. Ashwaq Talib Hashim


List Using Linked Memory

 Various cells of memory are not allocated


consecutively in memory.
 Not enough to store the elements of the list.
 With arrays, the second element was right next
to the first element.
 Now the first element must explicitly tell us
where to look for the second element.
 Do this by holding the memory address of the
second element
Linked List
Create a structure called a Node. 

 The object field will hold the actual list element.


 The next field in the structure will hold the
starting location of the next node.
 Chain the nodes together to form a linked list.
Linked List
 Picture of our list (2, 6, 7, 8, 1) stored as a
linked list:

head

2 6 8 7 1 size=5

current
Linked List
Note some features of the list:
 Need a head to point to the first node of the
list. Otherwise we won’t know where the start of
the list is.
 The current here is a pointer, not an index.
 The next field in the last node points to nothing.
We will place the memory address NULL which
is guaranteed to be inaccessible.
A Simple Linked List Class

• Declare Node structure for the nodes


– data: double-type data in this example
– next: a pointer to the next node in the list
class Node {
public:
double data; // data
Node* next; // pointer to next
};
A Simple Linked List Class
• Declare List, which contains
– Start: a pointer to the first node in the list.
Since the list is empty initially, start is set to NULL
– Operations on List
class List { private:
public: struct Node;
List(){ start=0;tail=0;current=0;} typedef Node *Link;
void creatlist(ItemType & elem); struct Node {
void InsertBeg(ItemType elem); ItemType elem;
void deletelast(void); Link next; };
void InsertAnyPos(ItemType elem, int pos); public:
void DeleteAnyPos(ItemType &elem, int pos); Link start;
void Display(void); Link tail;
Link current
};
Linked List Operations
void List::creatlist(ItemType &elem) 5
{
Link addedNode=new Node;
assert(addedNode);
addedNode->elem=elem; addedNode
if(start==0)
start=addedNode; addedNode
else 5
tail->next=addedNode; addedNode
Start 5
tail=addedNode;
tail->next=0;
Start 5
}

tail
Linked List Operations
void List::InsertAnyPos(ItemType elem, int pos)
{
int i=0;
Start
Link ptr;
ptr=start;
2 6 8 7 1 size=5 6
Link addedNode=new Node;
assert(addedNode); current 2 1
addedNode->elem=elem; 3
while(i<pos) 9
{
ptr=ptr->next; addedNode
i++;
}
addedNode->next=ptr->next;
ptr->next=addedNode;
}
Linked List Operations

void List::InsertBeg(ItemType elem)


{
Link temp=new Node;
assert(temp);
temp->elem=elem;
temp>next=start;
start=temp;
}
Linked List Operations
void List::DeleteAnyPos(ItemType &elem,int pos)
{ int i=0;
Link ptr=start;
Link ptr2=start;
while(i<pos)
{ ptr2=ptr;
ptr=ptr->next;
i++;
}
ptr2->next=ptr->next;
}
Linked List Operations
void List::deletelast(void)
{
Link ptr=start;
while(ptr->next->next!=NULL)
ptr=ptr->next;
ptr->next=NULL; }
Linked List Operations
void main(void)
{
List l;
ItemType x;
for(int i=0;i<5;i++)
{
cin>>x;
l.creatlist(x);
}
l.deletelast();
l.deletelast();
l .InsertBeg(8);
l.InsertAnyPos(90,2);
l.DeleteAnyPos(x,1);
l.Display(); }
Linked list applications
• Remove element from middle of a collection, maintain
order, no shifting. Add an element in the middle, no
shifting.
• If programming in C, there are no “growable-arrays”, so
typically linked lists used when # elements in a
collection varies, isn’t known, can’t be fixed at compile
time
• Linked Lists can be used to implement Stacks , Queues.
• Linked Lists can also be used to implement Graphs.
(Adjacency list representation of Graph).
LEC 6: Stack and Queues using Linked
Structures

Dr. Ashwaq Talib Hashim


Implementing stacks using arrays
Simple implementation •
The size of the stack must be determined •
when a stack object is declared
Space is wasted if we use less elements •
We cannot "push" more elements than the •
array can hold
Dynamic allocation of each
stack element
Allocate memory for •
each new element
dynamically

ItemType* itemPtr;
...
itemPtr = new ItemType;
*itemPtr = newItem;
Chaining the stack elements together
(cont.)

Each node in the stack should contain two •


parts:
info: the user's data –
next: the address of the next element in the –
stack
Stack class specification
// forward declaration of NodeType (like function prototype)
struct NodeType;

class StackType {
public:
StackType();
~StackType();
void MakeEmpty();
bool IsEmpty()
bool IsFull() ;
void Push(ItemType);
ItemType void Pop(void);
private:
NodeType* topPtr;
};
Function Push
void StackType::Push(ItemType newitem)
{
NodeType* location;
location = new NodeType;
if(location==NULL)
cout<<“exit”;
else
{ location->info = newItem;
location->next = topPtr;
topPtr = location;
}}
Function Pop
ItemType StackType::Pop(void)
{
If (IsEmpty())
cout<<" The Stack is Empty";
else
{
NodeType* tempPtr;
ItemType item = topPtr->info;
tempPtr = topPtr;
topPtr = topPtr->next;
delete tempPtr;
return item
}}
Other Stack functions
StackType>::StackType()
{
topPtr = NULL;
}

void StackType::MakeEmpty()
{
NodeType* tempPtr;
while(topPtr != NULL) {
tempPtr = topPtr;
topPtr = topPtr->next;
delete tempPtr;
}
}
Other Stack functions (cont.)
bool StackType::IsEmpty()
{
return(topPtr == NULL);
}
bool StackType::IsFull()
{
NodeType* location;
location = new NodeType;
if(location == NULL)
return true;
else {
delete location;
return false;
} }

StackType::~StackType()
{
MakeEmpty();
}
Implementing queues using
arrays
Simple implementation •
The size of the queue must be determined •
when a stack object is declared
Space is wasted if we use less elements •
We cannot "enqueue" more elements than •
the array can hold
Implementing queues using linked
lists
Allocate memory for each new element •
dynamically
Link the queue elements together •
Use two pointers, front and rear, to mark •
the front and rear of the queue
Queue class specification
// forward declaration of NodeType (like function prototype)
struct NodeType;
class QueType {
public:
QueType();
~QueueType();
void MakeEmpty();
bool IsEmpty() ;
bool IsFull() ;
void Enque(ItemType);
ItemType Deque(void);
private:
NodeType* front;
NodeType* rear;
};
Function Enqueue
void QueueType::Enqueue(ItemType newItem)
{
NodeType* newNode;
newNode = new NodeType;
newNode->info = newItem;
newNode->next = NULL;
if(rear == NULL)
front = newNode;
else
rear->next = newNode;
rear = newNode;
}
Function Dequeue
ItemTyp QueueType::Dequeue(void)
{
ItemType item;
NodeType* tempPtr;
if(IsEmpty())
cout<<"Queue is Empty";
else
{
tempPtr = front;
item = front->info;
front = front->next;
if(front == NULL)
rear = NULL;
delete tempPtr;
return item; }}
Other Queue functions
void QueueType::MakeEmpty()
{
NodeType* tempPtr;
while(front != NULL) {
tempPtr = front;
front = front->next;
delete tempPtr;
}
rear=NULL;
}
Other Queue functions (cont.)
bool QueueType::IsEmpty()
{
return(front == NULL); }
bool QueueType::IsFull()
{
NodeType *ptr;
ptr = new NodeType;
if(ptr == NULL)
return true;
else {
delete ptr;
return false;
}}
QueueType::~QueueType()
{ MakeEmpty(); }
LEC 7: Circular Linked Lists &
Double Liked Lists

Dr. Ashwaq Talib Hashim


Circular Linked Lists

• A Circular Linked List is a special type of Linked List


• It supports traversing from the end of the list to the
beginning by making the last node point back to the head
of the list
• A Rear pointer is often used instead of a Head pointer
Circular Linked List Definition

#include <iostream>
typedef int ItemType;
struct Node
{
ItemType info;
NodeType * next; };
class CQueType
{
public:
CQueType() { Front=NULL; Rear=NULL;}
void enQueue(ItemType value);
ItemType deQueue();
void displayQueue();
private:
Node* front;
Node* rear;
};
Circular Linked List Operations

// Function to create Circular queue


void CQueType ::enQueue(ItemType value)
{
Node *temp = new Node;
temp->info= value;
if (front == NULL)
front = temp;
else
rear->next = temp;

rear = temp;
rear->next = front;
}
Circular Linked List Operations
// Function to delete element from Circular Queue
ItemType CQueType ::deQueue()
{
if (front == NULL)
{
printf ("Queue is empty");
exit(0);
}
// If this is the last node to be deleted
ItemType value; // Value to be dequeued
if (front == rear)
{
value = front->info;
delete(front);
front = NULL;
rear = NULL;
}
Circular Linked List Operations

else // There are more than one nodes


{
Node *temp = front;
value = temp->info;
front = front->next;
rear->next= front;
delete(temp);
}

return value ;
}
Circular Linked List Operations

// Function displaying the elements of Circular Queue


void CQueType::displayQueue()
{
Node *temp = front;
cout<<"\nElements in Circular Queue are: ";
while (temp->next != front)
{
cout<< temp->info
temp = temp->next;
}
cout<<temp->info;
}
Circular Linked List Operations

/* Driver of the program */


int main()
{
// Create a queue and initialize front and rear
CQueType cq;
// Inserting elements in Circular Queue
cq.enQueue(14);
cq.enQueue(22);
cq.enQueue(6);
// Display elements present in Circular Queue
cq.displayQueue();
// Deleting elements from Circular Queue
cout<< cq.deQueue();
cout<< cq.deQueue();
// Remaining elements in Circular Queue
cq.displayQueue();

cq.enQueue(9);
cq.enQueue(20);
cq.displayQueue();
return 0;}
Doubly Linked Lists
Node data
 info: the user's data
 next, back: the address of the next
and previous node in the list

.back .info .next


Node data (cont.)
struct NodeType {
ListElementType info;
NodeType * next;
NodeType * back;
};
Doubly Linked List Definition
#include<iostream.h>
typedef int ListElementType;
class List {
public:
List();
void InsertItem (ListElementType item);
ListElementType DeleteBeg(void);
void printlist(void);

private:
struct NodeType {
ListElementType info;
NodeType* next;
NodeType * back;
};
NodeType* listData ;
};
Inserting into a Doubly Linked List

1. newNode->back = location->back; 3. location->back->next=newNode;


2. newNode->next = location 4. location->back = newNode;
Special case: inserting in the beginning
Inserting into a Doubly Linked List

void List ::InsertItem(ListElementType item)


{

NodeType * newNode;
NodeType * location;
bool found; else
listData = newNode;
newNode = new NodeType; location->back = newNode;
newNode->info = item; }
if (listData != NULL) {
if (location->info > item) { (3)
newNode->back = location->back; (1) (4)
newNode->next = location; (2)
if (location != listData) // special case
(location->back)->next = newNode; (3)
Inserting into a Doubly Linked List

else { // insert at the end


newNode->back = location;
location->next = newNode;
newNode->next = NULL;
}
}
else { // insert into an empty list
listData = newNode;
newNode->next = NULL;
newNode->back = NULL;
}
}
Deleting from a Doubly Linked List
ListElementType List::DeleteBeg(void)
{
ListElementType x;
NodeType *ptr;
ptr= listData;
assert(listData);
if(listData ->next==NULL)
listData =NULL;
else
{
listData = listData ->next;
listData ->back=NULL;
}
x=ptr->info;
delete ptr;
return x;
}
Deleting from a Doubly Linked List
ListElementType List::DeleteAnyPos(int pos)
{ ListElementType x;
NodeType *ptr;
ptr= listData;
int i=0;
while(i<pos )
{ptr=ptr->next;i++;}
if(ptr->next !=NULL) // if the deleted not at the end of list
{
ptr->back->next=ptr->next;
ptr->next->back=ptr->back;
}
else // if deleted node at last of list
{ ptr->back->next=0;
ptr->back=0; }
x=ptr->info;
delete ptr;
return x; }

Printing a Doubly Linked List
void List::printlist(void)
{
NodeType *ptr;
assert(listData);
ptr= listData;
while(ptr !=0)
{
cout<<ptr->info<<" ";
ptr=ptr->next;
}
}
Trees
Lec.8

Dr. Ashwaq T. Hashim


What is a Tree
• A tree is a finite nonempty
set of elements.
• It is an abstract model of a
hierarchical structure.
• consists of nodes with a
parent-child relation.
• Applications:
– Organization charts
– File systems
– Programming environments
Tree Terminology
• Root: node without parent (A) Subtree: tree consisting of a
• Siblings: nodes share the same parent node and its descendants
• Internal node: node with at least one child
(A, B, C, F)
• External node (leaf ): node without children
(E, I, J, K, G, H, D)
• Ancestors of a node: parent, grandparent,
grand-grandparent, etc.
• Descendant of a node: child, grandchild,
grand-grandchild, etc.
• Depth of a node: number of ancestors
• Height of a tree: maximum depth of any
node (3)
• Degree of a node: the number of its
children
• Degree of a tree: the maximum number of
its node.
Tree Terminology
• Path: traversal from node to node along the edges that results in a sequence
• Parent: any node, except root has exactly one edge running upward to another
node. The node above it is called parent.
• Child: any node may have one or more lines running downward to other nodes.
Nodes below are children.
• Leaf: a node that has no children
• Subtree: any node can be considered to be the root of a subtree, which consists of
its children and its children’s children and so on.
• Visiting: a node is visited when program control arrives at the node, usually for
processing.
• Traversing: to traverse a tree means to visit all the nodes in some specified order.
• Levels: the level of a particular node refers to how many generations the node is
from the root. Root is assumed to be level 0.
• Keys: key value is used to search for the item or perform other operations on it.
Tree Properties
Property Value
A
Number of nodes 9
Height 5
B C
Root Node A
Leaves C,D,F,H,I
D E F Interior nodes B,E,G
Ancestors of H G,E,B,A
Descendants of B D,E,F,G,G,H,I
G Siblings of E D,F
Degree of this tree 3
H I
Binary Trees
• Every node in a binary tree can have at
most two children.
• The two children of each node are called
the left child and right child corresponding
to their positions.
• A node can have only a left child or only a
right child or it can have no children at all.
Binary Trees
• A binary tree is a tree with the following
properties:
– Each internal node has at most two children
(exactly two for proper binary trees)
– The children of a node are an ordered pair
• We call the children of an internal node left
child and right child
• Alternative recursive definition: a binary tree is
either
– a tree consisting of a single node, or
– a tree whose root has an ordered pair of children,
each of which is a binary tree
Implementation of Trees

• The more common representation of general


trees is to store two pointers with each node:
the firstChild and the nextSibling. The figure
below illustrates how the following tree would
be represented using this technique.
First Child, Next Sibling Representation

Data
firstChild NextSibling
A

B C D

E F G H I

J K L
Binary Tree
• A binary tree is a tree with the following Applications:
properties: arithmetic expressions
– Each internal node has at most two
children (degree of two) decision processes
– The children of a node are an ordered pair searching

• We call the children of an internal node A


left child and right child
• Alternative recursive definition: a binary
tree is either
B C
– a tree consisting of a single node, OR
– a tree whose root has an ordered pair of
children, each of which is a binary tree
D E F G

H I
Array Representation
Array Representation

• Observe that there is a simple mathematical relationship


between the index of a node and the indices of its children
and parents. In particular:

• leftChild(i): if (2i ≤ n) then 2i, else null.


• rightChild(i): if (2i + 1 ≤ n) then 2i + 1, else null.

• Observe that the last leaf in the tree is at position n, so adding


a new leaf simply means inserting a value at position n + 1 in
the list and updating n.
Array Representation
Arithmetic Expression Tree
• Binary tree associated with an arithmetic expression
– internal nodes: operators
– external nodes: operands
• Example: arithmetic expression tree for the expression (2  (a -
1) + (3  b))

+
 

2 - 3 b

a 1
Arithmetic Expression Tree

• We can represent an arithmetic expression such as


• (A + B) * (C + D) * 2 – X / Y
as a binary tree, in which the leaves are constants or variables
and the nodes are operations:
Binary Tree Traversals
There are three natural ways of visiting or traversing every
node of a tree, preorder, postorder, and (for binary trees)
inorder. Let T be a tree whose root is r and whose subtrees
are T1, T2,… , Tm for m ≥ 0.
Let l, R, and T stand for moving left, right and root visiting
the node, and moving right.
Binary Tree Traversals
Preorder: Visit the root r, then recursively do a preorder traversal
of T1, T2,….., Tk. For example :( /, *, +, a, b, c ,−, d, e) for the
expression tree shown above.

Postorder: Recursively do a postorder traversal of T1, T2,… , Tk


and then visit r. Example : (a, b,+, c,*, d, e, − , / ). (Note that this is
not the same as reversing the preorder traversal.)

Inorder: (for binary trees) Do an inorder traversal of TL, visit r, do


an inorder traversal of TR. Example: (a, + , b , * , c , / , d, −, e).
Note that theses traversal correspond to the familiar prefix, postfix,
and infix notations for
Binary Search Trees
• A Binary Tree consists of a main node known as the Root. The Root then has two
sub-sections, ie. the left and the right half. The data subsequently stored after the
root is created depends on it's value compared to the root.
• To add the Following List of Numbers, we end up with a
Binary Tree like this:
Graphs
Lec.9

Dr. Ashwaq T. Hashim


What is a graph?
• A data structure that consists of a set of nodes
(vertices) and a set of edges that relate the nodes
to each other
• The set of edges describes relationships among
the vertices
Formal definition of graphs
• A graph G is defined as follows:
G=(V,E)
V(G): a finite, nonempty set of vertices
E(G): a set of edges (pairs of vertices)
Directed vs. undirected graphs

• When the edges in a graph have no


direction, the graph is called undirected
Directed vs. undirected graphs (cont.)

• When the edges in a graph have a direction, the


graph is called directed (or digraph)

Warning: if the graph is


directed, the order of the
vertices in each edge is
important !!
Graph terminology
• Adjacent nodes: two nodes are adjacent if they
are connected by an edge

• Path: a sequence of vertices that connect two


nodes in a graph
• Complete graph: a graph in which every vertex is
directly connected to every other vertex
Graph terminology (cont.)
• What is the number of edges in a complete
directed graph with N vertices?
N * (N-1)

2
O( N )
Graph terminology (cont.)
• What is the number of edges in a complete
undirected graph with N vertices?
N * (N-1) / 2

2
O( N )
Graph terminology (cont.)
• Weighted graph: a graph in which each edge
carries a value
Graph implementation
• Array-based implementation (Adjacency Tables)
The adjacency table has a natural interpretation:
adjacency[v][w] is true if and only if vertex v is
adjacent to vertex w. If the graph is directed, we
interpret adjacency [v][w] as indicating whether or not
the edge from v to w is in the graph. If the graph is
undirected, then the adjacency table must be
symmetric; that is, adjacency [v][w] = adjacency[w][v]
for all v and w. The representation of a graph by
adjacency sets and by an adjacency table is illustrated
in Figure below.
Adjacency Table -based
implementation

typedef int max_size


class Digraph {
int count; // number of vertices, at
most max_size
bool adjacency[max_size][max_size];
};
Graph implementation (cont.)
• Linked-list implementation
– A 1D array is used to represent the vertices
• A list is used for each vertex v which contains the
vertices which are adjacent from v (adjacency list)
typedef int max_size
typedef int ItemType

struct Node
{
ItemType info;
node * link; }
class Digraph {
int count; // number of vertices, at most
max_size
Node * G[max_size];
};
Linked List Implementation
Linked List Implementation
Adjacency matrix vs. adjacency list
representation

• Adjacency matrix
– Good for dense graphs --|E|~O(|V|2)
– Memory requirements: O(|V| + |E| ) = O(|V|2 )
– Connectivity between two vertices can be tested
quickly
• Adjacency list
– Good for sparse graphs -- |E|~O(|V|)
– Memory requirements: O(|V| + |E|)=O(|V|)
– Vertices adjacent to another vertex can be found
quickly
Searching and Sorting
Lec.10

Dr. Ashwaq T. Hashim


Searching and Sorting an Array
• Searching and sorting are two fundamental
algorithms often implemented with arrays
– Search an array to determine the location of a
particular value
• Find the student who got a ‘90’
– Sort an array to produce an ordered sequence of
data values
• Arrange an array of student grades in increasing order
(for example, when we want to print the list)
Searching
• In order to search an array, we need to know
the array element we are seeking - the search
target
• Examine each array element using a loop,
testing whether the array element is equal to
the target
• Exit the loop when the target is found: the
result is the subscript of the array element
• This process is called linear search
Searching
Two simple approaches to searching are:
• Linear search: This method traverses a list
sequentially to locate the search key.
• Binary search: This method works on sorted
lists by progressively making better guesses to
find the location of a search key.
Linear Array Search Algorithm
Algorithm
linearsearch (a,n,item,loc)
Begin
for i=0 to (n-1) by 1 do
if (a[i] = item) then
set loc=I
exit
endif
endfor
set loc -1
end
Analysis of Linear Search

• In the best possible case, the item may be found


at the first position. In that case, the search
operation terminates in success with just one
comparison.
• The worst case occurs when the item is present
at the last position or it is missing from the array.
In the former case, the search terminates in
success with n comparisons. In the latter case,
the search terminates in failure with n
comparisons. Thus, we find that in the worst
case, linear search carries out n operations.
Binary Search
This method works on sorted lists by progressively making better guesses to find the
location of a search key.
Illustration
3 10 15 20 35 40 60 Consider the following
Analysis of Binary Search

In each iteration, the search is reduced to one-


half of the array. For n elements in the array,
there will be a maximum of log2 n iterations.
Thus, the complexity of binary search is O(log2
n).
Sorting an Array
• Many kinds of data processing are more
efficient if the data is sorted before it is
processed
– Might this be the case for searching?
• For this reason, much effort has been spent
trying to develop efficient sorting algorithms
– Selection sort is a simple (but not efficient)
sorting algorithm
Selection Sort Algorithm
• Sorting Methods
• Bubble sort
• Selection sort
• Quick sort
• Insertion sort
• Bucket sort
• Merge sort
• Shell sort
Bubble Sort
• It requires n-1 passes to sort an array.
• In each pass every element a[i] is compared with a[i+1], for
i=0 to (n-k-1), where k is the pass number and if they are
out of order i.e. if a[i]>a[i+1], they are swapped.
• This will cause the largest element to move up or bubble
up.
• Thus after the end of the first pass the largest element in
the array will be placed in the last or nth position and on
the next pass, the next largest element will be placed at
position (n-1). This continues for each successive pass until
the last or (n-1)th pass when the second smallest element
will be placed at the second position.
Bubble Sort
Analysis of bubble sort
First pass requires n-1 comparison
Second pass requires n-2 comparison
kth pass requires n-k comparisons
Last pass requires only one comparison e the total comparisons are (worst
case):

For any case:


Analysis of Selection Sort
The first pass requires n-1 comparisons to find the
location of the smallest element.
The second pass requires n-2 comparisons.
The kth pass requires n-k comparisons.
The last pass requires only one comparison.
Therefore, the total number of comparisons are:
F(n)=(n-1)+(n-2)+……+(n-k)+…3+2+1
=n(n-1)/2
Thus, the complexity is O(n2)

You might also like