Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
CAT-1
Material for Data stuctures
U.Nagaraju
16MCM0004
M.tech (CCE)
If we arrange some data in an appropriate sequence, then it forms a Structure and gives us a meaning. T
meaning is called Information . The basic unit of Information in Computer Science is a bit, Binary Digit .
So, we found two things in Information: One is Data and the other is Structure .
What is Data Structure?
1. A data structure is a systematic way of organizing and accessing data.
2. A data structure tries to structure data!
Usually more than one piece of data
Should define legal operations on the data
The data might be grouped together (e.g. in an linked list)
3. When we define a data structure we are in fact creating a new data type of our own.
i.e. using predefined types or previously user defined types.
Such new types are then used to reference variables type within a program
1. Data structures study how data are stored in a computer so that operations can be implemen
efficiently
2. Data structures are especially important when you have a large amount of information
3. Conceptual and concrete ways to organize data for efficient storage and manipulation.
In this System each bit position represents a power of 2. The right most bit position represent 2 0 which equ
1. The next position to the left represents 21= 2 and so on. An Integer is represented as a sum of powers of 2
string of all 0s represents the number 0. If a 1 appears in a particular bit position, the power of 2 represented
that bit position is included in the Sum. But if a 0 appears, the power of 2 is not included in the Sum. F
example 10011, the sum is
Ones Complement Notation
Negative binary number is represented by ones Complement Notation. In this notation we represent a negat
number by changing each bit in its absolute value to the opposite bit setting. For example, since 0010011
represent 38, 11011001 is used to represent -38. The left most number is reserved for the sign of the numb
A bit String Starting with a 0 represents a positive number, where a bit string starting with a 1 represents
negative number.
In Twos Complement Notation is also used to represent a negative number. In this notation 1 is added to
Ones Complement Notation of a negative number. For example, since 11011001 represents -38 in On
Complement Notation 11011010 used represent -38 in Twos Complement Notation.
The assignment of bit string to character may be entirely arbitrary, but it must be adhered to consistently. It may
that some convenient rule is used in assigning bit string to character. The number of bits varies computer wise used
represent a character.
Some computers are use 7-bit (therefore allow up to 128 possible characters), some computers are use 8-bits (up
256 character), and some use 10-bits (up to 1024 possible characters). The number of bits necessary to represent
character in a particular computer is called the byte size and a group of bits that number is called abyte .
Array
Some arrays are multi-dimensional , meaning they are indexed by a fixed number of integers, for example by a tuple
four integers. Generally, one- and two-dimensional arrays are the most common. Most programming languages have
built-in array data type.
Link List
In computer science, a linked list is one of the fundamental data structures used in computer programming
consists of a sequence of nodes, each containing arbitrary data fields and one or two references ("links") pointing
the next and/or previous nodes. A linked list is a self-referential data type because it contains a link to another data
the same type. Linked lists permit insertion and removal of nodes at any point in the list in constant time, but do
allow random access.
1. Linearly-linked List
o Singly-linked list
o Doubly-linked list
2. Circularly-linked list
o Singly-circularly-linked list
o Doubly-circularly-linked list
3. Sentinel nodes
Stacks
Stack is an abstract data type with a bounded(predefined) capacity. It is a simple data structure that
allows adding and removing elements in a particular order. Every time an element is added, it goes
on the top of the stack, the only element that can be removed is the element that was at the top of
the stack, just like a pile of objects.
Applications of Stack
The simplest application of a stack is to reverse a word. You push a given word to stack - letter by
letter - and then pop letters from the stack.
There are other uses also like : Parsing, Expression Conversion(Infix to Postfix, Postfix to Prefix
etc) and many more.
Implementation of Stack
Stack can be easily implemented using an Array or a Linked List. Arrays are quick, but are limited in
size and Linked List requires overhead to allocate, link, unlink, and deallocate, but is not limited in
size. Here we will implement Stack using array.
/*
Class Stack
{
int top;
public:
int a[10];
Stack()
{
top = -1;
}
};
void Stack::push(int x)
{
if( top >= 10)
{
cout << "Stack Overflow";
*/
}
else
{
a[++top] = x;
cout << "Element Inserted";
}
}
int Stack::pop()
{
if(top < 0)
{
cout << "Stack Underflow";
return 0;
}
else
{
int d = a[top--];
return d;
}
}
void Stack::isEmpty()
{
if(top < 0)
{
cout << "Stack is empty";
}
else
{
cout << "Stack is not empty";
}
}
Position of Top
Status of Stack
-1
Stack is Empty
N-1
Stack is Full
Analysis of Stacks
Below mentioned are the time complexities for various operations that can be performed on the
Stack data structure.
Queue:
Queue is also an abstract data type or a linear data structure, in which the first element is inserted
from one end called REAR(also called tail), and the deletion of exisiting element takes place from
the other end called asFRONT(also called head). This makes queue as FIFO data structure, which
means that element inserted first will also be removed first.
The process to add an element into queue is called Enqueue and the process of removal of an
element from queue is called Dequeue.
Applications of Queue
Queue, as the name suggests is used whenever we need to have any group of objects in an order in
which the first one coming in, also gets out first while the others wait for there turn, like in the
following scenarios :
1. Serving requests on a single shared resource, like a printer, CPU task scheduling etc.
2. In real life, Call Center phone systems will use Queues, to hold people calling them in an order,
until a service representative is free.
3. Handling of interrupts in real-time systems. The interrupts are handled in the same order as they
arrive, First come first served.
Implementation of Queue
Queue can be implemented using an Array, Stack or Linked List. The easiest way of implementing a
queue is by using an Array. Initially the head(FRONT) and the tail(REAR) of the queue points at the
first index of the array (starting the index of array from 0). As we add elements to the queue, the tail
keeps on moving ahead, always pointing to the position where the next element will be inserted,
while the head remains at the first index.
When we remove element from Queue, we can follow two possible approaches (mentioned [A] and
[B] in above diagram). In [A] approach, we remove the element at head position, and then one by
one move all the other elements on position forward. In approach [B] we remove the element
from head position and then move headto the next position.
In approach [A] there is an overhead of shifting the elements one position forward every time we
remove the first element. In approach [B] there is no such overhead, but whener we move head one
position ahead, after removal of first element, the size on Queue is reduced by one space each time.
/* Below program is wtitten in C++ language */
{
int a[100];
int rear;
//same as tail
int front;
//same as head
public:
Queue()
{
rear = front = -1;
}
void enqueue(int x);
int dequeue();
void display();
}
return a[++front];
{
a[i]= a[i+1];
tail--;
}
Analysis of Queue
Enqueue : O(1)
Dequeue : O(1)
Size : O(1)
A Queue is defined by its property of FIFO, which means First in First Out, i.e the element which is
added first is taken out first. Hence we can implement a Queue using Stack for storage instead of
array.
For performing enqueue we require only one stack as we can directly push data into stack, but to
performdequeue we will require two Stacks, because we need to follow queue's FIFO property and
if we directly popany data element out of Stack, it will follow LIFO approach(Last in First Out).
int dequeue();
}
We know that, Stack is a data structure, in which data can be added using push() method and data
can be deleted using pop() method. To learn about Stack, follow the link : Stack Data Structure
x = S1.pop();
S2.push();
}
while(!S2.isEmpty()) {
x = S2.pop();
S1.push(x);
}
return x;
}
Circular Queue:
What is Circular Queue?
The queue is considered as a circular queue when the positions 0 and MAX-1 are
adjacent. Any position before front is also after rear.
Note:
Note that the container of items is an array. Array is stored in main memory. Main
memory is linear. So this circularity is only logical. There can not be physical circularity in
main memory.
See the logical circularity of the queue. Addition causes the increment in REAR. It
means that when REAR reaches MAX-1 position then Increment in REAR causes
REAR to reach at first position that is 0.
1
if( rear
rear
== MAX -1 )
= 0;
else
rear = rear + 1;
As we know that, Deletion causes the increment in FRONT. It means that when
FRONT reaches the MAX-1 position, then increment in FRONT, causes FRONT to
reach at first position that is 0.
1
if( front
front
== MAX -1 )
= 0;
else
front = front + 1;
Is_Full check.
Is_Empty check.
Addition or Insertion operation.
Deletion operation.
int isEmpty(CirQueue * q)
{
int empty=0;
if(q->count == 0)
empty = 1;
return empty;
}
/*To insert item into circular queue.*/
void insertCirQueue(CirQueue * q, int item)
{
if( isFull(q) )
{
printf("\nQueue Overflow");
return;
}
q->rear = (q->rear+1)%MAX;
q->ele[q->rear] = item;
q->count++;
printf("\nInserted item : %d",item);
}
/*To delete item from queue.*/
int deleteCirQueue(CirQueue * q, int *item)
{
if( isEmpty(q) )
{
printf("\nQueue Underflow");
return -1;
}
*item
= q->ele[q->front];
q->front = (q->front+1)%MAX;
q->count--;
return 0;
}
int main()
{
int item=0;
CirQueue q;
initCirQueue(&q);
insertCirQueue(&q,
insertCirQueue(&q,
insertCirQueue(&q,
insertCirQueue(&q,
insertCirQueue(&q,
insertCirQueue(&q,
10);
20);
30);
40);
50);
60);
Output:
Inserted item : 10
Inserted item : 20
Inserted item : 30
Inserted item : 40
Inserted item : 50
Queue Overflow
Deleted item is : 10
Deleted item is : 20
Deleted item is : 30
Deleted item is : 40
Deleted item is : 50
Inserted item : 60
Deleted item is : 60
Queue Underflow
They are a dynamic in nature which allocates the memory when required.
Linked lists let you insert elements at the beginning and end of the list.
Singly Linked List : Singly linked lists contain nodes which have a data part as well as an
address part i.e. next, which points to the next node in sequence of nodes. The operations we
can perform on singly linked lists are insertion, deletion and traversal.
Doubly Linked List : In a doubly linked list, each node contains two links the first link points to
the previous node and the next link points to the next node in the sequence.
Circular Linked List : In the circular linked list the last node of the list contains the address of
the first node and forms a circular chain.
Before inserting the node in the list we will create a class Node. Like shown below :
class Node {
public:
int data;
//pointer to the next node
node* next;
node() {
data = 0;
next = NULL;
}
node(int x) {
data = x;
next = NULL;
}
}
We can also make the properties data and next as private, in that case we will need to add the
getter and setter methods to access them. You can add the getters and setter like this :
int getData() {
return data;
}
void setData(int x) {
this.data = x;
}
node* getNext() {
return next;
}
LinkedList() {
head = NULL;
}
}
2. When a new Linked List is instantiated, it just has the Head, which is Null.
3. Else, the Head holds the pointer to the first Node of the List.
4. When we want to add any Node at the front, we must make the head point to it.
5. And the Next pointer of the newly added Node, must point to the previous Head, whether it be
NULL(in case of new List) or the pointer to the first Node of the List.
6. The previous Head Node is now the second Node of Linked List, because the new Node is
added at the front.
int LinkedList :: addAtFront(node *n) {
int i = 0;
//making the next of the new Node point to Head
n->next = head;
//making the new Node as Head
head = n;
i++;
//returning the position where Node is added
return i;
}
1. If the Linked List is empty then we simply, add the new Node as the Head of the Linked List.
2. If the Linked List is not empty then we find the last node, and make it' next to the new Node,
hence making the new node the last Node.
int LinkedList :: addAtEnd(node *n) {
//If list is empty
if(head == NULL) {
If the Node to be deleted is the first node, then simply set the Next pointer of the Head to point to
the next element from the Node to be deleted.
If the Node is in the middle somewhere, then find the Node before it, and make the Node before
it point to the Node next to it.
else {
while(ptr->next != n) {
ptr = ptr->next;
}
ptr->next = n->next;
return n;
}
}
Now you know a lot about how to handle List, how to traverse it, how to search an element. You can
yourself try to write new methods around the List.
If you are still figuring out, how to call all these methods, then below is how your main() method will
look like. As we have followed OOP standards, we will create the objects of LinkedList class to
initialize our List and then we will create objects of Node class whenever we want to add any new
node to the List.
int main() {
LinkedList L;
//We will ask value from user, read the value and add the value to our Node
int x;
The real life application where the circular linked list is used is our Personal Computers, where
multiple applications are running. All the running applications are kept in a circular linked list and
the OS gives a fixed time slot to all for running. The Operating System keeps on iterating over
the linked list until all the applications are completed.
Another example can be Multiplayer games. All the Players are kept in a Circular Linked List and
the pointer keeps on moving forward as a player's chance ends.
Circular Linked List can also be used to create Circular Queue. In a Queue we have to keep two
pointers, FRONT and REAR in memory all the time, where as in Circular Linked List, only one
pointer is required.
node() {
data = 0;
next = NULL;
}
node(int x) {
data = x;
next = NULL;
}
}
CircularLinkedList() {
head = NULL;
}
}
2. When a new Linked List is instantiated, it just has the Head, which is Null.
3. Else, the Head holds the pointer to the fisrt Node of the List.
4. When we want to add any Node at the front, we must make the head point to it.
5. And the Next pointer of the newly added Node, must point to the previous Head, whether it be
NULL(in case of new List) or the pointer to the first Node of the List.
6. The previous Head Node is now the second Node of Linked List, because the new Node is
added at the front.
int CircularLinkedList :: addAtFront(node *n) {
int i = 0;
/* If the list is empty */
if(head == NULL) {
n->next = head;
//making the new Node as Head
head = n;
i++;
}
else {
n->next = head;
//get the Last Node and make its next point to new Node
Node* last = getLastNode();
last->next = n;
//also make the head point to the new first Node
head = n;
i++;
}
//returning the position where Node is added
return i;
}
1. If the Linked List is empty then we simply, add the new Node as the Head of the Linked List.
2. If the Linked List is not empty then we find the last node, and make it' next to the new Node, and
make the next of the Newly added Node point to the Head of the List.
int CircularLinkedList :: addAtEnd(node *n) {
//If list is empty
if(head == NULL) {
//making the new Node as Head
head = n;
//making the next pointer of the new Node as Null
n->next = NULL;
}
else {
//getting the last node
node *last = getLastNode();
last->next = n;
//making the next pointer of new node point to head
n->next = head;
}
}
If the Node to be deleted is the first node, then simply set the Next pointer of the Head to point to
the next element from the Node to be deleted. And update the next pointer of the Last Node as
well.
If the Node is in the middle somewhere, then find the Node before it, and make the Node before
it point to the Node next to it.
If the Node is at the end, then remove it and make the new last node point to the head.
return NULL;
}
else if(ptr == n) {
ptr->next = n->next;
return n;
}
else {
while(ptr->next != n) {
ptr = ptr->next;
}
ptr->next = n->next;
return n;
}
}
Last Link's next points to first link of the list in both cases of singly as well as
doubly linked list.
First Link's prev points to the last of the list in case of doubly linked list.
Basic Operations
Following are the important operations supported by a circular list.
Insertion Operation
Following code demonstrate insertion operation at in a circular linked list
based on single linked list.
//insert link at the first location
void insertFirst(int key, int data) {
//create a link
struct node *link = (struct node*) malloc(sizeof(struct node));
link->key = key;
link->data= data;
if (isEmpty()) {
head = link;
head->next = head;
}else {
//point it to old first node
link->next = head;
head = link;
}
Deletion Operation
Following code demonstrate deletion operation at in a circular linked list
based on single linked list.
//delete first item
struct node * deleteFirst() {
//save reference to first link
struct node *tempLink = head;
if(head->next == head){
head = NULL;
return tempLink;
}
printf(" ]");
}
Link Each Link of a linked list can store a data called an element.
Next Each Link of a linked list contain a link to next link called Next.
Prev Each Link of a linked list contain a link to previous link called Prev.
LinkedList A LinkedList contains the connection link to the first Link called
First and to the last link called Last.
Each Link carries a data field(s) and a Link Field called next.
Each Link is linked with its next link using its next link.
Each Link is linked with its previous link using its prev link.
Last Link carries a Link as null to mark the end of the list.
Basic Operations
Following are the basic operations supported by an list.
How to do:
node
*p;
if (head1==NULL)
list is empty
return
(head2);
if (head2==NULL)
list is empty
return
(head1);
p=head1;
while (p->next!=NULL)
p=p->next;
p->next=head2;
//address
of the first node of the second linked list stored in the last node of the
first linked list
return
(head1);
}
Consider the linked list with its start pointer as begin1.For copying this given linked list into
another list, use a new pointer variable begin2 for the list in which source list will be copied.
Now we will traverse the entire source list from begin to the end by copying the contents to
the new target.
How to do:
Step 1: If Begin1 = Null Then
Print: Source List is Empty
Exit
[End If]
Step 2: Set Begin2 = Null
Step 3: If Free = Null Then
Print: Free space not available
Exit
Else
Allocate memory to the node New
Set New = Free And Free = Free Next
[End If]
First we check total no. Of nodes then (N/2)th and (N/2+1)th Node.
After finding these addresses we will store null in the next part of the (n/2)th node and
address of (n/2+1)th node will be stored in the new list pointer variable begin2.
Now our list divide into 2 parts n/2 and n-n/2 with list begin1 and begin2.
How to do:
Step 1: If Begin=Null
Print: Splitting cannot be performed on empty list
Exit
[End If]
Step 2: Set pointer = Begin And Count = 0
Step 3: Repeat Steps 4 and 5 While Pointer Null
Step 4:
Step 5:
The third pointer variable will be used to store the address of next to
next of current node.
How to do:
Step 1: If Begin = Null Then
Print: No node is present in link list
Exit
[End If]
Step 2: If Begin Next = Null Then
Print: link list is having only one node
Exit
[End If]
Step 3: If Begin Next Null Then
Set Pointer1 = Begin
Set Pointer2 = Begin Next
Set Pointer3 = Pointer2 Next
[End If]
Step 4: If Pointer3 = Null Then
Step 8:
Set Pointer1=Pointer2
Step 9:
Set Pointer2=Pointer3
Step 10:
[End Loop]
Step 11: Set Pointer2 Next=Pointer1
Step 12: Set Pointer3 Next=Pointer2
Step 13: Set Begin=Pointer3
Step 14: Exit
Binary Trees
Introduction
The depth of a node is the number of edges from the root to the node.
The height of a node is the number of edges from the node to the deepest leaf.
The height of a tree is a height of the root.
A full binary tree.is a binary tree in which each node has exactly zero or two
children.
A complete binary tree is a binary tree, which is completely filled, with the
possible exception of the bottom level, which is filled from left to right.
A complete binary tree is very special tree, it provides the best possible ratio between
the number of nodes and the height. The height h of a complete binary tree with N
nodes is at most O(log N). We can easily prove this by counting nodes on each level,
starting with the root, assuming that each level has the maximum number of nodes:
n = 1 + 2 + 4 + ... + 2h-1 + 2h = 2h+1 - 1
Traversals
A traversal is a process that visits all the nodes in the tree. Since a tree is a nonlinear
data structure, there is no unique traversal. We will consider several traversal
algorithms with we group in the following two kinds
depth-first traversal
breadth-first traversal
PreOrder traversal - visit the parent first and then left and right children;
InOrder traversal - visit the left child, then the parent and the right child;
PostOrder traversal - visit left child, then the right child and then the parent;
There is only one kind of breadth-first traversal--the level order traversal. This
traversal visits nodes by levels from top to bottom and from left to right.
In the next picture we demonstarte the order of node visitation. Number 1 denote the
first node in a particular traversal and 7 denote the last node.
Implementation
We implement a binary search tree using a private inner class BSTNode. In order to
support the binary search tree property, we require that data stored in each node is
Comparable:
public class BST <AnyType extends Comparable<AnyType>>
{
private Node<AnyType> root;
private class Node<AnyType>
{
private AnyType data;
private Node<AnyType> left, right;
public Node(AnyType data)
{
left = right = null;
this.data = data;
}
}
...
}
Insertion
The insertion procedure is quite similar to searching. We start at the root and
recursively go down the tree searching for a location in a BST to insert a new node. If
the element to be inserted is already in the tree, we are done (we do not insert
duplicates). The new node will always replace a NULL reference.
Draw a binary search tree by inserting the above numbers from left to right.
Searching
Searching in a BST always starts at the root. We compare a data stored at the root
with the key we are searching for (let us call it as toSearch). If the node does not
contain the key we proceed either to the left or right child depending upon
comparison. If the result of comparison is negative we go to the left child, otherwise to the right child. The recursive structure of a BST yields a recursive algorithm.
Searching in a BST has O(h) worst-case runtime complexity, where h is the height of
the tree. Since s binary search tree with n nodes has a minimum of O(log n) levels, it
takes at least O(log n) comparisons to find a particular node. Unfortunately, a binary
serch tree can degenerate to a linked list, reducing the search time to O(n).
Deletion
Deletion is somewhat more tricky than insertion. There are several cases to consider.
A node to be deleted (let us call it as toDelete)
is not in a tree;
is a leaf;
has only one child;
has two children.
If toDelete is not in the tree, there is nothing to delete. If toDelete node has only one
child the procedure of deletion is identical to deleting a node from a linked list - we
just bypass that node being deleted
Deletion starategy is the following: replace the node being deleted with the largest
node in the left subtree and then delete that largest node. By symmetry, the node being
deleted can be swapped with the smallest node is the right subtree.
See BST.java for a complete implementation.
Exercise. Given a sequence of numbers:
11, 6, 8, 19, 4, 10, 5, 17, 43, 49, 31
Draw a binary search tree by inserting the above numbers from left to right and then
show the two trees that can be the result after the removal of 11.
Non-Recursive Traversals
Depth-first traversals can be easily implemented recursively.A non-recursive
implementation is a bit more difficult. In this section we implement a pre-order
traversal as a tree iterator
public Iterator<AnyType> iterator()
{
return new PreOrderIterator();
}
where the PreOrderIterator class is implemented as an inner private class of the BST
class
private class PreOrderIterator implements Iterator<AnyType>
{
...
}
The main difficulty is with next() method, which requires the implicit recursive stack
implemented explicitly. We will be using Java's Stack. The algorithm starts with the
root and push it on a stack. When a user calls for the next() method, we check if the
top element has a left child. If it has a left child, we push that child on a stack and
return a parent node. If there is no a left child, we check for a right child. If it has a
right child, we push that child on a stack and return a parent node. If there is no right
child, we move back up the tree (by popping up elements from a stack) until we find a
node with a right child. Here is the next()implementation
public AnyType next()
{
Node cur = stk.peek();
if(cur.left != null)
{
stk.push(cur.left);
}
else
{
Node tmp = stk.pop();
while(tmp.right == null)
{
if (stk.isEmpty()) return cur.data;
tmp = stk.pop();
}
stk.push(tmp.right);
}
return cur.data;
}
The following example.shows the output and the state of the stack during each call
to next(). Note, the algorithm works on any binary trees, not necessarily binary
search trees..
Output
Stack
2
1
4
2
1
6
4
2
1
5
1
7
5
1
8
1
All copyrights@Nagaraju
1.
1.