Lecture Notes Data Structures CSC-214
Lecture Notes Data Structures CSC-214
INSTRUCTOR:
Shahid Iqbal Lone
e_mail: loneshahid@yahoo.com
COURSE BOOK:
OBJECTIVES:
With a dynamic learn-by-doing focus, this document encourages students to explore data
structures by implementing them, a process through which students discover how data
structures work and how they can be applied. Providing a framework that offers
feedback and support, this text challenges students to exercise their creativity in both
programming and analysis. Each laboratory work creates an excellent hands-on learning
opportunity for students. Students will be expected to write C-language programs,
ranging from very short programs to more elaborate systems. Since one of the goals of
this course is to teach how to write large, reliable programs. We will be emphasizing the
development of clear, modular programs that are easy to read, debug, verify, analyze, and
modify.
PRE-REQUISITE:
Data:
Data are simply collection of facts and figures. Data are values or set of
values. A data item refers to a single unit of values.
Data items that are divided into sub items are group items; those that are not
are called elementary items. For example, a students name may be divided into
three sub items [first name, middle name and last name] but the ID of a student
would normally be treated as a single item.
Student
Street Area
In the above example ( ID, Age, Gender, First, Middle, Last, Street, Area ) are
elementary data items, whereas (Name, Address ) are group data items.
Data Structure:
In computer science, a data structure is a particular way of storing and
organizing data in a computers memory so that it can be used efficiently. Data may
be organized in many different ways; the logical or mathematical model of a
particular organization of data is called a data structure. The choice of a particular
data model depends on the two considerations first; it must be rich enough in
structure to mirror the actual relationships of the data in the real world. On the other
hand, the structure should be simple enough that one can effectively process the
data whenever necessary.
Arrays:
The simplest type of data structure is a linear (or one dimensional) array. A
list of a finite number n of similar data referenced respectively by a set of n
consecutive numbers, usually 1, 2, 3 . . . . . . . n. if we choose the name A for the
array, then the elements of A are denoted by subscript notation
A 1, A 2, A 3 . . . . A n
or by the parenthesis notation
A (1), A (2), A (3) . . . . . . A (n)
or by the bracket notation
A [1], A [2], A [3] . . . . . . A [n]
Example:
A linear array A[8] consisting of numbers is pictured in following figure.
Linked List:
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 contains the address of the next node (link /next
pointer field) in the list.
Example:
Tree:
Data frequently contain a hierarchical relationship between various elements.
The data structure which reflects this relationship is called a rooted tree graph or,
simply, a tree.
Student
Street Area
Graph:
Data sometimes contains a relationship between pairs of elements which is
not necessarily hierarchical in nature, e.g. an airline flights only between the cities
connected by lines. This data structure is called Graph.
Queue:
A queue, also called FIFO system, is a linear list in which deletions can take
place only at one end of the list, the Font of the list and insertion can take place only
at the other end Rear.
Stack:
It is an ordered group of homogeneous items of elements. Elements are added to
and removed from the top of the stack (the most recently added items are at the
top of the stack). The last element to be added is the first to be removed (LIFO:
Last In, First Out).
Decleration of the Arrays: Any array declaration contains: the array name, the
element type and the array size.
Examples:
int a[20], b[3],c[7];
float f[5], c[2];
char m[4], n[20];
Dynamic Arrays
Dynamic array allocation is actually a combination of pointers and dynamic memory allocation.
Whereas static arrays are declared prior to runtime and are reserved in stack memory, dynamic
arrays are created in the heap using the new and released from the heap using delete operators.
Start by declaring a pointer to whatever data type you want the array to hold. In this case I've
used int :
int *my_array;
This C++ statement simply declares an integer pointer. Remember, a pointer is a variable that
holds a memory address. Declaring a pointer doesn't reserve any memory for the array - that will
be accomplished with new. The following C++ statement requests 10 integer-sized elements be
reserved in the heap with the first element address being assigned to the pointer my_array:
The new operator is requesting 10 integer elements from the heap. There is a possibility that there
might not be enough memory left in the heap, in which case your program would have to properly
Dynamic array allocation is nice because the size of the array can be determined at runtime and
then used with the new operator to reserve the space in the heap. To illustrate I'll uses dynamic
array allocation to set the size of its array at runtime.
Algorithm: (Traverse a Linear Array) This algorithm traverse a linear array LA with
lower bound LB and upper bound UB.
1. Repeat for K=LB to UB
Apply PROCESS to LA[K].
[End of loop].
2. Exit.
Bubble Sort:
The technique we use is called Bubble Sort because the bigger value gradually
bubbles their way up to the top of array like air bubble rising in water, while the
small values sink to the bottom of array.
This technique is to make several passes through the array. On each pass,
successive pairs of elements are compared. If a pair is in increasing order (or the
values are identical), we leave the values as they are. If a pair is in decreasing order,
their values are swapped in the array.
B u b b le S o r t
Pass = 1 Pass = 2 Pass = 3 Pass=4
2 1 5 7 4 3 1 2 5 4 3 7 1 2 4 3 5 7 1 2 3 4 5 7
1 2 5 7 4 3 1 2 5 4 3 7 1 2 4 3 5 7 1 2 3 4 5 7
1 2 5 7 4 3 1 2 5 4 3 7 1 2 4 3 5 7 1 2 3 4 5 7
1 2 5 7 4 3 1 2 4 5 3 7 1 2 3 4 5 7
1 2 5 4 7 3 1 2 4 3 5 7
1 2 5 4 3 7
U n d e r lin e d p a ir s s h o w t h e c o m p a r is o n s . F o r e a c h p a s s t h e r e a r e s i z e - 1
c o m p a r is o n s .
To t a l n u m b e r o f c o m p a r is o n s = ( s i z e - 1 ) 2
#include <iostream.h>
int const SIZE = 6
void BubbleSort(int [ ], int);
int main()
{
int a[SIZE]= {77,42,35,12,101,6};
int i;
cout<< The elements of the array before sorting\n;
for (i=0; i<= SIZE-1; i++) cout<< a[i]<<, ;
BubbleSort(a, SIZE);
cout<< \n\nThe elements of the array after sorting\n;
for (i=0; i<= SIZE-1; i++) cout<< a[i]<<, ;
return 0;
}
Home Work
Write a program to determine the median of the array given below:
(9, 4, 5, 1, 7, 78, 22, 15, 96, 45,25)
Note that the median of an array is the middle element of a sorted array.
3. return -1 [Un-Successful]
4. Exit.
/* This program use linear search in an array to find the LOCATION of the given
Key value */
Binary Search:
It is useful for the large sorted arrays. The binary search algorithm can only
be used with sorted array and eliminates one half of the elements in the array
being searched after each comparison. The algorithm locates the middle element of
the array and compares it to the search key. If they are equal, the search key is
found and array subscript of that element is returned. Otherwise the problem is
reduced to searching one half of the array. If the search key is less than the middle
element of array, the first half of the array is searched. If the search key is not the
middle element of in the specified sub array, the algorithm is repeated on one
quarter of the original array. The search continues until the sub array consist of one
element that is equal to the search key (search successful). But if Search-key not
found in the array then the value of END of new selected range will be less than the
START of new selected range. This will be explained in the following example:
Binary Search
Search-Key = 22 Search-Key = 8
Start=0 Start=0
A[0] 3 A[0]
End = 9
Mid=int(Start+End)/2
3 End = 9
Mid=int(Start+End)/2
A[1] 5 Mid= int (0+9)/2 A[1]
Mid=4
5 Mid= int (0+9)/2
Mid=4
A[2] 9 _________________ A[2] 9 _________________
A[3] 11 Start=4+1 = 5
A[3] Start=0
End = 9 11 End = 3
A[4] Mid=int(5+9)/2 = 7
15 _________________ A[4] 15 Mid=int(0+3)/2 = 1
_________________
A[5] 17 A[5]
Start = 5 17 Start = 1+1 = 2
End = 7 1 = 6 End = 3
A[6] 22 A[6]
Mid = int(5+6)/2 =5
_________________
22 Mid = int(2+3)/2 =2
A[7] _________________
25 A[7] 25
Start = 5+1 = 6 Start = 2
A[8] 37 End = 6 A[8]
Mid = int(6 + 6)/2 = 6 37 End = 2 1 = 1
NOTE: declaration of structure does not occupy space in memory. One has to
create the variables for the struct and variable will take spaces in memory. For
example:
Rect
Rect
Length
Length
10
Width
Width
8
Area
Area 0
struct Student {
char name[20];
char course[30];
int age;
int year;
};
For example:
strcpy(S1.name, Nazir Hussain);
strcpy(S1.course, CS-214 Data Structures);
S1.age = 21;
S1.year = 1989;
In the following program you will see the way to initialize the structure variables.
#include <iostream.h>
#include <conio.h>
#include <iomanip.h>
struct student
{ int ID; // 4 bytes
char name[10]; // 10 bytes
float grade; // 4 bytes
int age; // 4
char phone[10]; // 10
char e_mail[16]; // 16
};
// Prototyping of the functions
void display(struct student);
void main()
{
struct student s1={55,"Amir Ali",3.5f,23,"6535418","amir@yahoo.com"};
struct student s2={26,"Mujahid",2.9888f,25,"5362169", "muj@hotmail.com"};
struct student s3={39,"M Jamil",3.108f,30,"2345677","jam@hotmail.com"};
struct student s4={44,"Dilawar",2.7866f,31,"5432186","dil@hotmail.com"};
struct student s5={59,"S.Naveed",2.9f,27,"2345671","navee@yahoo.com"};
cout<<" Students Records Sheet\n";
cout<<" ~~~~~~~~~~~~~~~~~~~~~~\n\n";
cout<<"ID# NAME GRADE AGE PHONE E-MAIL\n";
cout<<"~~~ ~~~~ ~~~~~ ~~~ ~~~~~ ~~~~~~~\n";
display(s1); // structure pass to function
display(s2); // structure pass to function
display(s3);
display(s4);
display(s5);
}
#include <iostream.h>
#include <iomanip.h>
struct STU_GRADES
{ char name [30];
int exam1;
int exam2;
int exam3;
int final;
float sem_ave;
char letter_grade;
};
float calc_ave (int ex1, int ex2, int ex3, int final)
{
float ave;
int main()
{
struct STU_GRADES stu;
char more;
do
{
//pass the entire structure
stu= get_stu ( );
//pass elements of the strucutre
stu.sem_ave = calc_ave (stu.exam1, stu.exam2,
stu.exam3, stu.final);
//pass elements of the structure
stu.letter_grade = assign_let (stu.sem_ave);
//pass the entire structure
print_stu (stu);
cout << "\n\n\n Enter another student? (y/n) ";
cin >> more;
//grab the carriage return since
//character data is input next
cin.ignore ( );
} while (more == 'y' || more == 'Y');
return 0;
}
Pointers
Pointers are a fundamental part of C. If you cannot use pointers properly then you have
basically lost all the power and flexibility that C allows. The secret to C is in its use of
pointers.
Arrays,
Structures,
Functions.
NOTE: Pointers are perhaps the most difficult part of C to understand. C's
implementation is slightly different from other languages.
What is a Pointer?
A pointer is a variable which contains the address in memory of another variable. We can
have a pointer to any variable type.
int *p;
NOTE: We must associate a pointer to a particular type: You can't assign the address of
a short int to a long int, for instance.
long int A=10; short int B= 5; long int *p = &A; // worng assignment;
Similarly: float *T = &B; // wong pointer assignment.
Now the assignments x = 1 and y = 2 obviously load these values into the variables. ip is
declared to be a pointer to an integer and is assigned to the address of x (&x). So ip gets
loaded with the value 100 which is the address of x.
Next y gets assigned to the contents of ip. In this example ip currently points to memory
location 100 -- the location of x. So y gets assigned to the values of x -- which is 1. After
that assignment of 5 to variable x.
So ...
int *ip;
*ip = 50;
int *ip;
int x;
ip = &x; // setting the pointer
*ip = 100;
#include <iostream.h>
int main ( )
{ int a=3, b=5, S=0, D=0, M=0;
int *p1, *p2, *p3, *p4, *p5; // five pointers are declared
// assigning address of a, b, S, D and M to these pointers
p1 = &a; p2= &b; p3 = &S; p4 = &D; p5=&M;
*p3 = *p1 + *p2; // same as s = a + b;
cout<< *p3<<endl; // it prints 8
cout<< p1<<endl; // it prints the address of a
D = *p1 - b; // it calculates -2
cout<< *p4<<endl; // it prints -2
*p5 = a * *p2; // it calculates 15
cout<< M<<endl; // it prints 15
return 0;
}
The above program has been discussed in the class lecture in detail. If you still have
some confusion, contact the instructor verbally or through the e-mail.
The elements of the above given array will be stored as pictured in the following
figure. Each element of the array occupies 4 bytes (due to int). Assume first
element is stored at address 100, second element will store at address 104 and so
on:
100 104 108 112 116 120 124 128 132 136 140 144
5 2 6 9 12 7 56 34 76 37 55 69
#include <iostream.h>
int main( )
{ int a[12]= { 5, 2 , 6, 9, 12, 7, 56, 34, 11, 76, 37, 55,69};
int i, *p;
There are many cases when we may want to alter a passed argument in the function and
receive the new value back once to function has finished. C uses pointers explicitly to do
this. The best way to study this is to look at an example where we must be able to
receive changed parameters.
Pointers provide the solution: Pass the address of the variables to the functions and
access address in function.
Thus our function call in our program would look like this:
swap(&x, &y);
These are fairly straight forward and are easily defined. Consider the following:
struct Node {
int value;
struct Node* next;
};
// Allocate the pointers
struct Node *x;
struct Node *y;
struct Node *z;
Home Work
Exercise -1
Write a C program to read through a 10 elements array of integer type using pointers.
Search through this array to find the biggest and smallest value.
Exercise - 2
Write a program that takes three variable (a, b, c). Rotates the values stored so that value
a goes to b, b to c and c to a.
Note: make a function which takes pointers of these variables and using pointers it
rotates the values.
STACKS:
It is an ordered group of homogeneous items of elements. Elements are added to
and removed from the top of the stack (the most recently added items are at the
top of the stack). The last element to be added is the first to be removed (LIFO:
Last In, First Out).
Basic operations:
These are two basic operations associated with stack:
Push() is the term used to insert/add an element into a stack.
Pop() is the term used to delete/remove an element from a stack.
Other names for stacks are piles and push-down lists.
There are two ways to represent Stack in memory. One is using array and other is
using linked list.
STACK
Data 1 Data 2 Data 3
0 1 2 3 4 5 6 7 8
TOP 2 STACKSIZE 9
Push Operation
Push an item onto the top of the stack (insert an item)
Pop Operation
Here are the minimal operations we'd need for an abstract stack (and their typical
names):
int main( )
{ int item, choice;
while( 1 )
{
cout<< "\n\n\n\n\n";
cout<< " ******* STACK OPERATIONS ********* \n\n";
cout<< " 1- Push item \n 2- Pop Item \n";
cout<< " 3- Traverse / Display Stack Items \n 4- Exit.";
cout<< " \n\n\t Your choice ---> ";
cin>> choice;
switch(choice)
{ case 1: if(IsFull())cout<< "\n Stack Full/Overflow\n";
else
{ cout<< "\n Enter a number: "; cin>>item;
Push(item); }
break;
case 2: if(IsEmpty())cout<< "\n Stack is empty) \n";
else
{item=Pop();
cout<< "\n deleted from Stack = "<<item<<endl;}
break;
case 3: if(IsEmpty())cout<< "\n Stack is empty) \n";
else
{ cout<< "\n List of Item pushed on Stack:\n";
Traverse();
}
break;
int Pop( )
{
return Stack[Top--];
}
bool IsEmpty( )
{ if(Top == -1 ) return true else return false; }
bool IsFull( )
{ if(Top == STACKSIZE-1 ) return true else return false; }
void Traverse( )
{ int TopTemp = Top;
do{ cout<< Stack[TopTemp--]<<endl;} while(TopTemp>= 0);
}
#include <iostream.h.h>
#include <process.h>
struct node {
int info;
struct node *next;
};
bool IsEmpty()
{ if(TOP == NULL) return true; else return false; }
int main ()
{ struct node *T;
IN F IX , P O S T F IX A N D P R E F IX
N O T A T IO N S
I n f ix , P o s t f ix a n d P r e f ix n o t a t io n s a r e u s e d in m a n y
c a lc u la t o r s . T h e e a s ie s t w a y t o im p le m e n t t h e
P o s t f ix a n d P r e f ix o p e r a t io n s is t o u s e s t a c k . I n f ix
a n d p r e f ix n o t a t io n s c a n b e c o n v e r t e d t o p o s t f ix
n o t a t io n u s in g s t a c k .
T h e r e a s o n w h y p o s t f ix n o t a t io n is p r e f e r r e d is t h a t
y o u d o n t n e e d a n y p a r e n t h e s is a n d t h e r e is n o
p r e s c ie n c e p r o b le m .
Stacks are used by compilers to help in the process of converting infix to postfix
arithmetic expressions and also evaluating arithmetic expressions. Arithmetic
expressions consisting variables, constants, arithmetic operators and parentheses.
Humans generally write expressions in which the operator is written between the
operands (3 + 4, for example). This is called infix notation. Computers prefer
postfix notation in which the operator is written to the right of two operands. The
preceding infix expression would appear in postfix notation as 3 4 +.
To evaluate a complex infix expression, a compiler would first convert the expression
to postfix notation, and then evaluate the postfix version of the expression. We use
the following three levels of precedence for the five binary operations.
For example:
(66 + 2) * 5 567 / 42
to postfix
66 22 + 5 * 567 42 /
Algorithm: Infix_to_PostFix(Q, P)
Suppose Q is an arithmetic expression written in infix notation. This
algorithm finds the equivalent postfix expression P.
1. Push ( onto STACK, and add ) to the end of Q.
2. Scan Q from left to right and repeat Steps 3 to 6 for each element of Q until
the STACK is empty:
3. If an operand is encountered, add it to P.
4. If a left parenthesis is encountered, push it onto STACK.
5. If an operator is encountered, then:
a) Repeatedly pop from STACK and add to P each operator
(on the top of STACK) which has the same or
higher precedence/priority than
b) Add to STACK.
[End of If structure.]
6. If a right parenthesis is encountered, then:
a) Repeatedly pop from STACK and add to P each operator (on the
top of STACK) until a left parenthesis is encountered.
b) Remove the left parenthesis. [Do not add the left parenthesis to P.]
[End of If structure.]
[End of Step 2 loop.]
7. Exit.
For example:
Following is an infix arithmetic expression
(5 + 2) * 3 9 / 3
And its postfix is:
5 2 + 3 * 9 3 /
Now add $ at the end of expression as a sentinel.
5 2 + 3 * 8 4 / $
Scanned Elements Stack Action to do _
5 5 Pushed on stack
2 5, 2 Pushed on Stack
3 7, 3 Pushed on Stack
Following code will transform an infix arithmetic expression into Postfix arithmetic
expression. You will also see the Program which evaluates a Postfix expression.
// This program provides you the concepts that how an infix
// arithmetic expression will be converted into post-fix expression
// using STACK
#include<iostream.h>
#include<conio.h>
#include<string.h>
int main()
{ int const null=-1;
char Q[100],P[100],stack[100];// Q is infix and P is postfix array
int n=0; // used to count item inserted in P
int c=0; // used as an index for P
int top=null; // it assign -1 to top
int k,i;
cout<<Put an arithematic INFIX _Expression\n\n\t\t";
cin.getline(Q,99); // reads an infix expression into Q as string
while(top!= null)
{
for(i=0;i<=k;i++)
{
switch(Q[i])
{
case '+':
case '-':
for(;;)
{
if(stack[top]!='(' )
{ P[c++]=stack[top--];n++; }
else
break;
}
stack[++top]=Q[i];
break;
case '*':
case '/':
case '%':
for(;;)
{if(stack[top]=='(' || stack[top]=='+' ||
stack[top]=='-') break;
else
{ P[c++]=stack[top--]; n++; }
stack[++top]=Q[i];
break;
case '^':
for(;;)
{
if(stack[top]=='(' || stack[top]=='+' ||
stack[top]=='-' || stack[top]=='/' ||
stack[top]=='*' || stack[top]=='%') break;
else
{ P[c++]=stack[top--]; n++; }
}
stack[++top]=Q[i];
break;
case '(':
stack[++top]=Q[i];
break;
case ')':
for(;;)
{
if(stack[top]=='(' ) {top--; break;}
else { P[c++]=stack[top--]; n++;}
}
break;
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include<math.h>
#include <stdlib.h>
#include <ctype.h>
struct node {
int info;
struct node *next;
};
int main(void)
{char t;
struct node *Q, *A, *B;
cout<<"\n\n Put a post-fix arithmatic expression end with $: \n ";
while(1)
{ t=getche(); // this will read one character and store it in t
if(isdigit(t)) push(t-'0'); // this will convert char into int
else if(t==' ')continue;
else if(t=='$') break;
else
{ A= pop();
B= pop();
switch (t)
{
case '+':
push(B->info + A->info);
break;
case '-':
push(B->info - A->info);
break;
case '*':
push(B->info * A->info);
break;
case '/': push(B->info / A->info);
break;
case '^': push(pow(B->info, A->info));
break;
Q=pop(); // this will get top value from stack which is result
cout<<"\n\n\nThe result of this expression is = "<<Q->info<<endl;
return 0;
} // end of main function
Deletion in Queue:
5. Return ITEM
Following Figure shows that how a queue may be maintained by a circular array with
MAXSIZE = 6 (Six memory locations). Observe that queue always occupies
consecutive locations except when it occupies locations at the beginning and at the
end of the array. If the queue is viewed as a circular array, this means that it still
occupies consecutive locations. Also, as indicated by Fig(k), the queue will be empty
only when Count = 0 or (Front = Rear but not null) and an element is deleted. For
this reason, -1 (null) is assigned to Front and Rear.
MaxSize = 6
(a) Initially QUEUE is Empty Front = -1
Rear = -1
Count = 0 0 1 2 3 4 5
bool IsFull() { if( count== MAXSIZE) return true; else return false;}
Queue[rear]=ITEM;
count++;
}
int Dequeue()
{
if(IsEmpty()) { cout<<"\n\nQUEUE is empty\n"; return -1; }
return ITEM;
}
void Traverse()
{ int i;
if(IsEmpty()) cout<<"\n\nQUEUE is empty\n";
else
{ i = front;
While(1)
{ cout<< Queue[i]<<"\t";
if (i == rear) break;
else if(i == MAXSIZE -1) i = 0;
else i++;
}
}
}
switch(choice)
{
case 1:
cout"\n put a value:";
cin>>ITEM);
Enqueue(ITEM);break;
case 2:
ITEM=Dequeue();
if(ITEM!=-1)cout<<t<< " deleted \n";
break;
case 3:
cout<<"\n queue state\n";
Traverse(); break;
case 4:exit(0);
}
}
return 0;
}
#include <conio.h>
#include <iostream.h>
struct QUEUE
{ int val;
QUEUE *pNext;
};
void Enqueue(int);
int Dequeue(void);
void Traverse(void);
void main(void)
{ int ITEM, choice;
while( 1 )
{
cout<<" ******* QUEUE UNSING POINTERS ********* \n";
cout<<" \n\n\t ( 1 ) Enqueue \n\t ( 2 ) Dequeue \n";
cout<<"\t ( 3 ) Print queue \n\t ( 4 ) Exit.";
cout<<" \n\n\n\t Your choice ---> ";
cin>>choice);
case 3: Traverse();
break;
case 4: exit(0);
break;
NewNode->val = ITEM;
NewNode->pNext = NULL;
if (rear == NULL)
front = rear= NewNode;
else
{
rear->pNext = NewNode; rear = NewNode;
}
}
int Dequeue(void)
{ if(front == NULL) {cout<< \n <Underflow> QUEUE is empty\n";
return 0;
}
int ITEM = front->val;
if(front == rear ) front=rear=NULL;
else front = front-> pNext;
return(ITEM);
}
void Traverse(void)
{ if(front == NULL) {cout<< " \n <Underflow> QUEUE is empty\n";
return; }
QUEUE f = front;
while(f!=rear)
{ cout front->val << ", ";
f=f->pNext;
}
}
Linked List:
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.
The second part called the link field contains the address of the next node in
the list.
The Head is a special pointer variable which contains the address of the first
node of the list. If there is no node available in the list then Head contains NULL
value that means, List is empty. The left part of the each node represents the
information part of the node, which may contain an entire record of data (e.g. ID,
name, marks, age etc). the right part represents pointer/link to the next node. The
next pointer of the last node is null pointer signal the end of the list.
Advantages:
List of data can be stored in arrays but linked structures (pointers) provide
several advantages.
A linked list is appropriate when the number of data elements to be represented in
data structure is unpredictable. It also appropriate when there are frequently
insertions & deletions occurred in the list. Linked lists are dynamic, so the length of
a list can increase or decrease as necessary.
PTR
Algorithm: (Traversing a Linked List) Let LIST be a linked list in memory. This
algorithm traverses LIST, applying an operation PROCESS to each
element of list. The variable PTR point to the node currently being
processed.
1. Set PTR=HEAD. [Initializes pointer PTR.]
2. Repeat Steps 3 and 4 while PTR!=NULL.
3. Apply PROCESS to PTR-> INFO.
4. Set PTR= PTR-> NEXT [PTR now points to the next node.]
[End of Step 2 loop.]
5. Exit.
Search for wanted ITEM in List can be performed by traversing the list using a
pointer variable PTR and comparing ITEM with the contents PTR->INFO of each
node, one by one of list.
Head
Node A Node B
X
10.Exit.
Algorithm: DELETE(ITEM)
LIST is a linked list in the memory. This algorithm deletes the node
where ITEM first appear in LIST, otherwise it writes NOT FOUND
1. if Head =NULL then write: Empty List and return [Check for Empty List]
2. if ITEM = Head -> info then: [ Top node is to delete ]
Set Head = Head -> next and return
struct node
{
int info;
struct node *next;
};
struct node *Head=NULL;
Prev=Curr=NULL;
for(Curr = Head ; Curr != NULL ; Curr = Curr ->next)
{
if( NewNode->info < Curr ->info) break;
else Prev = Curr;
}
NewNode->next = Prev->next;
Prev->next = NewNode;
} // end of AddNode function
void DeleteNode()
{ int inf;
if(Head==NULL){ cout<< "\n\n empty linked list\n"; return;}
cout<< "\n Put the info to delete: ";
cin>>inf;
void Traverse()
{
for(Curr = Head; Curr != NULL ; Curr = Curr ->next )
int main()
{ int inf, ch;
while(1)
{ cout<< " \n\n\n\n Linked List Operations\n\n";
cout<< " 1- Add Node \n 2- Delete Node \n;
cout<< " 3- Traverse List \n 4- exit\n";
cout<< "\n\n Your Choice: "; cin>>ch;
switch(ch)
{ case 1: cout<< "\n Put info/value to Add: ";
cin>>inf);
AddNode(inf);
break;
case 2: DeleteNode(); break;
case 3: cout<< "\n Linked List Values:\n";
Traverse(); break;
case 4: exit(0);
} // end of switch
} // end of while loop
return 0;
} // end of main ( ) function
#include<iostream.h>
#include<fstream.h>
#include<stdlib.h> // for exit keyword
#include<iomanip.h>
#include<conio.h>
void LoadList();
void AddStudent(void);
void SaveList(void);
void DisplayList(void);
void Heading(void);
/*************************MAIN FUNCTION****************************/
void main(void)
{
short int Choice=0;
LoadList();
while( 1 )
{ system ("cls"); // this statement clears the screen
cout<<" Main Menu"<<endl;
cout<<" ~~~~~~~~~"<<endl;
cout<<"\n\t[1] Add a New Students's Record";
switch( Choice )
{
case 1:
case 2: page_no=0;
DisplayList( );
break;
case 3: SaveList( );
break;
case 4:
SaveList( );
cout<<"\n\n\n GoodBye!!!\n";
exit(0);
default:
cout<<"\nInvalid Choice...";
}
void AddStudent(void)
{
if(n==MAX-1)
{ cout<< "\n\n [ OverFlow !! ]";
cout<< "\n [ can not store new record ]\n\n\\n";
return;
}
n++;
cout<<"\n\n\n\n\n\t\t DATA FOR "<<n+1<<" STUDENT\n\n";
cout<< " PUT ID: "; cin>> Data[n].id;
cout<< " PUT NAME: "; cin.ignore(); // clears input buffer
cin.getline(Data[n].name, 20);
cout<< " put marks of three subjects separated by space: ";
cin>>Data[n].m1>>Data[n].m2>>Data[n].m3;
} // End of function
void SaveList(void)
{ ofstream pFile;
pFile.open("student.dat",ios::binary);
if(pFile==NULL)
{
cout<<"Cannot Open File \n Data not saved into file\n\n";
exit(0);
}
n++;
// Read First Record from file
piFile.read( (char*) &Data[n],sizeof(Data[n]));
}
n--;
piFile.close();
}
void DisplayList(void)
{
if(n == -1)
{ cout<< "\n\nUnderFlow !! [Empty Array] ";
cout<< "\n Nothing to Display \n\n";
return;
}
int i=0,tot;
double per;
char grade;
Heading( );
while (i<=n)
{ if( i % 20 == 0 && i != 0 )
{ cout<< "\n\n Press a key for next Page: .... ";
Heading( )
}
tot=Data[i].m1 + Data[i].m2 + Data[i].m3;
per = tot * 100.0 / 300.0;
if(per >= 80.0) grade='A';
else if(per>= 70.0) grade='B';
else if(per>= 60.0) grade= 'C';
else if(per>= 50.0) grade= 'D';
else grade='F';
// print record
cout<<setw(5)<<Data[i].id<<setw(3)<<" "<<setw(20)
<<setiosflags(ios::left)<<Data[i].name<<setw(6)
<<resetiosflags(ios::left)<<Data[i].m1<<setw(9)<<Data[i].m2
<<setw(9)<<Data[i].m3<<setw(8)<<tot<<setw(10)
<<setiosflags(ios::fixed)<<setiosflags(ios::showpoint)
<<setprecision(2)<<per<<setw(5)<<grade<<endl;
i++;
} // end of while loop
system("pause"); // it makes halt and need to press any key
}
void Heading( )
{
system ("cls"); // this statement clears the screen
page_no++;
// Print heading
cout<<"\t\t\t\tStudents Records List \t\t Page-No: "<<
page_no<<endl;
cout<<"\t\t\t\t~~~~~~~~~~~~~~~~~~~~~\n\n";
cout<<setw(8)<<" ID. "<<setw(20)<<" N A M E ";
cout<<setw(9)<<" Marks-1 "<<setw(9)<<" Marks-2 " <<setw(9)
<<" Marks-3 "<<setw(7)<<" Total "<<setw(9)<<" Per% "<<setw(6)
<<" Grade";
cout<<endl<<endl;;
}
Home Work:
Tree:
So far, we have been studying mainly linear types of data structures: arrays,
lists, stacks and queues. Now we defines a nonlinear data structure called Tree.
This structure is mainly used to represent data containing a hierarchical relationship
between nodes/elements e.g. family trees and tables of contents.
There are two main types of tree:
General Tree
Binary Tree
General Tree:
A tree where a node can has any number of children / descendants is called
General Tree. For example:
Binary Tree:
A tree in which each element may has 0-child , 1-child or maximum of 2-children.
A Binary Tree T is defined as finite set of elements, called nodes, such that:
If T does contain a root R, then the two trees T1 and T2 are called, respectively, the
left sub tree and right sub tree of R.
A tree is said to be complete if all its levels, except possibly the last have the
maximum number of possible nodes, and if all the nodes at the last level appear as
far left as possible.
B E
F G L M
N O P
Extended Binary Tree: 2-Tree:
A binary tree T is said to be a 2-tree or an extended binary tree if each node
N has either 0 or 2 children.
In such a case, the nodes, with 2 children are called internal nodes, and the
node with 0 children are called external node.
A
A
B
B
Preorder (N L R):
a) Process the node/root. (A B D F I C G H J L K)
b) Traverse the Left sub tree.
c) Traverse the Right sub tree.
2. Exit.
Inorder Traversal:
2. Exit.
Postorder Traversal:
2. Exit.
Recursion:
For implementing tree traversal logics as stated, two approaches are used, i.e. use
stacks or recursive functions. In this lecture notes, we will see only recursion
approach to implement these algorithms. Students will also use stacks to implement
these algorithms as homework.
(1) There must be certain (using arguments), called base criteria, for which the
procedure / function does not call itself.
(2) Each time the procedure / function does call itself, control must be closer to
the base criteria.
#include <stdio.h>
Dont run above program, it is still an explanation thus program is not valid logically.
The second property of recursion i.e. (after each cycle/iteration control must reach closer
to the base criteria) and it is ignored here, so the following program is logically invalid.
#include <stdio.h>
void numbers(int );
int main( )
{ int i=10;
numbers(n);
return 0;
}
void numbers(int n )
{ printf(Hello\t);
if(n >= 1 ) numbers(n+1); // this needs explanation
// after each iteration, control is going far from base criteria
}
Factorial Function:
The product of the positive integers from n to 1, inclusive, is called n_factorial and
is usually denoted by n!.
It is also convenient to define 0! = 1, so that the function is defined for all
nonnegative integers. Thus we have:
Program to find the factorial of the given To find the factorial of a given number using
number using for loop: recursion
50
30 55
25 35 53 60
10
62
31 37
20
Following figure shows a binary search tree. Notice that this tree is obtained by inserting
the values 13, 3, 4, 12, 14, 10, 5, 1, 8, 2, 7, 9, 11, 6, 18 in that order, starting from an
empty tree.
Sorting: Note that inorder traversal of a binary search tree always gives a sorted
sequence of the values. This is a direct consequence of the BST property. This
provides a way of sorting a given sequence of keys: first, create a BST with these
keys and then do an inorder traversal of the BST so created.
Inorder Travers (LNR) : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 18
Search: is straightforward in a BST. Start with the root and keep moving left or
right using the BST property. If the key we are seeking is present, this search
procedure will lead us to the key. If the key is not present, we end up in a null
link.
Insertion in a BST is also a straightforward operation. If we need to insert an
element n, we traverse tree starting from root by considering the above stated
rules. Our traverse procedure ends in a null link. It is at this position of this null
link that n will be included. .
Deletion in BST: Let x be a value to be deleted from the BST and let N denote
the node containing the value x. Deletion of an element in a BST again uses the
BST property in a critical way. When we delete the node N containing x, it would
create a "gap" that should be filled by a suitable existing node of the BST. There
are two possible candidate nodes that can fill this gap, in a way that the BST
property is not violated: (1). Node containing highest valued element among all
descendants of left child of N. (2). Node containing the lowest valued element
among all the descendants of the right child of N. There are three possible cases
to consider:
Deleting a leaf (node with no children): Deleting a leaf is easy, as we can
simply remove it from the tree.
Deleting a node with one child: Delete it and replace it with its child.
Deleting a node with two children: Call the node to be deleted "N". Do not
delete N. Instead, choose its in-order successor node "S". Replace the value of
N with the value of S. (Note: S itself has up to one child.)
As with all binary trees, a node's in-order successor is the left-most child of its
right subtree. This node will have zero or one child. Delete it according to one of
the two simpler cases above.
struct NODE
{
int info;
struct NODE *Left;
struct NODE *Right;
};
}
else
{ // traverse to right sub-tree and find null at right
if( pRoot->Right != NULL)
AttachNode( pRoot->Right, pNew ); // recursive call
else
pRoot->Right = pNew; // attaches node on left
}
}
}
void Insert(int x)
{
struct NODE *NewNode= (struct NODE *) malloc(sizeof(NODE));
NewNode->Left = NULL;
NewNode->Right= NULL;
NewNode->info = x;
AttachNode( Root, NewNode );
printf("%d\t",pRoot->info);
if(pRoot->Right) In_Order(pRoot->Right);
}
}
printf("%d\t",pRoot->info);
if(pRoot->Left) DisplayDescending(pRoot->Left);
}
}
void DeleteTree( struct NODE *pRoot) // This function deletes all nodes in the tree
{
if( pRoot )
{
if(pRoot->Right)
{
DeleteTree(pRoot->Right);
}
if(pRoot->Left)
{
DeleteTree(pRoot->Left);
}
free( pRoot );
}
}
switch(ch)
{
case 1:
printf("\n\n put a number: "); scanf("%d",&item);
Insert(item);
break;
case 2:
// Remove(); // This function is not defined.
break; // Students shall write this function as home work.
case 3:
printf("\n\n\n In-Order Traverse (ASCENDING ORDER)\n");
In_Order(Root);
printf("\n\n");
break;
case 4:
case 6:
printf("\n\n\nDESCENDING ORDER (Reverse )\n");
DisplayDescending(Root);
printf("\n\n");
break;
case 7:
DeleteTree(Root);
exit(0);
default:
printf("\n\nInvalid Input");
} // end of switch
} // end of while loop
} // end of main( ) function
Pass 1: A[1] is inserted either before of after A[0] so that: A[0], A[1] is sorted.
Pass 2: A[2] is inserted into its proper place in A[0], A[1], so that A[0], A[1], A[2]
are sorted.
Pass 3: A[3] is inserted into its proper place in A[0], A[1], A[2] so that: A[0], A[1],
A[2], A[3] are sorted.
............................................ ................
Pass N-1: A[N-1] is inserted into its proper place in A[0], A[1], . . . . . A[N-1] so
that: A[0], A[1], . . . . A[N-1] are sorted.
This sorting algorithm is frequently used when N is small. There remains only the
problem of deciding how to insert A[K] in its proper place in the subarray A[0], A[1],
. . . . A[K-1]. This can be accomplished by comparing A[K] with A[K-1], comparing
A[K] with A[K-2], comparing A[K] with A[K-3], and so on, until first meeting an
element A[i] (where i start from k-1) such that A[i] A[K]. then each of elements
A[K-1], A[K-2], . . . . A[i+1] is moved forward one location, and A[K] is then
inserted in the i+1 st position in the array.
A[0]
Pass A[1] A[2] A[3] A[4] A[5] A[6] A[7] A[8]
80 77 33 44 11 88 22 66 55
77
K=1 80 33 44 11 88 22 66 55
77
K=2 33 80 44 11 88 22 66 55
33
K=3 44 77 80 11 88 22 66 55
88
K=4 11 33 44 77 80 22 66 55
33
K=5 11 44 77 80 88 22 66 55
77
K=6 11 22 33 44 80 88 66 55
66
K=7 11 22 33 44 77 80 88 55
K=8 11 22 33 44 55 66 77 80 88
Sorted 11 22 33 44 55 66 77 80 88
#include<iostream.h>
#include<conio.h>
void main(void)
{ int i,n;
int A[SIZE];
cout<"\nData Collection for Insertion Sort\n";
cout<< "\nHow many value are to process: ";
cin>> n;
cout<<"\nINPUT "<<n<<" values:\n";
for( i=0; i< n ; i++ ) cin>>A[i];
InsertionSort( A,n);
cout<< "\nSORTED ARRAY\n";
for( i=0; i < n; i++ ) A[i]<<'\t';
Pass 1: Find the location LOC of the smallest in the list of N elements A[0], A[1], .
. . . . A[N-1], and then interchange A[LOC] and A[0]. Then:
A[0] is sorted.
Pass 2: Find the location LOC of the smallest in the sublist of N-1 elements A[1],
A[2], . . . A[N-1], and interchange A[LOC] and A[1]. Then:
A[0], A[1] is sorted. Since A[0] A[1].
............................................................
............................................................
Pass N-1: Find the location LOC of the smallest A[N-2] and A[N-1], and then
interchanged A[LOC] and A[N-1]. Then:
A[0], A[1], A[2], . . . . A[N-1] is sorted.
K=0, LOC=3 77 33 44 11 88 22 66 55
K=1, LOC=5 11 33 44 77 88 22 66 55
K=2, LOC=5 11 22 44 77 88 33 66 55
K=3, LOC=5 11 22 33 77 88 44 66 55
K=4, LOC=7 11 22 33 44 88 77 66 55
K=5, LOC=6 11 22 33 44 55 77 66 88
//
K=6, LOC=6 11 22 33 44 55 66 77 88
Sorted 11 22 33 44 55 66 77 88
void main(void)
{ int i,n ;
int A[ARRAY_SIZE];
cout<< "\n Selection Sort\n";
cout<< "How many values are to process: "; cin>>n;
cout<< "\PUT "<<n<< " Random values\n";
for( i=0; i< n; i++ ) cin>> nvdata[i] );
SelectionSort(A, n);
cout<<"\nSORTED ARRAY\n";
for( i=0; i< n; i++ ) cout<< A[i]<<"\t";
}
void SelectionSort( int A[ARRAY_SIZE], int n )
{ int K, loc, temp;
for( K = 0 ; K < N-1 ; K++ )
{ loc = MIN (A, K);
temp = A[K]; A[K] = A[loc]; A[loc] = temp;}
}
int MIN(int A[ARRAY_ZIZE], int K)
{ int min, j, loc;
min = A[K]; loc = K;
for( j = K+1; j <= K-1 ; j++)
if( min > A[j]){min = A[j]; loc = j;}
return loc;
}
Merging:
Suppose A is sorted list with r elements and B is a sorted list with s elements.
The operation that combine the elements of A and B into a single sorted list C with
n= r+s elements is called merging. One simple way to merge is to place the
elements of C after the elements of A and then use some sorting algorithm on the
entire lsit. This method does not take advantage of the fact that A and B are
individually sorted. A much more efficient algorithm is merge sort algorithm.
Suppose one is given two sorted decks of cards. The decks are merged as:
88 55 88 55
44 33 44 33
22
33
22
22
(a) (b)
88 55 88 55
44
55
44 44
33 33
22 22
88
55
(c) (d)
44
33
22
(e)
Merge Sort:
The merge sort splits the list to be
sorted into two equal halves, and places
them in separate arrays. Each array is
recursively sorted, and then merged back
together to form the final sorted list.
5 4 2 3
Pass 1:
Pass 2:
5 4 2 3
4 5 2 3
Pass 3:
2 3 4 5
Pass 4:
void main()
{int num,i;
merge_sort obj;
cout<<"***************************************************************"<<endl;
cout<<" MERGE SORT PROGRAM"<<endl;
cout<<"***************************************************************"<<endl;
cout<<endl<<endl;
cout<<"How many ELEMENTS you want to sort : ";
cin>>num;
cout<<endl;
Radix Sort:
Radix sort is the method that many people intuitively use when alphabetizing
a large list of names. (Here the radix is 26, the 26 letters of alphabets.
The basic procedure of radix sort is as follow:
Based on examining digits in some base-b numeric representation of items
(or keys)
Least significant digit radix sort
Processes digits from right to left
Create groupings of items with same value in specified digit
Collect in order and create grouping with next significant digit
55 24 92 40
Input 0 1 2 3 4 5 6 7 8 9
55 55
24 24
92 92
40 40
Input 0 1 2 3 4 5 6 7 8 9
40 40
92 92
24 24
55 55
Input 0 1 2 3 4 5 6 7 8 9
24 24
40 40
55 55
92 92
24 40 55 92
#include <iostream.h>
#include <conio.h>
int const MAX = 100;
int A[MAX];
int C[MAX][10];
int N;
class Radix_sort
{public:
void get();
void radix(int);
void display();
};
void Radix_sort::get()
{cout<<"\n\tHow many values you want to sort -->";
cin>>N;
if(N>MAX)
{cout<<"\n\n Maximum number of values is "<<MAX<<".\n please try
again....";
getche();
get();
}
int i=0;
cout<<"\n Put "<<N<<" values..\n";
for(i;i<N;i++)
{cin>>A[i];}
}
void Radix_sort::radix(int R)
{int i,j,X,D;
int Z=0;
D=R/10;
for(i=0;i<N;i++)
{for(j=0;j<10;j++)
{C[i][j]=-1;}
}
for(i=0;i<N;i++)
if(Z==1)
{R*=10;
radix(R);
}
}
void Radix_sort::display()
{clrscr();
cout<<"\n\n\t\t\t Sorted List\n\n";
for(int i=0;i<N;i++)
{cout<<A[i]<<"\t";}
}
void main()
{
Radix_sort obj;
obj.get();
if(N>1)obj.radix(10);
obj.display();