Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
INDUS UNIVERSITY
BE (EP)-III
Lab Manual
FALL 2018
Student Name: .
Student ID: .
LIST OF EXPERIMENTS
(EP-212A) Data Structures and Algorithms (0+1)
OBE ATTAINMENT (Course): OBE ATTAINMENT(Taxonomy): OBE ATTAINMENT(Program):
COs (Course Outcomes):CO-1,2,3 Domain: Cognitive Program Outcome (PO)
Level: C3 PLOs: PO-2
S.
Lab Experiments CLO PLO Taxonomy Marks Signature
No
Study different operations (insertion, deletion
1 and traversal) to modify the arrays using C++. 1 2 C3
EXPERIMENT #1
TO STUDY ARRAY AND ITS OPERATIONS (INSERTION, DELETION AND
TRAVERSAL)
Introduction An array is a series of elements of the same type placed in contiguous memory locations
that can be individually referenced by adding an index to a unique identifier. As discussed in the
class/lecture room, there are different algorithms related to an array (Traversing, Insertion, Deletion,
Modify, Sorting, Searching (Linear, Binary). Following algorithms helps you to understand and write
programs.
Traversing in Linear Array:
Description: It means processing or visiting each element in the array exactly for one operation. Let „A‟is
an array stored in the computer‟s memory and we want to display the contents of „A‟, then it has to be
traversed i.e. by accessing and processing each element of „A‟ exactly once. Linear array „A‟ with lower
boundary LB ( 0 in C++ ) and upper boundary ( UB =N - 1 in C++) where N is the number of values
stored in the array. If the array is empty then N will be equal 0. SIZE is the total locations /positions
available in array. This algorithm traverses „A‟ applying process to each element of „A‟.
EXPERIMENT # 2
TO STUDY RECORDS, SET STRUCTURES AND ARRAYS OF RECORDS
Introduction: C/C++ arrays allow you to define variables that combine several data items of the same
kind but Records is another user defined data type which allows you to combine data items of different
kinds. Structures are used to represent a record, suppose you want to keep track of your books in a
library. You might want to track the following attributes about each book:
Title
Author
Subject
Book ID
Defining a Structure:
To define a structure, you must use the struct statement. The struct statement defines a new data type,
with more than one member, for your program. The format of the struct statement is this:
struct [structure tag]
{
member definition;
member definition;
...
member definition;
} [one or more structure variables];
The structure tag is optional and each member definition is a normal variable definition, such as int i;
or float f; or any other valid variable definition. At the end of the structure's definition, before the final
semicolon, you can specify one or more structure variables but it is optional. Here is the way you would
declare the Book structure:
struct Books
{
char title[50]; char
author[50]; char
subject[100]; int
book_id;
}book;
Accessing Structure Members:
To access any member of a structure, we use the member access operator (.). The member access
operator is coded as a period between the structure variable name and the structure member that we wish
to access. You would use struct keyword to define variables of structure type. Following is the example
to explain usage of structure:
#include <iostream>
#include <cstring>
using namespace std;
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; // Declare Book1 of type Book
struct Books Book2; // Declare Book2 of type Book
// book 1 specification
strcpy( Book1.title, "Learn C++ Programming");
strcpy( Book1.author, "Chand Miyan");
strcpy( Book1.subject, "C++ Programming");
Book1.book_id = 6495407;
// book 2 specification
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Yakit Singha");
strcpy( Book2.subject, "Telecom");
Book2.book_id = 6495700;
// Print Book1 info
cout << "Book 1 title : " << Book1.title <<endl;
cout << "Book 1 author : " << Book1.author <<endl;
cout << "Book 1 subject : " << Book1.subject <<endl;
cout << "Book 1 id : " << Book1.book_id <<endl;
// Print Book2 info
cout << "Book 2 title : " << Book2.title <<endl;
cout << "Book 2 author : " << Book2.author <<endl;
cout << "Book 2 subject : " << Book2.subject <<endl;
When the above code is compiled and executed, it produces the following result:
Book 1 title : Learn C++ Programming
Book 1 author : Chand Miyan
Book 1 subject : C++ Programming
Book 1 id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Yakit Singha
Book 2 subject : Telecom
Book 2 id : 6495700
When the above code is compiled and executed, it produces the following result:
Book title : Learn C++ Programming
Book author : Chand Miyan
Book subject : C++ Programming
Book id : 6495407
Book title : Telecom Billing
Book author : Yakit Singha
Book subject : Telecom
Book id : 6495700
Lab Task:
Q1. Write a C++ program to store and display the information of Student (Name, ID, Contact,
Email, and Gender) in Structure.
Q2. Write a C++ program to add to distances in (inch-feet) system using structures. Distances
D1 and D2 should be defined by user at run time.
EXPERIMENT # 3
TO STUDY SEARCHING IN ARRAY ALGORITHMS (LINEAR SEARCH,
BINARY SEARCH)
Introduction: The linear search compares each element of the Linear Array „A‟ which has N values in it
with the search key until the search key is found. To determine that a value is not in the array, the
program must compare the search key to every element in the array „A‟. It is also called “Sequential
Search” because it traverses the data sequentially to locate the element.
Algorithm: (Linear Search)
LINEAR_SEARCH (A, N, SKEY)
Here A is a Linear Array with N elements and SKEY is a given item of information to search. This
algorithm finds the location of SKEY in A and if successful, it returns its location otherwise it returns -1
for unsuccessful. T is a variable that we use temporary.
1. Repeat step-2 for K = 0 to N-1
2. if(A [K] = SKEY)
T=K
K= N
1. If (k<N)
return K [Successful Search]
else
return -1 [Un-Successful]
4. End.
Binary Search.
Suppose DATA is an array that is sorted in increasing (or decreasing) numerical order or, equivalently,
alphabetically. Then there is an extremely efficient searching algorithm, called binary search, which can
be used to find the location LOC of a given ITEM of information in DATA.
The binary search algorithm applied to our array DATA works as follows:
During each stage of algorithm, our search for ITEM is reduced to a segment of elements of
DATA: DATA [BEG], DATA [BEG+1], DATA [BEG+2]. . . DATA [END]
Note that the variables BEG and END denote, respectively, the beginning and end locations of the
segment under consideration. The algorithm compares ITEM with the middle element DATA [MID] of
the segment, where MID is computed as
MID = INT ((BEG + END) / 2) (INT (A) refers to the integer value of A.)
If DATA [MID] = = ITEM, then the search is successful and we set LOC = MID.
Otherwise a new segment of DATA is obtained as follows:
a) If ITEM < DATA[MID], then ITEM can appear only in the left half of the segment:
DATA[BEG], DATA[BEG+1], . . . , DATA[MID-1]
So we reset END = MID –1 and begin searching again.
b) If ITEM > DATA[MID], then ITEM can appear only in the right half of the segment:
EXPERIMENT # 4
TO STUDY AND ANALYZE SORTING IN ARRAY (BUBBLE, INSERTION,
SELECTION)
Introduction: Sorting and searching are fundamental operations in computer science. Sorting refers to
the operation of arranging data in some given order. Such as increasing/ascending or decreasing
/descending order, with numeric data or alphabetically. There are so many ways / techniques to do
sorting. Following are some techniques:
Description: Sorting refers to the operation of re-arrangements of values in
Ascending or Descending order of the Linear Array „A‟. Here „A‟ is Linear Array
and N is the number of values stored in A.
Algorithm: (Bubble Sort) BUBBLE (A, N) Here A is an Array with N elements stored in it. This
algorithm sorts the elements in A.
Step 1. Repeat Steps 2,3 for Pass = 1 to N – 1
Step 2. Set Swapped = 0 and K = 0
Step 3. Repeat while K < (N – Pass)
(a) if A[ K ] > A[ K + 1] then
Interchange A[ K ] and A[ K + 1 ] and
Set Swapped = 1
[ End of if structure. ]
(b) Set K = K + 1
[ End of inner loop of step-3. ]
[ End of outer loop of step-1. ]
Step 4. End
Insertion Sort
Description: Here „A‟ is Linear Array and N is the number of values stored in A
Algorithm: (INSERTION SORT) INSERTION (A, N)
[ Where A is an array and N is the number of values in the array ]
Step 1. Repeat steps 2 to 4 for K=1,2,3, . . . . . N-1:
Step 2. Set TEMP = A[K] and i =K-1.
Step 3. Repeat while i >= 0 and TEMP < A[i]
a) Set A[i+1] = A[i]. [Moves element forward.]
b) Set i = i -1.
[End of loop.]
Step 4. Set A[i+1] =TEMP. [Insert element in proper place.]
[End of Step 2 loop.]
Step 5. Return.
Selection Sort:
Description: Here „A‟ is Linear Array and N is the number of values stored in A.
Algorithm: SELECTION (A, N)
Step 1. Repeat steps 2, 3and 5 for K=0 to. N-2:
Step 2. Set MIN = A[K] and LOC = K. [Initializes pointers.]
Step 3. Repeat Step 4 for J=K+1, K+2, . . . . . N:
Step 4. If MIN > A[J], then: MIN = A[J] and LOC = J.
Step 5. Set TEMP = A[K], A[K] = A[LOC] and A[LOC] = TEMP.
[End of step 1 loop.]
Step 6. Exit
Lab Task:
Q1. Convert all algorithms into C++ programming.
Q2. Write a program to Sort values in Ascending / Increasing Order using
Quick Sort Technique in Linear Array
Q3. Which of the sorting algorithms is more complex as compared to other? Use complexity of
algorithm technique to support your comments.
EXPERIMENT # 5
TO FAMILIARIZE WITH THE STRING OPERATION AND FUNCTIONS,
SORTING AN ARRAY OF STRINGS (RADIX SORT)
Introduction
Length: The number of characters in a string is called its length. The function length(s) returns the length
of strings.
Concatenation: Concatenation combines the characters of two strings. For example, concatenation of
string s1 with string s2 results in a string containing characters of s1 followed by those of s2. Note that
string terminator of s1 is replaced by the first character of s2, but string terminator of s2 is retained.
Substring: Accessing a substring from a given string requires three pieces of information:
a) The name of the string itself, (s)
b) the position of the first character of the substring in the given string (ip)
c) the length of the substring or the position of the last character of the substring. (len)
The function substring (s, ip, len) denotes the substring of a string s beginning in a position ip and having
a length len.
Index: The function index(T, P) is used to find the position where a string pattern P first appears in a
given string text T. This is called pattern matching.
Algorithm A1: length(s)
1. Initialize len to 0.
2. Set a variable to the beginning index of string s.
3. Repeat the following step till the string terminator is encountered.
4. len = len +1
5. Exit
Algorithm A2: concatenate (s1, s2)
1. Initialize i = strlen(s1)
2. Initialize j = strlen(s2)
3. Initialize count =0;
/ * This segment copies characters of s2 into array s1 * /
4. Repeat steps 5 to 7 while count <= j
5. s1[i] = s2[count]
6. i = i + 1
7. count = count + 1
8. Exit
Algorithm A3: substring (s, ip, len)
1. Initialize i = ip and count = 0
2. Use an array “dest‟ to hold the required substring
{
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
}
// Driver program to test above functions
int main()
{
int arr[] = {170, 45, 75, 90, 802, 24, 2, 66};
int n = sizeof(arr)/sizeof(arr[0]);
radixsort(arr, n);
print(arr, n);
return 0;
}
Lab Tasks
Q1. Implement the above algorithms in C/C++.
Q2. Design an algorithm that takes a string S and a char ‘a’ as input. It then displays the all
occurrences of a in S.
EXPERIMENT # 6
TO STUDY THE CONCEPT OF LINK LIST AND OPERATIONS (TRAVERSAL,
INSERTION, DELETION)
Introduction: A linked list or one way list is a linear collection of data elements, called nodes, where the
linear order is given by means of “pointers”. Each node is divided into two parts.
The first part contains the information of the element / node.
The second part called the link field contains the address of the next node in the list.
Example:
Description: Here LIST is a structure has two members (Info and Next), where Info is used to store the
data and Next is a pointer used to hold the address of next node. HEAD is a pointer to LIST, points to the
beginning/first element of the LIST. ITEM is the value needed to insert or delete in/from the LIST. The
initial value of the HEAD is NULL, it means that initially LIST is empty. New Node is pointer variable
which holds the address of newly created LIST node.
Operations on Linked List:
a) Creation of Link List
b) Traversing Linked List.
c) Searching in Linked List.
d) Insertion in Linked List
e) Deletion from Linked List
Algorithm to Linked List Creation
Algorithm: (ITEM), [ Here the initial value of FRONT & REAR are NULL ]
Step 1. Create dynamic NODE to store (Info and Next) for next node and store its address into NewNode
pointer [Insert ITEM in newly created Node for QUEUE.]
Step 2. Set NewNode -> Info = ITEM and NewNode -> Next = NULL
Step 3. If REAR = NULL then:
Set REAR and FRONT both = NewNode [ First Node Enqueued ]
Else
Set REAR -> Next = NewNode
Set REAR = NewNode
[ End of If Structure ]
Step 4. End.
Lab Task
Q1. Convert all above algorithm in C++ programming language.
Q2. Write an algorithm that creates a Link List in sorted manner.
Q3. Describe the key differences between arrays and linked list.
EXPERIMENT # 7
TO STUDY AND ANALYZE DOUBLE LINKED LIST (TRAVERSAL,
INSERTION AND DELETION)
Introduction: Different from a singly linked list, a doubly linked list allows us to go in both directions --
forward and reverse. Such lists allow for a great variety of quick update operations, including insertion
and removal at both ends, and in the middle. A node in a doubly linked list stores two references -- a next
link, which points to the next node in the list, and a prev link, which points to the previous node in the
list.
It is usually convenient to add special nodes at both ends of a doubly linked list, a header node just
before the head of the list, and a trailer node just after the tail of the list. These dummy or sentinel nodes
do not store any elements. The header has a valid next reference by a null prev reference, while the trailer
has a valid prev reference by a null next reference. A linked list object would simply need to store
references to these two sentinels and a size counter that keeps track of the number of elements (not
counting sentinels) in the list
Insertion and Deletion Methods
Lab Task:
Q1. Convert all above algorithm in C/ C++ programming language.
Q2. Write an algorithm for searching, sorting and traversing in circular linked list.
EXPERIMENT # 8
TO UNDERSTAND THE CONCEPTS OF STACKS & QUEUES, ASSOCIATED
OPERATIONS, IMPLEMENTATION OF STACKS AND QUEUES
Introduction: 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 de allocate, but is not limited in size. Here
we will implement Stack using array.
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 as FRONT(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
Basic features of Queue
1. Like Stack, Queue is also an ordered list of elements of similar data types.
2. Queue is a FIFO( First in First Out ) structure.
3. Once a new element is inserted into the Queue, all the elements inserted before the new element in the
queue must be removed, to remove the new element.
4. peek( ) function is oftenly used to return the value of first element without dequeuing it.
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 :
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 we remove the element
at head position, and then one by one move all the other elements on position forward we remove the
element from head position and then move head to the next position.
there is an overhead of shifting the elements one position forward every time we remove the first element
there is no such overhead, but when we move head one position ahead, after removal of first element, the
size on Queue is reduced by one space each time.
Lab Task:
Q1. Implement the Queue and Stack using Array and Link list.
Q2. What are the major differences between Stack and Queue?
EXPERIMENT # 9
TO STUDY THE RECURSIVE ALGORITHMS AND APPLICATIONS
Introduction Recursion is an important concept in computer science. Many algorithms can be best
described in terms of recursion. Recursion is the name for the case when a procedure P invokes itself or
invokes a series of other procedures that eventually invokes the procedure P again. Then P is called a
recursive procedure. In order to make sure that program will not continue to run indefinitely, a recursive
procedure must have the following two properties:
1. There must be certain criteria, called „base criteria‟, for which the procedure does not call itself.
2. Each time the procedure does call itself (directly or indirectly), it must be closer to the baser
criteria. A recursive procedure with these two properties is said to be „well-defined‟.
Factorial Function The product of the positive integers from 1 to n, inclusive, is called n factorial and
is usually denoted by n!
n! = 1 x 2 x 3 x . . . (n – 2) x (n – 1) x n
We know that 0! = 1.For every positive integer n; n! = n x (n – 1)!
Accordingly, the factorial function may also be defined as follows:
a) If n = 0, then n! = 1.
b) If n > 0, then n! = n x (n – 1)!
Obviously this definition is recursive, since it refers to itself when it uses (n –1)!. However, (a) the value
of n! is explicitly given when n = 0 (thus 0 is the base value); and (b) the value of n! for arbitrary n is
defined in terms of a smaller value of n which is closer to the base value 0. Accordingly, the definition is
not circular, or in other words, the procedure is well defined.
Suppose P is a recursive procedure. During the running of an algorithm or a program that contains P, we
associate a level number with each given execution of procedure P as follows. The original execution of
procedure P is assigned level 1; and each time procedure P is executed because of a recursive call, its
level is 1 more than the level of the execution that has made the recursive call. The depth of recursion of a
recursive procedure P with a given set of arguments refers to the maximum level number of P during its
execution.
Algorithm
This algorithm calculates n! and returns the value in the variable FACT.
Algorithm: FACTORIAL(FACT, n)
1. if (n = = 0) then set FACT = 1 and return.
2. FACT = n x FACTORIAL(FACT, n –1 )
3. Return.
Lab Task
a) This exercise will demonstrate how recursion can simplify solution of some complicated problems. It
addresses the problem of making combinations of a certain size out of a total group of elements. For
example, if we have twenty different books to pass out to four students, we can easily see that - to be
equitable - we should give each student five books. But how many combinations of five books can be
made out of a group of twenty books?There is a mathematical formula to solve this problem. Given that C
is the total number of combinations, Group is the total size of the group to pick from, Members is the size
of each subgroup, and Group > = Members,
C(Group,Members)=
EXPERIMENT # 10
TO UNDERSTAND A TREE, CONVERTING A BINARY SEARCH TREE TO
SORTED DOUBLY LINK LIST
Introduction: Trees are one of the most important data structures in computer science. They come in
many forms. They provide natural representations for many kinds of data that occur in applications, and
they are useful for solving a wide variety of algorithmic problems.
A tree is a collection of elements called nodes, one of which is distinguished as a root, with a relation
(“parenthood”) that places a hierarchical structure on the nodes. A node, like an element of a list, can be
of whatever type we wish.
A structure with a unique starting node (the root), in which each node is capable of having at most two
child nodes, and in which a unique path exists from the root to every other node is called a binary tree.
A root
B C
D E F
G H I J
A binary tree has a natural implementation in linked storage. In the implementation to follow, the pointer
variable root points to the root of the tree. With this pointer variable, it is easy to recognize an empty
binary tree as precisely the condition root = NULL, and to create a new, empty binary tree we need only
assign its root pointer to NULL.
One of the most important operations on a binary tree is traversal, moving through all the nodes of the
binary tree, visiting each one in turn. As for traversal of other data structures, the action we shall take
when we visit each node will depend on the application. For lists, the nodes come in a natural order from
first to last, and traversal follows the same order. For trees, however, there are many different orders in
which we can traverse all the nodes. Let V, L and R respectively represent visiting root, traversing left
subtree, and traversing right subtree. There are three standard traversal orders.
One of the most important operations on a binary tree is traversal, moving through all the nodes of the
binary tree, visiting each one in turn. As for traversal of other data structures, the action we shall take
when we visit each node will depend on the application. For lists, the nodes come in a natural order from
first to last, and traversal follows the same order. For trees, however, there are many different orders in
which we can traverse all the nodes. Let V, L and R respectively represent visiting root, traversing left
subtree, and traversing right subtree.
There are three standard traversal orders.
EXPERIMENT # 11
TO ANALYZE TREE TRAVERSAL (IN-ORDER, PRE-ORDER AND POST-ORDER), BREADTH-
FIRST SEARCH AND DEPTH-FIRST SEARCH ALGORITHM
Introduction: Traverse means to visit the vertices in some systematic order. You should be familiar with
various traversal methods for trees:
Preorder: visit each node before its children.
Post order: visit each node after its children.
In order (for binary trees only): visit left sub tree, node, right sub tree.
Code for Tree Traversal (In-order, Pre-order ,Post-order )
#include <iostream>
using namespace std;
// Node class
class Node {
int key;
Node* left;
Node* right;
public:
Node() { key=-1; left=NULL; right=NULL;
}; void setKey(int aKey) { key = aKey; };
void setLeft(Node* aLeft) { left = aLeft; };
void setRight(Node* aRight) { right = aRight; };
int Key() { return key; };
Node* Left() { return left; };
Node* Right() { return right; };
};
// Tree class
class Tree {
Node* root;
public:
Tree();
~Tree();
}
else {
cout << "add other node ... " << key << endl;
addNode(key, root);
}
}
// Add a node (private)
void Tree::addNode(int key, Node* leaf)
{ if ( key <= leaf->Key() ) {
if ( leaf->Left() != NULL )
addNode(key, leaf->Left());
else {
Node* n = new Node();
n->setKey(key); leaf-
>setLeft(n);
}
}
else {
if ( leaf->Right() != NULL )
addNode(key, leaf->Right());
else {
Node* n = new Node();
n->setKey(key); leaf-
>setRight(n);
}
}
}
// Print the tree in-order
// Traverse the left sub-tree, root, right sub-tree
void Tree::inOrder(Node* n) {
if ( n ) { inOrder(n-
>Left());
cout << n->Key() << " ";
inOrder(n->Right());
}
}
// Print the tree pre-order
// Traverse the root, left sub-tree, right sub-tree
void Tree::preOrder(Node* n) {
if ( n ) {
cout << n->Key() << " ";
preOrder(n->Left());
preOrder(n->Right());
}
}
// Print the tree post-order
// Traverse left sub-tree, right sub-tree, root
void Tree::postOrder(Node* n) {
if ( n ) { postOrder(n-
>Left()); postOrder(n-
>Right()); cout << n-
>Key() << " ";
}
}
// Test main program
int main() {
Tree* tree = new Tree();
tree->addNode(30);
tree->addNode(10);
tree->addNode(20);
tree->addNode(40);
tree->addNode(50);
cout << "In order traversal" <<
endl; tree->inOrder(tree->Root());
cout << endl;
cout << "Pre order traversal" << endl;
tree->preOrder(tree->Root());
cout << endl;
cout << "Post order traversal" << endl;
tree->postOrder(tree->Root());
We also saw another kind of traversal, topological ordering,. we'll see two other traversals: breadth first
search (BFS) and depth first search (DFS). Both of these construct spanning trees with certain properties
useful in other graph algorithms. We'll start by describing them in undirected graphs, but they are both also
very useful for directed graphs.
Breadth First Search
This can be throught of as being like Dijkstra's algorithm for shortest paths, but with every edge having the
same length. However it is a lot simpler and doesn't need any data structures. We just keep a tree (the
breadth first search tree), a list of nodes to be added to the tree, and markings (Boolean variables) on the
vertices to tell whether they are in the tree or list.
breadth first search:
unmark all vertices
choose some starting vertex x
mark x
list L = x
tree T = x
while L nonempty
choose some vertex v from front of list
visit v
for each unmarked neighbor w
mark w
add it to end of list
add edge vw to T
It's very important that you remove vertices from the other end of the list than the one you add them to, so
that the list acts as a queue (fifo storage) rather than a stack (lifo). The "visit v" step would be filled out later
depending on what you are using BFS for, just like the tree traversals usually involve doing something at
each vertex that is not specified as part of the basic algorithm. If a vertex has several unmarked neighbors, it
would be equally correct to visit them in any order. Probably the easiest method to implement would be
simply to visit them in the order the adjacency list for v is stored in.
Let's prove some basic facts about this algorithm. First, each vertex is clearly marked at most once,
added to the list at most once (since that happens only when it's marked), and therefore removed from the
list at most once. Since the time to process a vertex is proportional to the length of its adjacency list, the total
time for the whole algorithm is O(m).
Next, let's look at the tree T constructed by the algorithm. Why is it a tree? If you think of each
edge vw as pointing "upward" from w to v, then each edge points from a vertex visited later to one visited
earlier. Following successive edges upwards can only get stopped at x (which has no edge going upward
from it) so every vertex in T has a path to x. This means that T is at least a connected subgraph of G. Now
let's prove that it's a tree. A tree is just a connected and acyclic graph, so we need only to show that T has
no cycles. In any cycle, no matter how you orient the edges so that one direction is "upward" and the other
"downward", there is always a "bottom" vertex having two upward edges out of it. But in T, each vertex has
at most one upward edge, so T can have no cycles. Therefore T really is a tree. It is known as a breadth first
search tree.
We also want to know that T is a spanning tree, i.e. that if the graph is connected (every vertex has some path
to the root x) then every vertex will occur somewhere in T. We can prove this by induction on the length of
the shortest path to x. If v has a path of length k, starting v-w-...-x, then w has a path of length k-1, and by
induction would be included in T. But then when we visited w we would have seen edge vw, and if v were
not already in the tree it would have been added.
Breadth first traversal of G corresponds to some kind of tree traversal on T. But it isn't preorder, postorder, or
even inorder traversal. Instead, the traversal goes a level at a time, left to right within a level (where a level is
defined simply in terms of distance from the root of the tree). For instance, the following tree is drawn with
vertices numbered in an order that might be followed by breadth first search:
1
/|\
234/\
|
567|/|\
8 9 10 11
The proof that vertices are in this order by breadth first search goes by induction on the level number. By the
induction hypothesis, BFS lists all vertices at level k-1 before those at level k. Therefore it will place into L
all vertices at level k before all those of level k+1, and therefore so list those of level k before those of level
k+1. (This really is a proof even though it sounds like circular reasoning.)
Breadth first search trees have a nice property: Every edge of G can be classified into one of three groups.
Some edges are in T themselves. Some connect two vertices at the same level of T. And the remaining ones
connect two vertices on two adjacent levels. It is not possible for an edge to skip a level.
Therefore, the breadth first search tree really is a shortest path tree starting from its root. Every vertex has a
path to the root, with path length equal to its level (just follow the tree itself), and no path can skip a level
so this really is a shortest path.
Breadth first search has several uses in other graph algorithms, but most are too complicated to explain in
detail here. One is as part of an algorithm for matching, which is a problem in which you want to pair up the
n vertices of a graph by n/2 edges. If you have a partial matching, pairing up only some of the vertices, you
can extend it by finding an alternating path connecting two unmatched vertices; this is a path in which every
other edge is part of the partial matching. If you remove those edges in the path from the matching, and add
the other path edges back into the matching, you get a matching with one more edge. Alternating paths can
be found using a version of breadth first search.
A second use of breadth first search arises in certain pattern matching problems. For instance, if you're
looking for a small subgraph such as a triangle as part of a larger graph, you know that every vertex in the
triangle has to be connected by an edge to every other vertex. Since no edge can skip levels in the BFS tree,
you can divide the problem into subproblems, in which you look for the triangle in pairs of adjacent levels of
the tree. This sort of problem, in which you look for a small graph as part of a larger one, is known as sub
graph isomorphism. In a recent paper, I used this idea to solve many similar pattern-matching problems in
linear time.
Depth first search
Depth first search is another way of traversing graphs, which is closely related to preorder traversal of a tree.
Recall that preorder traversal simply visits each node before its children. It is most easy to program as a
recursive routine:
preorder(node v)
{
visit(v);
for each child w of v
preorder(w);
}
To turn this into a graph traversal algorithm, we basically replace "child" by "neighbor". But to prevent
infinite loops, we only want to visit each vertex once. Just like in BFS we can use marks to keep track of the
vertices that have already been visited, and not visit them again. Also, just like in BFS, we can use this search
to build a spanning tree with certain useful properties.
dfs(vertex v)
{
visit(v);
for each neighbor w of v
if w is unvisited
{
dfs(w);
add edge vw to tree T
}
}
The overall depth first search algorithm then simply initializes a set of markers so we can tell which vertices
are visited, chooses a starting vertex x, initializes tree T to x, and calls dfs(x). Just like in breadth first
search, if a vertex has several neighbors it would be equally correct to go through them in any order. I didn't
simply say "for each unvisited neighbor of v" because it is very important to delay the test for whether a
vertex is visited until the recursive calls for previous neighbors are finished.
The proof that this produces a spanning tree (the depth first search tree) is essentially the same as that for
BFS, so I won't repeat it. However while the BFS tree is typically "short and bushy", the DFS tree is typically
"long and stringy".
Just like we did for BFS, we can use DFS to classify the edges of G into types. Either an edge vw is in the
DFS tree itself, v is an ancestor of w, or w is an ancestor of v. (These last two cases should be thought of as a
single type, since they only differ by what order we look at the vertices in.) What this means is that if v and w
are in different subtrees of v, we can't have an edge from v to w. This is because if such an edge existed and
(say) v were visited first, then the only way we would avoid adding vw to the DFS tree would be if w were
visited during one of the recursive calls from v, but then v would be an ancestor of w.
As an example of why this property might be useful, let's prove the following fact: in any graph G, either
G has some path of length at least k. or G has O(kn) edges.
Proof: look at the longest path in the DFS tree. If it has length at least k, we're done. Otherwise, since each
edge connects an ancestor and a descendant, we can bound the number of edges by counting the total number
of ancestors of each descendant, but if the longest path is shorter than k, each descendant has at most k-1
ancestors. So there can be at most (k-1)n edges.
Lab Task:
Q1. Write a C++ program for Breadth search algorithm and Depth Search algorithm for tree.
EXPERIMENT # 12
TO UNDERSTAND THE GRAPH (IMPLEMENTATION, TRAVERSAL AND
SHORTEST PATH ALGORITHM)
Introduction: A set of items connected by edges. Each item is called a vertex or node. Formally, a
graph is a set of vertices and a binary relation between vertices, adjacency.
A graph is a structure consisting of a set of arrays (also called dimensions) \{v_1, v_2,\dots,v_n\} and a
set of edges \{e_1, e_2,\dots,e_m\}. An edge is a pair of vertices \{v_i, v_j\}\ i,j \in \{1..n\}. The two
vertices are called the edge endpoints. Graphs are ubiquitous in computer science. They are used to model
real-world systems such as the Internet (each node represents a router and each edge represents a
connection between routers); airline connections (each node is an airport and each edge is a flight); or a
city road network (each node represents an intersection and each edge represents a block). The wireframe
drawings in computer graphics are another example of graphs.
A graph may be either undirected or directed. Intuitively, an undirected edge models a "two-way" or
"duplex" connection between its endpoints, while a directed edge is a one-way connection, and is
typically drawn as an arrow. A directed edge is often called an arc. Mathematically, an undirected edge is
an unordered pair of vertices, and an arc is an ordered pair. For example, a road network might be
modeled as a directed graph, with one-way streets indicated by an arrow between endpoints in the
appropriate direction, and two-way streets shown by a pair of parallel directed edges going both
directions between the endpoints. You might ask, why not use a single undirected edge for a two-way
street. There's no theoretical problem with this, but from a practical programming standpoint, it's
generally simpler and less error-prone to stick with all directed or all undirected edges.
Shortest path algorithm
Dijkstra's algorithm, named after its discoverer, Dutch computer scientist Edsger Dijkstra, is a greedy
algorithm that solves the single-source shortest path problem for a directed graph with non negative edge
weights. For example, if the vertices (nodes) of the graph represent cities and edge weights represent
driving distances between pairs of cities connected by a direct road, Dijkstra's algorithm can be used to
find the shortest route between two cities. Also, this algorithm can be used for shortest path to destination
in traffic network.
Dijkstra‟s algorithm
Step 1 Label the start vertex as 0.
Step 2 Box this number (permanent label).
Step 3 Label each vertex that is connected to the start vertex with its distance (temporary label).
Step 4 Box the smallest number.
Step 5 From this vertex, consider the distance to each connected vertex.
Step 6 If a distance is less than a distance already at this vertex, cross out this distance and write in the
new distance. If there was no distance at the vertex, write down the new distance.
Step 7 Repeat from step 4 until the destination vertex is
boxed.
Note: When a vertex is boxed you do not reconsider it. You need
to show all temporary labels together with their crossings out.
Lab Task:
1. Implement the Dijkstra's algorithm using C or C++.