Professional Documents
Culture Documents
DataStructures Notes
DataStructures Notes
Chapter 1
Introduction
Bubble sort
The bubble sort is also known as the ripple sort. The bubble sort is
probably the first, reasonably complex module that any beginning
programmer has to write. It is a very simple construct which introduces
the student to the fundamentals of how sorting works.
A bubble sort makes use of an array and some sort of "swapping"
mechanism. Most programming languages have a built-in function to
swap elements of an array. Even if a swapping function does not exist,
only a couple of extra lines of code are required to store one array
element in a temporary field in order to swap a second element into its
place. Then the first element is moved out of the temporary field and
back into the array at the second element's position. The bubble sort gets
its name because elements tend to move up into the correct order like
bubbles rising to the surface
Here is a simple example of how a bubble sort works: Suppose you have
a row of children's toy blocks with letters on them. They are in random
order and you wish to arrange them in alphabetical order from left to
right.
Step 1. Begin with the first block. In this case, the letter G. (Fig. 1.)
Fig. 1
Fig. 2
If you were doing this by hand, you might just pick the blocks to be
moved with one in each hand and cross your arms to swap them. Or you
might move the first one out of its position temporarily, move the second
one in its place, them move the first one to the now empty position (this
is the difference between having a single function to do the swap, or
writing some code to do it).
Step 4. Compare the next block in line with the first, and repeat step 3.
Do this until you run out of blocks. Then begin step one again with the
second block. (Fig. 3,4,5,6)
Fig. 3 - Pass #2
Fig. 4 - Pass #3
Fig. 5 - Pass #4
Step-by-step example
Let us take the array of numbers "5 1 4 2 8", and sort the array from
lowest number to greatest number using bubble sort. In each step,
elements written in bold are being compared. Three passes will be
required.
First Pass:
( 5 1 4 2 8 ) ( 1 5 4 2 8 ),Here, algorithm compares the first two elements, and
swaps since 5 > 1.
( 1 5 4 2 8 ) ( 1 4 5 2 8 ), Swap since 5 > 4
( 1 4 5 2 8 ) ( 1 4 2 5 8 ), Swap since 5 > 2
( 1 4 2 5 8 ) ( 1 4 2 5 8 ), Now, since these elements are already in order(8 > 5),
algorithm does not swap them.
Second Pass:
( 1 4 2 5 8 ) ( 1 4 2 5 8 )
( 1 4 2 5 8 ) ( 1 2 4 5 8 ), Swap since 4 > 2
( 1 2 4 5 8 ) ( 1 2 4 5 8 )
( 1 2 4 5 8 ) ( 1 2 4 5 8 )
Now, the array is already sorted, but our algorithm does not know if it is
completed. The algorithm needs one whole pass without any swap to
know it is sorted.
Third Pass:
( 1 2 4 5 8 ) ( 1 2 4 5 8 )
( 1 2 4 5 8 ) ( 1 2 4 5 8 )
( 1 2 4 5 8 ) ( 1 2 4 5 8 )
( 1 2 4 5 8 ) ( 1 2 4 5 8 )
Programming code for bubble sort function is as follows
}
}
Selection Sort
Algorithm
Insertion Sort
Algorithm
The simplest way to insert next element into the sorted part is to sift it
down, until it occupies correct position. Initially the element stays right
after the sorted part. At each step algorithm compares the element with
one before it and, if they stay in reversed order, swap them. Let us see an
illustration.
Shell Sort
Shellsort, also known as Shell sort or Shell's method, is an in-
place comparison sort. It generalizes an exchanging sort, such
as insertion or bubble sort, by starting the comparison and exchange of
elements with elements that are far apart before finishing with
neighboring elements. Starting with far apart elements can move some
out-of-place elements into position faster than a simple nearest neighbor
exchange. The running time of Shellsort is heavily dependent on the gap
sequence it uses.
Shellsort is a multi-pass algorithm. Each pass is an insertion sort of the
sequences consisting of every h-th element for a fixed gap h (also known
as the increment). This is referred to as h-sorting.
An example run of Shellsort with gaps 5, 3 and 1 is shown below.
Quick Sort
Quicksort, or partition-exchange sort, is a sorting algorithm that, on
average, makes O(n log n) comparisons to sort n items. In the worst
case, it makes O(n2) comparisons, though this behavior is rare. Quicksort
is often faster in practice than other O(n log n) algorithms. Additionally,
quicksort's sequential and localized memory references work well with
a cache. Quicksort can be implemented with an in-place partitioning
algorithm, so the entire sort can be done with only O(log n) additional
space.[2]
Quicksort is a divide and conquer algorithm. Quicksort first divides a
large list into two smaller sub-lists: the low elements and the high
elements. Quicksort can then recursively sort the sub-lists.
The steps are:
At each iteration, all the elements processed so far are in the desired
position: before the pivot if less than the pivot's value, after the pivot
if greater than the pivot's value (loop invariant).
Each iteration leaves one fewer element to be processed (loop
variant).
The correctness of the overall algorithm can be proven via induction: for
zero or one element, the algorithm leaves the data unchanged; for a
larger data set it produces the concatenation of two parts, elements less
than the pivot and elements greater than it, themselves sorted by the
recursive hypothesis.
Following is the example of quicksort on a random set of numbers. The
shaded element is the pivot. It is always chosen as the last element of the
partition. However, always choosing the last element in the partition as
the pivot in this way results in poor performance on already sorted lists,
or lists of identical elements. Since sub-lists of sorted / identical
elements crop up a lot towards the end of a sorting procedure on a large
set, versions of the quicksort algorithm which choose the pivot as the
middle element run much more quickly than the algorithm described in
this diagram on large sets of numbers.
Following is the program code for quick sort
void quickSort(int arr[], int left, int right)
{
int i = left, j = right;
int tmp;
int pivot = arr[(left + right) / 2];
/* partition */
while (i <= j) {
while (arr[i] < pivot)
i++;
while (arr[j] > pivot)
j--;
if (i <= j) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
}
};
/* recursion */
if (left < j)
quickSort(arr, left, j);
if (i < right)
quickSort(arr, i, right);
}
Merge sort
Merge sort (also commonly spelled mergesort) is
an O(n log n) comparison-based sorting algorithm. Most
implementations produce a stable sort, which means that the
implementation preserves the input order of equal elements in the sorted
output. Merge sort is a divide and conquer algorithm that was invented
by John von Neumann in 1945.[1] A detailed description and analysis of
bottom-up mergesort appeared in a report by Goldstine and Neumann as
early as 1948.[2]
Conceptually, a merge sort works as follows
Searching Techniques
Searching refers to the operation of finding the location of a given item
in a collection of items. The search is said to be successful if ITEM does
appear in DATA and unsuccessful otherwise.
The following searching algorithms are discussed here.
1. Sequential Searching
2. Binary Search
Sequential Search
This is the most natural searching method. The most intuitive way to
search for a given ITEM in DATA is to compare ITEM with each
element of DATA one by one. .The algorithm for a sequential search
procedure is now presented
INPUT : List of Size N. Target Value T
OUTPUT : Position of T in the list-1
BEGIN
Set FOUND = false
Set I := 0
While (I <= N) and (FOUND is false)
IF List[i] ==t THEN
FOUND = true
ELSE
I = I+1
IF FOUND==false THEN
T is not present in the List
END
Binary Search
Note that the variable BEG and END denote the beginning and end
locations of the segment respectively. The algorithm compares ITEM
with the middle element DATA[MID] of the
segment, where MID is obtained by
MID = INT((BEG + END) / 2)
(We use INT(A) for 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: DATA[MID + 1], DATA[MID + 2],....,DATA[END]
So we reset BEG := MID + 1 and begin searching again.
Initially, we begin with the entire array DATA; i.e. we begin with
BEG = 1 and END = n, If ITEM is not in DATA, then eventually we
obtain END< BEG
This condition signals that the search is unsuccessful, and in this case we
assign LOC: = NULL. Here NULL is a value that lies outside the set of
indices of DATA. We now formally state the binary search algorithm
Algorithm:-
(Binary Search) BINARY(DATA, LB, UB, TEM, LOC)
Here DATA is sorted array with lower bound LB and upper bound
UB, and ITEM is a given item of information. The variables BEG,
END and MID denote, respectively, the beginning, end and middle
locations of a segment of elements of DATA. This algorithm finds
the location LOC of ITEM in DATA or sets LOC=NULL.
1. [Initialize segment variables.]
Set BEG := LB, END := UB and MID = INT((BEG + END)/ 2).
2. Repeat Steps 3 and 4 while BEG ≤ END and
DATA[MID] ≠ ITEM.
3. If ITEM<DATA[MID], then:
Set END := MID - 1.
Else:
Set BEG := MID + 1.
[End of If structure]
4. Set MID := INT((BEG + END)/2).
[End of Step 2 loop.]
5. If DATA[MID] :=ITEM, then:
Set LOC:=MID.
Else:
Set LOC := NULL.
[End of If structure.]
6. Exit.
Chapter 3
A stack is an ordered list in which all insertions and deletions are made
at one end, called the top. A queue is an ordered list in which all
insertions take place at one end, the rear, while all deletions take place
at the other end, the front. Given a stack S=(a[1],a[2],.......a[n]) then we
say that a1 is the bottommost element and element a[i]) is on top of
element a[i-1], 1<i<=n. When viewed as a queue with a[n] as the rear
element one says that a[i+1] is behind a[i], 1<i<=n.
procedure add(item : items);
{add item to the global stack stack;
top is the current top of stack
and n is its maximum size}
begin
if top = n then stackfull;
top := top+1;
stack(top) := item;
end: {of add}
Deletion in stack
procedure delete(var item : items);
{remove top element from the stack stack and put it in the item}
begin
if top = 0 then stackempty;
item := stack(top);
top := top-1;
end; {of delete}
These two procedures are so simple that they perhaps need no more
explanation. Procedure delete actually combines the functions TOP and
DELETE, stackfull and stackempty are procedures which are left
unspecified since they will depend upon the particular application. Often
a stackfull condition will signal that more storage needs to be allocated
and the program re-run. Stackempty is often a meaningful
condition.Procedures for addition and deletion in queue is given below.
Deletion in a queue
Circular Queue
A circular queue is a Queue but a particular implementation of a queue.
It is very efficient. It is also quite useful in low level code, because
insertion and deletion are totally independant, which means that you
don't have to worry about an interrupt handler trying to do an insertion at
the same time as your main code is doing a deletion
Algorithm for Insertion:-
Step-1: If "rear" of the queue is pointing to the last position then go to
step-2 or else step-3
Step-2: make the "rear" value as 0
Step-3: increment the "rear" value by one
Step-4:
1.if the "front" points where "rear" is pointing and the queue holds
a not NULL value for it, then its a "queue overflow" state, so quit; else
go to step-4.2
2. insert the new value for the queue position pointed by the "rear"
Algorithm for deletion:-
Step-1: If the queue is empty then say "empty queue" and quit; else
continue
Step-2: Delete the "front" element
Step-3: If the "front" is pointing to the last position of the queue then
step-4 else step-5
Step-4: Make the "front" point to the first position in the queue and quit
Step-5: Increment the "front" position by one
class cqueue
{
int q[5],front,rare;
public:
cqueue()
{
front=-1;
rare=-1;
}
void push(int x)
{
if(front ==-1 && rare == -1)
{
q[++rare]=x;
front=rare;
return;
}
else if(front == (rare+1)%5 )
{
cout <<" Circular Queue over flow";
return;
}
rare= (rare+1)%5;
q[rare]=x;
}
void pop()
{
if(front==-1 && rare== -1)
{
cout <<"under flow";
return;
}
else if( front== rare )
{
front=rare=-1;
return;
}
front= (front+1)%5;
}
Chapter 4
Linked List
A linked list is a data structure consisting of a group of nodes which
together represent a sequence. Under the simplest form, each node is
composed of a datum and a reference (in other words, a link) to the next
node in the sequence; more complex variants add additional links. This
structure allows for efficient insertion or removal of elements from any
position in the sequence.
A linked list whose nodes contain two fields: an integer value and a link
to the next node. The last node is linked to a terminator used to signify
the end of the list.
Linked lists are among the simplest and most common data structures.
They can be used to implement several other common abstract data
types, including stacks, queues,associative arrays, and S-expressions,
though it is not uncommon to implement the other data structures
directly without using a list as the basis of implementation.
The principal benefit of a linked list over a conventional array is that the
list elements can easily be inserted or removed without reallocation or
reorganization of the entire structure because the data items need not be
stored contiguously in memory or on disk. Linked lists allow insertion
and removal of nodes at any point in the list, and can do so with a
constant number of operations if the link previous to the link being
added or removed is maintained during list traversal.
On the other hand, simple linked lists by themselves do not
allow random access to the data, or any form of efficient indexing. Thus,
many basic operations — such as obtaining the last node of the list
(assuming that the last node is not maintained as separate node reference
in the list structure), or finding a node that contains a given datum, or
locating the place where a new node should be inserted — may require
scanning most or all of the list elements.
Basically, a linked list is a collection of nodes. Each node in the list has
two components – a components to store information about that node
(i.e. data) and a component to store the next “linked” node. The last
node in a linked list indicates that it does not have a “linked” node –
usually by a null reference. The address of the first node is stored in a
separate location, called the head or first.
The code snippet below shows a basic structure of a node declaration in
c++.
struct nodeType
{
int data;
nodeType *link;
};
The variable declaration is as follows…
nodeType *head;
Basic Operations of a Linked List
There are a few basic operations that are typically needed with a linked
list
Traversing/Searching a List
To traverse a linked list you need some way to move around the list. The
*head is a pointer in the list, but you cannot use it to traverse the list
because then you would loose a reference to the beginning of the list.
Typically to traverse the list, you will need to declare another pointer
that follows the same declaration as head, but that is not critical if you
change where it’s pointing to…
current = head;
while (current != NULL)
{
cout << current->data << " ";
current = current->link;
}
Item Insertion and Deletion
To insert a new node into a linked list, you need to do the following:
In code, assuming current was at the correct position, then it would look
something like this…
nodeType *newNode;
newNode = new nodeType;
newNode->data = 123;
newNode->link = current->link;
current->link = newNode;
Determine the node that you want to remove and point current to
the node prior to that node
Create a pointer to point to the node you want to delete (i.e. *p)
Make current’s link now point to p’s link
delete p from memory
In code, assuming current was at the correct position, then it would look
something like this…
deleteNode = new nodeType;
deleteNode = current->link;
current->link = deleteNode->link;
delete deleteNode;
class stack
{
int element;
stack* next;
public:
stack* push(stack*,int);
stack* pop(stack*);
void stack_display(stack*);
}*head,object;
stack* stack::push(stack* head,int key)
{
stack* temp,*temp1;
temp1=head;
temp=new stack;
temp->element=key;
temp->next=NULL;
if(head==NULL)
head=temp;
else
{
while(head->next!=NULL)
head=head->next;
head->next=temp;
head=temp1;
}
return head;
}
stack* stack::pop(stack* head)
{
stack* temp;
if(head!=NULL)
{
temp=head;
if(head->next==NULL)
{
cout<<”\nthe pooped element from the stack is: “<<head->element<<endl;
return NULL;
}
while(head->next->next!=NULL)
head=head->next;
cout<<”the popped element from the stack is “<<head->next->element;
cout<<endl;
head->next=head->next->next;
head=temp;
return head;
}
else
{
cout<<”\nit is impossible to pop an element from the stack as “;
return head;
}
}
void stack::stack_display(stack* head)
{
if(head!=NULL)
{
while(head->next!=NULL)
{
cout<<head->element<<”->”;
head=head->next;
}
cout<<head->element;
cout<<endl;
}
else
cout<<”the stack is empty\n”;
}
Code for the implementation of queue using linked list is given below
class queue
{
struct node *frnt,*rear;
public:
queue() // constructure
{
frnt=rear=NULL;
}
void insert();
void del();
void show(); //
};
void queue::insert()
{
int value;
struct node *ptr;
cout<<"\nInsertion\n";
cout<<"Enter a number to insert: ";
cin>>value;
ptr=new node;
ptr->data=value;
ptr->next=NULL;
if(frnt==NULL)
frnt=ptr;
else
rear->next=ptr;
rear=ptr;
cout<<"\nNew item is inserted to the Queue!!!";
getch();
}
void queue::del()
{
if(frnt==NULL)
{
cout<<"\nQueue is empty!!";
getch();
return;
}
struct node *temp;
temp=frnt;
frnt=frnt->next;
cout<<"\nDeletion Operation........\nDeleted value is "<<temp->data;
delete temp;
getch();
}
void queue::show()
{
struct node *ptr1=frnt;
if(frnt==NULL)
{
cout<<"The Queue is empty!!";
getch();
return;
}
cout<<"\nThe Queue is\n";
while(ptr1!=NULL)
{
cout<<ptr1->data<<" ->";
ptr1=ptr1->next;
}
cout<<"END";
getch();
}
Binary Trees
A binary tree is a tree data structure in which each node has at most
two child nodes, usually distinguished as "left" and "right". Nodes with
children are parent nodes, and child nodes may contain references to
their parents. Outside the tree, there is often a reference to the "root"
node (the ancestor of all nodes), if it exists. Any node in the data
structure can be reached by starting at root node and repeatedly
following references to either the left or right child. A tree which does
not have any node other than root node is called a null tree. In a binary
tree a degree of every node is maximum two. A tree with n nodes has
exactly n−1 branches or degree.
Binary trees are used to implement binary search trees and binary heaps.
A simple binary tree of size 9 and height 3, with a root node whose value is 2. The above tree is
unbalanced and not sorted.