You are on page 1of 377

DATA STRUCTURES AND ALGORITHM

ALGORITHMS WITH ANALYSES MULTIPLE CHOICE QUESTIONS AND ANSWERS QUESTIONS OF UNIVERSITIES, GATES AND SOLUTIONS
SAMPLE PROGRAMS FOR LINKED REPRESENTATION OF STACK, QUEUE AND TREES

Dr.Arup Kr.Bhaumik Santanu Haldar Subhrajit Sinha Roy

CONTENTS
Preface Acknowledgements 1. INTRODUCTION
1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 Information and Data Basic Operation of a Computer Solvability of Problem and Data Structure Mathematical Modeling of Data Representation of Numbers in Computer Group Data or Structured Data Algorithm and Notations Used in The Book MCQ Chapter 1 Exercises Chapter 1

2. PRELIMINARIES
2.1 2.2 2.3 2.4 2.5 Definition of algorithm Properties of an Algorithm Algorithm Development Life Cycle Example of some Algorithms Complexity of Algorithms (Analyzing phase) 2.5.1 Asymptotic notations 2.5.2 How can we estimate the time complexity in big Oh(O) notation? 2.5.3 How can we improve the complexity of algorithms? 2.5.4 Properties of Big Oh 2.5.5 Rate of growth of Big Oh notation Some popular Mathematical Notations and Functions Algorithmic Notation MCQ Chapter 2 Exercises Chapter 2

2.6 2.7 2.8 2.9

3. ARRAY 3.1 Introduction and Definition 3.2 Declaration of Array 3.3 Memory Representation of Array 3.4 Insertion in one dimentional Array 3.5 Deletion from one dimentional Array 3.6 Travarsing one dimentional Array 3.7 Merging two one dimentional Array 3.8 Matrix Addition 3.9 Sparse Matrix 3.10 Polynomial Representation 3.11 MCQ Chapter 3

3.12 4

Exercises Chapter 3

STRUCTURE AND POINTER 4.1 Definition of Structure 4.2 Basic of Pointer 4.3 Structure and Pointer 4.4 Passing Structure to Pointer 4.5 Self Refferential Structure 4.6 Dynamic Memory Allocation 4.7 MCQ Chapter 4 4.8 Exercises Chapter 4

5. STRINGS 5.1 Definition 5.2 Concatenation and substrings 5.3 String operations 5.3.1 Alphabet of a string 5.3.2 String substitution 5.3.3 String homomorphism 5.3.4 String projection 5.3.5 String Right quotient 5.3.6 String Syntactic relation 5.3.7 String Right cancellation 5.3.8 String Prefixes 5.4 String Topology 5.5 String datatypes 5.6 String length 5.7 String Representations 5.8 String as Vectors 5.9 String processing algorithms 5.9.1 String searching algorithms 5.9.1.1 Basic classification 5.9.1.2 Single pattern algorithms 5.9.1.3 Algorithms using finite set of patterns 5.9.1.4 Algorithms using infinite number of patterns 5.9.2 String manipulation algorithms or String Functions 5.9.3 String Sorting algorithms 5.9.4 Regular Expression Algorithms 5.9.5 Parsing a String 5.10 String Implementations 5.11 MCQ Chapter 5 5.12 Exercises Chapter 5 6. STACK 6.1 Definition 6.2 Basic Operations 6.3 Implementation of stack using array 6.4 Implementation of stack using dynamic memory allocation 6.5 Arithmetic Expressions

6.6 Converting Infix expressions to postfix notation 6.7 Converting Infix expression to prefix notation 6.8 Evaluating Postfix Expression 6.9 Evaluating Prefix Expression 6.10 Application of Stack 6.11 Reversing a string 6.12 Checking balanced parenthesis 6.13 Multiple Stacks 6.14 MCQ Chapter 6 6.15 Exercises Chapter 6 7. QUEUE 7.1 Definition 7.2 Implementation of queue using array 7.3 Implementation of Queue using dynamic memory allocation 7.4 Limitation of linear queue 7.5 Circular Queue 7.6 Deque(Double Ended Queue) 7.7 Priority Queue 7.8 Implementation of queue using stacks 7.9 Application of Queue 7.10 MCQ Chapter 7 7.11 Exercises Chapter 7 8. LINKED LIST Introduction Definition Advantages Disadvantages Operations on Linked List Types of Linked List Singly Linked List Reversing a Singly Linked List Polynomial using singly linked list Advantages and disadvantages of singly linked list Circular linked list Double linked list MCQ Chapter 8 Exercises Chapter 8 9. RECURSION Definition Types of recursion Linear recursion Binary recursion Multiple recursion Some recursive algorithms Factorial function GCD of two numbers

Exponential Power Fibonacci sequence Towers of Hanoi Tail of recursion Disadvantages of recursion Recursion Vs Iteration MCQ Chapter 9 Exercises Chapter 9 10. TREE Definition Basic terminologies Binary tree Types of binary trees Properties of binary trees Representation of binary tree Binary tree traversal methods Creation of binary tree from preorder and inorder traversal Creation of binary tree from postorder and inorder traversal Expression tree Binary search tree Heap Weight balanced binary tree AVL Search tree / Height balanced binary tree Threaded binary tree M-way search tree B-Tree/Balanced M-way search tree Red/Black tree MCQ Chapter 10 Exercises Chapter 10 11. GRAPH Definition Basic Terminologies Representation of a graph Operations on a graph Traversal of a graph Breadth first search Depth first search Shortest path calculation (Dijkstras algorithm) Warshalls algorithm MCQ Chapter 11 Exercises Chapter 11 12. SORTING 12.1 Definition 12.2 The family of sorting methods 12.3 Bubble Sort 12.4 Insertion Sort

12.5 Selection Sort 12.6 Merge Sort 12.7 Quick Sort 12.8 Heap Sort 12.9 Radix Sort 12.10 Comparison study of different sorting 12.11 MCQ Chapter 12 12.12 Exercise Chapter 12 13. SEARCHING 13.1 Introduction 13.2 Sequential Search 13.3 Binary Search 13.4 MCQ Chapter 13 13.5 Exercises Chapter 13 14. HASHING 14.1 Introduction 14.2 Hash Table 14.3 Hash Functions 14.4 Collision 14.5 Clustering 14.6 Collision resolution Technique 14.7 Load Factor 14.8 Analysis of Open Addressing and Chaining 14.9 MCQ Chapter 14 14.10 Exercises Chapter 14 15. FILES 15.1 Introduction 15.2 Definition 15.3 File Classification 15.3.1 Non Structured file 15.3.1.1 Text File 15.3.1.2 System File 15.3.1.2.1 Command File (.COM): 15.3.1.2.2 Executable File (.EXE) 15.3.1.2.3 Dynamic Link Laboratory File(.DLL) 15.4 Structured File 15.4.1 Sequential File 15.4.2 Index Sequential File 15.5 MCQ Chapter 15 15.6 Exercises Chapter 15 16. Various Questions on Data Structure

Preface
The data nowadays play very important role in computation. Versatile usage of computers in every spheres of life enhances handling of different kinds of data and its representation in computers memory. But a programmer must have clear idea about requirement of data structure and algorithm for writing solution of a problem in computer. Otherwise, efficiency will never be achieved. Though there are several books on data structure available in the market but in a particular book all the topics like algorithms, analyses, equivalent programs in C language , Multiple Choice Questions (MCQ ) and excersies are not available. On delivering lectures for 10 years since in theory and lab classes in this subject we have faced many questions of students and their suggestions. In many occasions we have used our intuition to make the students understanding in different topics of this subject. We have incorporated all of these strategies in our book and also programs are written in C language to encourage students to try for testing of algorithms. The book has been written by covering syllabi of B.Tech, B.E., MCA, BCA & Bsc. Computer sc. courses of all most all Indian and Foreign Universities. Ho.pe everybody from new learner to expert programmer may get help from the book.

Acknowledgements
The most inspiaratation that all of us have received from our beloved students, thus we must thank them at first. Secondly, Mr. S.K.Bagal and Mr. Lahiri of S. Chand & Co.Ltd. both of them have tendered their help, courage and moral support to write the book, all of us are very thankful to both of them. While preparation of this manuscript we have received help and criticisims from our colleague Mr. Animesh Hazra, Lecturer Computer Sc. & Engg. So we convey our thanks to Mr.Hazra.

Chapter 1 Introduction
1.1: Information and Data
Information: The Oxford dictionary meaning is (1) Facts or Knowledge provided or learned. (2) Computer Data. Data: The Oxford dictionary meaning is (1) Facts or statistics used for reference or analysis (2) The quantities, characters or symbols operated on by a Computer. Thus, from the definitions of the dictionary one can assume that the terms information and data can be used interchangeably. But in context of the computer any recorded or stored fact, which needs for processing by the Central Processing Unit (CPU) is called data i.e. for example Students name, Address, Marks obtained in different subjects when recorded is called data and when a program is written to find grades of students on the basis of marks obtained in various subjects the output will appear as A, B, C etc. or First Class, 2 nd Class etc., which are information. Moreover, if grades are recorded or stored for further computation then these will be termed as data also. 1.2 Basic Operation of a Computer Any computer has three distinct units, viz. Input/Output, Memory and Central Processing Unit (CPU). The input unit is used to feed data to the computers memory and the output unit is used to show the result after computation. The memory unit is used to store data and program. The CPU is the main unit comprising of two sub units viz. Control unit (CU) and the Arithmetic Logic Unit (ALU). The CPU does following operations in steps in repeated manner: Step 1: Fetch an instruction from memory Step 2: Decode the instruction Step 3: Fetch the operands needed for the operation Step 4: Execute the instruction in ALU It is clear from the steps of operations that first of all computer needs an instruction i.e. a command (to do some operations) and then operand (or data) is required for completion of operation. The operations performed by a bare CPU are very basic such as Addition, Subtraction, Multiplication, AND, OR, NOT etc. on binary data. Thus, if everything i.e. instructions and data are represented in binary then the binary sequence is called machine code. But to work with machine code is very difficult either to write a full program or later on to debug it. To make the understanding of machine code just easy the equivalent hexadecimal representation is used. Thus, for a bare CPU the data types like integers, real etc. has no meaning, these are the descriptions of data done through high level language (HLL) and the translator program i.e. either compiler or interpreter takes responsibility to convert integer, real etc. data type to binary format and vice versa.

1.3 Solvability of Problem and Data Structure


There are various types of problem framed in different aspects of life in our society e.g. computation of salary, income tax etc. for employees, debit, credit analysis and budgeting of any firm, weather forecasting, designing and analyzing structures for cars, airplane, building etc., analysis for gene etc. The quicker solutions of these

problems ensure progress of a society. Nowadays, computers are used in every sphere of life to get solutions of problems in a quickest and accurate way. But computer needs appropriate program for each problem. Problems like computation of factorial of a positive integer, sorting a list of names, computation of salary of employees, computation of free memory blocks etc. are required different methods with different data set (values) to solve each of these. Whenever a problem is to be solved through a computer, a program is required to develop, and in a program data are passed through variables, where variables are used to represent memory references either in single location or in a group of locations to store atomic or group data respectively.

1.4 Mathematical Modeling of Data


In our mathematics any real value is classified into: i) ii) Integer numbers (both +ve and ve) e.g. 0, 1, 2, 3, . . . etc Rational numbers: Number derived from division of two integers (x, y) such as x/y, where y 0. The number x/y is also called a fraction and if the magnitude of x is less than y then the fraction is proper. Otherwise, if magnitude of x is greater than y then the fraction is improper fraction. Thus, 1/2 , 5/2, 1/3, 1/7 etc. are all fractions out of which 5/2 is only improper fraction. But 1/3 and 1/7 do not provide exact values since divisions will never terminate. These are called periodic or recurring fractions. In mathematics value of 1/3 is written as 0.3, which represents 0.333333333 and value of 1/7 is written as 0.1428571, which represents 0.14285711428571 . iii) Irrational Numbers: These are the numbers obtained from non-rational roots of algebraic equations such as 2, 3, 5 etc. These numbers cannot be expressed either as terminating decimal or a periodic decimal. These are represented geometrically by points, namely the gaps between rational points on a line.

1.5 Representation of Numbers in Computer


Almost all Compilers / Interpreters written for high level languages e.g. FORTRAN, C, COBOL, PASCAL etc. allow data to be declared in following types: A. Numerical (i) Integer: generally two bytes i.e. 16 bits are considered to represent both negative and positive integers. With 16 bits or two bytes decimal integers, numbers from -32768 to +32767 only can be represented. Where for representation of ve numbers 2s complement notation is used. Since in 2s complement notation -32768 is 1000 0000 0000 0000 & +32767 is 0111 1111 1111 1111. However, in case of only positive numbers the range of decimal numbers 0 65535 is represented. Therefore it is observed that a very small decimal value can be represented by 16 bits or two bytes of data. But in our environment often we need large values to represent physical quantities such as net asset value of a small company 10,00,000,000/- (Rs.10 Crore), population of any country 100,00,000,000 ( 100 Crore) etc. Thus further grouping of bytes are required to represent large numbers. The simple integers are called primitive data types. Almost all high level languages provide additional data types to accommodate large values in computers. For example, when 4 bytes are grouped, the range of decimal values -2,147,483,648 to 2,147,483,647 are

represented . In general the following formula is used to establish relationships between binary numbers and the equivalent decimal integers. -2n-1 +1 N 2n-1 -1 , Where n = no. of bits, N= decimal integers. Negative numbers are represented by 2s complement notation. In C-language the following data types are used to represent integers. Range Bytes Represents -32768 - 32767 2 Whole numbers 0 - 65535 2 -do-2,147,438,648 4 -do2,147,438,647 Unsigned long 0 - 429,496,7295 4 -doIn high level languages there is another data type available to represent fractional decimal numbers, which is called float in C Language and Real in FORTRAN. But in C language to accommodate more decimal digits double and long double types are also used, which is described below: Type int Unsigned int long

Type

Range From To -38 Float 3.4 x 10 - 3.4 x 10-38 -308 double 1.7 x 10 1.7 x 10-308 long double 3.4 x 10-4932 3.4 x 10-4932

Bytes Represents 4 8 10 Fractional numbers -do-do-

B.Character Single characters e.g. letters, digits, punctuation symbols etc. are represented by character data type in C language the keyword char is used to declare character data type. Either ASCII or EBCDIC format is used for depicting a character and one byte of space occupied in memory for storing the same. The ASCII format has been used in C language to describe character data type and if anybody wants to write a in computer, actually the binary sequence 10010111, equivalent to ASCII value 97 is stored in memory.

1.6 Group Data or Structured Data


The most rigorous usage of computers happen in the areas of banking, insurance, health, university etc. where data are generally represented in the form of record, which represents a group of molecular or elemental information. As for example a record of a registered student of any university looks like: 103000532 Manmohan Sing B.Tech CSE 2007

Where the first element represents Registration number, the second element is name, the third one is the course of study, fourth one is the subject and the last one is the year of registration. All the elements together comprise

a record of a registered student and carry meaningful information. But no single (primitive) data types available in any HLL to represent the entire record in computer. However, all third generation languages have provision of declaration of record to support different data types. But do not allow populate data in file with different structues, e.g. COBOL language allows three kinds of file structure like Sequential, Random and Index Sequential. On the contrary the 'C' Language does not allow Random and Index Sequential file structure in general to populate data, special programs are required for these files.

1.7 Algorithm and Notations Used in The Book


We have used 'C' Language and it's notation in this book for writing algorithms, but the full syntaxes of the language have not been considered. Thus, one can convert algorithms to equivalent programs very easily just by maintaing syntaxes only. Moreover, very simple conventions have been followed to name variables or identifiers in our program e.g. if the variables start with i it represents integers data type etc. ---------------------write for others. The book has been divided into --- nos of chapters, where in chapter-1, following topis are covered --------we have provided questions and solutions of these, where questions are collected from different Universities to provide guide line for preparation of different examinations.

1.8 MCQ Chapter 1

1. The most widely used method for interpreting bit setting as non-negative integer is (a)Binary number system (b)BCD system (c)ASCII system (d)None of the above 2. Any string of bits of length n represents a unique non-negative integer between (a) 0 and 2n-1-1 (b) 0 and 2n-1 (c) 1 and 2n-1 (d) None of the above 3. With 1' s complement method, the range of the numbers that can be represented using n bits is : (a) a 1 followed by using n - 1 zeroes to a 0 followed by n - 1 ones (b) a 0 followed by using n - 1 ones to a 1 followed by n - 1 zeroes (c) 2n-1-1 to 2n-1-1 (d) None of the above. 4. Using 2's complement method, the range of the numbers that can be represented using n bits is : (a) a 1 followed by using n - 1 zeroes to a 0 followed by n - 1 ones (b) a 0 followed by using n - 1 ones to a 1 followed by n - 1 zeroes (c) 2n-1-1 to 2n-1-1 (d) None of the above. 5. Key concept for representing a real number is a mantissa times (a) 10 raised to an exponent (b) 2 raised to an exponent (c) base raised to an exponent (d) None of the above.

6. The set of native data types that a particular computer can support is determined by : (a) type of hardware company (b) what functions have been wired into hardware (c) what software support is required (d) None of these. 7. While considering data structure implementation, the factor(s) under consideration is (are) : (a) time (b) time and space (c) time, space and processor (d) None of these. 8. Which is not true ? (a) Abstract data type is the useful tool for specifying the logical properties of the data type (b) While defining an abstract data type as a mathematical concept, the space and efficiency is not of major concern (c) Every abstract data type can be implemented using any programming language (d) None of these. 9. The number of 1s in the binary representation of 3 * 4096 + IS * 256 + 5 * 16 + 3 are (a) 8 (b) 9 (GATE-1995) (c) 10 (d) 12. 10. Consider the following floating point representation 31 24 23 Exponent Mantissa (GATE- 1996) 0

The exponent is in 2' s complement representation and mantissa is in the sign magnitude representation. The range of the magnitude of .the normalized numbers in this representation is (a) 0 to 1 (b) 0.5 to 1 -23 (c) 2 to 0.5 (d) 0.5 to (1-2-23). 11. The octal representation of an integer is 3428. If this were to be treated as an eight bit integer, its decimal equivalent is (GATE-1998) (a) 226 (b) -98 (c) 76 (d) -30 12. Zero has two representations in: (a) Sign magnitude (c) All of the above (GATE-1999) (b) 2's complement (d) None of the above.

13. Consider the values A = 2.0 x 1030, B = - 2.0 x 1030, C = 1.0, and the sequence (GATE-2000) X := A + B Y := A + C X := X + C Y:= Y + B executed on a computer where floating point numbers are represented with 32 bits. The values for X and Y will be

(a) X = 1.0, Y = 1.0 (b) X = 1.0, Y = 0.0 (c) X = 0.0, Y = 1.0 (d) X = 0.0, Y = 0.0 14. The number 43 in 2' s complement representation is (a) 01010101 (b) 11010101 (d) 10101011 (c) 00101011 15. Which of the following is (are) example(s) of data abstraction? (a) List of student information (b) File of employee records (c) Bank account database (d) All of the above. 16. Data structure means (a) The separation of the representation of data from the applications that use the data at a logical level (b)the logical picture of a data type, plus the specifications of the operations required to create and manipulate objects of this data type (c)a collection of data elements whose organization is characterized by accessing operations that are used to store and retrieve the individual data elements (d) None of these. 17. Abstract Data Type means that (a) The separation of the representation of data from the applications that use the data at a logical level (b) The logical picture of a data type, plus the specifications of the operations required to create and manipulate objects of this data type (c) A collection of data elements whose organization is characterized by accessing operations that are used to store and retrieve the individual data elements (d) None of these. 18. Why is writing easily modifiable code important? a) Easily modifiable code generally has a quicker run time. b) Most real world programs require change at some time. c) Most text editors make it easy to modify code. d) Several people may be writing the same function at the same time. 19. Which phase of the software life-cycle is usually the most expensive? a) Analysis and specification of the task b) Design c) Implementation d) Maintenance and evolution 20. What will happen if a function is executed and the precondition for the function is not met? a) An error message will be printed. b) The program will loop indefinitely. c) The system will crash. d) Any of the above results could happen. 21. If the precondition fails, it is a good idea to write a useful error message and then halt the program. Why is the program halted? a) Most operating systems forbid continuation. b) The function is no longer guaranteed to make the postcondition true. c) The function's memory requires have become exponential (or worse). d) The function's running time has become exponential (or worse).

22.

23.

24.

25.

26.

27.

Which of these is used to stop the program execution when a precondition is not met. a) assert(); b) exit(); c) return(); d) void(); Which of these statements will always cause a program to halt? (x is an int variable). a) assert(10 > 0); b) assert(10 < 0); c) assert(x < 0); d) None of the above will always cause a program to halt. What does a run-time analysis usually count? a) The number of arithmetic and other operations required for the program to run b) The number of megabytes required for the program to run c) The number of seconds required for the program to run d) The number of seconds plus the number of megabytes e) The number of seconds times the number of megabytes Why is it important to test boundary values when testing programs? a) Calculating by hand, it's easy to find the right answers for boundary values. b) Debuggers are easier to use when testing boundary values. c) In practice, a large proportion of errors arise from boundary values. d) The correct execution of a function on all boundary values proves a function is correct. How may boundary values for a function be found? a) Pick values that are one step away from different behavior. b) Pick values that make the precondition equal to the postcondition. c) Pick values where the precondition is false. d) Pick values where the postcondition is false. Which software tool will best help you determine whether your test cases are fully exercising your code? a) Compiler b) Debugger c) Make d) Pine e) Profiler 28. A text is made up of the characters a, b, c , d, e each occurring with the probability .12, .4, .15, .08 and .25 respectively. The optimal coding technique will have the average length of (a) 2.15 (b) 3.01 (c) 2.3 (d) 1.78 29. In the previous question, which of the following characters will have codes of length 3? (a) Only c (b) Only b (c) band c (d) Only d 30. Which of the following abstract data types can be used to represent a many to many relation? (a) Tree (b) Plex (c) Graph (d) Queue 31. The principle of locality justifies the use of (a) interrupts (b) DMA (c) polling (d) cache memory

32. Unrestricted use of goto is harmful, because it (a) makes debugging difficult (b) increases the running time of programs (c) increases memory requirement 9f programs (d) results in the compiler generating longer machine code

33. The main() function is always (a)a called function (b) a calling function (c) recursive function (d) used at the end of the program (e) None of these. 34. The first digit of a decimal constant must be (a) zero (b) a non-zero number (e) a negative number (d) an integer (e) None of these. 35. Floating point numbers are used instead of integers to (a) permit the use of decimal points in numbers (b) avoid being too specific about what value a number has (c) conceal the true value of the numbers (d) All of the above (e) None of these. 36. An expression (a) is a collection of data objects and operators that can be evaluated to a single value (b) is a name that substitutes for a sequence of characters (c) causes the computer to carry out some action (d) All of the above (e) None of these. 37. Consider the following arithmetic expression 2 * x / (3 * y) Suppose x and y are floating-point variables that have been assigned the values x = 8.8 and y = 3.5. What would be the value of the expression? (a)1.61373 (b) 20.53333 (c)1.67619 (d) None of these (e) 2.51429 38. The statement (i = (j = 4) + (k = 9): (a) assigns a value 13 to i (b) assigns a value 4 to i (c) gives an error message (d) assigns a value 7 to i (e) None of these. 39. If p and q are assigned the values 2 and 3 respectively then the statement p = q++ (a) gives an error message (b) assigns a value 4 to p (c) assigns a value 3 to p (d) assigns a value 5 to p (e) None of these. 40. If the variables i,j and k are assigned the values 5, 3 and 2 respectively, then the expression i=j+(k++=6)+7): (a) gives an error message (b) assigns a value 16 to i (c) assigns'a value 18 to i (d) assigns a value 19 to i (e) None of these.

41. Which of the following is not a programming control structure? (a) Repetition (b) selection (c) Sequency (d) Sorting. 42. External documentation includes (a) a printout of the program's code (b) flowcharts (c) IPO charts (d) pseudocode (e) All of the above. 43. Errors in a program are called (a) accidents (b) annoyances (c) bugs(d) mistakes (e) typing errors. 44. Typing the instruction grosspay = hoursWorked hourlypay is an example of (a) an entry error (b) a function error (c) a logic error (d) a syntax error. 45. The step-by-step instructions that solve a problem are called (a) an algorithm (b) a list (c) a plan (d) a sequential structure. 46. The set of instructions for how to tie a bow is an example of the structure (a) control (b) repetition (c) selection (d) sequence (e) switching. 47. Which of the following control structures is used in every program? (a) Repetition (b) Selection (c) Sequence (d) Switching. 48. The instruction "If it's raining outside, then take an umbrella to work" is an example of the structure (a) control (b) Repetation (c) selection (e) switching. 49. The recipe instruction "Beat until smooth" is an example of the structure (a) control (b) repetition (c) selection (d) sequence (e) switching. 50. Sending a copy of data to a program module is called (a) passing a value (b) making a reference (c) recursion (d) setting a condition. 51. Paying attention to the important properties while ignoring inessential details is known as. (a) selectiveness (b) polymorphism

(c) abstraction (d) summarizing. 52. A program that predicts the exact sequence in which events will take place is said to be (a) compiled (b) interpreted (c) procedural (d) object-oriented. 53. Using a statement at the wrong time or with an inappropriate object creates a (a) logical error (b) syntax error (c) compiler error (d) language error. 54. A translator that notes whether you have used a language correctly may be called a (a) theasurus (c) coder (b) compiler (d) decoder.

Solutions:
1. a 16. c 31. d 46. d 2. d 17. b 32. a 47. c 3. a 18. b 33. b 48. c 4. a 19. d 34. b 49. b 5. c 20. d 35. b 50. a 6. b 21. 36. a 51. d 7. c 22. b 37. b 52. c 8. c 23. d 38. a 53. a 9. c 24. a 39. c 54. b 10. d 25. d 40. a 11. d 26. 41. c 12. a 27. b 42. a 13. d 28. a 43. c 14. c 29. a 44. c 15.d 30. b/c 45. a

1.10 Exercise Chapter 1 1. Describe one good method for precisely specifying what a function must do, without indicating how the function accomplishes its work. Provide an example, using a small function. 2. What is a precondition? What is a post-condition? 3. It's recommended that the precondition be checked at the start of a function. What's a good approach if a function discovers that its precondition is not true? 4. Is it always possible for a function to check that its precondition is true? 5. Suppose that you accidentally call a correctly implemented function, but the precondition is false. Is the function guaranteed to halt with a nice message? Is the function allowed to erase everything on your hard drive? 6. Write the first few lines of this function so that it uses the assert facility to check its precondition:
void exam(int i) // Precondition: i is not equal to 42. ...

7. Give a concise formula that gives the approximate number of digits in a positive integer. The integer is written in base 10. 8. Why is the order of an algorithm generally more important than the speed of the processor? 9. With about three or four sentences, explain the basic features of your debugger and how they help you find bugs.

Chapter - 2

PRELIMINARIES
2.1 Definition of algorithm
An algorithm is a set of steps to solve a particular problem. When we write the different steps for the preparation of a cup of tea or coffee then it is also an algorithm. The word algorithm is a Persian term derived from the name of a Persian author and great mathematician Abu Abd Allah Jafar Mohammad ibn Musba al Khowarizmi. He was born on 780 A.D. in Baghdad. He worked on algebra, geometry and astronomy.

2.2

Properties of an Algorithm

Properties of an algorithm include the following criteria: 1) Input: An algorithm should have some inputs. Example: If we want to write an algorithm to check a given number is odd or even, we can take the number as its input. 2) Output: At least one output should be returned by the algorithm after the completion of the specific task based on the input(s) given. Example: If we want to write an algorithm to check a given number is odd or even, we can return 0 indicating that the number is odd and 1 indicating the number is even. 3) Definiteness: Every statement of the algorithm should be clear and unambiguous. Example: If we write a statement like iResult = iNumber % x or y, then this is not clear that what operation should be done. That statement should be either iResult = iNumber % x or iResult = iNumber % y. 4) Finiteness: No infinite loop should be allowed in an algorithm. Example: while(1<2) { iNumber = iNumber/2; } This type of loop should not be allowed in the algorithm as it leads an infinite loop (because 1 is always less than 2). 5) Effectiveness: Writing an algorithm is a priori process of actual implementation of the algorithm. So, a person should do analysis of algorithm in finite amount of time with pen and paper to judge the performance for giving the final version of the algorithm.

2.3

Algorithm Development Life Cycle

The life cycles of an algorithm includes the following phases: 1) Design Phase: Some of the techniques used in the design phase of an algorithm are (i) Brute-Force Method (ii) Divide-Conquer Method (iii) Greedy Method (iv) Dynamic Programming (v) Backtracking 2) Writing Phase: Basically algorithm is written in a modular approach. Actually a function written to solve a particular problem is itself an algorithm. To make clear each and every step in the algorithm, write comments wherever necessary.

3) Testing Phase: After writing an algorithm, it is necessary to check that the algorithm gives correct result for every valid input. 4) Analyzing Phase: Suppose for a problem P, ten correct algorithms are designed. Now which one is to be chosen that depends on the performance of them. Mainly this performance is judged based on how much time and space the algorithm takes. That is after testing phase of an algorithm it is required to analyze the time complexity and space complexity of it. In section 2.5 this is discussed in details.

2.4

Example of some Algorithms

Example 1: Write an algorithm to find the sum of two integer numbers. Inputs : Two numbers. Formula: sum of two numbers is sum = number1 + number2; input two numbers in number1 & number2 variables and add these two by addition operation and put the result in third variable sum. Output : Sum of these two input numbers. 1. 2. 3. 4. 5. 6. 7. 8. Algorithm fnSum(iNumber1,iNumber2) // Purpose : This algorithm finds the summation of two numbers. // Inputs : Two numbers are iNumber1 and iNumber2. // Output : iSum = Sum of iNumber1 and iNumber2.; the variables are labled with i as prefixed because //integer numbers are considered. { iSum = iNumber1 + iNumber2; //Find the summation. return(iSum); //Return the value. }//End of Algorithm

Example 2: Write an algorithm to find the roots of a quadratic equation Ax2 + Bx + C = 0. Inputs: Values of the coefficients A, B and C. Formula: X = (-B + (B^2 4*A*C)) / 2 * A and X = (-B - (B^2 4*A*C)) / 2 * A Output: Roots for real cases and for imaginary roots, output is Imaginary Root 1. Algorithm fnQuadraticEquation(fA,fB,fC) 2. // Purpose : This algorithms computes the roots of a quadratic equation. 3. // Input : fA,fB and fC are the variables to hold values of the coefficients A, B and C respectively. 4. // fA, fB and fC are real or float variables, and to represent float variables f is used as prefix. 5. // Output : Roots for real cases for imaginary roots, output is Imaginary Root 6. { 7. fY = fB2 4*fA*fC; 8. if(fY>0) //If two different roots exist. 9. { 10. fX1 = (-fB + sqrt(fY))/2*fA; //Compute the first root. 11. fX2 = (-fB sqrt(fY))/2*fA; //Compute the second root. 12. print(fX1,fX2); //Print the calculated values. 13. } 14. elseif(fY=0) //If only one root exists. 15. { 16. fX=-fB/2*fA; //Compute the only root. 17. print(fX); //Print the calculated value of the only root. 18. } 19. else //If no real solution exists. 20. print(Imaginary Root); 21. }//End of Algorithm

Example 3: Write an algorithm to find the largest element from a non-empty array. Inputs: Total number of element and the array. Formula: The logic; assume initially first element is the largest one. Check rest n-1 elements of the array with the first one one by one and if any one is found larger the first update the first the current larger and so Output: The largest element. 1. Algorithm fnFindLargestElement(iN, arrData[]) 2. // Purpose : This algorithms find the largest element from a non-empty array. 3. // Input : iN is total number of elements and arrData[] is the array . 4. // Output : iLargest which contains the largest element. 5. { 6. iLargest = arrData[0]; //Assume initially first element is the largest one. 7. for (iCounter = 1;iCounter<n;iCounter++) //Check rest n-1 elements of the array. 8. if(iLargest<arrData[iCounter]) 9. iLargest = arrData[iCounter]; 10. return(iLargest); //Return the value of iLargest. 11. }//End of Algorithm Example 4: Write an algorithm to find the factorial value of a given element. Inputs: The given element. Formula: N! = 1.2.3N Output: The calculated factorial value. 1. Algorithm fnFactorial(iNumber) 2. // Purpose : This algorithm finds the factorial value of a given number. 3. // Input : iNumber is the required number. 4. // Output : iFactorial which is factorial of iNumber. 5. { 6. iFactorial = 1; 7. for(iCounter = 2;iCounter<=iNumber;iCounter++) 8. iFactorial = iFactorial*iCounter; 9. return(iFactorial); 10. }//End of Algorithm

2.5

Complexity of Algorithms(Analyzing phase):

Generally the complexity of an algorithm is measured in two phases. When one measures the complexity of an algorithm by pen and paper, he/she can only predict the complexity which gives an idea of how much time or space this algorithm takes to finish its execution. This phase is called the priory analysis. After implementing the algorithm in computer, we get the actual time and space. This phase of analyzing the algorithm is called the posteriori analysis. Complexity of an algorithm can be of two types: (1) Time complexity: The analysis of algorithm for the prediction of computation time for execution of each and every instruction in the algorithm is called the time complexity of the algorithm. (2) Space Complexity: The analysis of algorithm for prediction of memory requirement to run the algorithm is called the space complexity of the algorithm. 2.5.1 Asymptotic notations There are some notations to determine the complexity of an algorithm in priory analysis. The term Asymptote means a line whose distance to a given curve is tends to zero. An asymptote may or may not intersect its associated curve. The notations are as follows:

Big Oh (O) notation: The function f(n) = O(g(n)) (read as f of n is big Oh of g of n) iff f(n) c*g(n) for all n, n n0 where c and n0 are positive constants. The function g(n) indicates the upper bound of f(n) and so g(n) should be as small as possible for which the statement f(n) = O(g(n)) is true. Example: Suppose Now we can write f(n) f(n) = 2n3 + 3n2 + n + 10 = 2n3 + 3n2 + n + 10 for all n 2

5n3 i.e. here c = 5,n0 = 2 and g(n) = n3.

Hence we can write f(n) = O(g(n)) = O(n3) See here the statements f(n) = O(n4) or f(n) = O(n5) and so on are also true. But we should not write so because the values of g(n) for the statements are not as small as possible for which the statement f(n) = (g(n)) is true. c*g(n) f(n) Time i

n0 n (Problem Size) Figure 2.1: Upper Bound of an algorithm (Big Oh (O) notation) Omega () notation: The function f(n) = (g(n)) (read as f of n is omega of g of n) iff f(n) c*g(n) for all n, n n0 where c and n0 are positive constants. The function g(n) is only a lower bound of f(n) and so g(n) should be as large as possible for which the statement f(n) = (g(n)) is true. = 2n3 + 3n2 + n + 10 = 2n3 + 3n2 + n + 10 2n3 for all n 1 3 i.e. here c = 2,n0 = 1 and g(n) = n . Example: Suppose Now we can write f(n) f(n) Hence we can write f(n) = (g(n)) = (n3) Observe that, for the above example the statements f(n) = (n2) or f(n) = (n) or f(n) = (1) are also correct. But we should not write so because the values of g(n) for the statements are not as large as possible for which the statement f(n) = (g(n)) is true.

f(n) c*g(n) Time

n0 n(Problem Size) Figure 2.2: Lower Bound of an algorithm (Omega () notation) Theta () notation: The function f(n) = (g(n)) (read as f of n is theta of g of n) iff c 1*g(n) f(n) c2*g(n) for all n, n n0 where c1,c2 and n0 are positive constants. The function g(n) is a tight bound of f(n) because g(n) is both upper and lower bound on f(n). Example: Suppose Now we can write, f(n) = 2n3 + 3n2 + n + 10 3 5n 2n3 + 3n2 + n + 10 2n3 for all n 2 i.e. here c1 = 5, c2 = 2, n0 = 2 and g(n) = n3. Hence we can write f(n) = (g(n)) = (n3) c1*g(n) f(n) c2*g(n) Time

n0 n (Problem Size) Figure 2.3: Tight bound of an algorithm (Theta () notation) Little oh (o) notation: The function f(n) =o(g(n)) (read as f of n is little oh of g of n) iff f(n) lim n g(n) =0

f(n) = 2n3 + 3n2 + n + 10 limn (2n3 + 3n2 + n + 10)/n4 = limn (2/n + 3/n2 + 1/n3 + 10/n4) =0 i.e. here g(n) = n4. Hence we can write f(n) = o(g(n)) = o(n4) Example: Suppose Now we can write Little omega () notation: The function f(n) = (g(n)) (read as f of n is little omega of g of n) iff g(n) lim =0 n f(n) Example: Suppose f(n) = 2n3 + 3n2 Now we can write limn n/(2n3 + 3n2) = limn (1/2n2 + 1/3n) =0 i.e. here g(n) = n. Hence we can write f(n) = (g(n)) = (n) During the execution of a program, estimating the upper bound of time (CPU time) that the program takes to finish its execution is the main factor. If it takes the less time than our estimation then the CPU performance will be improved. But if it takes more time than our calculation then it will cause the degradation of CPU performance. Now we know well that the Big Oh (O) notation gives the upper bound estimation and thats why we will discuss about big Oh (O) notation in details through out the book. 2.5.2 How can we estimate the time complexity in big Oh(O) notation? Assumptions: 1) All the statements take equal time for execution. 2) Each statement takes unit time to execute. Example 1: Calculate the time complexity for the following algorithm. 1. Algorithm fnSearch(arrData[],iN,iItem) 2. // Purpose : This algorithm searches a particular element in an array. 3. // Input : arrData[] is an array of iN(= n) integers. Search for the element iItem in the array. 4. // Output : The algorithm returns 1 for successful search and 0 for an unsuccessful search. 5. { 6. for(iCounter = 0;iCounter<iN;iCounter++) 7. if(arrData[iCounter]==iItem) 8. return 1; //Successful search. 9. return 0; //Unsuccessful search. 10. }//End of Algorithm Solution: Line 7 and line 8 take unit time to execute and line 6 takes n time to execute. So lines 6, 7 and 8 take 2n time for execution. Line 9 takes unit time to execute. Therefore, f(n) = TL6 * (TL7 + TL8) + TL9 = n(1+1) + 1 = 2n + 1 3n for all n 1 Now from the definition of big Oh(O) notation we can writef(n) = O(n) Example 2: Comment on the time complexity of the following algorithm. 1. Algorithm fnMatrixMultiplication(arrA,arrB,iN) 2. // arrA and arrB are two matrix each of the order nXn. Value of the variable iN = n.

3. { 4. for(iX=0;iX<iN;iX++) 5. { 6. for(iY=0;iY<iN;iY++) 7. { 8. arrC[iX][iY]=0; //arrC is the resultant matrix 9. for(iZ=0;iZ<iN;iZ++) 10. arrC[iX][iY] = arrC[iX][iY] + arrA[iX][iZ]*arrB[iZ][iY]; 11. } 12. } 13. //Print the contents of the resultant matrix. 14. for(iX=0;iX<iN;iX++ 15. for(iY=0;iY<iN;iY++) 16. print(arrC[iX][iY]); 17. }//End of Algorithm Solution: For calculating the time complexity of the above algorithm we can divide it into two segments. Segment 1 is a nested for loop containing line 4 to line12 and segment 2 is a nested for loop containing line14 to line 16. In segment 1: Line 10 takes unit time for execution. Line 9 takes n times for execution. Line 8 takes unit time for execution. Line 6 takes n times for execution. Line 4 takes n times for execution. Now segment 1 takes T1 = TL4 * (TL6 * (TL8 + TL9 * TL10)) = n*(n*(1+ n*1) 2 3 =n +n In Segment 2: Line 16 takes unit time to execute. Line 15 takes n times to execute. Line 14 takes n times to execute. So segment 2 takes T2 = TL14 * TL15 * TL16 =n*n*1 Therefore the total time f(n) = T1 + T2 = (n + n ) + n
3 2 2 3 2 3

= n + 2n 3n

for all n 1
3

Now from the definition of the big Oh (O) notation we can conclude f(n) = O(n ). Example 3: Calculate the time complexity for the following recurrence relation. T(n) = 2T(n-1) + 1 for n > 1 =1 for n = 1 Solution: T(n) = 2T(n-1) + 1 = 2[2T(n-2) + 1] + 1 2 = 2 T(n-2) + 2 + 1 = . = . k = 2 T(n-k) + . + 2 + 1 [up to k terms] =2
n-1

+ ..+ 2 + 2

[Let n-k = 1. So T(1) = 1]

= (2 1)/(2-1) =2 12
n n

for all n 1
n

Therefore, from the definition of the big Oh (O) notation we can conclude f(n) = O(2 ). Example 4: Calculate the time complexity for the following recurrence relation. T(n) = 2T(n/2) + n for n > 1 =1 for n = 1 Solution: T(n) = 2T(n/2) + n 2 = 2[2T(n/2 ) + n/2] + n = 2 T(n/2 ) + 2n = = k k = 2 T(n/2 ) + kn [up to k terms] = nT(1) + nlogn [ Let 2 = n. So, k = logn] = n + nlogn [ as T(1) = 1] 2nlogn for all n 2 Hence, from the definition of the big Oh (O) notation we can write f(n) = O(nlogn). 2.5.3 How can we improve the complexity of algorithms? Example 1: Write an algorithm to calculate the sum of first n natural numbers. Procedure 1: Inputs: iNumber which contains the value of n Output: iSum which contains the calculated sum 1. Algorithm Summation(iNumber) 2. { 3. iSum = 1; 4. for(iCounter = 2;iCounteriNumber;iCounter++) 5. iSum = iSum + iCounter; 6. return(iSum); 7. }//End of Algorithm Complexity: Line 3 takes unit time for execution. Line 4 takes n times for execution. Line 5 takes unit time for execution. Line 6 takes unit time for execution. Therefore f(n) = TL3 + TL4*TL5 + TL6 = 1 + n*1 + 1 = n + 2 2n for all n 2 So, f(n) = O(n) Procedure 2: The problem is S = 1+2+3+4+. + n = n(n+1)/2 Now we can directly compute the value of S without using any loop. Inputs: iNumber which contains the value of n Output: iSum which contains the calculated sum 1. Algo Summation(iNumber) 2. { 3. iSum = iNumber*(iNumber + 1)/2;
k 2 2

4.

return(iSum); 5. }//End of Algo Complexity: Line 3 takes unit time. Line 4 takes unit time. Therefore, f(n) = TL3 + TL4 =2 So, f(n) = O(1) i.e. the algorithm of procedure 2 takes constant time and obviously the second approach is better than the first one in respect of time complexity. Example 2: Write an algorithm to compute the value of x where n is integral power of 2. Procedure 1: Inputs: iX = the value of x and iN = the value of n. Output: iResult = the calculated result. 1. Algorithm Power(iX,iN) 2. { 3. iResult = 1; 4. for(iCounter = 1;iCounteriN;iCounter++) 5. iResult = iResult*iX; 6. return(iResult); 7. }//End of Algo Complexity: Line 3 takes unit time for execution. Line 4 takes n times for execution. Line 5 takes unit time for execution. Line 6 takes unit time for execution. Therefore, f(n) = TL3 + TL4*TL5 + TL6 = 1 + n*1 + 1 = n + 2 2n for all n 2 So, f(n) = O(n) Procedure 2: Suppose n=8. We can calculate the value I following way: iResult = x 2 iResult = iResult * iResult = x iResult = iResult * iResult = x
4 8 n

iResult = iResult * iResult = x i.e. after logn time we are getting the desired result. Inputs: iX = the value of x and iN = the value of n. Output: iResult = the calculated result. 1. Algo Power(iX,iN) 2. { 3. iResult = x; 4. for(iCounter = 1;iCounterlog(iN);iCounter++) 5. iResult = iResult*iResult; 6. return(iResult); 7. }//End of Algo Complexity: Line 3 takes unit time for execution. Line 4 takes logn times for execution. Line 5 takes unit time for execution. Line 6 takes unit time for execution.

Therefore,

f(n) = TL3 + TL4*TL5 + TL6 = 1 + (logn)*1 + 1 = logn + 2 4logn for all n 2 So, f(n) = O(logn). Hence we are getting better time complexity in second approach. In this section we examine the asymptotic behavior of polynomials in n. In particular, we will see that as n gets large, the term involving the highest power of n will dominate all the others. Therefore, the asymptotic behavior is determined by that term. Theorem: Consider a polynomial in n of the form

where

. Then

. . Since n is non-negative, a particular term will be . Recall too that we have .

Proof: Each of the terms in the summation is of the form negative only if

. Hence, for each term in the summation,

stipulated that the coefficient of the largest power of n is positive, i.e.,

Note that for integers

for

. Thus

Now the constants .

and

, such that for all

. Thus,

This property of the asymptotic behavior of polynomials is used extensively. In fact, whenever we have a function, which is a polynomial in n, we will immediately ``drop'' the less significant terms (i.e., terms involving powers of n which are less than m), as well as the leading coefficient, , to write .

2.5.4 Properties of Big Oh


In this section we examine some of the mathematical properties of big oh. In particular, suppose we know that f1(n) = O(g1(n)) and f2(n) = O(g2(n)). Theorem 1: If f1(n) = O(g1(n)) and f2(n) = O(g2(n)), then f1(n) + f2(n) = O(max(g1(n), g2(n))). Proof: From the definition of big O notation, there exist four positive constants n1, n2, c1 and c2 such that f1(n) c1*g1(n) for n n1 and f2(n) c2*g2(n) for n n2 Let n0 = max(n1,n2) and c0 = 2*max(c1,c2). Now, f1(n) + f2(n) c1*g1(n) + c2*g2(n) c0*(g1(n) + g2(n))/2 c0*max(g1(n), g2(n)) f1(n) + f2(n) = O(max(g1(n),g2(n))) for n n0

Thus,

Theorem 2: If f1(n) = O(g1(n)) and f2(n) = O(g2(n)), then f1(n)*f2(n) = O(g1(n)*g2(n)). Solution: By Definition, there exist four positive constants, n1,n2, c1 and c2 such that f1(n) c1*g1(n) for all n n1 and f2(n) c2*g2(n) for all n n2. Let n0 = max (n1, n2) and c0 = c1*c2. Consider the product f1(n)*f2(n) for n n0. Now, f1(n) + f2(n) (c1*g1(n)) * (c2*g2(n)) for n n0 c0*(g1(n)*g2(n)) Thus, f1(n)*f2(n) = O(g1(n)*g2(n)). Theorem 3: If f1(n) = O(g1(n)) and g2(n) is a function whose value is non-negative for integers n0, then f1(n)*f2(n) = O(g1(n)*g2(n)). Solution: By definition, there exist two positive constants n1 and c1 such that f1(n)c1*g1(n) for all nn1. Since g2(n) is never negative, f1(n)*g2(n) c1*g1(n)*g2(n) for all n n1 Thus, f1(n)*f2(n) = O(g1(n)*g2(n)) (Transitive Property) Theorem 4: If f(n)=O(g(n)) and g(n)=O(h(n)) then f(n)=O(h(n)). Solution: By definition, there exist four positive constants, n1, n2, c1 and c2 such that f(n) c1*g(n) for all n n1 and g(n) c2*h(n) for all n n2. Let n0 = max (n1, n2) and c0 = c1*c2. Then, f(n) c1*g(n) for all n n1 c1*(c2*h(n)) for all n n0 c0*h(n) Thus, f(n)=O(h(n)).

2.5.5 Rate of growth of Big O notation


Suppose there are two algorithms A and B. Now we say A is better than B in respect of time complexity if A takes less time to execute than B. Comparison between algorithms is generally done by some standard functions described in the following table. O(1) Constant time O(logn Logarithmic time ) O(n) Linear time c Polynomial time O(n ) O(c )
n

Exponential time

and their orders are: O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) Note that, time complexity O(1) of an algorithm does not mean that the algorithm takes unit time to execute rather it takes some constant time.

2.6

Some popular Mathematical Notations and Functions

1. Floor function ( ): This function takes the nearest lower integer value of a fractional number. e.g. floor(3/2) = 1.ss 2. Ceiling function ( ): This function takes the nearest upper integer value of a fractional number. e.g. ceiling(3/2) = 2. 3. Mod function (%): This function gives the remainder value of a division operation. e.g. 12 mod 5 = 2. 4. a0 + a1 + a2 + .. + an = (an-1 -1)/(a-1). 5. 1 + 2 + 3 + ..+ n = n(n+1)/2 6. 12 + 22 + 32 + .+ n2 = n(n+1)(2n+1)/6 7. Logax = k(logbx) where k is some constant.

2.7

Algorithmic Notation

This section describes the format that is used to write an algorithm throughout the book. As C is most popular language, we have followed most syntax of C. 2.7.1 Algorithm number: Each algorithm is assigned a unique number to identify a particular algorithm. For example Algorithm 10.2 denotes second algorithm in chapter 10. 2.7.2 Line number: In each algorithm we specify line numbers to analyze or describe it easily. 2.7.3 Comments: The statement after double slash (//) denotes a comment .At the beginning of each algorithm we have specified purpose of this algorithm, input(s) needed for this algorithm, output returned by this algorithm and some special comments if necessary. Each line of an algorithm may contain some comments to specify its purpose. 2.7.4 Variable names: The name of an integer variable starts with i, name of a float variable starts with f, name of a character variable starts with c, name of a pointer variable starts with ptr, name of an array starts with arr and name of a function or algorithm starts with fn. But in some algorithm we have not followed this syntax for simplicity. 2.7.4 Operator: We will use basically three types of operators 1. Assignment operator 2. Relational operator 3. Relational operator

2.7.4.1 Assignment operator: Our assignment operator is = like as C. For example iElement = arrData[3] assigns the value of arrData[3] to iElement.

2.7.4.2 Relational operator: These operators are used to compare two operands to check whether they are equal to each other or unequal or one is greater than the other. The following figure shows these operators along with their works. Operator Operation < Less than > Greater than <= Less than or equal to >= Greater than or equal to == Equal to != Not equal to 2.7.4.3 Logical operator: Logical operators are logical and denoted by &&, logical or denoted by || and logical not denoted by !. Their truth tables are described below: Logical and (&&) Exp1 0 0 1 1 Exp2 0 1 0 1 Result 0 0 0 1 Exp1 0 0 1 1 Logical or (||) Exp2 0 1 0 1 Result 0 1 1 1 Logical not(!) Exp1 0 1 Result 1 0

2.7.5 Input and Output: Data may be taken as input from the user by scan with the following form: scan(Variable names); Similarly to print some output print statement is used with the following form: print(statement and/or name of variables); 2.7.6 Control structures: Control statements are also similar to C language. Basically there are three types control structures: 1. Sequential: 2. Conditional 3. Repetitive 2.7.6.1 Sequential logic: In a sequential approach all the statements are executed in the same order as they are written. 2.7.6.2 Conditional logic: In this approach, based on some condition, the different sets of statements are executed. An if statement is a conditional control structure that executes a set of statements based upon some specified criteria. 7.6.1.1 Simple if statement: The syntax is if(conditions) { -------------Statements -------------} The logic of this structure is if the condition is true then the statements in the if block are executed otherwise the statements are skipped. 7.6.1.2 Simple else statement: The syntax is if(conditions) {

-------------Statement 1 -------------} else { -------------Statement 2 -------------} If the condition of if statement is true then statement 1 is executed otherwise statement 2 is executed. 7.6.1.3 Nested if statement: This structure has the form: if(condition 1) { -------------Statement 1 -------------} else if(condition 2) { -------------Statement 2 -------------} else if(condition n) { -------------Statement n -------------} else { -------------Statement n+1 -------------} Here only one block is executed based on the specified criteria. 7.7.6.3 Repetitive logic: Here different sets of statements are executed repeatedly for some time. Time of repetitions is called number of iteration. For this purpose we will use three kinds of loops: 1. for loop 2. while loop 3. do-while loop 2.7.6.3.1 for loop: The syntax is: for(initialize counters; test condition; increment counters) { ------------------Statements;

------------------} 2.7.6.3.2 while loop: The syntax is: while(conditions) { ------------------Statements; ------------------} do-while loop: The syntax is: do { ------------------Statements; ------------------} while(conditions);

2.7.6.3.3

2.8 MCQ Chapter 2


1. Example(s) of O(1) algorithms is (are): (a) printing a character to the screen (b) incrementing a variable (c) adding two numbers together (d) All of the above. 2. Example(s) of O(N) algorithms is (are): (a) initializing all of the elements in an one-dimensional array to zero (b) incrementing all the elements in an one-dimensional array (c) multiplying two numbers by performing successive addition operations (d) All of the above. 3. Example(s) of O(N2) algorithms is (are) (a) initializing all the elements in a two dimensional array to zero (b) printing out all the elements in a two dimensional array (c) searching for the smallest element in an unsorted two-dimensional array (d) All of the above. 4. Three algorithms do the same task. Algorithm 1 is O(N2), Algorithm 2 is O(N), and Algorithm 3 is O(Log2 N). Which algorithm should execute the fastest for large values of N? (a) O(N) (b)O(N) (c)O(log2 N) (d) None of these. 5. Which of the following algorithm should execute the slowest for large values of N? (a) O(N) (b) O(N2) (c) O(log2 N) (d) None of these. 6. What should never be found in the top level of a top-down design?

(a) Details (b) Coding (c) Decisions (d) None of these 7. Describe Process Program File in terms of Big-O, if N refers to the number of lines in the Program File (a) O(N) (b) O(1) (c) O(log2 N) (d) O(N2). 8. Describe Line Status of the Process Program- File in terms of Big-O, if N refers to the number of lines in the Program File (a) O(N) (b) O(1) (c) O(log2 N) (d) O(N2). 9. Which of these is the correct big-O expression for 1+2+3+...+n? a. O(log n) b. O(n) c. O(n log n) d. O(n) Which of the following formulas in big-O notation best represent the expression n+35n+6? a. O(n) b. O(n) c. O(n) d. O(42) What term is used to describe an O(n) algorithm. a. Constant b. Linear c. Logarithmic d. Quadratic Here is some code for an integer variable n:
while (n > 0) { n = n/10; // Use integer division }

10.

11.

12.

What is the worst-case time analysis for the above loop? a. O(1) b. O(log n) c. O(n) d. O(n) Express the formula (n - 2)*(n - 4) using big-O notation: a. O(1) b. O(n2) c. O(log n) d. O(n)

13.

e. None of the above 14. The running time of an algorithm T(n), where 'n' is the input size is given by T(n) = 8T(n/2) + qn, if n > 1 = p, if n = 1 where p, q are constants. The order of this algorithm is (a) n2 (b) nn (c) n3 (d) n

15. Consider the following two functions. f(n) = n3, if 0 :s; n < 10,000 n2, otherwise g (n) = n, if 0 :s; n < 100 n2+5n, otherwise Which of the following is/are true? (a) f(n) is O(n3) (b) g(n) is O(n3) (c) O(f(n)) is same as O(g(n)) (d) g(n) is O(n2) 16. The recurrence relation that arises in relation with the complexity of binary search is (a) T(n) = T(n/2) + k, where k is a constant (b) T(n) = 2T(n/2) + k, where k is a constant (c) T(n) = T(n/2) + log(n) (d) T(n) = T(n/2) + n 17. . The order of an algorithm that finds whether a given Boolean function of 'n' variables, produces a 1 is (a) constant (b) linear (c) logarithmic (d) exponential 18. The Ackermann's function (a) has quadratic time complexity (c) can't be solved iteratively (b) has exponential time complexity (d) has logarithmic time complexity

19. The running time of an algorithm is given by T(n) = T(n - 1)+ T(n - 2) - T(n - 3), if n > 3 = n, otherwise. The order of this algorithm is (a) n (b) log n (c) nn

(d) n2

20. What should be the relation between T(l), T(2) and T(3), so that the previous question, gives an algorithm whose order is constant? (a) T(l) + T(2) = T(3) (c) T(1) - T(3) = (2) (b) T(1) + T(3) = 2T(2) (d) T(1) + T(2) = T(3) 21. The running time T(n), where 'n' is the input size of a recursive algorithm is given as follows. T(n) = c + T(n - 1), if n > 1 d, if n = 1 The order of this algorithm is (a) n2 (b) n (c) n3 (d) nn 22. There are 4 different algorithms Al, A2 , A3 , A4 to solve a given problem with the order log(n), log(log(n)), nlog(n), n log(n) respectively. Which is the best algorithm? (a) Al (b) A2 (c) A4 (d) A3

23. The concept of order (Big 0) is important because (a) it can be used to decide the best algorithm that solves a given problem (b) it determines the maximum size of a problem that can be solved in a given system, in a given amount of time (c) it is the lower bound of the growth rate of the algorithm (d) none of the above 24. An algorithm is made up of 2 modules M1 and M2. If order of M1 is f (n) and M2 is g (n) then the order of the algorithm is (a) max (f (n) , g (n) ) (b) min (f (n) , g (n) ) (c) f(n) + g(n) (d) f(n) * g(n) 25. Let m, n be positive integers. Define Q(m,n) as Q(m, n) = 0, if m < n = Q(m - n, n) + p, if m n Then Q(m, 3) is (a div b, gives the quotient when a is divided by b) (a) a constant (b) p * (m mod 3) (c) p * (m div 3) (d) 3 * p

Solutions:
1. d 16. a 2. d 17. d 3. d 18. c 4. c 19. a 5. b 20. a 6. c 21. b 7. a 22. b 8. c 9. b 23. a,b 24. a 10. b 25. c 11. b 12. b 13. b 14. c 15.c, d

Excersies Chapter 2
1. Find (a) 4.3 , - 4.3 (b) 4.3 , - 4.3 2. Find (a) 50%6, 80%6 (b) -70%6, -45%10 3. Define Algorithm development life cycle. 4. Define Algorithm and design an algorithm to find out the total number of even and odd number in a group of 50 numbers. 5. Explain the different ways of analyzing algorithms. 6. What are the differences between bottom up and top down approach of programming? 7. What is structured and modular programming? Differentiate between them. 8. Solve the recurrent relation T(n) = T(n/2) + 1 T(1) = 1 9. Solve the recurrent relation T(n) = T(n/2) + n T(1) = 1 10. Solve the recurrent relation Xn = 2Xn-1 1, n>1 X1=2

(GATE 1988)

(GATE 1989)

11. Convert each time formula to the best possible big-O notation. Do not include any spurious constants in your big-O answer. Time Formula 10n 2n 2n + 10n . . . Big-O

3 times log (base 2) of n .

12. Write the simplest big-O expression to describe the number of operations required for the following algorithm:
for (i = 1; i < N; ++i) { ...statements that require exactly i operations... }

Chapter - 3 ARRAY
3.1 INTRODUCTION & DEFINITION

Depending upon the requirement of data and its structures needed to represent various kinds of problems and deriving out solutions through computer the Data Structure is classified into linear and non linear classes. The array supports linear representation of homogeneous data elements, which facilitates to put a group of same data items together and hence it allows to formation of a structure. An array is referred by two things one is index i.e. the location and the other is value in the location. In Programers view point an array is seemed to be formed in contiguous locations of computer memory but it is not true always in systems perspective. One of the major advantages of array is that to use in a program is very easy but the main difficulty lies in the fact as array is formed in primary memory of a computer thus it is volatile.

DEFINITION
An array is defined as a finite ordered set of homogeneous elements. Finite means there is a specific number of elements. Ordered means the elements of the array are indexed. Homogeneous means all the elements of the array must be of same type (e.g. int, float, char etc.).

3.2

DECLARATION OF ARRAY

3.2.1 Declaration: One dimensional array: int iarrMarks[100] // Declares array iarrMarks[] with 100 integers float farrNum[100] // Declares array farrNum[] with 100 floating point numbers. char carrName[100] // Declares array carrName[] with 100 characters In General case, consider an array as a[l1..u1] Obviously the above array contains (u1 l1 + 1) elements. In C, the lower index l1 for an array is 0. Two dimensional array: int iarrMatrix[20][30] //Declares two dimensional array with order 20X30 (i.e. 20 rows and 30 columns). Now consider a two dimensional array in general as a[l1..u1][l2..u2] Then the above array contains (u1 l1 + 1) rows and (u2 l2 + 1) columns. So the total number of elements in the array is (u1 l1 + 1) (u2 l2 + 1). N dimensional array: If we interpret the indices to be N-dimensional (i1, i2, i3, in) then it is called an N-dimensional array. In the N-dimensional array, if the array is declared as a[l 1u1][l2u2] [lnun] where li and ui are the lower bound and upper bound respectively of the ith dimension then The total number of elements = (u1-l1+1)(u2-l2+1)(un-ln+1) n = (ui-li+1) i=1

3.3

MEMORY REPRESENTATION OF ARRAY

There are two ways to store an array into memory-1) Row major ordering and 2) Column major ordering. They are described in this section. Here it is considered that each element takes only one byte to store its value. 3.3.1 Row major ordering: In row major ordering the rows of the array are stored first. Consider an one dimensional array a[l1..u1]. Suppose its base address (address of the first element) is . l1 l1+1 . i1 . u1

Then the address of the element a[i1] is + i1 l1. Now consider a 2-dimensional array a[0..3][0..3]. Its rows and elements in each row are depicted below. a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] a[2][0] a[2][1] a[2][2] a[3][0] a[3][1] a[3][2] Row 4 Row 3 Row 2 Row 1

For a general case suppose the declaration of a 2-dimensional array be a[l 1u1][l2u2] and the base address is . l2 l2+1 . i2 . u2 l1 l2 +1 . i1 -1 i1 . u1

Now to calculate the address of the element a[i1][i2], first traverse the i1-l1 rows (each row contains u2-l2+1 elements ) and then i2-l2 elements. Hence the address of a[i1][i2] = + (i1-l1)(u2-l2+1)+(i2-l2). Now consider a 3-dimensional array a[0..2][0..2][0..1]. a[0][0][0] a[0][0][1] a[0][1][0] a[0][1][1] a[0][2][0] a[0][2][1] a[1][0][0] a[1][0][1] a[1][1][0] a[1][1][1] a[1][2][0] a[1][2][1] a[2][0][0] a[2][0][1] a[2][1][0] a[2][1][1] a[2][2][0] a[2][2][1] Row 3 Row 2 Row 1 Page 3 Row 3 Row 2 Row 1 Page 2 Row 3 Row 2 Row 1 Page 1

If a declaration of a 3-dimensional array be a[l1u1][l2u2][l3u3] and the base address be then the address of the element a[i1][i2][i3] = +(i1-l1)(u2-l2+1)(u3-l3+1)+(i2-l2) (u3-l3+1)+(i3-l3) Hence for an N-dimensional array a[l1u1][l2u2]..[lnun], if the base address be (i.e. the address of a[l1] [l2]..[ln]), then the address of a[i1][i2]..[in] = + (i1-l1)(u2-l2+1)(u3-l3+1)( un-ln+1) + (i2-l2)(u3-l3+1)(u4-l4+1)( un-ln+1) + . + . + (in-ln) n = + j=1.n (ij-lj)aj with aj= (uk-lk+1) k=j+1 an= 1

3.3.2 Column major ordering: In this technique we store the columns of an array first. Consider an one dimensional array a[l1..u1]. Suppose its base address (address of the first element) is . l1 l1+1 . i1 . u1

Then the address of the element a[i1] is + i1 l1. Consider a 2-dimensional array a[0..3][0..2]. Its columns and elements in each column are depicted below. a[0][0] a[1][0] a[2][0] a[3][0] a[0][1] a[1][1] a[2][1] a[3][1] a[0][2] a[1][2] a[2][2] a[3][2] Column 3 Column 2 Column 1

Suppose the declaration of a 2-dimensional array be a[l1u1][l2u2] and the base address be . l2 l2+1 . i2-1 i2 . u2 l1 l2 +1 . i1 . u1 Now to calculate the address of a[i1][i2] first traverse the i2-l2 columns (each column has u1-l1+1 elements) and then i1 l1 elements. Hence the address of a[i1][i2] = + (i1-l1)+(i2-l2) (u1-l1+1).

Now consider a 3-dimensional array a[0..2][0..2][0..1] ; a[0][0][0] a[0][1][0] a[0][2][0] a[0][0][1] a[0][1][1] a[0][2][1] a[1][0][0] a[1][1][0] a[1][2][0] a[1][0][1] a[1][1][1] a[1][2][1] a[2][0][0] a[2][1][0] a[2][2][0] a[2][0][1] a[2][1][1] a[2][2][1] If a declaration of a 3-dimensional array be a[l1u1][l2u2][l3u3] and the base address be then the address of a[i1][i2][i3] = +(i1-l1) +(i2-l2)(u1-l1+1)+(i3-l3)(u2-l2+1)(u1-l1+1) Hence for an N-dimensional array a[l1u1][l2u2][lnun], if the base address be (i.e. the address of a[l1,l2,..,ln]) then the address of a[i1][i2][in] = + (i1-l1) + (i2-l2)(u1-l1+1) + . + . + (in-ln) (un-1-ln-1+1)(u1-l1+1) = + j=1.n (ij-lj)aj with 1 aj= (uk-lk+1) k=j-1 a1= 1 Column 2 Column 1 Page 3 Column 2 Column 1 Page 2 Column 2 Column 1 Page 1

3.4

INSERTION INTO ONE DIMENSIONAL ARRAY

Algorithm 3.1 describes the process of insertion into one dimensional array. Algorithm 3.1 1. Algorithm fnInsertion_into_ 1D_Array(arrData, n, k, item) 2. // Purpose : This algorithm inserts an element into one dimensional array. 3. // Input : arrData[] is an one dimensional array with n number of elements. Element item is to be inserted into the kth position in the array. 4. // Output : None. 5. { 6. for(i=n-1;i>=k-1;i--) 7. arrData [i+1]= arrData [i]; 8. arrData[k-1]=item; // Insert the item. 9. n=n+1; // Set size of the array. 10. }// End of Algorithm. Example: Consider an array arrData[] ={10,30,40,50}; Index Element 0 1 0 1 3 0 2 4 0 3 50

We have to insert 20 in to the 2nd position in the array. So, here nown=4, k=2, item=20 fnInsertion_into_ 1D_Array (arrData, 4, 2, 20) { for(i=3;i>=1;i--) i.e. the for loop will be executed for 3 times. i=3 arrData[3+1]= arrData[3]; Index 0 Element 1 0 i=2 arrData[2+1]= arrData[2]; Index 0 Element 1 0 i=1 arrData[1+1]= arrData[1]; Index 0 Element 1 0 Execution of for loop is stopped here. arrData[k-1]=item; or arrData[1]=20; Index 0 Element 1 0 n=n+1; or n=5;

1 3 0 1 3 0 1 3 0 1 2 0

2 4 0 2 4 0 2 3 0 2 3 0

3 5 0 3 4 0 3 4 0 3 4 0

4 50

4 50

4 50

4 50

} So finallyIndex Element 0 1 0 1 2 0 2 3 0 3 4 0 4 50

3.5

DELETION FROM ONE DIMENSIONAL ARRAY

Algorithm 3.2 describes the process of deletion from one dimensional array. Algorithm 3.2 1. Algorithm fnDeletion_from_1D_Array (arrData, n, k) 2. // Purpose : This algorithm deletes an element from one dimensional array. 3. // Input : arrData[] is an one dimensional array with n number of elements. Element item is to be deleted from the kth position of the array. 4. // Output : Deleted element item. 5. { 6. item= arrData[k-1]; //Item deleted. 7. for(i=k-1;i<n-1;i++) 8. arrData[i]= arrData[i+1]; 9. n=n-1; //Set size of the array. 10. return item; 11. }//End of Algorithm Example: Let array arrData[]={10,20,30,40,50}; 0 1 2 1 2 3 0 0 0 nd We have to delete the 2 element from the array. Therefore, n=5, k=2 fnDeletion_from_1D_Array(arrData,5,2) { item = arrData[1] = 20; for(i=1;i<5-1;i++) i.e. the for loop will be executed for 3 times. i=1 arrData[1]= arrData[1+1]; Index 0 1 2 Element 1 3 3 0 0 0 i=2 arrData[2]= arrData[2+1]; Index 0 1 2 Element 1 3 4 0 0 0 i=3 arrData[3]= arrData[3+1]; Index 0 1 2 Element 1 3 4 0 0 0 Execution of for loop stops here. n=n-1; or n=4; } Index Element 3 4 0 4 50

3 4 0 3 4 0 3 5 0

4 50

4 50

4 50

So finally Index Element 0 1 0 1 3 0 2 4 0 3 50

3.6

TRAVERSING ONE DIMENSIONAL ARRAY

Traversing means to access all the elements of the array (starting from the first element up to the last one). Algorithm 3.3 describes the process. Algorithm 3.3 1. Algorithm fnTraverse_1D_Array(arrData, n) 2. // Purpose : This algorithm prints all the elements of a one dimensional array. 3. // Input: arrData[] is an one dimensional array with n number of elements. 4. // Output : None. 5. { 6. for(i=0;i<n;i++) 7. print arrData[i]; 8. }//End of Algorithm

3.7

MERGING TWO ONE DIMENSIONAL ARRAYS

Merging means combining the elements of two arrays into a third array. The general technique is first copy all the elements of the first array into third array and then copy the elements of the second array. If you want to get a sorted array then you can sort the resultant third matrix. Another approach is sorting while merging. For this technique merging is done of two sorted arrays. This technique is described in chapter 12 (Merge sort) in details. Here we will discuss the first approach of merging i.e. we are bothered about whether the resultant array is sorted or not. Suppose we have to merge the elements of the arrays arrA and arrB into the resultant array arrC. First Array Second Array Third Array arrA[0] arrA[1] arrA[2] arrA[3] arrA[4] 10 55 45 30 20 arrB[0] arrB[1] arrB[2] arrB[3] 15 12 27 24 arrC[0] arrC[1] arrC[2] arrC[3] arrC[4] arrC[5] arrC[6] arrC[7] arrC[8] 10 55 45 30 20 15 12 27 24

Algorithm 3.4 describes the procedure. Algorithm 3.4 1. Algorithm fnMerge(arrA, arrB, arrC, AN, BN) 2. // Purpose : This algorithm merges two one dimensional arrays. 3. // Input : arrA and arrB are two one imensional arrays and contain AN and BN elements respectively. Elements of arrA and arrB are copied into arrC. 4. // Output : None. 5. {

6. //First copy the elements of arrA into arrC. 7. for(i=0;i<AN;i++) 8. arrC[i] = arrA[i]; 9. //Now copy the elements of arrB into arrC. 10. for(i=0;i<BN;i++) 11. arrC[AN+i] = arrC[i]; 12. }//End of Algorithm

3.8

MATRIX ADDITION
A= 9 6 2 5 B= 8 1 4 7

Consider two matrices A and B.

Then their addition produces resultant matrix C as C= 9+8 6+1 2+4 5+7 = 17 7 6 12

Algorithm 3.5 describes the matrix addition procedure. Algorithm 3.5 1. Algorithm fnMatrix_Addition(arrA,arrB,arrC,iN,iM) 2. // Purpose : This algorithm performs the addition of two matrices. 3. // Input : arrA and arrB are two iN X iM matrices. arrC is resultant matrix. 4. // Output : None. 5. { 6. for(i = 0;i<iN;i++) 7. for(j=0;j<iM;j++) 8. arrC[i][j] = arrA[i][j] + arrB[i][j]; 9. }// End of Algorithm

3.9

SPARSE MATRIX

Matrices with a relatively high proportion of zeros are called sparse matrices. If the matrix is sparse we must consider an alternate way of representing it rather than the normal row major or column major arrangement. This is because if majority of elements of the matrix are 0 then an alternative through which we can store only the non-zero elements and keep intact the functionality of the matrix can save a lot of memory space. Example of a sparse matrix is: 0 1 2 3 4 0 0 0 3 0 0 1 0 0 0 0 1 4 2 0 0 0 0 0 3 0 0 0 0 0 4 0 0 0 1 0 0 5 0 0 0 0 0 6 0 8 0 0 0 7 0 0 4 0 0 0 8 0 0 0 0 1 9 15 0 0 0 0

5 6 7 8

0 0 0 0

0 0 0 0

2 0 0 0 0

0 0 9 0

0 0 0 0

0 3 1 0 0

0 0 0 0

0 0 0 -2

0 0 0 0

0 0 0 0

The above 9 X 10 sparse matrix takes 90 memory spaces to save it into memory according to row major or column major ordering. A common way of representing non-zero elements of a sparse matrix is the 3-tuple forms. In this technique the above matrix looks like: int arrSpmtx[12][3]={ 9 10 11 // 9X10 matrix having 11 non-zero elements. 0 9 15 1 6 -8 2 0 -3 2 7 40 3 4 10 4 1 14 4 8 -1 5 2 20 6 5 31 7 3 -9 8 7 -2 }; The first row of the matrix arrSpmtx stores the order of the original matrix and number of nonzero elements. The next rows store the values of the nonzero elements and their positions in the original matrix. For the given example 12X3=36 memory spaces are required to store the whole matrix i.e. (90-36) = 54 memory spaces can be saved. Algorithm to create a 3-tuple of a given matrix Algorithm 3.6 1. Algorithm fnSparse_Matrix_Using_3tuple(arrOriginal, iRow, iColumn, arrSpmtx) 2. // Purpose : This algorithm creates a 3-tuple of a given matrix. 3. // Input : arrOriginal is the original matrix with order iRow X iColumn and arrSpmtx is its 3-tuple form. 4. // Output : None. 5. { 6. Index = 0; 7. iNon_Zero_Elements = fnCount_Non_Zero_Elements(arrOriginal, iRow, iColumn); 8. arrSpmtx[Index][0] = iRow; 9. arrSpmtx[Index][1] = iColumn; //Set first row 10. arrSpmtx[Index][2] = iNon_Zero_Elements; 11. Index++; 12. for(i=0;i<iRow;i++) 13. for(j=0;j<iColumn;j++) 14. if(arrOriginal[i][j] != 0) 15. { 16. arrSpmtx[Index][0] = i; 17. arrSpmtx[Index][1] = j; //Set next rows 18. arrSpmtx[Index][2] = arrOriginal[i][j]; 19. Index++; 20. }

21. }//End of algorithm Algorithm 3.7 1. Algorithm fnCount_Non_Zero_Elements(arrOriginal, iRow, iColumn) 2. // Purpose : This algorithm counts number of non-zero elements in a matrix. 3. // Input : arrOriginal matrix with order iRow X iColumn. 4. // Output : Returns number of non-zero elements. 5. { 6. iNon_Zero_Elements = 0; 7. for(i=0;i<iRow;i++) 8. for(j=0;j<iColumn;j++) 9. if(arrOriginal[i][j] != 0) 10. iNon_Zero_Elements++; 11. return iNon_Zero_Elements; 12. }// End of algorithm Two general types of n-square sparse matrices are triangular matrix and tri-diagonal matrix. Triangular matrix: In a matrix where all entries on or below (or on or above) the main diagonal are non-zero is called a lower (upper) triangular matrix. 15 -8 40 10 -3 14 31 20 -1 -9 Lower triangular matrix 31 -9 14 40 15 Upper triangular matrix 20 10 -1 -3 -8

Lower triangular matrix: Suppose we want to place in memory the lower triangular array a[][]. Clearly it would be wasteful to store those entries above the main diagonal of a[][], since we know they are all zero; hence we store only the other entries of a[][] in an one dimensional array b[]. b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9] b[10] 15 -8 40 10 -3 14 31 20 -1 -9 a[1][1] a[2][1] a[2][2] a[3][1] a[3][2] a[3][3] a[4][1] a[4][2] a[4][3] a[4][4] Observe first that b[] will contain only1 + 2 + 3 + + n = n(n+1)/2 elements, which is about half as many elements as a 2-dimensional n X n array. Since we will require the value of a[j][k] in our programs, we will want the formula that gives us the integer x in terms of j and k whereb[x] = a[j][k] Observe that x represents the no of elements in the list up to and including a[j][k]. Now there are1 + 2 + 3 + .. + (j-1) = j(j-1)/2 elements in the rows above a[j][k], and there are k elements in the row j up to and including a[j][k]. Accordingly, x = j(j-1)/2 + k yields the index that access the value a[j][k] from the linear array b[]. Algorithm 3.8 1. Algorithm fna_to_b(a,b,n)

2. // Purpose : This algorithm stores the elements of a lower triangular matrix to a one dimensional array. 3. // Input : The non-zero values of the n X n lower triangular matrix a is to be stored into the onedimensional array b[]. 4. // Output : None. 5. { 6. for(j=1;j<=n;j++) 7. { 8. for(k=1;k<=j;k++) 9. { 10. x=j(j-1)/2 + k; 11. b[x] = a[j][k]; 12. } 13. } 14. }//End of algorithm

Algorithm 3.9 1. Algorithm fnb_to_a(a,b,n) 2. // Purpose : This algorithm restores the elements of a lower triangular matrix. 3. // Input : The elements of the n X n lower triangular matrix a is to be restored from the onedimensional array b[]. 4. // Output : None. 5. // Comments : Restore the nonzero elements stored into the one-dimensional array b[] to the n X n lower triangular matrix a[][] and fills the other entries of the lower triangular matrix a[][] by zeros. 6. { 7. i=1; 8. for(j=1;j<=n; j++) 9. { 10. for(k=1;k<=j; k++) 11. { 12. a[j][k] = b[i]; // stores the non-zero element 13. i++; 14. } 15. for(k=j+1;k<=n; k++) //stores the zero values 16. a[j][k] = 0; 17. } 18. }//End of algorithm

3.10 POLYNOMIAL REPRESENTATION


A polynomial can be represented using array. Suppose we have an integer array of 10 elements: int arrPoly[10]; Using the above array, the following polynomial is to be stored. 2A7 + 10A5+8A2+ 15A + 21 Consider a general term of a polynomial as CAE. The procedure is to store the coefficient C in the index E of the array. Thus the array representation of the given polynomial looks like this: Index 0 Coefficient 2 1 1 1 5 2 3 4 5 8 0 0 1 0 6 7 8 9 0 2 0 0

As array is static in nature, so this process to store a polynomial is not an efficient one. The efficient way is to store a polynomial in a linked list, which is described in details in chapter 8.

3.11 MCQ Chapter 3


1. Structured data type made up of finite collection of ordered elements, all of which is of same data type is: (a) record (b) array (c) file (d) None of these 2. Elements of array are accessed by (a) accessing function in built-in data structure (b) mathematical function (c) index (d) None of these. 3. Array is (a) Linear data structure (b) non-linear data structure (c) Complex data structure (d) None of these. 4. Row-major order in two dimensional array refers to (a) all elements of a row are stored in memory in sequence followed by next row in sequence and so on. (b) all elements of a row are stored in memory in sequence followed by next column in sequence and so on. (c) all elements of a column are stored in memory in sequence followed by next column in sequence and so on. (d) None of these 5. Suppose that a two - dimensional arraydeclared ac; "char a [5][6]," is internally stored in contiguous memory locations starting from the locations "1000" in a column-major manner. The address where a [i] U] would be stored is (a) 1000+i+j*5 (b) 1000+i*4+j (c) 1000 + i + j * 6 (d) 1000 + i * 6 + j

6. A matrix. a, is called lower triangular if and only if for all j > i aij = O. If such a matrix is mapped to Idimensional matrix A then it could be mapped to the following index of A . (a) 1/2 * i (i + 1) + j (b) i + j (c) i (i + 1) + j (d) None of the above 7. In a compact single dimensional array representation for lower triangular matrices (i.e., an the elements above the diagonal are zero) of size n x n, nonzero elements (i.e., of the lower triangle) of each row are stored one after another. starting from the first row, the index cl: the (i,jih element of the lower triangular matrix in this representation is: (GATE-1994) (a) i + j (b) i + j - 1 (c) j + i (i - 1)/2 (d) i + j (j - 1)/2 8. Let A = (aij) be an n-rowed square matrix and /12 be the matrix obtained by interchanging the first and second rows of the n-rowed Identity matrix. Then All2 is such that its first:

(a) row is the same as its second row (b) row is same as the second row of A (c) column is the same as the second column of A (d) row is all zero

(GATE-1997)

9. Let A be a two dimensional array declared as follows: A: array [1..10] [1..15] of integer; Assuming that each integer takes one memory location the array is stored in row-major order and the first element of the array is stored at location. 100, what is the address of the element A [l][J] ? (GATE-1998) (a) 15i + j + 84 (b) 15j + i + 84 (c) Wi + j + 89 (d) 10j + i + 89 10. The information about an array that is used in a program will be stored in (a) symbol table (b) activation record (c) system table (d) dope vector 11. Which of the following expressions accesses the (i,j)lh entry of a (m x n) matrix stored in column major form? (a) n x (i-l) + j (b) m x(j -1) + i (c) m x (n-j) + j (d) n x (m-i) + j 12. Sparse matrices have (a) many zero entries (c) higher dimension (b) many non-zero entries (d) none of the above

Solutions:
1. b 2. c 3. a 4. a 5. a 6. d 7. c 8. c 9. a 10. a 11. a 12. a

3.12 Exercise Chapter 3


1. Define an array. How can an array be declared? 2. What is row major and column major ordering of an array? Explain with a suitable example. 3. Write an algorithm to find out the maximum and the 2nd maximum number from an array of integers. 4. Wtite a C function to find out whether an element aij in an array A[I,J} such that aij is the greatest value in ith row and smallest value in jth column. What is the time complexity of your function? 5. Write a C program to find out whether a matrix is symmetric or not. 6. Consider two single dimention arrays of size 20 and 30 respectively. Write an algorithm tofind out the elements which are common to both the arrays. 7. An integer array is declared as A[I,J,K]. The starting address of this array is 1000. Considering integer size is of 4 bytes, find out the location of A[i,j,k] in column major fasion. 8. An array A contains 25 positive integers. Write an algorithm which will find out all pairs of elements whose sum is 30. 9. An array A contains 25 positive integers. Write an algorithm which will find out the number of odd and even numbers in that array. 10. Write a C program which will input an integer array of various sizes and compute the average value of the elements of that array.

Chapter - 4 STRUCTURE AND POINTER


4.1 DEFINITION OF STRUCTURE
We have seen in chapter 3 that an array can be used to store the homogeneous data i.e. the data of same data types (e.g. int, float, char etc.). But using a structure we can create a user defined data type consisting of logically related data items of different data types. The general format of a structure definition is as follows: struct structure_name { ---------------------structure elements; ---------------------};

4.2

BASIC OF POINTER

Consider the following statement: int iData = 10; When compiler compiles the above statement, it allocates a memory space from the heap (available memories) to store the variable iData like the following: Name of the variable: Value of the variable: Address of the variable: iData 10 2020

Now we can access the value 10 by two ways: 1. By name and 2. By address. To store the address of iData into another variable, we need a pointer variable and the declaration is: int *iptr1; //Signifies that value at address iptr1 is some integer value. char *cptr1; //Signifies that value at address cptr1 is some character value. For pointer operations, two operators are available 1) * (value at) 2) &(address of). Suppose we want to store the address of iData into iptr1. The declaration is: iptr1 = &iData;

Name of the variable: Value of the variable:

iptr1 2020

Address of the variable: 4010 Now we can print the value 10 in two different ways: printf(%d,iData); printf(%d,*iptr1);

4.3

STRUCTURE AND POINTER

In the previous section we have seen a pointer pointing to an int or a pointer pointing to a char. Similarly we can have a pointer pointing to a struct. Consider the following program code: struct date { int dd; int mm; int yy; }; main() { struct date d1={06,04,2008}; //Declare d1 as structure variable. struct date *d2; //Declare d2 as a pointer to a structure. d2 = &d1; printf(%d/%d/%d,d1.dd,d1.mm,d1.yy); printf(%d/%d/%d,d2->dd,d2->mm,d2->yy); } Name of the variable: Value of the variable: Address of the variable: 4050 d1.dd 06 4052 d1.mm 04 d1.yy 2008 4054 d2 4050 8020

4.4

PASSING STRUCTURE TO FUNCTIONS


struct date { int dd; int mm; int yy; }; main() { struct date d1; //Declare d1 as structure variable. fnGetDate(&d1); //Pass the structure by its address. fnShowDate(&d1); //Pass the structure by its address. } void fnGetDate( struct date *d2) { *d2->dd = 06; *d2->mm = 04;

*d2->yy = 2008; } void fnShowDate( struct date *d2) { printf(%d/%d/%d,d2->dd,d2->mm,d2->yy); }

4.5

SELF REFERENTIAL STRUCTURE

Consider the following structure declaration: struct node { int iData; struct node *ptrNext; }; Notice that here the structure element ptrNext contains the address of a node similar to itself. Such type of structure is called self referential structure.

4.6

DYNAMIC MEMORY ALLOCATION

The dynamic or run time memory allocation helps us to make efficient use of by allocating the required amount of memory whenever is needed. C provides the following dynamic allocation and de-allocation functions: (I) malloc() (II) calloc() (III) realloc() (IV) free() (I) malloc(): The general syntax of malloc() function is: (data_type *) malloc(size of one block * number of blocks) Suppose we want to allocate the memory space for previously defined structure node in run time using malloc() function. Then the statement is: struct node *ptrNode = (struct node*)malloc(sizeof(struct node)); (II) calloc():The general syntax of calloc() function is: (data_type *) calloc(number of blocks , size of one block) Suppose we want to allocate the memory space for the structure node using calloc() function. Then the statement is: struct node *ptrNode = (struct node*)calloc(sizeof(struct node)); (III) realloc(): In some situation, the previously allocated memory is insufficient to run the correct application. It is also possible that the amount of memory allocated is large enough than the required memory. In both the cases it is necessary to reallocate the memory spaces and this can be done using realloc() function. The general syntax of realloc() function is: (data_type *) realloc(ptr, new size) ptr is a pointer holding the starting address of the allocated memory block. Suppose now we want to allocate 2 block of node in the pointer variable ptrNode. Then the statement is: ptrNode = (struct node*)realloc(ptrNode, 2* sizeof(struct node)); (IV) free(): Now to de-allocate the memory spaces we can use the function free() and the declaration is: free(name of pointer variable);

4.7

MCQ Chapter 4
1. Dynamic memory allocation use

a) Calloc b) Malloc c) Free d) all of these 2. Self referential structure refers a) own instance b) own address c) own variables d) none of these 3. Default allocation of variables in malloc() is equal to a) zero b) garbage c) any ve value d) any +ve value 4. Default allocation of variables in calloc() is equal to a) zero b) garbage c) any ve value d) any +ve value 5. A pointer contains a) address of other variables b) address of other pointers c) both a) and b) d) none of these 6. when compiled a structural code changes to a) Executable code b) Non executable code c) Assignment code d) None of these 7. A record structure can be represented as a) Hierarchical structure b) Canonical structure c) Linear structure d) All of these 8. Dynamic memory allocation is used to a) For program efficiency b) For space management c) For time management d) All of these 9. Advantage of using structure is a) To use logically related data items but of different datatypes. b) To represent the records in a hierarchical manner c) To create an user defined data type d) None of these 10. In C, size of a structure variable depend on a) The variable of the maximum size used in the structure b) The numbers of variables used in the structure c) Both a) and b) d) None of these

Solutions:
1. d 2. a 3. b 4. a 5. c 6. c 7. d 8. b 9. a 10. b

4.8 Exercises Chapter 4


1. What is a Structure? What is a Pointer? 2. How can a structure be passed by a pointer? 3. What is dynamic memory allocation? what is its advantages? 4. What is self refferntial structure? Discuss with an example. 5. How can a structure be passed to a function? 6. The following is a list of entries, with level numbers, in a file of employee record: 1. Employee(200) 2. Name 3. Last 4. First 5. Middle 6. Address 7. Street 8. Area 9. City 10. State 11. Country 12. Pin Code 13. Age 14. Salary 15. Dependents a) Draw the corresponding hierarchical structure. b) Which of the items are elementary items? Describe and write down a record in C to implement this structural representation of data.

Chapter 5 STRINGS
5.1 Definition:
Let be an alphabet, a non-empty finite set. Elements of are called symbols or characters. A string (or word) over is any finite sequence of characters from . For example, if = {0, 1}, then 0101 is a string over . The length of a string is the number of characters in the string (the length of the sequence) and can be any nonnegative integer. The empty string is the unique string over of length 0, and is denoted or . The set of all strings over of length n is denoted n. For example, if = {0, 1}, then 2 = {00, 01, 10, 11}. Note that 0 = {} for any alphabet . The set of all strings over of any length is the Kleene closure of and is denoted as *. In terms of n,

For example, if = {0, 1}, * = {, 0, 1, 00, 01, 10, 11, 000, 001, 010, 011, }. Although * itself is countably infinite, all elements of * have finite length. A set of strings over (i.e. any subset of *) is called a formal language over . For example, if = {0, 1}, the set of strings with an even number of zeros ({, 1, 00, 11, 001, 010, 100, 111, 0000, 0011, 0101, 0110, 1001, 1010, 1100, 1111, }) is a formal language over .

5.2 Concatenation and substrings


Concatenation is an important binary operation on *. For any two strings s and t in *, their concatenation is defined as the sequence of characters in s followed by the sequence of characters in t, and is denoted st. For example, if = {a, b, , z}, s = data, and t = structure, then st =datastructure and ts = structuredata. String concatenation is an associative, but non-commutative operation. The empty string serves as the identity element; for any string s, s = s = s. Therefore, the set * and the concatenation operation form a monoid, the

free monoid generated by . In addition, the length function defines a monoid homomorphism from * to the non-negative integers. A string s is said to be a substring or factor of t if there exist (possibly empty) strings u and v such that t = usv. The relation "is a substring of" defines a partial order on *, the least element of which is the empty string.

5.3 String operations


A number of additional operations on strings commonly occur in Data Structure. They are explained below:

5.3.1 Alphabet of a string


The alphabet of a string is a list of all of the letters that occur in a particular string. If s is a string, its alphabet is denoted by Alph(s)

5.3.2 String substitution


Let L be a language, and let be its alphabet. A string substitution or simply a substitution is a mapping f that maps letters in to languages (possibly in a different alphabet). Thus, for example, given a letter a , one has f(a) = La where is some language whose alphabet is . This mapping may be extended to strings as f() = for the empty string , and f(sa) = f(s)f(a) for nonempty string . String substitution may be extended to the entire language as

An example of string substitution occurs in regular languages, which are closed under string substitution. That is, if the letters of a regular language are substituted by other regular languages, the result is still a regular language.

5.3.3 String homomorphism


A string homomorphism is a string substitution such that each letter is replaced by a single string. That is, f(a) = s, where s is a string, for each letter a. String homomorphisms are homomorphisms, preserving the binary operation of string concatenation. Given a language L, the set f(L) is called the homomorphic image of L. The inverse homomorphic image of a string s is defined as

f-1(s) = { w | f(w) = s}
while the inverse homomorphic image of a language L is defined as

f-1(L) = { s | f(s) L}
Note that, in general, f(f-1(L)) L, while one does have

f(f-1(L))

L and L

f-1(f(L))

for any language L. Simple single-letter substitution ciphers are examples of string homomorphisms.

5.3.4 String projection


If s is a string, and is an alphabet, the string projection of s is the string that results by removing all letters which are not in . It is written as . It is formally defined by removal of letters from the right hand side:

(s) = = (t) = (t)

if s = , the empty string if s = ta and a

if s = ta and a

Here denotes the empty string. The projection of a string is essentially the same as a projection in relational algebra. String projection may be promoted to the projection of a language. Given a formal language L, its projection is given by

(L) = { (s) | s L} 5.3.5 String Right quotient


The right quotient of a letter a from a string s is the truncation of the letter a in the string s, from the right hand side. It is denoted as s / a. If the string does not have a on the right hand side, the result is the empty string. Thus:

(sa) / b

=s =

if a = b if a b

The quotient of the empty string may be taken: / a = Similarly, given a subset S M of a monoid M, one may define the quotient subset as

S / a = { s M | sa S}
Left quotients may be defined similarly, with operations taking place on the left of a string.

5.3.6 String Syntactic relation


The right quotient of a subset S relation of S. It is given by M of a monoid M defines an equivalence relation, called the right syntactic

~s = { (s, t) M x M | S / s = S / t }

The relation is clearly of finite index (has a finite number of equivalence classes) if and only if the family right quotients is finite; that is, if {S / m | m M } is finite. In this case, S is a recognizable language, that is, a language that can be recognized by a finite state automaton. This is discussed in greater detail in the article on syntactic monoids.

5.3.7 String Right cancellation


The right cancellation of a letter a from a string s is the removal of the first occurrence of the letter a in the string s, starting from the right hand side. It is denoted as s a and is recursively defined as

(sa) b

=s

if a=b

= ( s b )a if a b
The empty string is always cancellable: a = Clearly, right cancellation and projection commute:

(L) a = (L) ( s a ) 5.3.8 String Prefixes


The prefixes of a string is the set of all prefixes to a string, with respect to a given language:

Pref L(s) = { t | s = tu for u L }


The prefix closure of a language is

A language is called prefix closed if Pref (s) = L. Clearly, the prefix closure operator is idempotent:

Pref (Pref (L)) = Pref (L)


The prefix relation is a binary relation s t such that if and only if s Pref L(t).

Prefix grammars generate languages that are prefix-closed.

5.4 String Topology


Strings admit the following interpretation as nodes on a graph: Fixed length strings can be viewed as nodes on a hypercube; Variable length strings (of finite length) can be viewed as nodes on the k-ary tree, where k is the number of symbols in ; Infinite strings can be viewed as infinite paths on the k-ary tree.

The natural topology on the set of fixed length strings or variable length strings is the discrete topology, but the natural topology on the set of infinite strings is the limit topology, viewing the set of infinite strings as the inverse limit of the sets of finite strings. This is the construction used for the p-adic numbers and some constructions of the Cantor set, and yields the same topology.

5.5 String datatypes


A string datatype is a datatype modeled on the idea of a formal string. Strings are such an important and useful datatype that they are implemented in nearly every programming language. In some languages they are available as primitive types and in others as composite types. The syntax of most high-level programming languages allows for a string, usually quoted in some way, to represent an instance of a string datatype; such a meta-string is called a literal or string literal.

5.6 String length


Although formal strings can have an arbitrary (but finite) length, the length of strings in real languages is often constrained to an artificial maximum. In general, there are two types of string datatypes: fixed length strings which have a fixed maximum length and which use the same amount of memory whether this maximum is reached or not, and variable length strings whose length is not arbitrarily fixed and which use varying amounts of memory depending on their actual size. Most strings in modern programming languages are variable length strings. Despite the name, even variable length strings are limited in length; although, generally, the limit depends only on the amount of memory available.

5.7 String Representations


Representations of strings depend heavily on the choice of character repertoire and the method of character encoding. Older string implementations were designed to work with repertoire and encoding defined by ASCII, or more recent extensions like the ISO 8859 series. Modern implementations often use the extensive repertoire defined by Unicode along with a variety of complex encodings such as UTF-8 and UTF-16. Most string implementations are very similar to variable-length arrays with the entries storing the character codes of corresponding characters. The principal difference is that, with certain encodings, a single logical character may take up more than one entry in the array. This happens for example with UTF-8, where single characters can take anywhere from one to four bytes. In these cases, the logical length of the string differs from the logical length of the array. The length of a string can be stored implicitly by using a special terminating character; often this is the null character having value zero, a convention used and perpetuated by the popular C programming language[1]. Hence, this representation is commonly referred to as C string. The length of a string can also be stored explicitly, for example by prefixing the string with the length as a byte value a convention used in Pascal; consequently some people call it a P-string. In terminated strings, the terminating code is not an allowable character in any string. The term bytestring usually indicates to strings of bytes rather than bits or the wider concept of characters, which may take more space than a byte that are not terminated in this way, and in bytes may take any value.

Here is an example of a null-terminated string stored in a 10-byte buffer, along with its ASCII representation: F R A N K NUL k 4 52 41 4E 4B 00 6 e f w

6B 66 66 77

The length of a string in the above example is 5 characters, but it occupies 6 bytes. Characters after the terminator do not form part of the representation; they may be either part of another string or just garbage. (Strings of this form are sometimes called ASCIZ strings, after the original assembly language directive used to declare them.)

Here is the equivalent (old style) Pascal string stored in a 10-byte buffer, along with its ASCII representation: F R A N K k e f w

length

05

46 52 41 4E 4B 6B 66 66 77

Both character termination and length codes limit strings: for example, C character arrays that contain Nul characters cannot be handled directly by C string library functions: strings using a length code are limited to the maximum value of the length code. Both of these limitations can be overcome by clever programming, of course, but such workarounds are by definition not standard. Historically, rough equivalents of the C termination method appear in both hardware and software. For example "data processing" machines like the IBM 1401 used a special word mark bit to delimit strings at the left, where the operation would start at the right. This meant that while the IBM 1401 had a seven-bit word in "reality", almost no-one ever thought to use this as a feature, and override the assignment of the seventh bit to (for example) handle ASCII codes. It is possible to create data structures and functions that manipulate them that do not have the problems associated with character termination and can in principle overcome length code bounds. It is also possible to optimize the string represented using techniques from run length encoding (replacing repeated characters by the character value and a length) and Hamming encoding. While these representations are common, others are possible. Using ropes makes certain string operations, such as insertions, deletions, and concatenations more efficient.

5.8 String as Vectors

While character strings are very common uses of strings, a string in computer science may refer generically to any vector of homogenously typed data. A string of bits or bytes, for example, may be used to represent data retrieved from a communications medium. This data may or may not be represented by a string-specific datatype, depending on the needs of the application, the desire of the programmer, and the capabilities of the programming language being used.

5.9 String processing algorithms


There are many algorithms for processing strings, each with various trade-offs. Some categories of algorithms include string searching algorithms for finding a given substring or pattern; string manipulation algorithms; sorting algorithms; regular expression algorithms; and parsing a string.

Advanced string algorithms often employ complex mechanisms and data structures, among them suffix trees and finite state machines.

5.9.1 String searching algorithms


String searching algorithms, sometimes called string matching algorithms, are an important class of string algorithms that try to find a place where one or several strings (also called patterns) are found within a larger string or text. Let be an alphabet (finite set). Formally, both the pattern and searched text are concatenations of elements of . The may be a usual human alphabet (for example, the letters A through Z in English). Other applications may use binary alphabet ( = {0,1}) or DNA alphabet ( = {A,C,G,T}) in bioinformatics. In practice how the string is encoded can affect the feasible string search algorithms. In particular if a variable width encoding is in use then it is slow (time proportional to N) to find the Nth character. This will significantly slow down many of the more advanced search algorithms. A possible solution is to search for the sequence of code units instead, but doing so may produce false matches unless the encoding is specifically designed to avoid it. 5.9.1.1 Basic classification The various String algorithms can be classified by the number of patterns each uses. 5.9.1.2 Single pattern algorithms Let m be the length of the pattern and let n be the length of the searchable text. Algorithm Native string search algorithm Preprocessing time Matching time1 0 (no preprocessing) (n m)

Rabin-Karp string search algorithm

(m)

average (n+m), worst (n m) (n) (n) (n/m), O(n) (n)

Finite state automaton based search Knuth-Morris-Pratt algorithm Boyer-Moore string search algorithm Bitap algorithm (shift-or, shift-and, Baeza-Yates-Gonnet)

(m ||) (m) (m + ||) (m + ||)

The BoyerMoore string search algorithm has been the standard benchmark for the practical string search literature. 5.9.1.3 Algorithms using finite set of patterns Aho-Corasick algorithm Commentz-Walter algorithm Rabin-Karp string search algorithm

5.9.1.4 Algorithms using infinite number of patterns Naturally, the patterns can not be enumerated in this case. They are represented usually by a regular grammar or regular expression.

5.9.2 String manipulation algorithms or String Functions


String functions are used to manipulate a string or change or edit the contents of a string. They also are used to query information about a string. They are usually used within the context of a computer programming language. The most basic example of a string function is the length(string) function, which returns the length of a string (not counting any terminator characters or any of the string's internal structural information) and does not modify the string. For example, length("hello world") returns 11. There are many string functions which exist in other languages with similar or exactly the same syntax or parameters. For example in many languages the length function is usually represented as len(string). Even though string functions are very useful to a computer programmer, a computer programmer using these functions should be mindful that a string function in one language could in another language behave differently or have a similar or completely different function name, parameters, syntax, and results. Popular String Functions used in C and their jobs are given below: 1. char *strcpy( char *s1, const char *s2)

copies the string s2 into the character array s1. The base address of s1 is returned.

2. char *strncpy( char *s1, const char *s2, int n)

copies at most n characters of the string s2 into the character array s1. The base address of s1 is returned.

3. char *strcat( char *s1, const char *s2)

appends the string s2 to the end of character array s1. The first character from s2 overwrites the '\0' of s1. The base address of s1 is returned.

4. char *strncat( char *s1, const char *s2, int n)

appends at most n characters of the string s2 to the end of character array s1. The first character from s2 overwrites the '\0' of s1. The base address of s1 is returned.

5. char *strchr( const char *s, int c)

returns a pointer to the first instance of c in s. Returns a NULL pointer if c is not encountered in the string.

6. char *strrchr( const char *s, int c)

returns a pointer to the last instance of c in s. Returns a NULL pointer if c is not encountered in the string.

7. int strcmp( const char *s1, const char *s2)

compares the string s1 to the string s2. The function returns 0 if they are the same, a number < 0 if s1 < s2, a number > 0 if s1 > s2.

8. int strncmp( const char *s1, const char *s2, size_t n)

compares up to n characters of the string s1 to the string s2. The function returns 0 if they are the same, a number < 0 ifs1 < s2, a number > 0 if s1 > s2.

9. int strspn( char *s1, const char *s2)

returns the length of the longest substring of s1 that begins at the start of s1and consists only of the characters found in s2.

10. int strcspn( char *s1, const char *s2)

returns the length of the longest substring of s1 that begins at the start of s1and contains none of the characters found in s2.

11. int strlen( const char *s) determines the length of the string s. Returns the number of characters in the string before the '\0'.

12. char *strpbrk( const char *s1, const char *s2)

returns a pointer to the first instance in s1 of any character found in s2. Returns a NULL pointer if no characters from s2 are encountered in s1.

13. char *strstr( const char *s1, const char *s2)

returns a pointer to the first instance of string s2 in s1. Returns a NULL pointer if s2 is not encountered in s1.

14. char *strtok(char *s1, const char *s2)

repeated calls to this function break string s1 into "tokens"--that is the string is broken into substrings, each terminating with a '\0', where the '\0' replaces any characters contained in string s2. The first call uses the string to be tokenized as s1; subsequent calls use NULL as the first argument. A pointer to the beginning of the current token is returned; NULL is returned if there are no more tokens.

5.9.3 String Sorting algorithms


In computer science and mathematics, a sorting algorithm is an algorithm that puts elements of a list in a certain order. The most-used orders are numerical order and lexicographical order. Efficient sorting is important to optimizing the use of other algorithms (such as search and merge algorithms) that require sorted lists to work correctly; it is also often useful for canonicalizing data and for producing human-readable output. More formally, the output must satisfy two conditions: The output is in increasing or decreasing order (each element is no smaller/greater than the previous element according to the desired total order); The output is a permutation, or reordering, of the input.

Wellknown Sorting Algorithms are as follows: 1. 2. 3. 4. 5. 6. 7. Bubble Sort Insertion Sort Selection Sort Merge Sort Quick Sort Heap Sort Radix Sort

This wellknown sort algorithms are discussed in details in chapter 12.

5.9.4 Regular Expression Algorithms


In computing, regular expressions provide a concise and flexible means for identifying strings of text of interest, such as particular characters, words, or patterns of characters. Regular expressions (abbreviated as regex or regexp, with plural forms regexes, regexps, or regexen) are written in a formal language that can be interpreted by a regular expression processor, a program that either serves as a parser generator or examines text and identifies parts that match the provided specification.We will not discuss this algorithms as this part is beyond the scope of this book.

5.9.5 Parsing a String


In computer science and linguistics, parsing (more formally: syntactic analysis) is the process of analyzing a sequence of tokens to determine grammatical structure with respect to a given (more or less) formal grammar. A parser is thus one of the components in an interpreter or compiler, where it captures the implied hierarchy of the input text and transforms it into a form suitable for further processing (often some kind of parse tree, abstract syntax tree or other hierarchical structure) and normally checks for syntax errors at the same time.

5.11 String Implementations


Some languages like C implement strings as templates that can be used with any primitive type, but this is the exception, not the rule. If an object-oriented language represents strings as objects, they are called mutable if the value can change at runtime and immutable if the value is frozen after creation. For example, Ruby has mutable strings, while Python's strings are immutable. Other languages, most notably Prolog and Erlang, avoid implementing a string datatype, instead adopting the convention of representing strings as lists of character codes. Algorithms of some popular string functions: Algorithm myStrlen(s1) // Purpose : This algorithm determines the length of the string. // Input : String s1. // Output : Returns the number of characters in the string s1 before the '\0'. { iLength = 0; while(*s1!=\0) { iLenght++; s1++; } return iLength; }// End of Algorithm. Algorithm myStrcpy(s1,s2) // Purpose : This algorithm copies a string into a character array. // Input : s1 is the string and s2 is a character array. // Output : None. { while(*s1!=\0) { s2[iCounter] = *s1; iCounter++; s1++; } s2[iCounter] = *s1; }// End of Algorithm Algorithm myStrcat(s1,s2) // Purpose : This algorithm appends the string to the end of character array.

// Input : s1 is the string and s2 is a character array. // Output : None. { iLength = myStrlen(s2); while(*s1!=\0) { s2[iLength] = *s1; iLength++; s1++; } }// End of Algorithm Algorithm myStrcmp(s1,s2) // Purpose : This algorithm compares two strings. // Input : s1 and s2 are the string. // Output : The algorithm returns 0 if they are the same, a number < 0 if s1 < s2, a number > 0 if s1 > s2. { String1 = s1; String2 = s2; while(*String1!=\0 && * String2!=\0) { if(*String1!=* String2) break; String1 ++; String2 ++; } if(*s1 == \0 && *s2==\0) return 0; else return (myStrlen(s1) myStrlen(s2)); }// End of Algorithm

MCQ Chapter 5
1. The null character is represented by (a) \n (b) \0 (c) NULL (d) \r 2. The null character needs a space of (a) zero bytes (b) one byte (c) three bytes (d) four bytes 3. When we concatenate two string of size m and n, the resultant string will be of size (a) m + n (b) less than m + n (c) m * n (d) max(m, n) 4. To find an instance of a String of length l, in another string of length m, the Kunth Morrues Pratt algorithms time is proportional to (at worst case) (a) m + l (b) m (c) l (d) m * l

5. Which string operation is not available in C? (a) Concatenation (b) Pattern Matching (c) Reversing the string (d) none of these 6. String concatenation means (a) Combining two strings (b) Extracting to substring out of a string (c) Partitioning a string (d) None of these 7. Pattern matching refers to (a) finding position where a string pattern P first appears in a given string (b) Checking wheteher two strings are identical (c) Checking whether a pattern occurs in a given string (d) None of these 8. Macro expansion of C is an example of (a) String matching and manipulation (b) Application of hashing for a practical program (c) String operation which does not require string matching (d) None of these 9. In C strings are stored in (a) Linked list of character (b) Linkedlist of usignned character (c) Array of character (d) array of integer 10. Problem of garbage collection arises with which method of string representation? (a) Fixed length method (b) Index table method (c) Linked list method (d) None of these 11. String Homomorphism is one kind of (a) String Substitution (b) String Concatenation (c) Partitioning a string (d) String matching 12. String Projection is one kind of (a) String Substitution (b) String Concatenation (c) String representation (d) String matching 13. String right cancellation is one kind of (a) String Substitution (b) String Concatenation (c) String representation

(d) String matching 14. Strlen() function returns (a) integer value (b) float value (c) charcter value (d) None of these 15. Byte String is made of (a) array of bits (b) array of Bytes (c) array of chatracters (d) none of these 16. Strcat() function returns (a) Character value (b) integer value (c) pointer value (d) None of these

Solutions:
1. b 16. c 2. b 3. b 4. a 5. c 6. a 7. c 8. a 9. c 10. b 11. a 12. c 13. a 14. a 15.b

Exercise Chapter 5
1. What is a string? How can it be implemented in C? 2. Give some data structure which is used to implement string. Which of these is the best and why? 3. Write an Algorithm to insert a string x to a master string X. Use data structure of your choice suitably. 4. Write an algorithm to concatenate between two strings str1 and str2. Use array to represent sting. 5. It is required to find a string s in a text p. write an algorithm and a C program to implement that. 6. Consider a string X=abcde. List all substrings of X. write a C function to compute all the substrings from a given string. 7. Check a string W=abccba if it is a palindrome string. Write a C program to find if a string is a palindrome or not. 8. Write a C program to collect the garbage value from a string i.e to collect the unused portion of a string. Use both array and linked list representations 9. What will be the string stored in the next figure? Char

link

1 2 3 4 5 6 7 8 9 10 11 12

Read Love We Data Structure To NULL

7 11 2 9 12 1 Start 10. Write a C program to comput if a string is a particular type X x Y where X is a arbitrary string of 0 and 1, Y is the reverse of X and x is any character value. (Ex0110a0110, 110b011)

Chapter - 6 STACK
6.1 Definition
A stack is a linear data structure in which insertion of new data element and deletion of existing data element is done from only one end, called top of the stack. As all the insertion and deletion operations are done from one end of the stack, the last inserted item will be deleted first. That is why stack is called LIFO (Last In First Out) list or FILO (First In Last Out) list. In our daily life, a common model of such structure is a pile of plates or a stack of coins.

6.2

Basic Operations

The followings are the basic operations associated with a stack: 1) fnInitialize(S): Initialize the stack S. 2) fnPush(S, iData): Insert the element iData on top of the stack S. 3) fnPop(S): Delete the top most item from the stack S and return the removed item. 4) fnFull(S): This function checks whether the stack S is full or not. To indicate S is full this function returns TRUE and FALSE otherwise. During PUSH operation Full(S) condition should be checked if there is any restriction on the maximum number of elements in the stack. Generally if we implement the stack using array then stack full condition is checked. But in case of implementation of stack using dynamic memory allocation such type of checking is not needed at all as here we can insert any number of data in the stack. 5) fnEmpty(S): This is also a Boolean function and returns TRUE for empty stack and FALSE for nonempty stack. During POP operation Empty(S) condition is checked. fnPush(S, 10) fnFull(S) returns FALSE 15 10 fnPop(S) fnEmpty(S) returns 10 10 fnPush(S, 15) fnFull(S) returns FALSE fnPop(S) fnEmpty(S) returns 15 10 fnPush(S, 5) fnFull(S) returns TRUE fnPop(S) fnEmpty(S) returns 15 10

FALSE

FALSE

TRUE

Figure 6.1: PUSH and POP operations on a Stack S

6.3

Implementation of stack using array

In this section we will create a stack of integers using array. For this purpose an integer array arrSTACK of size MAXSIZE may be used and the array declaration is: int arrSTACK[MAXSIZE]; Here top is global integer variable and the initial value of top is -1. From the array declaration the maximum value of top may be MAXSIZE. So, top = MAXSIZE indicates stack is full and top = -1 indicates stack is empty. Algorithm for PUSH operation on the stack Algorithm fnPush( arrSTACK[], iData) //Purpose : This algorithm inserts an item into the stack. //Input : arrSTACK[] is the array in which the data item iData is to be inserted. //Output : None { if(fnFull() == TRUE) // Checking for stack full condition. stack is full; else { top = top + 1; // Increase the current length of the stack.. arrSTACK[top] = iData; // insert the item iData. } }//End of Algorithm Algorithm for POP operation from the stack Algorithm fnPop(arrSTACK[]) //Purpose : This algorithm deletes the top element from the stack. // Input : The array arrSTACK[] on which the stack is being implemented. //Output : The top most item iData that has been deleted. { if(fnEmpty() == TRUE) //Checking for stack empty condition. stack is empty; else { iData = arrSTACK[top] // item deleted. top = top 1; // Shorten the current length of the stack. return iData; // Return the value of the item that has been deleted. } }//End of Algorithm Remember: During a POP operation, the item is not deleted from the array, but current length of the stack is reduced by decrementing top. Suppose the value of MAXSIZE is 2. Now consider the following sequence of push and pop operations: Initially the value of top is -1. fnPUSH(arrSTACK[],25) 1 fnPUSH(arrSTACK[],15) 1 15 top fnPOP(arrSTACK[]) 1 15

25

top

25

25

top

arrSTACK[0] = 25

arrSTACK[1] = 15

return 15

Now in this situation, one more push operation will overwrite the value of arrSTACK[1]. fnPUSH(arrSTACK[],20) 1 0 20 25 top

arrSTACK[1] = 20 Algorithm to display the elements of the stack Stack elements reside in the range from 0 to top of the array. So it is required to display the elements of the array from index 0 to index top. Algorithm fnDisplay(arrSTACK[]) // Purpose : This algorithm shows the elements of the stack. // Input : arrSTACK[] is the name of the stack. // Output : None. { if(fnEmpty() == TRUE) // If stack is empty. There is nothing to display; else for(iCounter=0;iCounter<=top;iCounter++) print arrSTACK[iCounter]; }// End of Algorithm

Algorithm for checking the stack full condition The stack is full if the value of top reaches MAXSIZE - 1 i.e. the maximum size of the stack. Algorithm fnFull() // Purpose : This algorithm checks whether the stack is full or not. // Input : None. // Output : This algorithm returns TRUE if stack is full and returns FALSE if stack is not full. { if (top == MAXSIZE - 1) return TRUE; else return FALSE }// End of Algorithm

Algorithm for checking the stack empty condition The stack is empty if the value of top reaches -1. Algorithm fnEmpty() // Purpose : This algorithm checks whether the stack is empty or not. // Input : None.

// Output : This algorithm returns TRUE if stack is empty and returns FALSE if stack is not empty. { if (top == -1) return TRUE; else return FALSE }// End of Algorithm

6.4

Implementation of stack using dynamic memory allocation

If we implement stack using array, then insertion of new data elements may cause an overflow and so the stack full condition is to be checked during each push operation. But stack using dynamic memory allocation does not demand to check the stack full condition in each push operation as here we can insert any number of data elements depending upon the available memory. Figure 6.2 shows a stack using dynamic memory allocation: NULL ptrTop Figure 6.2: Stack using dynamic memory allocation Each node of the above list has two fields: data field and pointer field. Data field contains the data element and the pointer field contains the address of the next node in the list. So from the concept of self referential structure the structure definition of such a node is: struct Node { int iData; struct Node *ptrNext; }; typedef struct Node StackNode; Here, top is a StackNode type pointer variable and initially the value of top is NULL indicating that the list is empty. StackNode *top = NULL; Algorithm for Push Operation on the stack Algorithm fnPush(iData) // Purpose : This algorithm inserts an item on the top the stack. // Input : The item iData that is to be inserted into the stack. // Output : None. { StackNode *ptrNewNode; // Assign a pointer to new node. ptrNewNode = (StackNode*) malloc(sizeof(StackNode)); //Create the new node. ptrNewNode->iData = iData; //Load the data field of the newly created node. ptrNewNode->ptrNext = top; // Now new node will point to the current top node. top = ptrNewNode; //New node is now the top node of the list. }// End of Algorithm Algorithm for Pop Operation from the stack Algorithm fnPop()

// Purpose : This algorithm deletes an item from the top of the stack. // Input : None. // Output : The item iData that has been deleted. { StackNode *ptrDeleteNode; if(fnEmpty() == TRUE) //Check the stack empty condition. Stack is Empty; else { iData = top->iData; // The item to be deleted. ptrDeleteNode = top; //Set the pointer to the node to be deleted. top = top->ptrNext; //Set the new value of top. free(ptrDeleteNode); //Free the node that has been deleted. return(iData); //Return the data of the deleted node. } }// End of Algorithm Algorithm to display the elements of the stack Algorithm fnDisplay() // Purpose : This algorithm displays the items of the stack. // Input : None. // Output : None. { StackNode *ptrDisplayNode; if(fnEmpty() == TRUE) //Check stack empty condition. There is nothing to display; else { ptrDisplayNode = top; //Initialize the pointer to top node. while(ptrDisplayNode!=NULL) //Until list is empty. { print (ptrDisplayNode->iData); //Print the data. ptrDisplayNode = ptrDisplayNode -> ptrNext; //Go to next node. } } }// End of Algorithm Algorithm for checking the stack empty condition Algorithm fnEmpty() // Purpose : This algorithm checks whether the stack is empty or not. // Input : None. // Output : This algorithm returns TRUE if stack is empty and returns FALSE if stack is not empty. { if (top == NULL) return TRUE; else return FALSE }// End of Algorithm Example: Suppose we want to create a stack using dynamic memory allocation. The following statements show how the different operations are performed on the stack. Initially the value of top is NULL.

Statement 1: fnPush(20) Statement 2: fnPush(25) Statement 3: fnPop() Statement 4: fnPush(15) Statement 5: fnPop()

20 top 25 top 20 top 15 top 20 top

NULL 20 NULL

NULL

20

NULL

NULL

6.5

Arithmetic Expressions

An expression is defined as combination of some operands and operators. Basically three types of notation for an expression are available and the notations are as follows: 1) Infix Notation: In this notation operators are written in between the operands. The expression to multiply two numbers X and Y is written in infix notation as: X*Y 2) Prefix Notation: In this notation operators are written before the operands. Prefix notation is also called polish notation as the polish mathematician Jan Lukasiewicz developed this notation. The expression to multiply two numbers X and Y is written in prefix notation as: *XY 3) Postfix Notation: In this notation operators are written after the operands. This notation is also called the reverse polish notation. The expression to multiply two numbers X and Y is written in postfix notation as: XY*

6.6

Converting Infix expressions to postfix notation

For this conversion, only the operators of the infix expression are pushed onto the stack according to the following rule: 1. If the priority of the stack top operator is greater than or equal to the priority of the incoming operator then the new operator can not be pushed onto the stack. In other words, the stack top operator is to be popped up until the priority of the stack top operator is less than the priority of the incoming operator. The following table shows the order of priority of some commonly used operator: Operators Unary (-,+,!,log, sin etc.) ,^ (Exponentiation Operator), $ *, / +,<,<=,>,>=,!= Priority 5 4 3 2 1

Table 6.1: Priority of some commonly used operator Here a stack Oprtstk is used to push the operators of the infix expression I and a string P is used to store the equivalent postfix notation. The operands of I are directly added to P.

Example 1: Convert the following infix expression to its corresponding postfix notation: I = A+B*C/(D+E) Step 1: First input character is A, which is an operand. So add A to P. P: A

Oprtstk Step 2: Second input character + is an operator. The operator stack is empty. So push + onto the stack. P: A

+ Oprtstk

top

Step 3: Third input character B is an operand. So add B to P. P: AB

+ Oprtstk

top

Step 4: Next input symbol * is an operator and the stack top operator is +. As priority of + is less than the priority of *, push * onto the operator stack without any pop operation. P: AB

* + Oprtstk

top

Step 5: Next input symbol C is an operand. So add C to P. P: ABC

* + Oprtstk top

Step 6: Next input symbol / is an operator and the stack top operator is *. The priority of * is equal to the priority of /. So pop * from operator stack and add * to P P: ABC*

+ Oprtstk

top

Now the priority of the stack top operator (+) is less than the priority of the current symbol /. So, no more pop operation takes place. Push / onto the operator stack.

/ + Oprtstk

top

Step 7: Next input symbol is (. Push ( onto the stack. P: ABC* ( / + top

Oprtstk Step 8: Next input character D is an operand. So add D to P. P: ABC*D ( / + Oprtstk top

Step 9: Next input symbol is + which is an operator and the stack top element is (. So there is no need to compare the priorities of operator. Push + onto the stack. P: ABC*D + ( / + Oprtstk Step 10: Next input symbol E is an operand. So add E to P. P: ABC*DE + top ( / + Oprtstk Step 11: Now the last input symbol is ). So pop all the operators from the operator stack until a left parenthesis ( is encountered. P: ABC*DE+ ( / + Oprtstk top top

Now pop ( from the stack, but dont add it to P.

/ + Oprtstk

top

Step 12: End of expression. So pop operators from the operator stack until stack is empty and add the symbols to P. Finally, P: ABC*DE+/+ Algorithm to convert an infix expression to postfix notation: Algorithm fnInfixToPostfix (I, P) // Purpose : This algorithm finds the postfix notation of an infix expression. // Input : I is an arithmetic expression in infix notation. P is the equivalent postfix notation. // Output : None. // Comments : Oprtstk is the operator stack. { while(not end of I) { symbol = next input character from I; if(symbol is an operand) Add it to P; elseif(symbol is an operator) { while(fnTop(Oprtstk)>=symbol and !Empty(Oprtstk) and fnTop(Oprtstk)!=() { n = pop(Oprtstk); add n to P; } push(Oprtstk , symbol); } elseif(symbol is () push(Oprtstk , symbol); else // right parenthesis is encountered. { while(Oprtstk[top] != ( ) //Pop from stack until left parenthesis is encountered. { n = pop(Oprtstk); add n to P; } n = pop(Oprtstk); //only pop left parenthesis (. Dont add it to P. } }// end of while while(not stack empty) {

n = pop(Oprtstk); add n to P; } }// End of algorithm Example 2: Find postfix notation for A$B*C-D+E/F*(G+H) Solution: Symbol Scanne d A $ B * C D + E / F * ( G + H ) End Comments A is an operand. Add A to P. Stack is empty. So push $ onto stack. B is an operand. Add B to P. Priority($) > Priority(*). So pop $ from stack and add it to P. Then push * onto the stack. C is an operand. Add C to P. Priority(*) > Priority(-). So pop * from stack and add it to P. Then push onto the stack. D is an operand. Add D to P. Priority(-) = Priority(+). So pop from stack and add it to P. Push + onto stack. E is an operand. Add E to P. Priority(+)<Priority(/). So no pop operation takes place. Push / onto stack. F is an operand. Add F to P. Priority(/) = Priority(*). So pop / and add / to P. Push * onto stack. Push ( onto the stack. G is an operand and add G to P. Push + onto stack. H is an operand. Add it to P. Pop all the elements from the stack until ( is encountered and execute one more pop operation to delete ( from the stack. Pop all the elements from the stack until stack is empty. Stack Contents Empty $ $ * * + + +/ +/ +* +*( +*( +*(+ +*(+ +* Empty Postfix Expression (P) A A AB AB$ AB$C AB$C* AB$C*D AB$C*DAB$C*D-E AB$C*D-E AB$C*D-EF AB$C*D-EF/ AB$C*D-EF/ AB$C*D-EF/G AB$C*D-EF/G AB$C*D-EF/GH AB$C*D-EF/GH+ AB$C*D-EF/GH+*+

Example 3: Find postfix notation for A+B*logC/D Solution: Symbol Comments Scanned A + B * log A is an operand. Add A to P. Push + onto stack. B is an operand. Add it to P. Priority(+)<Priority(*). So no pop operation. Push * onto the stack. Priority(*)<Priority(log). So no pop operation. Push log onto the stack.

Stack Contents Empty + + +* + * log

Postfix Expression (P) A A AB AB AB

C /

D End

C is an operand. Add C to P. Priority(log)>Priority(/). Pop log and add it to P. Again, Priority(*)=Priority(/). Pop * and add * to P. Now Priority(+) < Priority(/). So no more pop operation. Push / onto the stack. D is an operand. Add D to P. Pop all the elements from stack until it is empty.

+ * log +/

ABC ABClog*

+/ Empty

ABClog*D ABClog*D/+

6.7

Converting Infix expression to prefix notation

Rule for pop operation from the operator stack: 1. If the priority of the stack top operator is greater than the priority of the incoming operator then the new operator can not be pushed onto the stack. In other words the stack top operator is to be popped up until the priority of the stack top operator is less than or equal to the priority of the incoming operator. Example 1: Convert the following infix expression to its corresponding prefix notation: I = A+B*C/(D+E) Solution: First take the reverse of I. I = reverse(I) = (E+D)/C*B+A Step 1: First input character is (. Push ( onto operator stack. P:

( Oprtstk

top

Step 2: Second input character E is an operand. So add E to P. P: E

( Oprtstk

top

Step 3: Third input character + is an operator. Push + onto stack. P: E

top ( Oprtstk Step 4: Next input symbol D is an operand. So add D to P. P: ED

+ ( Oprtstk

top

Step 5: Next input symbol is ). So pop all the operators from the stack until a left parenthesis is encountered and add them to P. P: ED+

( Oprtstk Now pop ( from the stack, but dont add it to P.

top

P: ED+

Oprtstk Step 6: Next input symbol / is an operator and the stack is now empty. Push / onto the stack. P: ED+

/ Oprtstk

top

Step 7: Next input symbol C is an operand. Add C to P. P: ED+C

/ Oprtstk

top

Step 8: Next input character * is an operator. Priority(/) = Priority(*). So push * onto stack without any pop operation. P: ED+C

* /

top

Oprtstk Step 9: Next input symbol is B which is an operand. Add B to P. P: ED+CB

* / Oprtstk

top

Step 10: Next input symbol + is an operator. Priority(*) > Priority(+). So pop * and add it to P. Now stack top operator is /. Again Priority(/) > Priority(+). So pop / and add it to P. Now Stack is empty. So push + onto the stack without any comparison. P: ED+CB*/

+ Oprtstk

top

Step 11: Now the last input symbol A is an operand. Add A to P. P: ED+CB*/A

+ Oprtstk

top

Step 12: End of expression. So pop operators from the operator stack until stack is empty and add the symbols to P. P: ED+CB*/A+ Finally P = reverse(P) = +A/*BC+DE Algorithm to convert infix expression to prefix notation: Algorithm fnInfixToPrefix(I,P) // Purpose : This algorithm finds the prefix notation of an infix expression. // Input : I is an arithmetic expression in infix notation. P is the equivalent prefix notation. // Output : None. // Comments : P = reverse(P) and I = reverse(I). Oprtstk is the operator stack. { while(not end of I) { Symbol = Next input character of I; if(Symbol is an operand) Add it to P; elseif(Symbol is an operator) { while(Oprtstk[top] > Symbol and !Empty(Oprtstk) and Oprtstk[top]!=() { n = pop(Oprtstk); add n to P; } push(Oprtstk , Symbol); } elseif(Symbol is () push(Oprtstk , Symbol); else // right parenthesis is encountered { while(Oprtstk[top] != ( ) //Pop from stack until left parenthesis is encountered

{ n = pop(Oprtstk); add n to P; } pop(Oprtstk); //only pop (. Dont add it to P } } // end of while while(Empty(Oprtstk)==FALSE) // Until Stack is empty { n = pop(Oprtstk); add n to P; } P = reverse(P); }// End of Algorithm Example 2: Find prefix notation for A/B$C+ (D*E-log F) Solution: I = A/B$C+ (D*E-log F) I = Reverse of I = (log F E*D)+C$B/A Symbol Comments Scanne d ( Push ( onto the stack. log Push the unary operator log onto the stack. F F is an operand. Add F to P. Priority(log) > Priority(-). So pop log and add log to P. Push onto stack. E E is an operand. Add E to P. * Priority(-) < Priority(*). So no pop. Push * onto the stack. D D is an operand. Add D to P. ) Pop * , - from stack and add them to P. Then pop ( , but dont add it to P. + Now stack is empty. Push + onto the stack. C C is an operand. Add C to P. $ Priority(+)<Priority($). So no pop operation. Push $ onto stack. B Add operand B to P. / Priority($)>Priority(/). So pop $ from stack and add it to P. Next, Priority(+) < Priority($). Hence no more pop. Push / onto the stack. A Add operand A to P. End Pop all the elements from the stack until stack is empty. Finally, P = reverse of P = +/A$BC-*DElogF

Stack Contents ( ( log ( log (((-* (-* Empty + + +$ +$ +/ +/ Empty

Prefix Expression (P)

F Flog FlogE FlogE FlogED FlogED*FlogED*FlogED*-C FlogED*-C FlogED*-CB FlogED*-CB$ FlogED*-CB$A Flog ED*-CB$A/+

6.8

Evaluating Postfix Expression

To evaluate a postfix expression an operand stack (Oprndstk) is used rather than an operator stack i.e. here the operands of the postfix expressions are pushed onto the stack. Example 1: Evaluate the following postfix expression: 234*+8 Step 1: First input element is 2 which is an operand. So push 2 onto the operand stack.

top

Oprndstk Step 2: Next input element is 3 which is also an operand. So push 3 onto the operand stack.

3 2

top

Oprndstk Step 3: Third input element 4 is again an operand. So push 4 onto the operand stack.

4 3 2 Oprndstk

top

Step 4: Next input symbol is * which is an operator. Now pop two elements from the stack and calculate the result asresult = Element returned from the second pop operation * Element returned from the first pop operation =3*4 = 12. Now push the result 12 onto the operand stack.

12 2 Oprndstk

top

Step 5: Next + is also an operator. So result = 2 + 12 = 14. Push result onto the stack.

14 Oprndstk

top

Step 6: Next input symbol is operand 8. Push 8 onto the operand stack.

8 14

top

Oprndstk Step 7: Next input symbol is operator -. So compute the result as 14 -8 = 6 and push 6 into the stack.

6 Oprndstk

top

Step 8: End of expression. So finally pop the only element from the stack and thus 6 is the final result. Algorithm to evaluate a postfix expression: Algorithm fnPostfixEvaluation (P,I) // Purpose : This algorithm evaluates a postfix expression. // Input : P is an arithmetic expression in postfix notation. I is finally calculated result. // Output : This algorithm returns the result after evaluating the postfix expression. // Comments : Oprndstk is the operand stack. { while(not end of Q) { Symbol = Next input character of P; if(Symbol == Operand) push(Oprndstk, Symbol); else { Operand2 = pop(Oprndstk); Operand1 = pop(Oprndstk);

Value = result of applying Symbol to Operand1 and Operand2; push(Oprndstk, Value); } } I = pop(Oprndstk); return(I); }// End of Algorithm

6.9

Evaluating Prefix Expression

Here also an operand stack is used rather than an operator stack. For this evaluation, reverse of the given prefix expression is taken and scanned until the end of P to find the result. Example 1: Evaluate the following prefix expression: P =-+2*348 P = reverse(P) = 8 4 3 * 2 + Step 1: First input element is 8 which is an operand. So push 8 onto the operand stack.

8 Oprndstk

top

Step 2: Next input element is 4 which is also an operand. So push 4 onto the operand stack.

4 8

top

Oprndstk Step 3: Third input element is 3 which is an operand. So push 3 onto the operand stack.

3 4

top

8 Oprndstk Step 4: Next input symbol is * which is an operator. Now pop two elements from the stack and calculate the result asresult = Element returned from the first pop operation * Element returned from the second pop operation =3*4 = 12. Now push the result 12 onto the operand stack.

12 8

top

Oprndstk Step 5: Next input symbol 2 is an operand. Push 2 onto the stack.

2 12 8 Oprndstk Step 6: Next input symbol is operator +. So result = 2 + 12 = 14. Push 14 onto the operand stack. top

14 8 Oprndstk

top

Step 7: Next input symbol - is an operator. So compute the result as 14 -8 = 6 and push 6 onto the stack.

6 Oprndstk

top

Step 8: End of expression. So finally pop the only element from the stack and thus 6 is the final result. Algorithm to evaluate a prefix expression Algorithm fnPrefixEvaluation (P, I) // Purpose : This algorithm evaluates a prefix expression. // Input : P is an arithmetic expression in prefix notation. I is finally calculated result. // Output : This algorithm returns the result after evaluating the prefix expression. // Comments : P = reverse (P). Oprndstk is operand stack. { while(not end of P) { Symbol = Next input character of P; if(Symbol == Operand) push(Oprndstk, Symbol); else { Operand1 = pop(Oprndstk); Operand2 = pop(Oprndstk); Value = Result of applying Symbol to Operand1 and Operand2; push(Oprndstk, Value); } } I = pop(Oprndstk); return(I); }// End of Algorithm

6.10 Application of Stack


The applications of stack are as follows: 1. Reversing a string. 2. Checking the balanced parenthesis. 3. Conversion from infix to postfix and prefix expression. 4. Evaluation of postfix and prefix expression. 5. Simulating recursion.

6.11 Reversing a string


Consider the string strName[] = DATA. Now using stack, reverse of the string strName can be obtained by executing the following sequence of statements. After all the push operations, pop the data until stack is empty. A top fnPush(arrSTACK[], D) 3 strName[0] = fnPop(arrSTACK[]) = A fnPush(arrSTACK[], A) strName[1] = fnPop(arrSTACK[]) = T T fnPush(arrSTACK[], T) 2 strName[2] = fnPop(arrSTACK[]) = A fnPush(arrSTACK[], A) strName[3] = fnPop(arrSTACK[]) = D A 1 Now the string strName contains reverse of the original information. D 0

Algorithm to reverse a string using stack Algorithm fnReverse(strName) // Purpose : This algorithm reverse a given string. // Input : strName is the required string. // Output : None. // Comments : arrSTACK is a character array to implement the stack. { iCounter = 0; while(strName[iCounter]!=\0) //Until end of string. { fnPush(arrSTACK[], strName[iCounter]); //Push the character into the stack. iCounter = iCounter + 1; } iCounter = 0; while(fnEmpty() != TRUE) //Until the stack is not empty. { strName[iCounter] = fnPop(arrSTACK[]); //Pop the character and store into array strName[]. iCounter = iCounter + 1; } }//End of Algorithm

6.12 Checking balanced parenthesis


Suppose a string arrExpression[] = ((()())). Now we can check easily by stack whether the parenthesis is balanced or not. If left parenthesis (() is encountered then push it onto the stack. When a right parenthesis ()) is encountered then pop one left parenthesis from the stack. At the end, if the stack is empty then conclude that the parenthesis is balanced. Algorithm to check balanced parenthesis Algorithm fnCheck_balanced_parenthesis(arrExpression) // Purpose : This algorithm checks balanced parenthesis. // Input : arrExpression is the required expression. // Output : This algorithm returns TRUE if parenthesis is balanced and returns FALSE otherwise. // Comments : arrSTACK is a character array to implement the stack. { iCounter = 0; while(strExpression[iCounter]!=\0) //Until end of string. { if(strExpression[iCounter] == () fnPush(arrSTACK[], strExpression[iCounter]); //Push ( onto the stack. if(strExpression[iCounter] == )) fnPop(arrStack[]); //Pop ( from the stack. iCounter = iCounter + 1; } return(fnEmpty()); //If stack is empty return TRUE otherwise return FALSE. }// End of Algorithm

6.13 Multiple Stacks


We can implement two stacks in the same array where one stack grows from left to right and other from right to left. The value of the top (top1) for the first stack is initialized from -1 and for the second stack the top (top2) is

initialized from the MAXSIZE of the array. So top1 is incremented by 1 during the push operation onto the first stack and in case of push operation onto the second stack, the value of top2 is to be decremented by 1. Stack 1 Stack 2

10

11

12

top1 Figure 6.3: Two stack using same array

top2

For the two-stack of the figure 6.3 the initial value of top1 is -1 and for top2 the initial value is 13. The stack full condition for two-stack is top2 = top1 + 1. Stack empty condition for the first stack is top1 = -1 and for the second stack the condition is top2 = MAXSIZE. Algorithm of insertion into two-stack Algorithm fnPushTwoStack(iStackNo, idata) // Purpose : This algorithm inserts an item into the two-stack. // Input : iStackNo denotes the stack number. iData is the data item to be inserted onto the either stack. // Output : None. // Comments : top1 (top of the first stack) is initialized from -1 and top2 (top of the second stack) is initialized from MAXSIZE. { if (top2 == top1 + 1) Stack is full; else { if (iStackNo == 1) { top1 = top1 + 1; arrStack[top1] = iData; } elseif (iStackNo == 2) { top2 = top2 -1; arrStack[top2] = iData; } else Invalid Stack number; } }//End of Algorithm Algorithm of deletion from the two-stack Algorithm fnPopTwoStack(iStackNo) // Purpose : This algorithm deletes an item from two-stack. // Input : iStackNo is the stack number from which the item is to be deleted. // Output : The item iData which has been deleted.

{ if (iStackNo == 1) { if(top1 == -1) First stack is empty; else { iData = arrStack[top1]; top1 = top1 - 1; return iData; } } elseif (iStackNo == 2) { if (top2 == MAXSIZE) Second stack is empty; else { iData = arrStack[top2]; top2 = top2 + 1; return iData; } } else Invalid stack number; }// End of Algorithm

/****Implementation of stack using C****/ /****File name : stack.c****/


#include<stdio.h> #include<conio.h> #define MAXSIZE 50 //Prototype Declaration void fnPush( int[], int ); int fnPop(int[]); void fnDisplay(int[]); int fnFull(); int fnEmpty(); int top=-1; void fnPush( int arrSTACK[], int iData) //Purpose : This function inserts an item into the stack. //Input : arrSTACK[] is the array in which the data item iData is to be inserted. //Output : None { if(fnFull() == 1) // Checking for stack full condition. printf("stack is full"); else {

top = top + 1; arrSTACK[top] = iData; } }//End of function

// Increase the current length of the stack.. // insert the item iData.

int fnPop(int arrSTACK[]) //Purpose : This function deletes the top element from the stack. // Input : The array arrSTACK[] on which the stack is being implemented. //Output : The top most item iData that has been deleted. { int iData; if(fnEmpty() == 1) //Checking for stack empty condition. { //printf("stack is empty"); return -1; } else { iData = arrSTACK[top]; // item deleted. top = top - 1; // Shorten the current length of the stack. return iData; // Return the value of the item that has been deleted. } }//End of function void fnDisplay(int arrSTACK[]) // Purpose : This function shows the elements of the stack. // Input : arrSTACK[] is the name of the stack. // Output : None. { int iCounter; if(fnEmpty() == 1) // If stack is empty. printf("There is nothing to display"); else for(iCounter=0;iCounter<=top;iCounter++) printf("%d\n",arrSTACK[iCounter]); }// End of function int fnFull() // Purpose : This function checks whether the stack is full or not. // Input : None. // Output : This function returns TRUE if stack is full and returns FALSE if stack is not full. { if (top == MAXSIZE - 1) return 1; else return 0; }// End of function int fnEmpty() // Purpose : This function checks whether the stack is empty or not. // Input: None. // Output : This function returns TRUE if stack is empty and returns FALSE if stack is not empty. { if (top == -1) return 1;

else return 0; }// End of function void main() { int arrSTACK[50],iChoice,iData; clrscr(); do { printf("1. Push\n"); printf("2. Pop\n"); printf("3. Display\n"); printf("4. Exit\n"); printf("Enter your choice\n"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("Enter the element"); scanf("%d",&iData); fnPush(arrSTACK,iData); break; case 2: iData = fnPop(arrSTACK); if(iData == -1) printf("Stack is empty\n"); else printf("Item deleted = %d\n",iData); break; case 3: printf("\n******Stack Content******\n"); fnDisplay(arrSTACK); printf("\n*************************\n"); break; case 4: exit(1); } }while(1); getch(); }

/****C code to implement a stack using dynamic memory allocation****/ /*File Name : DMASTACK.C */
#include<stdio.h> #include<conio.h> #define MAXSIZE 50; struct Node { int iData; struct Node *ptrNext; }; typedef struct Node StackNode;

StackNode *top = NULL; //Prototype Declaration void fnPush(int); int fnPop(); void fnDisplay(); int fnEmpty(); void fnPush(int iData) // Purpose : This function inserts an item on the top the stack. // Input : The item iData that is to be inserted into the stack. // Output : None. { StackNode *ptrNewNode; // Assign a pointer to new node. ptrNewNode = (StackNode*) malloc(sizeof(StackNode)); //Create the new node. ptrNewNode->iData = iData; //Load the data field of the newly created node. ptrNewNode->ptrNext = top; // Now new node will point to the current top node. top = ptrNewNode; //New node is now the top node of the list. }// End of function int fnPop() // Purpose : This function deletes an item from the top of the stack. // Input: None. // Output : The item iData that has been deleted or -1 for an empty stack. { int iData; StackNode *ptrDeleteNode; if(fnEmpty() == 1) //Check the stack empty condition. //Stack is Empty; return -1; else { iData = top->iData; // The item to be deleted. ptrDeleteNode = top; //Set the pointer to the node to be deleted. top = top->ptrNext; //Set the new value of top. free(ptrDeleteNode); //Free the node that has been deleted. return(iData); //Return the data of the deleted node. } }// End of Function void fnDisplay() // Purpose : This function displays the items of the stack. // Input: None. // Output : None. { StackNode *ptrDisplayNode; if(fnEmpty() == 1) printf("There is nothing to display\n"); else { ptrDisplayNode = top; while(ptrDisplayNode!=NULL)

//Check stack empty condition.

//Initialize the pointer to top node. //Until list is empty.

{ printf("%d\n",ptrDisplayNode->iData); ptrDisplayNode = ptrDisplayNode -> ptrNext; } } }// End of Function int fnEmpty() // Purpose : This function checks whether the stack is empty or not. // Input : None. // Output : This function returns TRUE if stack is empty and returns FALSE if stack is not empty. { if (top == NULL) return 1; else return 0; }// End of Function void main() { int iChoice,iData; clrscr(); do { printf("1. Push\n"); printf("2. Pop\n"); printf("3. Display\n"); printf("4. Exit\n"); printf("Enter your choice\n"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("Enter the element"); scanf("%d",&iData); fnPush(iData); break; case 2: iData = fnPop(); if(iData == -1) printf("Stack is empty\n"); else printf("Item deleted = %d\n",iData); break; case 3: printf("\n******Stack Content******\n"); fnDisplay(); printf("\n*************************\n"); break; case 4: exit(1); } //Print the data. //Go to next node.

}while(1); }

/****Reverse a string using stack****/ /* File Name: REVERSE.C */


#include<stdio.h> #include<conio.h> #define MAXSIZE 50 void fnPush(char[],char); char fnPop(char[]); int top=-1; void fnReverse(char strName[]) // Purpose : This function reverse a given string. // Input: strName is the required string. // Output : None. // Comments : arrSTACK is a character array to implement the stack. { int iCounter = 0; int arrSTACK[50]; while(strName[iCounter]!='\0') //Until end of string. { fnPush(arrSTACK, strName[iCounter]); //Push the character into the stack. iCounter = iCounter + 1; } iCounter = 0; while(fnEmpty() != 1) //Until the stack is not empty. { strName[iCounter] = fnPop(arrSTACK); //Pop the character and store into array strName[]. iCounter = iCounter + 1; } }//End of Function void fnPush( char arrSTACK[], char cData) //Purpose : This function inserts an item into the stack. //Input : arrSTACK[] is the array in which the data item iData is to be inserted. //Output : None { if(fnFull() == 1) // Checking for stack full condition. printf("stack is full"); else { top = top + 1; // Increase the current length of the stack.. arrSTACK[top] = cData; // insert the item iData. } }//End of function char fnPop(char arrSTACK[]) //Purpose : This function deletes the top element from the stack. // Input: The array arrSTACK[] on which the stack is being implemented. //Output : The top most item iData that has been deleted.

{ char cData; if(fnEmpty() == 1) //Checking for stack empty condition. { //printf("stack is empty"); return -1; } else { cData = arrSTACK[top]; // item deleted. top = top - 1; // Shorten the current length of the stack. return cData; // Return the value of the item that has been deleted. } }//End of function int fnFull() // Purpose : This function checks whether the stack is full or not. // Input: None. // Output : This function returns TRUE if stack is full and returns FALSE if stack is not full. { if (top == MAXSIZE - 1) return 1; else return 0; }// End of function int fnEmpty() // Purpose : This function checks whether the stack is empty or not. // Input: None. // Output : This function returns TRUE if stack is empty and returns FALSE if stack is not empty. { if (top == -1) return 1; else return 0; }// End of function void main(void) { char strMyString[20]; clrscr(); printf("Enter your string"); gets(strMyString); fnReverse(strMyString); printf("%s",strMyString); getch(); }

/****C code to find the postfix and prefix notation of an infix expression ****/ /* File Name: CONVERSION.C */
#include<stdio.h> #include<conio.h>

#include<string.h> #define MAXSIZE 50 void fnPush(char[],char); char fnPop(char[]); char fntop(char[]); void fnResize(char *); int top=-1;//Oprtstk[50]; void fnInfixToPostfix (char I[20], char P[20]) // Purpose : This function finds the postfix notation of an infix expression. // Input: I is an arithmetic expression in infix notation. P is the equivalent postfix notation. // Output : None. // Comments : Oprtstk is the operator stack. { int iIndexI=0,iIndexP=0; char Oprtstk[50]; char symbol,n; while(I[iIndexI]!='\0') { symbol = I[iIndexI]; //Get next character of I if(symbol == '+' || symbol == '-' || symbol == '*' || symbol == '/') { while(fnPriority(fntop(Oprtstk))>=fnPriority(symbol) && !fnEmpty() && fntop(Oprtstk)!='(') { n = fnPop(Oprtstk); P[iIndexP] = n; iIndexP++; } fnPush(Oprtstk , symbol); } else if(symbol == '(') fnPush(Oprtstk , symbol); else if(symbol == ')') // right parenthesis is encountered. { while(fntop(Oprtstk) != '(' ) //Pop from stack until left parenthesis is encountered. { n = fnPop(Oprtstk); P[iIndexP] = n; iIndexP++; } n = fnPop(Oprtstk); //only pop left parenthesis (. Dont add it to P. } else { P[iIndexP] = symbol; iIndexP++; } iIndexI++; }// end of while while(!fnEmpty())

{ n = fnPop(Oprtstk); P[iIndexP] = n; iIndexP++; } P[iIndexP]='\0'; }// End of Function void fnInfixToPrefix (char I[20], char P[20]) // Purpose : This function finds the postfix notation of an infix expression. // Input: I is an arithmetic expression in infix notation. P is the equivalent postfix notation. // Output : None. // Comments : Oprtstk is the operator stack. { int iIndexI=0,iIndexP=0; char Oprtstk[50]; char symbol,n,*temp; I = strrev(I); fnResize(I); while(I[iIndexI]!='\0') { symbol = I[iIndexI]; //Get next character of I if(symbol == '+' || symbol == '-' || symbol == '*' || symbol == '/') { while(fnPriority(fntop(Oprtstk))>fnPriority(symbol) && !fnEmpty() && fntop(Oprtstk)! ='(') { n = fnPop(Oprtstk); P[iIndexP] = n; iIndexP++; } fnPush(Oprtstk , symbol); } else if(symbol == '(') fnPush(Oprtstk , symbol); else if(symbol == ')') // right parenthesis is encountered. { while(fntop(Oprtstk) != '(' ) //Pop from stack until left parenthesis is encountered. { n = fnPop(Oprtstk); P[iIndexP] = n; iIndexP++; } n = fnPop(Oprtstk); //only pop left parenthesis (. Dont add it to P. } else { P[iIndexP] = symbol; iIndexP++; } iIndexI++; }// end of while

while(!fnEmpty()) { n = fnPop(Oprtstk); P[iIndexP] = n; iIndexP++; } P[iIndexP]='\0'; P = strrev(P); fnResize(I); I = strrev(I); }// End of Function void fnResize(char *temp) { while(*temp!='\0') { if(*temp=='(') *temp = ')'; else if(*temp==')') *temp='('; temp++; } } int fnPriority(char operator1) { switch(operator1) { case '+': case '-': return 1; break; case '*': case '/': return 2; break; } } char fntop(char Oprtstk[]) { return Oprtstk[top]; } void fnPush( char arrSTACK[], char cData) //Purpose : This function inserts an item into the stack. //Input : arrSTACK[] is the array in which the data item iData is to be inserted. //Output : None { if(fnFull() == 1) // Checking for stack full condition. printf("stack is full"); else

{ top = top + 1; arrSTACK[top] = cData; } }//End of function char fnPop(char arrSTACK[]) //Purpose : This function deletes the top element from the stack. // Input : The array arrSTACK[] on which the stack is being implemented. //Output : The top most item iData that has been deleted. { char cData; if(fnEmpty() == 1) //Checking for stack empty condition. { //printf("stack is empty"); return -1; } else { cData = arrSTACK[top]; // item deleted. top = top - 1; // Shorten the current length of the stack. return cData; // Return the value of the item that has been deleted. } }//End of function int fnFull() // Purpose : This function checks whether the stack is full or not. // Input : None. // Output : This function returns TRUE if stack is full and returns FALSE if stack is not full. { if (top == MAXSIZE - 1) return 1; else return 0; }// End of function int fnEmpty() // Purpose : This function checks whether the stack is empty or not. // Input : None. // Output : This function returns TRUE if stack is empty and returns FALSE if stack is not empty. { if (top == -1) return 1; else return 0; }// End of function void main(void) { char I[20],P[20]; int iChoice; clrscr(); // Increase the current length of the stack.. // insert the item iData.

do { printf("1. Infix to Postfix\n"); printf("2. Infix to Prefix\n"); printf("3. Exit\n"); printf("Enter your choice\n"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("Enter the infix expression\n"); gets(I); fnInfixToPostfix (I,P); printf("\nEquivalent postfix expression is:%s\n",P); break; case 2: printf("Enter the infix expression\n"); gets(I); fnInfixToPrefix (I,P); printf("Equivalent postfix expression is:%s\n",P); break; case 3: exit(1); default: printf("Wrong Choice\n"); } }while(1); }

6.14 MCQ Chapter 6 1. Stack can be implemented by (a) Array (b) Linked List

(c) Both (a) and (b)

(d) none of these

2. Consider the following four statements. (i) A stack is an ordered collection of items into which new items may be inserted and from which items may be deleted at the one end called top of the stack. (ii) A stack is an ordered collection of items into which new items may be inserted into an arbitrary location. (iii) Dynamic implementation stacks using Java language are practically impossible. (iv) One of the stack applications is used in complier design. Which one of the following is correct in relation to the stacks? (a) (i) and (iv) only (b) (i), (ii) and (iii) only (c) (i) only (d) (iv) only (e) (i), (ii) and (iv) only 3. Consider the following infix expression:

(( A+B) * C (D-E)) ^ (F + G) Which of the following is a / are correct equivalent expression(s) for the above? (a) ^ - * + A B C D E F G+ (b) ^ - * + A B C D E F+ G (c) ^ - * + A B C D E + F G (d) - ^ * + A B C D E + F G 4. Which of the following statements is correct in connection with stacks? (a) Return and remove the most recently inserted item from the stack, if stack is not empty. (b) Insert a new item to the top of the stack, if stack is full. (c) Elements can be deleted from both ends. (d) Return and remove the least recently inserted item from the stack, if stack is not empty. 5. Which of the following is a / are possible operation(s) in connection with stacks? (a) Reverse the order of elements on stack S using two additional stacks. (b) Reverse the order of elements on stack S using additional variables. (c) Reverse the order of elements on stack S using one stack and some additional queues. (d) Sort the elements of stacks using one additional stack. Questions No. 6 and No. 7 are based on the following postfix expression S and the initial values of the variables. S=AB-C+DEF-+^ Assume that A=3, B=2, C=1, D=1, E=2, F=3 6. If the above S is evaluated using a stack, what is/are the intermediate value(s) on the top of the stack? (a) 3 (b) 0 (c) 2 (d) -1 7. What would be the final output of the stack? (a) 1 (b) 2 (c) 0 (d) -1 8. Evaluate the following prefix expression + * 2 + / 1 4 2 5 1 (a) 13 (b) 14 (c) 15 (d) 27 9. Evaluate the following prefix expression - * 6 3 4 1 (a) 25 (b) 15 (c) 23 (d) 12 10. Evaluate the following prefix expressions + + 2 6 + - 1 3 2 4 (a) 25 (b) 15 (c) 23 (d) 12

11. Convert the following infix expression to postfix notation (A + 2) * (B + 4) - 1 (a) A 2 + B 4 + * 1 (b) 1 * + 4 B + 2 A (c) A 2 B 4 + + X l (d) A 2 B 4 1 + + * -. 12. Convert the following infix expression to post fix notation Z (((X + I) * 2) - 5)/Y (a) Z X I + * 2 5 / Y (b) Z X I + 2 * 5 Y I (c) Z X I + 2 * 5 Y / (d) Z X I + 2 5 * - Y /-. 13. A data structure in which an element is added and removed only from one end. is known (a) queue (b) stack (c) in-built data structure (d) None of these 14. Get a node, store new element and insert the new node at the top" refers to insert operation in non empty (a) stack (c) array (b) queue (d) None of these. 15. A data structure in which elements are added or removed only at a fixed end is known as (a) queue (b) array (c) stack (d) Linked List. 16. One can convert an infix expression to a postfix expression using a (a) stack (b) queue (c) dequeue (d) none of these 17. The integers 1, 2, 3, 4 are pushed into the stack in that order. They may be popped out of the stack in any valid order. The integers which are popped out produce a permutation of the numbers 1,2,3,4. Which of the following permutation can never be produced in such a way? (0) 1,2,3,4. (b) 4,3,2, 1. (c) 4, 2, 3. 1. (d) 3, 2,4, 1. 18. Which of the following types of expressions do not require precedence rules for evaluation? (a) Fully parenthesized infix expression (b) Postfix expression (c) Partially parenthesized infix expression (d) More than one of the above 19. Consider the following statements. Which of the following is TRUE. (a) An expression in prefix form is the reverse of the same expression in postfix form. (b) An expression in prefix form is the reverse of the same expression in postfix form if the operators are

+, * and /. (c) None of the above. (d) Both (a) & (b). 20. Consider the following recursive "C" function to compute the l1-th term in the Fibonacci sequence which is denoted by F (11). int fib (int n) if n(==O) return 0; else if (n == 1) return 1; else return (Fib (n-1) + fib (n-2)); } Number of additions performed by fib (n) is : (a) F(n + 1) (b) F(n + 1) - 1 (c) 2n (d) None of the above 21. The prefix expression for the following infix expression a*(b+c)/e-f is : (a) /*a+bc-e-f (b) -/*abcef (e) -/* a + bce (d) none of the above 22. The following sequence of operations are performed on a stack PUSH(10), PUSH(20), POP, PUSH(10), PUSH(20), POP, POP. POP, PUSH(20), POP The sequence of values popped out is : (GATE-1991) (a) 20, 10, 20, 10, 20 (b) 20, 20, 10, 10, 20 (c) 10, 20, 20, 10, 20 (d) 20, 20, 10, 20, 10 23. ikn O(n), where O(n) stands for order n is : (more than 1 choice may be correct). (a) O(n2) (b)O(n3) (c)O(3n2) (d) all of these 24. Which of the following permutation can be obtained in the output (in the same order) using a stack assuming that the input is the sequence 1,2,3,4,5 in that order? (GATE-1994) (a) 3, 4, 5, 1, 2 (b) 3, 4, 5, 2, 1 (e) 1, 5, 2. 3, 4 (d) 5, 4, 3, 1, 2 25. The postfix expression for the infix expression A + B * (C + D)/ F + D * E is: (a) AB+CD+*F/D+E* (e) A*B+CD/F*DE++ (b) AB+ CD+ * F/D+E* (d) A + * BCD/F* DE++ (GATE-1995)

26. Which of the following is essential for converting an infix expression to the postfix form efficiently. (GATE-1997) (a) An operator stack (b) An operand stack (e) An operand stack and operator stack (d) a parse tree

27. If the sequence of operations - push (1), push (2), pop, push (1) , push (2), pop, pop, pop, push (2), pop, are performed on a stack, the sequence of popped out values are (a) 2, 2, 1, 1, 2 (b) 2, 2, 1, 2, 2 (c) 2, 1, 2, 2, 1 (d) 2, 1, 2, 2, 2 28. In evaluating the arithmetic expression 2 * 3 - (4 + 5), using stacks to evaluate its equivalent post-fix form, which of the following is stack output? (a) -3 (b) 7 (c) 3 (d) 0 29. Stack A has the entries a, b, c (with a on top). Stack B is empty. An entry popped out of stack A can be printed immediately or pushed to stack B. An entry popped out of stack B can only be printed. In this arrangement, which of the following permutations of a, b, c is not possible? (a) b a c (b) b c a (c) cab (d) a b c 30. In the previous problem, if the stack A has 4 entries, then the number of possible permuta tions will be (a) 24 (b) 12 c) 21 (d) 14 31. Stacks cannot be used to (a) evaluate an arithmetic expression in postfix form (b) implement recursion (c) convert a given arithmetic expression in infix form to its equivalent postfix form. (d) allocate resources (like CPU) by the operating system 32. The postfix expression for the infix expression A + B * (C + D) / F + D * E is: (a) AB + CD + * F / D + E * (b) ABCD + *F / + DE* + (c) A*B + CD / F*DE ++ (d) A + *BCD / F*DE ++ 33. The infix priorities of + , *, ^, / could be (a) 5, 1,2, 7 (b) 7,5,2, 1 (c) 1, 2, 5, 7 (d) 5,2,2,4

34. The expression 1 * 2 ^ 3 * 4 ^ 5 * 6 will be evaluated to (a) 3230 (b) 16230 . (c) 49152 (d) 173458 35. Stack is useful for implementing (a) radix sort (b) breadth first search (c) recursion (d) depth first search 36. An item that is read as input can be either pushed to a stack and later popped and printed, or printed directly. Which of the following will not definitely be the output if the input is the sequence of items - 1, 2 , 3 , 4 , 5? (a) 3, 4, 5, 1, 2 (b) 3, 4, 5, 2, 1 (c) 1, 5, 2, 3, 4 (d) 5, 4, 3, 1, 2 37. Which of the following is essential for converting an infix expression to the' postfix form efficiently? (a) An operator stack (b) An operand stack (c) An operator stack and an operand stack (d) A parse tree 38. Which of the following can be describe as a record?

(a) (c)

Stack List^.Info

(b) (d)

List^ Stack^.Info.

39. Which data structure would you mostly likely see in a nonrecursive implementation of a recursive algorithm? (a) Link list (b) Queue (c) Stack (d) Trees. 40. Preorder is same as (a) depth-first order (c) topological order (b) breadth-first order (d) linear order

41. Which of the following can be describe as a pointer? (a) Stack (b) List^ (c) List^.Info (d) Stack^.Info 42. Which of the following can be describe as a array? (a) Stack (b) List^ (c) List^.Info (d) Stack^.Info 43. Which of the following can be describe as a integer? (a) Stack (b) List^ (c) List^.Info (d) Stack^.Info

Solutions:
1. c 2. a 3. c 4. a 16. a 17. c 18. d 19. d 31. d 32. b 33. d 34. c 6.15 Exercise Chapter 6 1. 2. 5. a 6. c 20. b 21. b 35. c,d 36. d 7. a 22. b 37. a 8. b 23. d 38. b 9. b 24. b 39. c 10. d 25. b 40. a 11. a 26. a 41. a 12. c 27. a 42. c 13. b 28. a 43. d 14. a 29. c 15. c 30. b

What is a stack? State one real life example for stack and show how it can be called as stack. Calculate the value of the following postfix expression: a) 5 7 8 + * 4 6 9 / + * b) 2 4 7 - / 5 3 5 * + / Convert the following infix notation to postfix expression ((X + Y) (W + Z) * K / L ) * M Convert the following postfix notation to infix expression abcdef+*Write a C program to implement a stack using (i) Array, (ii) Linked List

3. 4. 5.

6. What is Dynamic Memory allocation related to stack? write a C function using Dynamic Memory allocation by which you can implement a Stack. 7. How can you represent a polynomial expression using stack? Give one example and show how you can compute the result of the expression by stack. 8. How can you check a string for palindromeness by a stack? Give one example and show how you can compute that a string is palindrome or not by stack.

9. How can you reverse a string using stack? Give one example and show how you can reverse a given string by stack. 10. What is a multiple stack? State one application of multiple stack and show how multiple stack is implemented there.

Chapter - 7 QUEUE
7.1 Definition
A queue is a linear data structure in which deletion can take place only at one end, called the front, and insertions can take place only at the other end called the rear. Rear Rear Rear Front 10 INSERT(10) Front 20 10 INSERT(20) Front 30 20 10 INSERT(30) DELETE() DELETE() Front Rear 30 20 Front 30 Rear

Figure 7.1: Insertion and deletion operations in a queue Thus here the first inserted item is deleted first. So queue is called a FIFO (First In First Out) list.

7.2

Implementation of queue using array

In this section we will create a queue of integers using array. For this purpose an integer array arrQueue of size MAXSIZE is used and the array declaration is: int arrQueue[MAXSIZE]; Here front and rear are two integer variables and the initial value of both is 0. From the above array declaration the maximum value of rear may be MAXSIZE. So, rear = MAXSIZE indicates queue is full and front = rear indicates queue is empty. Algorithm 7.1 Algorithm for insertion into queue 1. Algorithm fnQInsertion (arrQueue[], iData) 2. // Purpose : This algorithm inserts an item in a linear queue. 3. // Input : arrQueue is the array to implement the queue. iData is the item to be inserted into the queue. 4. // Output : None. 5. // Comments : rear and front both are initialized from 0. 6. { 7. if(fnQFull() == TRUE) // Check for queue full condition. 8. Queue is full; 9. else 10. { 11. arrQueue[rear] = iData; //Insert the item iData onto queue. 12. rear = rear+1; //Extend the length of queue. 13. } 14. }// End of Algorithm Algorithm 7.2 Algorithm for deletion from the linear queue 1. Algorithm fnQDeletion (arrQueue[]) 2. // Purpose : This algorithm deletes an item from the linear queue. 3. // Input : arrQueue is the array to implement the queue. 4. // Output : The item iData that has been deleted. 5. // Comments : rear and front both are initialized from 0. 6. { 7. if(fnQEmpty() == TRUE) // Check for queue empty condition. 8. Queue is empty; 9. else 10. { 11. iData = arrQueue[front]; // item deleted. 12. front = front+1; // Shorten the length of queue. 13. return iData; //Return the data. 14. } 15. }// End of Algorithm Algorithm 7.3 Algorithm to display elements of queue Queue elements are in the range from front to rear of the array arrQueue[]. So elements of the array arrQueue[] from the index front to index rear is to be displayed. 1. Algorithm fnQDisplay (arrQueue[]) 2. // Purpose : This algorithm displays the elements of a linear queue. 3. // Input : arrQueue is the array to implement the queue. 4. //Output : None. 5. {

6. if (fnQEmpty() == TRUE) // Check for queue empty condition. 7. Nothing to display; 8. else 9. { 10. for(iCounter = front;iCounter<rear;iCounter++) 11. print arrQueue[iCounter]; 12. } 13. }// End of Algorithm Algorithm 7.4 Algorithm for checking the queue full condition The queue is full if the value of rear reaches MAXSIZE i.e. the maximum size of the queue. 1. Algorithm fnQFull() 2. // Purpose : This algorithm checks whether the queue is full or not. 3. // Input : None. 4. // Output : This algorithm returns TRUE if queue is full and returns FALSE if queue is not full. 5. { 6. if (rear == MAXSIZE) 7. return TRUE; 8. else 9. return FALSE; 10. }// End of Algorithm Algorithm 7.5 Algorithm for checking the queue empty condition The queue is empty if the value of front reaches rear. 1. Algorithm fnQEmpty() 2. // Purpose : This algorithm checks whether the queue is empty or not. 3. // Input : None. 4. // Output : This algorithm returns TRUE if queue is empty and returns FALSE if queue is not empty. 5. { 6. if (front == rear) 7. return TRUE; 8. else 9. return FALSE; 10. }// End of Algorithm The following example shows how the above algorithms work: Example 7.1: Let MAXSIZE=2. Initially: front = rear = 0 2 1 0 front=rear fnQInsertion(arrQueue[],10) 2 1 0 1 0 rear front

fnQInsertion(arrQueue[],20) 2 rear 1 20 0 1 front 0 fnQInsertion(arrQueue[],30) 2 rear 1 20 0 1 front 0 As rear = MAXSIZE, queue is full. So, no further insertion will take place. fnQDeletion(arrQueue[]) 2 rear 1 2 front 0 0 10 item deleted = 10

fnQDeletion(arrQueue[]) 2 front=rear 1 20 0 10 item deleted = 20 fnQDeletion(arrQueue[]) 2 front=rear 1 20 0 10 As front = rear, queue is empty. So, no further deletion will take place.

7.3

Implementation of Queue using dynamic memory allocation

If we implement queue using array, then insertion of new data elements may cause an overflow and so the queue full condition is to be checked during each insertion operation. But queue using dynamic memory allocation does not demand to check the queue full condition at all in each push operation as here we can insert any number of data elements depending upon the available memory. Figure 6.2 shows a queue using dynamic memory allocation: NULL front Figure 7.2: Queue using dynamic memory allocation rear

Here new data item is inserted at the end of the list and existing data item is deleted from the beginning of the list. Each node of the above list has two fields: data field and pointer field. Data field contains the data element and the pointer field contains the address of the next node in the list. So from the concept of self referential structure the structure definition of such a node is: struct Node { int iData; struct Node *ptrNext; }; typedef struct Node QueueNode; We can implement the linear queue using only one pointer pointing to the first node in the list. But for simplicity we are using two pointer variables front and rear. Here, front and rear both are QueueNode type global pointer variable and their initial value is NULL. QueueNode *front,*rear; front = NULL; rear = NULL; The NULL value of front (or rear) indicates empty queue. Algorithm for insertion operation Algorithm 7.6 1. Algorithm fnQInsertion(iData) 2. // Purpose : This algorithm inserts an element into linear queue using dynamic memory allocation. 3. // Input : Value of the item iData that is to be deleted. 4. // Output : None. 5. // Comments : Initial value of both rear and front is NULL. 6. { 7. QueueNode *ptrNewNode; //Assign a pointer to new node. 8. ptrNewNode = (QueueNode*) malloc(sizeof(QueueNode)); //Create the new node. 9. ptrNewNode->iData = iData; //Load the data field of the newly created node. 10. ptrNewNode->ptrNext = NULL; // Now new node is the last node in the list. 11. if(fnQEmpty() == TRUE) // If list is empty. 12. front = rear = ptrNewNode; //Set both the value of front and rear. 13. else 14. { 15. rear->ptrNext = ptrNewNode; //Now current rear points to the new node. 16. rear = ptrNewNode; //Set the rear to new node.. 17. } 18. }// End of Algorithm Algorithm for deletion Operation Algorithm 7.7 1. Algorithm fnQDelete() 2. // Purpose : This algorithm deletes an item from a linear queue. 3. // Input : None 4. // Output : The data item iData that has been deleted. 5. // Comments : Initial value of both rear and front is NULL. 6. { 7. QueueNode *ptrDeleteNode; 8. if(fnQEmpty() == TRUE) //Check the queue empty condition. 9. Queue is Empty; 10. else

11. { 12. iData = front->iData; 13. ptrDeleteNode = front; 14. front = front->ptrNext; 15. free(ptrDeleteNode); 16. return(iData); 17. } 18. }// End of Algorithm

//The data item to be deleted. //Set the pointer to the node to be deleted. //Set the new value of top. //De-allocate memory for the node that has been deleted. //Return the data of the deleted node.

Algorithm to display the elements of linear queue using dynamic memory allocation In this implementation, front points to the first node in the list. So to display all the elements of the queue we have to traverse the list starting from the front node till a NULL value (i.e. end of list) is encountered. The following algorithm describes the procedure. Algorithm 7.8 1. Algorithm fnQDisplay() 2. // Purpose : This algorithm displays the elements of a linear queue using dynamic memory allocation. 3. // Input : None. 4. // Output : None. 5. { 6. QueueNode *ptrDisplayNode; 7. if(fnQEmpty() == TRUE) // If queue is empty. 8. There is nothing to display; 9. else 10. { 11. ptrDisplayNode = front; //Initialize the pointer to front node. 12. while(ptrDisplayNode != NULL) //Until list is empty. 13. { 14. print (ptrDisplayNode->iData); //Print the data. 15. ptrDisplayNode = ptrDisplayNode -> ptrNext; //Go to next node. 16. } 17. } 18. }// End of Algorithm Algorithm for checking the queue empty condition Algorithm 7.9 1. Algorithm fnQEmpty() 2. // Purpose : This algorithm checks whether the queue is empty or not. 3. // Input : None. 4. // Output : This algorithm returns TRUE if queue is empty and returns FALSE if queue is not empty. 5. { 6. if (front == NULL || rear == NULL) 7. return TRUE; 8. else 9. return FALSE; 10. }// End of Algorithm The following example shows how the above algorithms work: Example 7.2: Initially: front = rear = NULL fnQInsertion(10)

ptrNewNode = (QueueNode*) malloc(sizeof(QueueNode))

ptrNewNode ptrNewNode->iData = 10; 10 ptrNewNode ptrNewNode->ptrNext = NULL 10 NULL

ptrNewNode if(rear = NULL or front = NULL) front = rear = ptrNewNode 10 NULL // this is true for our case

front = rear fnQDelete() 10 iData = front->iData = 10 ptrDeleteNode = front 10 NULL NULL front

ptrDeleteNode = front front = front -> ptrNext = NULL free(ptrDeleteNode) return 10

7.4

Limitation of linear queue


5 rear 4 50 3 4 front 0 2 1 0

Consider the snapshot above in a linear queue. The queue can contain maximum 5 elements. Currently the queue has only 2 elements. But no more elements can be inserted into the queue as the value of rear is 5 i.e. 3 spaces is being simply wasted. This limitation can be solved using circular queue.

7.5

Circular Queue

In a circular queue when rear (or front) reaches MAXSIZE - 1 then rear (or front) will be 0 i.e. the increment of rear (or front) is rear = (rear + 1) % MAX, and also front = (front + 1) % MAX 4 50 3 4 front 0 2 1 0 rear Now we can insert a new element in the array index 0. 4 3 .

2 1 0

n-1

Figure 7.3: Circular queue of n elements Here also the initial value of both front and rear is 0. So, queue empty condition for a circular list is front = rear. Now suppose MAXSIZE = 3 and execute the following sequence of statements: fnCQInsert(arrCQueue[],10) arrCQueue[0] = 10 rear = (rear + 1)%3 =1 2

1 0
fnCQInsert(arrCQueue[],20) arrCQueue[1] = 20 rear = (rear + 1)%3 = 2

rear 10 front

2 1 0 20 10

rear

front

fnCQInsert(arrCQueue[],30) arrCQueue[2] = 30 rear = (rear + 1)%3 = 0

2 1 0

30 20 10 front rear

Now the queue is full. Unfortunately here also front = rear. So it is difficult to differentiate between the queue full and queue empty condition. But notice one thing, if front reaches rear during the deletion operation then obviously it indicates that the queue is empty and if rear be equal to front during the insertion operation into queue then this means queue is full. To implement this logic two Boolean variables boolQfull (to indicate queue is full) and boolQempty (to indicate queue is empty) are taken. Initially boolQfull is set to FALSE and boolQempty is set to TRUE. Algorithm for insertion into circular queue Algorithm 7.10 1. Algorithm fnCQInsert(arrCQueue[], iData) 2. // Purpose : This algorithm inserts an item into circular queue. 3. //Input : arrCQueue is the array to implement the circular queue and iData is the item to be inserted. 4. //Output : None. 5. // Comments : Boolean variable boolQfull = TRUE indicates that queue is full. 6. { 7. if(boolQfull == TRUE) //If queue is full. 8. Queue is full; 9. else 10. { 11. arrCQueue[rear] = iData; //Insert the data. 12. rear = (rear + 1) % MAXSIZE; //Increment rear. 13. boolQempty = FALSE; //As an insertion takes place, so now queue is not empty. 14. if (rear == front) 15. boolQfull = TRUE; //if rear be equal to front then queue is full. 16. } 17. }// End of Algorithm Algorithm for deletion into circular queue Algorithm 7.11 1. Algorithm fnCQDelete(arrCQueue[]) 2. // Purpose : This algorithm deletes an item from circular queue. 3. //Input : arrCQueue is the array to implement the circular queue. 4. //Output : This algorithm returns the item iData that has been deleted. 5. // Comments : Boolean variable boolQempty = TRUE indicates that queue is empty. 6. { 7. if(boolQempty == TRUE) //If queue is empty. 8. Queue is empty; 9. else 10. { 11. iData = arrCQueue[front] //item deleted. 12. front = (front + 1) % MAXSIZE; //Increment rear.

13. boolQfull = FALSE; 14. if (front == rear) 15. boolQempty = TRUE; 16. } 17. }//End of Algorithm

//Set boolQfull to false as now queue is not full. //if front be equal to rear then queue is empty.

7.6

Deque(Double Ended Queue)

Deque: It is a linear list where items can be inserted or deleted at either end but not from the middle. That is, here we can insert or delete elements from both the rear and front end. Hence it is also called a double ended queue. Deletion Insertion 10 20 5 30 20 40 Insertion Deletion front rear There are two types of deque due to the restrictions of performing insertion and deletion only at one end. They are: 1) Input restricted deque: Here insertion can be done at one end of the list but allows deletion from both the rear and front end. 2) Output restricted deque: Here deletion can be done at one end of the list but allows insertion from both the rear and front end. Since both insertion and deletion are performed from either end, it is necessary to design four algorithms to perform the following four operations: 1) Insertion of new element into the rear end of the queue: This operation is similar to normal insertion into a linear queue discussed in the section 7.2. 2) Insertion of new element into the front end of the queue: This operation is a tricky one. Here the new element is inserted at the front end of the queue. As here a new data item is being inserted so queue full condition is to be checked. The general logic to implement this operation is first decrement the value of front and then insert the data item at the current position of the front. So if the value of front is 0, then no further insertion can take place as after decrementing new value of front will be -1 which is not feasible (because array index -1 does not exist). Hence in this case queue full condition is front = 0. 3) Deletion of existing data item from the rear end of the queue: Here first store the data value of the index rear from the array and then decrement the value of rear. The queue empty condition is similar to linear queue discussed in the section 7.2. 4) Deletion of existing data item from the front end of the queue: This is similar to the deletion operation from the linear queue discussed in the section 7.2. Algorithm for insertion into the rear end of the queue Algorithm 7.12 1. Algorithm fnDQInsert_At_Rear(arrDeque[], iData) 2. // Purpose : This algorithm inserts an element at the rear of a queue. 3. // Input : arrDeque[] is the array to implement the queue and iData is the data item to be inserted. 4. // Output : None. 5. // Comments : Value of both front and rear are initialized from 0. 6. { 7. if (fnQFull() == TRUE) 8. Queue is full; 9. else 10. {

11. arrDeque[rear] = iData; 12. rear = rear + 1; 13. } 14. }//End of Algorithm Algorithm for insertion into the front end of the queue Algorithm 7.13 1. Algorithm fnDQInsert_At_Front(arrDeque[], iData) 2. // Purpose : This algorithm inserts an element at the front of a queue. 3. // Input : arrDeque[] is the array to implement the queue and iData is the data item to be inserted. 4. // Output : None. 5. // Comments : Value of both front and rear are initialized from 0. 6. { 7. if (front == 0 ) 8. Queue is full; 9. else 10. { 11. front = front - 1; 12. arrDeque[front] = iData; 13. } 14. }//End of Algorithm Algorithm for deletion from the rear end of the queue Algorithm 7.14 1. Algorithm fnDQDelete_From_Rear(arrDeque[]) 2. // Purpose : This algorithm deletes an element from the rear end of a queue. 3. // Input : arrDeque[] is the array to implement the queue. 4. // Output : The data item iData that has been deleted. 5. // Comments : Value of both front and rear are initialized from 0. 6. { 7. if (fnQEmpty() == TRUE) 8. Queue is empty; 9. else 10. { 11. rear = rear - 1; 12. iData = arrDeque[rear]; 13. return iData; 14. } 15. }//End of Algorithm Algorithm for deletion from the front end of the queue Algorithm 7.15 1. Algorithm fnDQDelete_From_Front(arrDeque[]) 2. // Purpose : This algorithm deletes an element from the front end of a queue. 3. // Input : arrDeque[] is the array to implement the queue. 4. // Output : The data item iData that has been deleted. 5. // Comments : Value of both front and rear are initialized from 0. 6. {

7. if (fnQEmpty() == TRUE) 8. Queue is empty; 9. else 10. { 11. iData = arrDeque[front]; 12. front = front + 1; 13. return iData; 14. } 15. }//End of Algorithm

7.7

Priority Queue

A priority queue is a collection of elements such that each element has been assigned a priority such that the order in which elements are deleted and processed comes from the following rules: 1. An element of higher priority is processed before any element of lower priority. 2. Two elements with same priority are processed according to the order in which they are added to the queue. To implement the priority queue minimum two queues are needed one to store the elements and the other to assign the corresponding priorities. There are two types of priority queue: 1) Ascending priority queue: Here the elements can be inserted arbitrarily but only the element with the smallest priority can be deleted. 2) Descending priority queue: Here the elements can be inserted arbitrarily but only the element with highest priority can be deleted. Here we will discuss the insertion and deletion algorithm for ascending priority queue. From the properties of ascending priority queue we can insert data in any where of the array but the data with smallest priority can be deleted. For this purpose, during insertion first find the position of the new data element in the array according to its priority and store the data in that position. Hence the array becomes a sorted one in ascending order according to the priorities of the elements and hence the first data value of the array is the data item with smallest priority which is to be deleted.

Algorithm for insertion into ascending priority queue Algorithm 7.16 1. Algorithm fnAPQInsert(arrQueue[], arrPQueue[], iData, iPriority) 2. // Purpose : This algorithm inserts an element in an ascending priority queue. 3. // Input : arrQueue[] is the array to store the data value and arrPQueue[] is the array to store the //corresponding priorities. iData and iPriority are the data item and its priority respectively. 4. // Output : None. 5. // Comments : Value of both front and rear are initialized from 0. 6. { 7. if(rear == MAXSIZE) 8. Queue is full; 9. else 10. { 11. iPosition = fnFind_Position(arrPQueue[], iPriority); 12. fnInsertion_into_Array(arrQueue[], rear, iPosition, iData); 13. fnInsertion_into_Array(arrPQueue[], rear, iPosition, iPriority); 14. rear = rear + 1; 15. } 16. }// End of Algorithm

Algorithm for deletion from the ascending priority queue Algorithm 7.17 1. Algorithm fnAPQDelete(arrQueue[], arrPQueue[]) 2. // Purpose : This algorithm inserts an element in an ascending priority queue. 3. // Input : arrQueue[] is the array to store the data value and arrPQueue[] is the array to store the //corresponding priorities. 4. // Output : The data item iData that has been deleted. 5. // Comments : Value of both front and rear are initialized from 0. 6. { 7. if(front == rear) 8. Queue is empty; 9. else 10. { 11. iData = arrQueue[front]; 12. iPriority = arrPQueue[front]; 13. front = front + 1; 14. return iData; 15. } 16. }// End of Algorithm

7.8

Implementation of queue using stacks

Stack is a Last In First Out list. But queue follows the rule First In First Out. Now a queue can be implemented using two stacks. Suppose two stacks Stack1 and Stack2 are available. Now inset the items in the first stack Stack1. But for deletion use Stack2. First pop all the elements from Stack1 and push them into Stack2. Now the top element of Stack2 is the element that has been inserted first into the Stack1. So now pop the top element of Stack2 and display it. Then again pop all the elements of Stack2 and push them into Stack1 and perform the similar kind of operation for more insertion and deletion operations. The following example describes this process. Suppose we have two stacks Stack1 and Stack2 with same size. Consider the following statements: PUSH (Stack1, 10) PUSH (Stack1, 20) PUSH (Stack1, 30) PUSH (Stack1, 40) After executing the above statements, the content of Stack1 and Stack2 is shown in figure7.4.

40 30 20 10
Stack1 Stack2 Figure 7.4 Now pop all the elements from Stack1 and insert them into Stack2 (shown in figure7.5). PUSH (Stack2, POP (Stack1)) // Pop 40 from Stack1 and Push 40 onto Stack2 PUSH (Stack2, POP (Stack1)) // Pop 30 from Stack1 and Push 30 onto Stack2 PUSH (Stack2, POP (Stack1)) // Pop 20 from Stack1 and Push 20 onto Stack2

PUSH (Stack2, POP (Stack1))

// Pop 10 from Stack1 and Push 10 onto Stack2

10 20 30 40
Stack2 Figure 7.5 Now pop from Stack2 deletes element 10 which is first inserted element. Thus we can implement a queue. After deleting the element from Stack2, again push all the elements of Stack2 onto Stack1 (shown in figure 7.6). That is, external Push operation is performed on Stack1 and Final Pop operation is performed from Stack2. Stack1

40 30 20
Stack1 Figure 7.6 Stack2

Algorithm 7.18 1. Algorithm fnQ_Using_Stack_Insert(arrStack1[], iData) 2. // Purpose : This algorithm inserts an item in a queue implemented using two stacks. 3. // Input : arrStack1[] is the array to implement Stack1 and iData is the data item to be inserted. 4. // Output : None 5. { 6. if (top1 == MAXSIZE-1) 7. Queue is full; 8. else 9. fnPush(arrStack1[], iData); 10. }//End of Algorithm Algorithm 7.19 1. Algorithm fnQ_Using_Stack_Delete(arrStack1[], arrStack2[]) 2. // Purpose : This algorithm inserts an item in a queue implemented using two stacks. 3. // Input : arrStack1[] and arrStack2[] are the arrays to implement Stack1 and Stack2 respectively. 4. // Output : The data item iData that has been deleted.

5. { 6. if ( top1 == -1) 7. Queue is empty; 8. else 9. { 10. while(top1 != -1) 11. fnPush(arrStack2[], fnPop(arrStack1[])); 12. iData = fnPop(arrStack2[]); 13. while(top2 != -1) 14. fnPush(arrStack1[], fnPop(arrStack2[])); 15. return iData; 16. } 17. }// End of Algorithm

7.9

Application of Queue

1) Job queue and process queue are implemented using queue for process scheduling. 2) Printer server routine is implemented using queue. 3) All types of customer service software (like Railway ticket reservation) are designed using queue for proper service to customer. /* Program to implement a linear queue using array */ /* QArray.c*/ #include<stdio.h> #include<conio.h> #define MAXSIZE 50 #define TRUE 1 #define FALSE 0 int front=0,rear=0; //Prototype Declaration void fnQInsertion(int[], int); int fnQDeletion(int[]); void fnQDisplay (int []); int fnQFull(); int fnQEmpty(); void fnQInsertion (int arrQueue[], int iData) // Purpose : This function inserts an item in a linear queue. // Input: arrQueue is the array to implement the queue. iData is the item to be inserted into the queue. // Output : None. // Comments : rear and front both are initialized from 0. { if(fnQFull() == TRUE) // Check for queue full condition. printf("\nQueue is full\n"); else { arrQueue[rear] = iData; //Insert the item iData onto queue. rear = rear+1; //Extend the length of queue. } }// End of Function

int fnQDeletion (int arrQueue[]) // Purpose : This function deletes an item from the linear queue. // Input : arrQueue is the array to implement the queue. // Output : The item iData that has been deleted. // Comments : rear and front both are initialized from 0. { int iData; if(fnQEmpty() == TRUE) // Check for queue empty condition. printf("\nQueue is empty\n"); else { iData = arrQueue[front]; // item deleted. front = front+1; // Shorten the length of queue. return iData; //Return the data. } }// End of Function void fnQDisplay (int arrQueue[]) // Purpose : This function displays the elements of a linear queue. // Input: arrQueue is the array to implement the queue. //Output : None. { int iCounter; if (fnQEmpty() == TRUE) // Check for queue empty condition. printf("\nNothing to display\n"); else { for(iCounter = rear-1;iCounter>=front;iCounter--) printf("%d\n",arrQueue[iCounter]); } }// End of function int fnQFull() // Purpose : This function checks whether the queue is full or not. // Input: None. // Output : This function returns TRUE if queue is full and returns FALSE if queue is not full. { if (rear == MAXSIZE) return TRUE; else return FALSE; }// End of function int fnQEmpty() // Purpose : This function checks whether the queue is empty or not. // Input: None. // Output : This function returns TRUE if queue is empty and returns FALSE if queue is not empty. { if (front == rear) return TRUE; else

return FALSE; }// End of function void main() { int arrQUEUE[50],iChoice,iData; clrscr(); do { printf("1. Insertion\n"); printf("2. Deletion\n"); printf("3. Display\n"); printf("4. Exit\n"); printf("Enter your choice\n"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("Enter the element"); scanf("%d",&iData); fnQInsertion(arrQUEUE,iData); break; case 2: if(fnQEmpty()==TRUE) printf("Queue is empty\n"); else printf("Item deleted = %d\n",fnQDeletion(arrQUEUE)); break; case 3: printf("\n******Queue Content******\n"); fnQDisplay(arrQUEUE); printf("\n*************************\n"); break; case 4: exit(1); default: printf("\nWrong Choice\n"); } }while(1); } /* Program to implement a linear queue using linked list */ /* QLinked.c*/ #include<stdio.h> #include<conio.h> #include<alloc.h> #define MAXSIZE 50; #define TRUE 1 #define FALSE 0 struct Node { int iData; struct Node *ptrNext;

}; typedef struct Node QueueNode; QueueNode *front=NULL,*rear=NULL; //front = NULL; //rear = NULL; void fnQInsertion(int iData) // Purpose : This function inserts an element into linear queue using dynamic memory allocation. // Input: Value of the item iData that is to be deleted. // Output : None. // Comments : Initial value of both rear and front is NULL. { QueueNode *ptrNewNode; //Assign a pointer to new node. ptrNewNode = (QueueNode*) malloc(sizeof(QueueNode)); //Create the new node. ptrNewNode->iData = iData; //Load the data field of the newly created node. ptrNewNode->ptrNext = NULL; // Now new node is the last node in the list. if(fnQEmpty() == TRUE) // If list is empty. front = rear = ptrNewNode; //Set both the value of front and rear. else { rear->ptrNext = ptrNewNode; //Now current rear points to the new node. rear = ptrNewNode; //Set the rear to new node.. } }// End of Function int fnQDelete() // Purpose : This function deletes an item from a linear queue. // Input: None // Output : The data item iData that has been deleted. // Comments : Initial value of both rear and front is NULL. { int iData; QueueNode *ptrDeleteNode; if(fnQEmpty() == TRUE) //Check the queue empty condition. { printf("\nQueue is Empty\n"); return 0; } else { iData = front->iData; //The data item to be deleted. ptrDeleteNode = front; //Set the pointer to the node to be deleted. front = front->ptrNext; //Set the new value of top. free(ptrDeleteNode); //Deallocate memory for the node that has been deleted. return(iData); //Return the data of the deleted node. } }// End of Function void fnQDisplay() // Purpose : This function displays the elements of a linear queue using dynamic memory allocation. // Input: None.

// Output : None. { QueueNode *ptrDisplayNode; if(fnQEmpty() == TRUE) printf("\nNothing to display\n"); else { ptrDisplayNode = front; while(ptrDisplayNode != NULL) { printf("%d\n",ptrDisplayNode->iData); ptrDisplayNode = ptrDisplayNode -> ptrNext; } } }// End of Function

// If queue is empty.

//Initialize the pointer to front node. //Until list is empty. //Print the data. //Go to next node.

int fnQEmpty() // Purpose : This function checks whether the queue is empty or not. // Input: None. // Output : This function returns TRUE if queue is empty and returns FALSE if queue is not empty. { if (front == NULL || rear == NULL) return TRUE; else return FALSE; }// End of Function void main() { int iChoice,iData; clrscr(); do { printf("1. Insertion\n"); printf("2. Deletion\n"); printf("3. Display\n"); printf("4. Exit\n"); printf("Enter your choice\n"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("Enter the element"); scanf("%d",&iData); fnQInsertion(iData); break; case 2: if(fnQEmpty()==TRUE) printf("Queue is empty\n"); else printf("Item deleted = %d\n",fnQDelete()); break;

case 3: printf("\n******Queue Content******\n"); fnQDisplay(); printf("\n*************************\n"); break; case 4: exit(1); default: printf("\nWrong Choice\n"); } }while(1); } /* Program to implement a Priority Queue*/ /* File Name: PriorityQ.c */ #include<stdio.h> #include<conio.h> #define MAXSIZE 50 #define TRUE 1 #define FALSE 0 int front=0,rear=0; //Prototype Declaration void fnAPQInsert(int [], int [], int, int); int fnAPQDelete(int[], int[]); int fnFind_Position(int[], int); void fnInsertion_into_Array(int[], int, int, int); void fnQDisplay (int[],int[]); int fnQFull(); int fnQEmpty(); void fnAPQInsert(int arrQueue[], int arrPQueue[], int iData, int iPriority) // Purpose : This function inserts an element in an ascending priority queue. // Input: arrQueue[] is the array to store the data value and arrPQueue[] is the array to store the //corresponding priorities. iData and iPriority are the data item and its priority respectively. // Output : None. // Comments : Value of both front and rear are initialized from 0. { int iPosition; if(fnQFull()==TRUE) printf("\nQueue is full\n"); else { if(fnQEmpty()==TRUE) iPosition = front; else iPosition = fnFind_Position(arrPQueue, iPriority); fnInsertion_into_Array(arrQueue, rear, iPosition, iData); fnInsertion_into_Array(arrPQueue, rear, iPosition, iPriority); rear = rear + 1; } }// End of Function

int fnFind_Position(int arrPQueue[], int iPriority) // Purpose : This function finds the position of the element according to priority. // Input: arrPQueue[] is the array to store the priorities and iPriority is the priority of the new element. // Output : Returns the position. // Comments : Value of both front and rear are initialized from 0. { int iCounter; for(iCounter = front;iCounter<rear;iCounter++) if(arrPQueue[iCounter]>iPriority) return iCounter; return iCounter; }// End of Function void fnInsertion_into_Array(int arrData[], int n, int k, int item) // Purpose : This function inserts an element into one dimensional array. // Input: arrData[] is an one dimensional array with n number of elements. Element item is to be inserted into the kth position in the array. // Output : None. { int i; for(i=n-1;i>=k;i--) arrData [i+1]= arrData [i]; arrData[k]=item; // Insert the item. }// End of Function int fnAPQDelete(int arrQueue[], int arrPQueue[]) // Purpose : This function inserts an element in an ascending priority queue. // Input: arrQueue[] is the array to store the data value and arrPQueue[] is the array to store the //corresponding priorities. // Output : The data item iData that has been deleted. // Comments : Value of both front and rear are initialized from 0. { int iData,iPriority; if(fnQEmpty()==TRUE) printf("\nQueue is empty\n"); else { iData = arrQueue[front]; iPriority = arrPQueue[front]; front = front + 1; return iData; } }// End of Function void fnQDisplay (int arrQueue[],int arrPQueue[]) // Purpose : This function displays the elements of a linear queue. // Input: arrQueue is the array to implement the queue. //Output : None. { int iCounter; if (fnQEmpty() == TRUE) // Check for queue empty condition.

printf("\nNothing to display\n"); else { for(iCounter = rear-1;iCounter>=front;iCounter--) printf("%d\t%d\n",arrQueue[iCounter],arrPQueue[iCounter]); } }// End of function int fnQFull() // Purpose : This function checks whether the queue is full or not. // Input: None. // Output : This function returns TRUE if queue is full and returns FALSE if queue is not full. { if (rear == MAXSIZE) return TRUE; else return FALSE; }// End of function int fnQEmpty() // Purpose : This function checks whether the queue is empty or not. // Input: None. // Output : This function returns TRUE if queue is empty and returns FALSE if queue is not empty. { if (front == rear) return TRUE; else return FALSE; }// End of function void main() { int arrQUEUE[50],arrPQueue[50],iChoice,iData,iPriority; clrscr(); do { printf("****PRIORITY QUEUE****\n"); printf("1. Insertion\n"); printf("2. Deletion\n"); printf("3. Display\n"); printf("4. Exit\n"); printf("Enter your choice\n"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("Enter the element and its priority"); scanf("%d%d",&iData,&iPriority); fnAPQInsert(arrQUEUE,arrPQueue,iData,iPriority); break; case 2: if(fnQEmpty()==TRUE)

printf("Queue is empty\n"); else printf("Item deleted = %d\n",fnAPQDelete(arrQUEUE,arrPQueue)); break; case 3: printf("\n******Queue Content******\n"); fnQDisplay(arrQUEUE,arrPQueue); printf("\n*************************\n"); break; case 4: exit(1); default: printf("\nWrong Choice\n"); } }while(1); } /* Program to implement a Deque*/ /* File Name: Deque.c */ /* Program to implement a deque */ /* DEQUE.c */ #include<stdio.h> #include<conio.h> #define MAXSIZE 50 #define TRUE 1 #define FALSE 0 int front=0,rear=0; //Prototype Declaration void fnDQInsert_At_Rear(int[], int); void fnDQInsert_At_Front(int[], int); int fnDQDelete_From_Rear(int[]); int fnDQDelete_From_Front(int[]); void fnQDisplay (int []); int fnQFull(); int fnQEmpty(); void fnDQInsert_At_Rear(int arrDeque[], int iData) // Purpose : This function inserts an element at the rear of a queue. // Input: arrDeque[] is the array to implement the queue and iData is the data item to be inserted. // Output : None. // Comments : Value of both front and rear are initialized from 0. { if (fnQFull() == TRUE) printf("\nQueue is full"); else { arrDeque[rear] = iData; rear = rear + 1; } }//End of function void fnDQInsert_At_Front(int arrDeque[], int iData)

// Purpose : This function inserts an element at the front of a queue. // Input: arrDeque[] is the array to implement the queue and iData is the data item to be inserted. // Output : None. // Comments : Value of both front and rear are initialized from 0. { if (front == 0 ) printf("\nQueue is full\n"); else { front = front - 1; arrDeque[front] = iData; } }//End of function int fnDQDelete_From_Rear(int arrDeque[]) // Purpose : This function deletes an element from the rear end of a queue. // Input: arrDeque[] is the array to implement the queue. // Output : The data item iData that has been deleted. // Comments : Value of both front and rear are initialized from 0. { int iData; if (fnQEmpty() == TRUE) printf("\nQueue is empty\n"); else { rear = rear - 1; iData = arrDeque[rear]; return iData; } }//End of function int fnDQDelete_From_Front(int arrDeque[]) // Purpose : This function deletes an element from the front end of a queue. // Input: arrDeque[] is the array to implement the queue. // Output : The data item iData that has been deleted. // Comments : Value of both front and rear are initialized from 0. { int iData; if (fnQEmpty() == TRUE) printf("\nQueue is empty\n"); else { iData = arrDeque[front]; front = front + 1; return iData; } }//End of function void fnQDisplay (int arrQueue[]) // Purpose : This function displays the elements of a linear queue. // Input: arrQueue is the array to implement the queue. //Output : None.

{ int iCounter; if (fnQEmpty() == TRUE) // Check for queue empty condition. printf("\nNothing to display\n"); else { for(iCounter = rear-1;iCounter>=front;iCounter--) printf("%d\n",arrQueue[iCounter]); } }// End of function int fnQFull() // Purpose : This function checks whether the queue is full or not. // Input: None. // Output : This function returns TRUE if queue is full and returns FALSE if queue is not full. { if (rear == MAXSIZE) return TRUE; else return FALSE; }// End of function int fnQEmpty() // Purpose : This function checks whether the queue is empty or not. // Input: None. // Output : This function returns TRUE if queue is empty and returns FALSE if queue is not empty. { if (front == rear) return TRUE; else return FALSE; }// End of function void main() { int arrDQUEUE[50],iChoice,iData; clrscr(); do { printf("1. Insert At Front\n"); printf("2. Insert At Rear\n"); printf("3. Delete From Front\n"); printf("4. Delete From Rear\n"); printf("5. Display\n"); printf("6. Exit\n"); printf("Enter your choice\n"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("Enter the element"); scanf("%d",&iData);

fnDQInsert_At_Front(arrDQUEUE,iData); break; case 2: printf("Enter the element"); scanf("%d",&iData); fnDQInsert_At_Rear(arrDQUEUE,iData); break; case 3: if(fnQEmpty()==TRUE) printf("Queue is empty\n"); else printf("Item deleted = %d\n",fnDQDelete_From_Front(arrDQUEUE)); break; case 4: if(fnQEmpty()==TRUE) printf("Queue is empty\n"); else printf("Item deleted = %d\n",fnDQDelete_From_Rear(arrDQUEUE)); break; case 5: printf("\n******Queue Content******\n"); fnQDisplay(arrDQUEUE); printf("\n*************************\n"); break; case 6: exit(1); default: printf("\nWrong Choice\n"); } }while(1); }

7.10 MCQ Chapter - 7 Questions No. 1 and No. 2 are based on the following straight queue which can be allocated eight integers and five operations. front = 3 rear= 5 Queue = - , - , 2 , 4 ,5, - , - , (for notational convenience used to denote an empty cell) The following operations have to be performed. (i) 6 is added to the queue. (ii) Two elements are deleted from the queue. (iii) 7 and 8 are added to the queue. (iv) Two elements are deleted from the queue. (v) 2 and 3 are added to the queue. 1. What are the final front and rear values when the above operations are performed into a straight queue? (a) front = 7 rear=2 (b) front = 2 rear=7 (c) front = 7 rear=8 (d) front = 5 rear=8 2. What are the final front and rear values when the above operations are performed into a circular queue?

(a) front = 7 rear=3 (b) front = 2 rear=7 (c) front = 2 rear=8 (d) front = 5 rear=8 3. The initial configuration of a queue is a, b, c, d, and a is in the front end. To get the configuration d, c, b, a, one needs a minimum of (a) 2 deletions and 3 additions (b) 3 deletions and 2 additions (c) 3 deletions and 3 additions (d) 3 deletions and 4 additions 4. Queue can be used to implement (a) radix sort (b) quick sort (c) recursion (d) depth first search

5. The process of accessing data stored in a tape is similar to manipulating data on a (a) stack (b) queue (c) list (d) heap 6. Which of the following can be describe as a pointer? (a) Queue (b) Queue.Rear (c) Queue.Rear^ (d) Queue.Front^.Info. 7. Which of the following can be describe as a integer? (a) Queue (b) Queue.Rear (c) Queue.Front (d) Queue.Front^.Info. 8. Which of the following can be describe as a record? (a) Queue.Front^.Next (b) Queue.Rear (c) Queue.Front^.Next (d) Queue.Front^.Info. 9. In a queue (where q.rear and q.ront are pointers to the ends of a queue) (a) the number of the total elements is fixed. (b) If q.rear>q.front then queue is empty (c) the number of elements at any time is (q.rear q.front 1) (d) None of these 10. FRONT = REAR refers that a queue is (a) Full (b) Empty (c) Circular (d) None of these 11. N elements of a queue are to be reversed using another queue. The number of ADD and REMOVE operations required to do so is (a) 2*N (b) 4*N (c) N (d) the task cant be performed 12. Queue (a) Can be created by setting up an ordinary contigous array to hold the items (b) can take care of delete operation automatically (c) need a pointer to add or delete an item (d) None of these

Solutions:
1. c 2. a 3. a 4. d 5. b 6. b 7. d 8. a 9. d 10. b 11. d 12. a 7.11 Exercise Chapter - 7 1. What is a queue? Give one practical example and show why we will call it a queue. 2. Write a C program to implement a queue using (i) Array, (ii) Linked List 3. What is Dynamic Memory allocation related to queue? write a C function using Dynamic Memory allocation by which you can implement a Queue. 4. What is a Circular queue? why we use circular queue instead of linear queue? 5. Give short note on: (i) Dequeue (ii) Pririty Queue

6. How can you implement a queue using stack? write a C program to implement that. 7. State one application of queue and implement it using a C Program

Chapter - 8 LINKED LIST


8.1 Introduction
In the third chapter we have discussed about array. An array is a static data structure, i.e. the total number of spaces to keep data elements in an array whenever define in a source program remains constant during execution time of the program. So, during execution time we cant change the size of an array. Hence the spaces defined for an array may be wasted during run time or more spaces may be required in execution time. Now if we allocate memory spaces during execution time depending upon the need of user, then it will be more flexible. From this aspect the concept of linked list is derived. Linked list is dynamic data structure.

8.2

Definition

A linked list is a dynamic linear data structure which contains a collection of data elements, called nodes, where the linearity is maintained by means of pointer.

8.3

Advantages

1) Number of nodes in a linked list can be increased or decreased during run time of a program. That is, linked list is dynamic in nature. 2) In this technique memory can be allocated or de-allocated whenever required. That is, memory can be utilized efficiently. 3) Insertion and deletion operations are easier and efficient. 4) Many complex applications (like polynomial addition or multiplication) can be done more efficiently.

8.4

Disadvantages
1) Random access is not possible. 2) In each node extra memory spaces are needed to store the address of the other node.

8.5

Operations on Linked List

There are basic type of operations associated with a list data structure. 1. Counting total number of nodes. 2. Insertion of a new node in the list. 3. Deletion of an existing node from the list. 4. Searching a specified node in the list. 5. Display all nodes of the list.

8.6

Types of Linked List

There are two types of linked list depending on the number of links: 1. Singly Linked list: In this list each node has only one address field to contain the address of the next node in the list. Node A Node B Node C Node D 12 B 15 C 18 D 20 NULL

Figure 8.1: An example of a singly linked list with four nodes In figure 8.1 each node has two fields. First one is data field to store the information and the second field is link field which contains the address of the next node in the list. So from the concept of self referential structure, the structure definition for each node is: struct Node { int iData; //Data value of a node. struct node *ptrNext; //Pointer to the next node. }; typedef struct Node SLLNode; //Rename the data type of each node as SLLNode. 2. Doubly Linked List: Here each node contains two pointer fields, one contains the address of the next node and the other link keeps the track of the previous node in the list. Figure 8.2 shows an example of a doubly linked list. So the structure definition of such a node is: struct Node { struct Node * ptrLeft; //Pointer to the previous node. int iData; //Data value or information. struct Node * ptrRight; //Pointer to the next node. }; typedef struct Node DLLNode; //Rename the data type of each node as DLLNode.

Node A NULL 10 B A

Node B 20 D B 30

Node D NULL

Figure 8.2: An example of a doubly linked list with three nodes

8.7

Singly linked list

8.7.1 Counting total number of nodes


Algorithm 8.1 1. Algorithm fnSLL_Count_Total_Nodes(ptrStart) 2. // Purpose : This algorithm counts the total number of nodes in a singly linked list. 3. // Input : ptrStart is the address of the first node in the list. 4. // Output : This algorithm returns total number of nodes iCount in the list. 5. { 6. SLLNode *ptrTemp; //Create a new pointer ptrTemp of SLLNode type. 7. ptrTemp = ptrStart; //Initialize ptrTemp. 8. iCount = 0; //Initialize counter. 9. while(ptrTemp != NULL) //Repeat steps 11 and 12 until ptrTemp reaches NULL. 10. { 11. iCount = iCount + 1; //Increment value of iCount. 12. ptrTemp = ptrTemp->ptrNext; //Advance ptrTemp to next node. 13. } 14. return(iCount); //Return total number of nodes. 15. }//End of Algorithm

How the algorithm 8.1 works: Consider the following list: 10 ptrStart Step 1: (Line 7) ptrTemp = ptrStart 10 ptrTemp ptrStart Step 2: (Line 9 to 13) ptrTemp != NULL is true. So iCount = 1 and ptrTemp is advanced by one node. 10 20 40 NULL 20 40 NULL 20 40 NULL

ptrStart

ptrTemp

Step 3: (Line 9 to 13) Again ptrTemp != NULL is true. So iCount = 2 and ptrTemp is advanced by one node. 10 ptrStart 20 40 NULL ptrTemp

Step 4: (Line 9 to 13) Again ptrTemp != NULL is true. So iCount = 3 and ptrTemp is advanced by one node. 10 ptrStart NULL Step 5: Now ptrTemp = NULL. So control goes to outside the while loop in Line 14 which returns the value of iCount (total number of nodes). 20 40 NULL ptrTemp =

8.7.2 Find the address of the node at specific position


Algorithm 8.2 1. Algorithm fnSLL_Find_Position(ptrStart, iPosition) 2. // Purpose : This algorithm finds the address of a specific node. 3. // Input : ptrStart is address of the first node and address of the node at iPosition in the list is required. 4. // Output : This algorithm returns the address of the node and for non existence position it returns 0. 5. { 6. SLLNode *ptrTemp; //Create a new pointer ptrTemp of SLLNode type. 7. ptrTemp = ptrStart; //Initialize ptrTemp. 8. if(iPosition<0 || iPosiotion > fnSLL_ Count_Total_Nodes(ptrStart)) 9. return 0; //If position is negative or greater than total nodes then return 0. 10. for(iCounter = 1; iCounter < iPosition; iCounter++) //Go to particular node. 11. ptrTemp = ptrTemp->ptrNext; 12. return ptrTemp; //Return address. 13. }// End of Algorithm How the algorithm 8.2 works: Consider the following list: 10 20 40 NULL

ptrStart Suppose we want to find the address of the third node in the list. So iPosition = 3. Step 1: (Line 7) ptrTemp = ptrStart 10 ptrTemp ptrStart Step 2: (Line 8) Value of iPosition is not negative or not greater than the total nodes. So go to Line 10. 20 40 NULL

Step 3: The for loop of Line 10 executes for 2 times (1 to 2) and accordingly ptrTemp is advanced by two nodes in Line 11. 10 ptrStart 20 40 NULL ptrTemp

Step 4: Now ptrTemp is pointing to the third node in the list. So return ptrTemp (Line 12).

8.7.3 Inserting a node


For the insertion of a node in a singly linked list, we will use a pointer variable, ptrStart to keep the track of the first node in the list. Initially ptrStart is set to NULL to indicate that the list is empty. Consider the singly linked list in figure 8.3. A new node ptrNewNode is to be inserted after the node ptrTemp1. The data value of the new node is 30. 10 20 ptrTemp1 40 NULL

ptrNewNode Figure 8.3: Insertion of a new node in a singly linked list Algorithm 8.3 1. Algorithm fnSLL_Insert (ptrTemp1, iData) 2. //Purpose : This algorithm inserts a new node in a single linked list after a given node. 3. //Input : The new node with data value iData is to be inserted after node ptrTemp1. 4. //Output : None. 5. { 6. SLLNode *ptrNewNode; //Create a new pointer for the new node. 7. ptrNewNode = (SLLNode*) malloc(sizeof(SLLNode)); //Create the new node. 8. ptrNewNode->iData = iData; // Set the data value into new node. 9. ptrNewNode->ptrNext = ptrTemp1->ptrNext; //New node now points to next node of given node. 10. ptrTemp1->ptrNext = ptrNewNode; //Given node now points to new node. 11. }// End of algorithm. How the algorithm 8.3 works: Consider the list of figure 8.3. Step 1: ptrNewNode = (SLLNode*) malloc(sizeof(SLLNode)); 10 20 ptrTemp1 40 NULL

ptrNewNode

Step 2: ptrNewNode->iData = 30 10 20 ptrTemp1 30 ptrNewNode Step 3: ptrNewNode->ptrNext = ptrTemp1->ptrNext; 10 20 ptrTemp1 30 ptrNewNode Step 4: ptrTemp1->ptrNext = ptrNewNode; 10 ptrTemp1 20 40 NULL 40 NULL 40 NULL

30 ptrNewNode

8.7.3.1

Insert at the beginning

Algorithm 8.4 1. Algorithm fnSLL_Insert_At_Beginning (ptrStart, iData) 2. //Purpose : Insert a new node at the beginning of the list. 3. //Input : ptrStart is the address of the first node and iData is the data value of new node. 4. //Output : This algorithm returns the address of the first node (ptrStart). 5. { 6. SLLNode *ptrNewNode; 7. ptrNewNode = (SLLNode*) malloc(sizeof(SLLNode)); //Create the new node. 8. ptrNewNode->iData = iData; //Copies the data value into new node. 9. ptrNewNode->ptrNext = ptrStart; //New node now points to currently first node 10. ptrStart = ptrNewNode; //Set new node as the first node in the list. 11. return ptrStart; //Return the updated address of the first node. 12. }// End of Algorithm

How the algorithm 8.4 works. Consider the following list: ptrStart 20 Node2 30 NULL

Node 1 Node 2 Figure 8.4: A singly linked list of two nodes Suppose a node with the data value 10 is to be inserted at the beginning of the list in figure 8.4. So the function call is: fnSLLInsert_At_Beginning(ptrStart,10) Step 1: Create the new node. ptrNewNode = (SLLNode *) malloc(sizeof(SLLNode));

ptrNewNode Step 2: Copies the data value into new node. ptrNewNode->iData = 10 10 ptrNewNode Step 3: Link the new node to the first node of the linked list. ptrNewNode->ptrNext = ptrStart 10 ptrNewNode 20 ptrStart 30 NULL

Step 4: Set new node as the first node in the list. ptrStart = ptrNewNode 10 ptrStart=ptrNewNode 20 30 NULL

8.7.3.2

Insert at the last of the list

Algorithm 8.5 1. Algorithm fnSLL_ Insert_At_End (ptrStart, iData) 2. // Purpose : This algorithm inserts a node at the end of the list 3. // Input : ptrStart is the address of the first node and iData is the data value. 4. // Output : None. 5. { 6. SLLNode *ptrPosition; 7. iTotalNodes = fnSLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. 8. if(iTotalNodes != 0) //If list is not empty. 9. { 10. ptrPosition = fnSLL_Find_Position(ptrStart, iTotalNodes); //Get the address of the last node. 11. fnSLL_Insert(ptrPosition, iData); //Insert new node after the node ptrPosition. 12. } 13. }//End of Algorithm How the algorithm 8.5 works:

Consider the singly linked list of figure 8.4. Suppose a new node with data value 40 is to be inserted at the last of the list in figure 8.4. So the function call is: fnSLLInsert_At_End(ptrStart, 40) Step 1: (Line 7) iTotalNodes = 2; Step 2: (Line 10) ptrPosition = Node2 Step 3: (Line 11) fnSLL_Insert(ptrPosition, 40) 20 Node1 30 Node2 40 NULL

Node3

8.7.3.3

Insert at the specific position

Suppose we want to insert the new node at fifth position in the list. That means the new node is to be inserted after fourth node. So first we have to find the address of the fourth node after which we can insert the new node. Again if the list is empty or the position of the new node is 1, then the new node is to be inserted as first node. Algorithm 8.6 describes the total procedure. Algorithm 8.6 1. Algorithm fnSLL_Insert_At_Specific_Position (ptrStart, iData, iPosition) 2. // Purpose : This algorithm inserts a new node at a specific position of the list. 3. // Input : ptrStart = the address of the first node. iData = the data value. iPosition = the position where the new node is to be inserted. 4. // Output : This algorithm returns the address of the first node (ptrStart). 5. { 6. SLLNode *ptrNewNode, *ptrPosition; 7. ptrPosition = fnSLL_Find_Position(ptrStart, iPosition -1); //Get the address of the node at iPosition -1. 8. iTotal = fnSLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. 9. if(iPosition <= iTotal+1 && iPosition >= 0) //If position is valid. 10. { 11. if(iPosition == 1 || ptrStart == NULL) //If position is 1 or list is empty. 12. ptrStart = fnSLL_Insert_At_Beginning (ptrStart, iData); //Insert node at beginning. 13. else 14. fnSLL_Insert(ptrPosition, iData); //Insert new node after the node ptrPosition. 15. } 16. else 17. Not a valid position; 18. return ptrStart; 19. }//End of Algorithm

8.7.4 Deleting a node


Consider the following linked list with three nodes P, Q and R. Suppose we have to delete node Q which is after node P. Now after deletion of node Q, node R will be the next node of P. Therefore we have to store the address of node R in the address field of node P. Algorithm 8.7 describes the deletion process. ptrNodeP ptrNodeQ

NULL Node P Node Q Node R

Algorithm 8.7 1. Algorithm fnSLL_Delete (ptrNodeP, ptrNodeQ) 2. //Purpose : This algorithm deletes an existing node from a single linked list. 3. //Input : Node ptrNodeQ is to be deleted which is after node ptrNodeP. 4. //Output : The data value of the node that has been deleted. 5. { 6. ptrNodeP->ptrNext = ptrNodeQ->ptrNext; // Now node P points to the node next to node Q. 7. ptrNodeQ->ptrNext = NULL //Set address field of node Q to NULL. 8. iData = ptrNodeQ->iData; //Stores the data value of the deleted node. 9. free(ptrNodeQ); //Returns the address of node Q to heap. 10. return(iData); //Return the data that has been deleted. 11. }// End of algorithm. How the algorithm 8.7 works: Consider the following list with three nodes: Node P Node Q 10 Q 20 R ptrNodeP ptrNodeQ

Node R 40 NULL

Step 1: (Line 6) ptrNodeP->ptrNext = ptrNodeQ->ptrNext Node P 10 R 20 ptrNodeP Node Q R ptrNodeQ Node R 40 NULL

Step 2: (Line 7) ptrNodeQ->ptrNext = NULL Node P 10 R 20 ptrNodeP Step 3: (Line 8) iData = 20 Step 4: (Line 9) free(ptrNodeQ) Node P 10 R ptrNodeP Step 5: (Line 10) Return 20(data value of deleted node). Node R 40 NULL Node Q NULL ptrNodeQ Node R 40 NULL

8.7.4.1

Delete from the beginning

Algorithm 8.8 1. Algorithm fnSLL_Delete_From_Beginning (ptrStart) 2. // Purpose : This algorithm deletes the first node from the list. 3. // Input : ptrStart is the address of the first node in the list. 4. //Output : Returns the address of the updated first node in the list. 5. { 6. SLLNode *ptrDeletedNode; 7. if(ptrStart == NULL) //If the list is empty. 8. { 9. There is nothing to delete. 10. return; 11. } 12. ptrDeletedNode = ptrStart; //Set the pointer to first node. 13. iData = ptrDeletedNode->iData; //Stores the data value of the deleted node. 14. ptrStart = ptrStart->ptrNext; //Now the second node is the first node of the list. 15. free(ptrDeletedNode); //Return deleted node to heap. 16. return(ptrStart); 17. }//End of Algorithm How the algorithm 8.8 works: Consider the following list with three nodes: Node P Node Q 10 ptrStart Step 1: (Line 12) ptrDeletedNode = ptrStart Node P 10 Q 20 ptrDeletedNode ptrStart Step 2: (Line 13) iData = 10 Step 3: (Line 14) ptrStart = ptrStart->ptrNext Node P 10 Q ptrDeletedNode Step 4: (Line 15) free(ptrDeletedNode) Node Q 20 R ptrStart Node R 40 NULL Node Q 20 R ptrStart Node R 40 NULL Node Q R Node R 40 NULL Q 20 R

Node R 40 NULL

Step 5: (Line 16) Return new value of ptrStart.

8.7.4.2

Delete from the last

Algorithm 8.9 1. Algorithm fnSLL_Delete_From_End (ptrStart) 2. // Purpose : This algorithm deletes the last node from the list 3. // Input : ptrStart is the address of the first node in the list. 4. // Output : None. 5. // Comments: This algorithm works if number of nodes in the list are more than one. If the list contains only one node, call the function to delete node from the beginning of the list. 6. { 7. SLLNode *ptrNodeP, *ptrNodeQ; 8. if(ptrStart == NULL) //If list is empty. 9. { 10. There is nothing to delete. 11. return; 12. } 13. iTotal = fnSLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. 14. if(iTotal > 1) //If list contains more than one nodes. 15. { 16. ptrNodeP = fnSLL_Find_Position(ptrStart, iTotal -1); //Get the address of node P. 17. ptrNodeQ = fnSLL_Find_Position(ptrStart, iTotal); //Get the address of node Q. 18. iData = fnSLL_Delete(ptrNodeP, ptrNodeQ); 19. } 20. }//End of Algorithm How the algorithm 8.9 works: Consider the following list with three nodes: Node A Node P 10 ptrStart Step 1: (Line 13) iTotal = 3 Step 2: (Line 16) ptrNodeP = fnSLL_Find_Position(ptrStart, 2) Node A Node P 10 ptrStart P 20 Q ptrNodeP Node Q 40 NULL P 20 Q

Node Q 40 NULL

Step 3: (Line 17) ptrNodeQ = fnSLL_Find_Position(ptrStart, 3) Node A 10 ptrStart P 20 Node P Q ptrNodeP Node Q 40 NULL ptrNodeQ

Step 4: (Line 18) iData = fnSLL_Delete(ptrNodeP, ptrNodeQ) = 40 Node A 10 P ptrStart Node P 20 NULL ptrNodeP

8.7.4.3

Delete from the specific position

Algorithm 8.10 1. Algorithm fnSLL_Delete_from_Specific_Position (ptrStart, iPosition) 2. // Purpose : This algorithm deletes the last node from the list 3. // Input : ptrStart is the address of the first node in the list. iPosition is the position from where the node is to be deleted. 4. //Output : Returns the updated address of the first node in the list. 5. { 6. SLLNode *ptrNodeP, *ptrNodeQ; 7. iTotal = fnSLL_Count_Total_Nodes(ptrStart); 8. if(iPosition <= iTotal && iPosition > 0) //If valid position. 9. { 10. if(iPosition == 1) // If node is in the first position. 11. ptrStart = fnSLL_Delete_From_Beginning (ptrStart); 12. else 13. { 14. ptrNodeP = fnSLL_Find_Position(ptrStart, iPosition -1); //Get the address of node P. 15. ptrNodeQ = fnSLL_Find_Position(ptrStart, iPosition); //Get the address of node Q. 16. iData = fnSLL_Delete(ptrNodeP, ptrNodeQ); //Delete Q and get data value of Q. 17. } 18. } 19. else 20. Not a valid position; 21. return ptrStart; 22. }//End of Algorithm

8.7.5 Display the nodes


Algorithm 8.11 1. Algorithm fnSLL_Display_Nodes(ptrStart) 2. // Purpose : This algorithm displays all the nodes in the list. 3. // Input : ptrStart is the address of the first node in the list. 4. //Output : None. 5. { 6. SLLNode *ptrTemp; 7. ptrTemp = ptrStart; 8. while(ptrTemp!=NULL) 9. { 10. print(ptrTemp->iData); 11. ptrTemp = ptrTemp->ptrNext;

12. } 13. }//End of Algorithm

8.8

Reversing a singly linked list

Algorithm 8.12 1. Algorithm fnSLL_Reverse (ptrStart) 2. // Purpose : This algorithm reverse all the nodes in the list. 3. // Input : ptrStart, the address of the first node in the list. 4. //Output : The new value of ptrStart. 5. { 6. SLLNode *ptrTemp1,*ptrTemp2,*ptrTemp3; 7. ptrTemp3 = ptrStart; 8. ptrTemp2 = NULL; 9. while( ptrTemp3 != NULL) 10. { 11. ptrTemp1 = ptrTemp2; 12. ptrTemp2 = ptrTemp3; 13. ptrTemp3 = ptrTemp3->ptrNext; 14. ptrTemp2->ptrNext = ptrTemp1; 15. } 16. ptrStart = ptrTemp2; 17. return ptrStart; 18. }// End of Algorithm How the algorithm 8.12 works: Suppose a linked list with three nodes is given (figure 8.5). We have to reverse this list according to the algorithm. 10 20 Figure 8.5: A linked list of three nodes Step 1: Initially ptrTemp3=ptrStart and ptrTemp2 = NULL. ptrTemp2 = NULL 10 20 30 NULL 30 NULL

ptrTemp3 = ptrStart Step 2: Check the condition: ptrTemp3 NULL ptrTemp1 = ptrTemp2; ptrTemp2 = ptrTemp3; ptrTemp3 = ptrTemp3->ptrNext; ptrTemp2->ptrNext = ptrTemp1; ptrTemp1 = NULL 10 NULL 20 30 NULL

ptrTemp2 ptrStart Step 3:

ptrTemp3

Check the condition: ptrTemp3 NULL ptrTemp1 = ptrTemp2; ptrTemp2 = ptrTemp3; ptrTemp3 = ptrTemp3->ptrNext; ptrTemp2->ptrNext = ptrTemp1; 10 NULL 20 ptrTemp2 30 NULL

ptrTemp1 ptrStart Step 4:

ptrTemp3

Check the condition: ptrTemp3 NULL ptrTemp1 = ptrTemp2; ptrTemp2 = ptrTemp3; ptrTemp3 = ptrTemp3->ptrNext; ptrTemp2->ptrNext = ptrTemp1; ptrTemp3 = NULL 10 NULL 20 ptrTemp1 30 ptrTemp2

ptrStart Step 5:

Now set ptrStart to ptrTemp2 as currently ptrTemp2 is the first node. ptrStart = ptrTemp2;

10

NULL

20

30 ptrStart

8.9

Polynomial using singly linked list

8.9.1 Representation of polynomial


In representation of a polynomial, there are some terms. Each term has coefficient and exponent. Now we can define a structure for each node and whenever required we can create such new node against each term. Therefore the structure definition looks like: struct Polynomial { int iCoeff; int iExp;

struct Polynomial *ptrNext; }; typedef Polynomial PolyNode; Example: 4 3 2 5A + 7A + A + 6 The linked list representation of the above polynomial equation is: 5 4 7 3 1 2 6 0 NULL

8.9.2 Create polynomial list


We know how to append a node at the end of a singly linked list. Now to create a polynomial using linked list, we need to append the new node at the last of the list. Algorithm 8.13 describes how to create such a polynomial. Algorithm 8.13 1. Algorithm fnCreate_Polynomial(ptrPoly, iCoeff, iExp) 2. // Purpose : This algorithm creates a node in a polynomial. 3. // Input : ptrPoly is name of the polynomial. iCoeff and iExp are the coefficient and exponent of the node respectively. 4. // Output : Returns the starting address (ptrPoly) of the polynomial. 5. { 6. PolyNode *ptrTemp,*ptrNewNode ; 7. ptrTemp = ptrPoly; 8. ptrNewNode = (PolyNode*) malloc(sizeof (PolyNode)) ; 9. if ( ptrPoly == NULL ) 10. ptrPoly = ptrNewNode; //Set new node as first node if list is empty. 11. else 12. { 13. while ( ptrTemp -> ptrNext != NULL ) // traverse the entire linked list. 14. ptrTemp = ptrTemp -> ptrNext; 15. ptrTemp -> ptrNext = ptrNewNode; //Set new node as the next node of ptrTemp. 16. } 17. ptrNewNode -> iCoeff = iCoeff ; // Set coefficient in the new node. 18. ptrTemp -> iExp = iExp ; // Set exponent in the new node. 19. ptrTemp -> ptrNext = NULL ; // Set link field of new node to NULL as this is the last node. 20. return ptrPoly; //Returns the address of the starting node. 21. }/ End of Algorithm

Addition of two polynomials


Algorithm 8.14 1. Algorithm fnPolyADD(ptrPolyA, ptrPolyB, ptrPolyC) 2. //Purpose : This algorithm adds two polynomials A and B and stores the result in C. 3. //Input : ptrPolyA pointer to the first node of polynomial A. 4. // : ptrPolyB pointer to the first node of polynomial B. 5. // : ptrPolyC pointer to the first node of the resultant polynomial C. 6. //Output : Returns the pointer(ptrPolyC) to polynomial C. 7. {

8. PolyNode *ptrTempA, *ptrTempB; 9. ptrTempA = ptrPolyA; 10. ptrTempB = ptrPolyB; 11. while(ptrTempA != NULL && ptrTempB != NULL) 12. { 13. if(ptrTempA->iExp == ptrTempB->iExp) 14. { 15. iX = ptrTempA->iCoeff + ptrTempB->iCoeff; 16. if(iX != 0) 17. insert iX and ptrTempA->iExp (or ptrTempB->iExp) into C; 18. ptrTempA = ptrTempA->ptrNext; 19. ptrTempB = ptrTempB->ptrNext; 20. } 21. elseif(ptrTempA->iExp < ptrTempB->iExp) 22. { 23. insert ptrTempB->iCoeff and ptrTempB->iExp into C; 24. ptrTempB = ptrTempB->ptrNext; 25. } 26. else 27. { 28. insert ptrTempA->iCoeff and ptrTempA->iExp into C; 29. ptrTempA = ptrTempA->ptrNext; 30. } 31. } 32. while(ptrTempA != NULL) 33. { 34. insert ptrTempA->iCoeff and ptrTempA->iExp into C; 35. ptrTempA = ptrTempA->ptrNext; 36. } 37. while(ptrTempB != NULL) 38. { 39. insert ptrTempB->iCoeff and ptrTempB->iExp into C; 40. ptrTempB = ptrTempB->ptrNext; 41. } 42. return ptrPolyC; 43. } // End of Algorithm Example How the algorithm 8.14 works. A = 3x7 + 4x4 + 5x3 B = -3x7 + 5x5 + 2x ptrTempA = ptrPolyA

ptrTempB = ptrPolyB

-3
Step 1:

ptrTempA->iExp = ptrTempB->iExp;

iX = ptrTempA->iCoeff + ptrTempB->iCoeff = 3 + (-3) = 0; As iX is 0, so no insertion into C. ptrTempA = ptrTempA->ptrNext; ptrTempB = ptrTempB->ptrNext; ptrPolyA ptrTempA

4
ptrTempB

N N

ptrPolyB

-3
Step 2:

ptrTempA->iExp < ptrTempB->iExp; insert ptrTempB->iCoeff and ptrTempB->iExp into C; ptrTempB = ptrTempB->ptrNext; ptrPolyA ptrTempA

N N

ptrPolyB

ptrTempB

-3
C

5
N

5
Step 3:

ptrTempA->iExp > ptrTempB->iExp; insert ptrTempA->iCoeff and ptrTempA->iExp into C; ptrTempA = ptrTempA->ptrNext; ptrPolyA ptrTempA

N N

ptrPolyB

ptrTempB

-3

7
C

5 4

5
Step 4:

ptrTempA->iExp > ptrTempB->iExp; insert ptrTempA->iCoeff and ptrTempA->iExp into C; ptrTempA = ptrTempA->ptrNext;

ptrPolyA

ptrTempA=NULL

7
ptrPolyB

NULL

ptrTempB

-3 5
Step 5: C

5 4

NULL

NULL

As ptrTempB NULL insert ptrTempB->iCoeff and ptrTempB->iExp into C; ptrTempB = ptrTempB->ptrNext; ptrPolyA ptrTempA=NULL

7
ptrPolyB

NULL ptrTempB=NULL

-3 5
C

5 4

NULL

2
8.9.4 Multiplication of two polynomials

NULL

Algorithm 8.15 1. Algorithm fnPolyMUL (ptrPolyA, ptrPolyB, ptrPolyC) 2. //Purpose : This algorithm multiplies two polynomials A and B and stores the result in C. 3. //Input : ptrPolyA pointer to the first node of polynomial A. 4. // : ptrPolyB pointer to the first node of polynomial B. 5. // : ptrPolyC pointer to the first node of the resultant polynomial C. 6. //Output : Returns the pointer to polynomial C ptrPolyC. 7. { 8. PolyNode *ptrTempA, *ptrTempB; 9. ptrTempA = ptrPolyA; 10. ptrTempB = ptrPolyB; 11. if(ptrPolyA == NULL) //If polynomial A is empty, then set polynomial C as B. 12. return ptrPolyB; 13. if(ptrPolyB == NULL) //If polynomial B is empty, then set polynomial C as A. 14. return ptrPolyA; 15. while ( ptrPolyA != NULL) // for each term of the first list A

16. { 17. while (ptrPolyB != NULL) 18. // multiply each term of the second list B with a term of A. 19. { 20. coeff1 = ptrTempA->iCoeff * ptrTempB->iCoeff; 21. exp1 = ptrTempA->iExp + ptrTempB->iExp; 22. insert coeff1 and exp1 into C1; 23. ptrTempB = ptrTempB->ptrNext; 24. fnPolyADD (ptrPolyC, ptrPolyC1, ptrPolyC); 25. } 26. ptrTempB = ptrPolyB; // reposition q to the starting of B. 27. ptrTempA = ptrTempA->ptrNext; // go to the next node of A. 28. return ptrPolyC; 29. } 30. }//End of Algorithm

8.10 Advantages and disadvantages of singly linked list


Advantages: 1. Accessibility of a node in the forward direction is easier. 2. Insertion and deletion of nodes are easier. Disadvantages 1. Accessing the preceding node of a current node is not possible as there is no backward traversal. 2. Accessing a node is time consuming.

8.11 Circular linked list


If a pointer ptrP to a node in a linear list is given, we cant reach any nodes that precede the node to which ptrP is pointing. This is the main drawback of the linear singly linked list. We can overcome this disadvantage by introducing another kind of list namely circular linked list. Here the pointer field of the last node contains the address of the first node in the list instead of NULL. Hence starting from the any node of the list, we can traverse the entire list. Figure.. shows a circular linked list. So we can easily convert a linear linked list to circular linked list by replacing the NULL pointer in the last node of the list with the address of its first node as shown in algorithm 8.16.

Figure: Circular Linked List Algorithm 8.16 1. Algorithm fnCreate_CLL (ptrStart) 2. // Purpose : This algorithm creates a circular linked list from a linear list. 3. // Input : ptrStatr is the address of the first node in the list. 4. // Output : None. 5. { 6. SLLNode *ptrTemp; 7. ptrTemp = ptrStart;

8. while(ptrTemp->ptrNext != NULL) 9. ptrTemp = ptrTemp ->ptrNext; 10. ptrTemp->ptrNext = ptrStart; 11. }// End of algorithm

//Repeat step 9 until ptrTemp reaches to last node. //Advance the value of ptrTemp to next node. //Sets the pointer of last node to the first node in the list.

8.12 Double linked list


Although a circular linked list has advantages over a linear list, it still has several drawbacks: (i) One cant traverse such a list backward. (ii) A node cant be deleted from a circular linked list (as well as linear linked list), given only a pointer to that node. In cases where these facilities are required the appropriate data structure is a double linked list. The node structure of such a list has been discussed in the section 8.6. Like singly linked list, here also we take a variable ptrStart which contains the address of the first node in the list. Initially value of ptrStart is NULL to indicate that the list is empty. DLLNode *ptrStart = NULL; 8.12.1 Counting total number of nodes Algorithm 8.17 1. Algorithm fnDLL_Count_Total_Nodes(ptrStart) 2. // Purpose : This algorithm counts the total number of nodes in a double linked list. 3. // Input : ptrStart is the address of the first node in the list. 4. // Output : This algorithm returns total number of nodes iCount in the list. 5. { 6. DLLNode *ptrTemp; //Create a new pointer ptrTemp of DLLNode type. 7. ptrTemp = ptrStart; //Initialize pointer. 8. iCount = 0; //Initialize counter. 9. while(ptrTemp != NULL) //Repeat steps 11 and 12 until ptrTemp reaches NULL. 10. { 11. iCount = iCount + 1; //Increment value of iCount. 12. ptrTemp = ptrTemp->ptrRight; //Advance ptrTemp to next node. 13. } 14. return(iCount); //Return total number of nodes. 15. }//End of Algorithm 8.12.2 Find the address of the node at specific position Algorithm 8.18 1. Algorithm fnDLL_Find_Position(ptrStart, iPosition) 2. // Purpose : This algorithm finds the address of a specific node. 3. // Input : ptrStart is address of the first node and address of the node at iPosition in the list is required. 4. // Output : This algorithm returns the address of the desired node. 5. { 6. DLLNode *ptrTemp; 7. ptrTemp = ptrStart; 8. for(iCounter = 1; iCounter < iPosition && ptrTemp!=NULL; iCounter++) 9. ptrTemp = ptrTemp->ptrRight; 10. return ptrTemp;

11. }// End of Algorithm 8.12.3 Inserting a node into a double linked list Consider the double linked list in figure 8.2. Suppose a new node C is to be inserted after node B. The data value of the new node is 25. Algorithm 8.19 1. Algorithm fnDLL_Insert (ptrNodeB, iData) 2. //Purpose : This algorithm inserts a new node C after node B in a double linked list. 3. //Input : ptrNodeB is the address of node B. iData is data value of node C. 4. //Output : None. 5. { 6. DLLNode *ptrNodeC; //ptrNodeC is the address of node C. 7. ptrNodeC = (DLLNode*) malloc(sizeof(DLLNode)); //Create node C. 8. ptrNodeC->iData = iData; // Set the data value into new node C. 9. ptrNodeC->ptrLeft = ptrNodeB; //Set B as the left node of C. 10. ptrNodeC->ptrRight = ptrNodeB->ptrRight; //Set D as the right node of C. 11. ptrNodeB->ptrRight->ptrLeft = ptrNodeC; //Set C as the right node of D. 12. ptrNodeB->ptrRight = ptrNodeC; //Set C as the left node of B. 13. }// End of algorithm. How the algorithm 8.19 works. Consider the list of figure 8.2. Node C is to be inserted after node B. Step 1: ptrNodeC = (DLLNode*) malloc(sizeof(DLLNode));

ptrNodeC Step 2: ptrNodeC ->iData = 25 25 ptrNodeC Step 3: ptrNodeC->ptrLeft = ptrNodeB; Node A NULL 10 B A Node B 20 D Node C B 25 ptrNodeC Step 4: ptrNodeC->ptrRight = ptrNodeB->ptrRight; Node A NULL 10 B A Node B 20 D B 30 Node D NULL B 30 Node D NULL

ptrNodeB Node C B 25 ptrNodeC Step 5: ptrNodeB->ptrRight->ptrLeft = ptrNodeC; Node A NULL 10 B A Node B 20 ptrNodeB Node C B 25 ptrNodeC Step 6: ptrNodeB->ptrRight = ptrNodeC; Node A NULL 10 B A Node B 20 ptrNodeB Node C B 25 ptrNodeC D C C 30 Node D NULL D D C 30 Node D NULL D

8.12.3.1

Insert at the beginning

Algorithm 8.20 1. Algorithm fnDLL_Insert_At_Beginning (ptrStart, iData) 2. //Purpose : Insert a new node P at the beginning of the list. 3. //Input : ptrStart is the address of the first node and iData is the data value of new node P. 4. //Output : This algorithm returns the address of the first node (ptrStart). 5. { 6. DLLNode *ptrNodeP; //ptrNodeP is the address of node P. 7. ptrNodeP = (DLLNode*) malloc(sizeof(DLLNode)); //Create the new node. 8. ptrNodeP->iData = iData; //Copies the data value into new node. 9. ptrNodeP->ptrLeft = NULL; //Set NULL to left link of P. 10. ptrNodeP->ptrRight = ptrStart; //Set currently first node as the left node of P. 11. ptrStart->ptrLeft = ptrNodeP; //Set P as the left node of currently first node. 12. ptrStart = ptrNodeP; //Now P is the first node in the list. 13. return ptrStart; //Return the updated address of the first node. 14. }// End of Algorithm

How the algorithm 8.20 works. Consider the list of figure 8.2. Node P is to be inserted at the beginning in the list. Suppose data value of P is 25. Step 1: ptrNodeP = (DLLNode*) malloc(sizeof(DLLNode));

ptrNodeP Step 2: ptrNodeP ->iData = 25 25 ptrNodeP Step 3: ptrNodeP->ptrLeft = NULL; NULL 25

ptrNodeP Step 4: ptrNodeP->ptrRight = ptrStart; Node A NULL 10 Node P NULL 25 A B A Node B 20 D B 30 Node D NULL

ptrNodeP Step 5: ptrStart->ptrLeft = ptrNodeP; Node A 10 Node P NULL 25 A Node B A 20 Node D 30 NULL

ptrNodeP

8.12.3.2

Insert at the last of the list

Algorithm 8.21 1. Algorithm fnDLL_ Insert_At_End (ptrStart, iData) 2. // Purpose : This algorithm inserts a node P at the end of the list.

3. // Input : ptrStart is the address of the first node and iData is the data value of P. 4. // Output : None. 5. { 6. DLLNode *ptrPosition; 7. iTotalNodes = fnDLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. 8. if(iTotalNodes != 0) //If list is not empty. 9. { 10. ptrPosition = fnDLL_Find_Position(ptrStart, iTotalNodes); //Get the address of the last node. 11. fnDLL_Insert(ptrPosition, iData); //Insert new node after the node ptrPosition. 12. } 13. }//End of Algorithm How the algorithm 8.21 works. Consider the singly linked list of figure 8.2. Suppose a new node with data value 40 is to be inserted after node D. Step 1: iTotalNodes = 3; Step 2: ptrPosition = Node D Step 3: fnDLL_Insert(ptrPosition, 40) Node A 10 Node B A 20 Node D 30 P Node P D 25 NULL

8.12.3.3 Insert at the specific position


Algorithm 8.22 1. Algorithm fnDLL_Insert_At_Specific_Position (ptrStart, iData, iPosition) 2. // Purpose : This algorithm inserts a new node at a specific position of the list. 3. // Input : ptrStart is address of the first node. iData is data value of new node. iPosition is the position where the new node is to be inserted. 4. // Output : This algorithm returns the address of the first node (ptrStart). 5. { 6. DLLNode *ptrNewNode, *ptrPosition; 7. ptrPosition = fnDLL_Find_Position(ptrStart, iPosition -1); //Get the address of the node at position -1. 8. iTotal = fnDLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. 9. if(iPosition <= iTotal+1 && iPosition >= 0) //If position is valid. 10. { 11. if(iPosition == 1 || ptrStart == NULL) //If position is 1 or list is empty.

12.

ptrStart = fnDLL_Insert_At_Beginning (ptrStart, iData); beginning. else fnDLL_Insert(ptrPosition, iData);

//Insert node at

13.
14.

//Insert new node after the node ptrPosition.

15. } 16. else 17. Not a valid position; 18. return ptrStart; 19. }//End of Algorithm

8.12.4

Deleting a node form a double linked list

Consider the double linked list shown in figure 8.2. Suppose node B is to be deleted. So now node A points to node D and node D points to node A and node B no more points to any node i.e. the links of B are NULL. (1) Node A NULL 10 ptrNodeA D A Node B 20 D A Node D 30 NULL

ptrNodeB (2)

ptrNodeD

For link 1: ptrNodeD->ptrLeft = ptrNodeA. Now address of node D, ptrNodeD, is stored in the right link field of node B i.e. ptrNodeB->ptrRight = ptrNodeD and also ptrNodeB->ptrLeft = ptrNodeA. Now we can rewrite the statement for link 1 asptrNodeB->ptrRight->ptrLeft = ptrNodeB->ptrleft For link 2: Similarly we can write for link 2ptrNodeB->ptrLeft->ptrRight = ptrNodeB->ptrRight Now set the link fields of B to NULL. ptrNodeB->ptrLeft = ptrNodeB->ptrRight = NULL (1) Node A NULL 10 ptrNodeA Algorithm 8.23 describes the deletion process. Algorithm 8.23 1. Algorithm fnDLL_Delete(ptrNodeB) 2. //Purpose : This algorithm deletes an existing node B from a double linked list. D NULL Node B 20 NULL A Node D 30 NULL

ptrNodeB (2)

ptrNodeD

3. //Input : ptrNodeB is the address of node B. 4. //Output : The data value of the deleted node. 5. { 6. iData = ptrNodeB->iData; //Stores the data value of the deleted node. 7. ptrNodeB->ptrRight->ptrLeft = ptrNodeB->ptrleft; 8. ptrNodeB->ptrLeft->ptrRight = ptrNodeB->ptrRight; 9. ptrNodeB->ptrLeft = ptrNodeB->ptrRight = NULL; 10. free(ptrNodeB); //Returns the address of node Q to heap. 11. return(iData); 12. }// End of algorithm

8.12.4.1

Delete from the beginning

Algorithm 8.24 1. Algorithm fnDLL_Delete_From_Beginning(ptrStart) 2. // Purpose : This algorithm deletes the first node from the list. 3. // Input : ptrStart is the address of the first node in the list. 4. //Output : Returns the address of the updated first node in the list. 5. { 6. DLLNode *ptrDeletedNode; 7. if(ptrStart == NULL) //If the list is empty. 8. { 9. There is nothing to delete. 10. return; 11. } 12. ptrDeletedNode = ptrStart; //Set the pointer to first node. 13. iData = ptrStart->iData; //Stores the data value of the deleted node. 14. ptrStart = ptrStart->ptrRight; //Now the second node is the first node of the list. 15. ptrStart->ptrLeft = NULL; 16. ptrDeletedNode->ptrRight = NULL; 17. free(ptrDeletedNode); //Return deleted node to heap. 18. return(ptrStart); 19. }//End of Algorithm

8.12.4.2

Delete from the last

Algorithm 8.25 1. Algorithm fnDLL_Delete_From_End (ptrStart) 2. // Purpose : This algorithm deletes the last node from the list 3. // Input : ptrStart is the address of the first node in the list. 4. // Output : The data value of the last node. 5. // Comments: This algorithm works if number of nodes in the list are more than one. If the list contains only one node, call the function to delete node from the beginning of the list. 6. { 7. DLLNode *ptrLastNode; 8. if(ptrStart == NULL) //If list is empty. 9. { 10. There is nothing to delete.

11. 12. 13. 14. 15.

return; } iTotal = fnDLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. if(iTotal > 1) //If list contains more than one nodes. { 16. ptrLastNode = fnDLL_Find_Position(ptrStart, iTotal); //Get the address of node Q. 17. ptrLastNode->ptrLeft->ptrRight = NULL; //Set NULL to right link field of second last node. 18. ptrLastNode->ptrLeft = NULL; //Set NULL to the left link field of last node. 19. iData = ptrLastNode->iData; 20. free(ptrLastNode); 21. } 22. return iData; 23. }//End of Algorithm

8.12.4.3

Delete from the specific position

Algorithm 8.26 1. Algorithm fnDLL_Delete_from_Specific_Position (ptrStart, iPosition) 2. // Purpose : This algorithm deletes the last node of the list. 3. // Input : ptrStart is the address of the first node in the list. iPosition is the position from where the node is to be deleted. 4. //Output : Returns the updated address of first node in the list. 5. { 6. DLLNode *ptrPosition; 7. iTotal = fnDLL_Count_Total_Nodes(ptrStart); 8. if(iPosition <= iTotal && iPosition > 0) //If valid position. 9. { 10. if(iPosition == 1) //If node is in the first position. 11. ptrStart = fnDLL_Delete_From_Beginning(ptrStart); 12. elseif(iPosition == iTotal) //If the node is the last node. 13. iData = fnDLL_Delete_From_End(ptrStart); 14. else 15. { 16. ptrPosition = fnDLL_Find_Position(ptrStart, iPosition); //Get the address of the node. 17. iData = fnDLL_Delete(ptrPosition); 18. } 19. } 20. else 21. Not a valid position; 22. return ptrStart; 23. }//End of Algorithm

8.12.5

Display the nodes

Algorithm 8.27 1. Algorithm fnDLL_Display_Nodes(ptrStart) 2. // Purpose : This algorithm displays all the nodes in the list.

3. // Input : ptrStart is the address of the first node in the list. 4. //Output : None. 5. { 6. DLLNode *ptrTemp; 7. ptrTemp = ptrStart; //Initialize pointer. 8. while(ptrTemp!=NULL) //Repeat steps 10 and 11 until ptrTemp is NULL. 9. { 10. print(ptrTemp->iData); //Print the data. 11. ptrTemp = ptrTemp->ptrRight; //Advance ptrTemp to next node. 12. } 13. }//End of Algorithm

8.12.6 Advantages and disadvantages of double linked list


Advantages: 1. Both way traversal are possible here. 2. A node can be deleted from a double linked list, given only a pointer to that node. Disadvantages 1. Here insertion is slightly more complex. /* C code to implement the operations on a singly linked list and reverse the list*/ /* File name : SLL.c */ #include <stdio.h> #include <conio.h> #include <alloc.h> struct Node { int iData; struct node *ptrNext; }; typedef struct Node SLLNode;

//Data value of a node. //Pointer to the next node. //Rename the data type of each node as SLLNode.

//Prototype Declaration int fnSLL_Count_Total_Nodes(SLLNode *); SLLNode* fnSLL_Find_Position(SLLNode *, int ); void fnSLL_Insert (SLLNode *, int ); SLLNode *fnSLL_Insert_At_Beginning (SLLNode *, int ); void fnSLL_Insert_At_End (SLLNode *, int ); SLLNode *fnSLL_Insert_At_Specific_Position (SLLNode *, int , int); int fnSLL_Delete (SLLNode *, SLLNode *); SLLNode *fnSLL_Delete_From_Beginning (SLLNode *); void fnSLL_Delete_From_End (SLLNode *); SLLNode *fnSLL_Delete_from_Specific_Position (SLLNode *, int); SLLNode *fnSLL_Reverse (SLLNode *); void fnSLL_Display_Nodes(SLLNode *); int fnSLL_Count_Total_Nodes(SLLNode *ptrStart) // Purpose : This Function counts the total number of nodes in a singly linked list. // Input : ptrStart is the address of the first node in the list.

// Output : This function returns total number of nodes iCount in the list. { int iCount; SLLNode *ptrTemp; //Create a new pointer ptrTemp of SLLNode type. ptrTemp = ptrStart; //Initialize ptrTemp. iCount = 0; //Initialize counter. while(ptrTemp != NULL) //Repeat steps 11 and 12 until ptrTemp reaches NULL. { iCount = iCount + 1; //Increment value of iCount. ptrTemp = ptrTemp->ptrNext; //Advance ptrTemp to next node. } return(iCount); //Return total number of nodes. }//End of Function SLLNode* fnSLL_Find_Position(SLLNode *ptrStart, int iPosition) // Purpose : This Functionfinds the address of a specific node. // Input : ptrStart is address of the first node and address of the node at iPosition in the list is required. // Output : This Function returns the address of the node and for non existence position it returns 0. { int iCounter; SLLNode *ptrTemp; //Create a new pointer ptrTemp of SLLNode type. ptrTemp = ptrStart; //Initialize ptrTemp. if(iPosition<0 || iPosition > fnSLL_Count_Total_Nodes(ptrStart)) return 0; //If position is negative or greater than total nodes then return 0. for(iCounter = 1; iCounter < iPosition; iCounter++) //Go to particular node. ptrTemp = ptrTemp->ptrNext; return ptrTemp; //Return address. }// End of Function void fnSLL_Insert (SLLNode *ptrTemp1, int iData) //Purpose : This function inserts a new node in a single linked list after a given node. //Input : The new node with data value iData is to be inserted after node ptrTemp1. //Output : None. { SLLNode *ptrNewNode; //Create a new pointer for the new node. ptrNewNode = (SLLNode*) malloc(sizeof(SLLNode)); //Create the new node. ptrNewNode->iData = iData; // Set the data value into new node. ptrNewNode->ptrNext = ptrTemp1->ptrNext; //New node now points to next node of given node. ptrTemp1->ptrNext = ptrNewNode; //Given node now points to new node. }// End of function SLLNode *fnSLL_Insert_At_Beginning (SLLNode *ptrStart, int iData) //Purpose : Insert a new node at the beginning of the list. //Input : ptrStart is the address of the first node and iData is the data value of new node. //Output : This function returns the address of the first node (ptrStart). { SLLNode *ptrNewNode; ptrNewNode = (SLLNode*) malloc(sizeof(SLLNode)); //Create the new node. ptrNewNode->iData = iData; //Copies the data value into new node. ptrNewNode->ptrNext = ptrStart; //New node now points to currently first node ptrStart = ptrNewNode; //Set new node as the first node in the list. return ptrStart; //Return the updated address of the first node.

}// End of function void fnSLL_Insert_At_End (SLLNode *ptrStart, int iData) // Purpose : This function inserts a node at the end of the list // Input : ptrStart is the address of the first node and iData is the data value. // Output : None. { int iTotalNodes; SLLNode *ptrPosition; iTotalNodes = fnSLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. if(iTotalNodes != 0) //If list is not empty. { ptrPosition = fnSLL_Find_Position(ptrStart, iTotalNodes); //Get the address of the last node. fnSLL_Insert(ptrPosition, iData); //Insert new node after the node ptrPosition. } }//End of function SLLNode *fnSLL_Insert_At_Specific_Position (SLLNode *ptrStart, int iData, int iPosition) // Purpose : This function inserts a new node at a specific position of the list. // Input : ptrStart = the address of the first node. iData = the data value. iPosition = the position where the new node is to be inserted. // Output : This function returns the address of the first node (ptrStart). { int iTotal; SLLNode *ptrNewNode, *ptrPosition; ptrPosition = fnSLL_Find_Position(ptrStart, iPosition -1); //Get the address of the node at iPosition -1. iTotal = fnSLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. if(iPosition <= iTotal+1 && iPosition >= 0) //If position is valid. { if(iPosition == 1 || ptrStart == NULL) //If position is 1 or list is empty. ptrStart = fnSLL_Insert_At_Beginning (ptrStart, iData); //Insert node at beginning. else fnSLL_Insert(ptrPosition, iData); //Insert new node after the node ptrPosition. } else printf("Not a valid position\n"); return ptrStart; }//End of function int fnSLL_Delete (SLLNode *ptrNodeP, SLLNode *ptrNodeQ) //Purpose : This function deletes an existing node from a single linked list. //Input : Node ptrNodeQ is to be deleted which is after node ptrNodeP. //Output : The data value of the node that has been deleted. { int iData; ptrNodeP->ptrNext = ptrNodeQ->ptrNext; // Now node P points to the node next to node Q. ptrNodeQ->ptrNext = NULL; //Set address field of node Q to NULL. iData = ptrNodeQ->iData; //Stores the data value of the deleted node. free(ptrNodeQ); //Returns the address of node Q to heap. return(iData); //Return the data that has been deleted. }// End of function.

SLLNode *fnSLL_Delete_From_Beginning (SLLNode *ptrStart) // Purpose : This function deletes the first node from the list. // Input : ptrStart is the address of the first node in the list. //Output : Returns the address of the updated first node in the list. { int iData; SLLNode *ptrDeletedNode; if(ptrStart == NULL) //If the list is empty. { printf("There is nothing to delete\n"); return ptrStart; } ptrDeletedNode = ptrStart; //Set the pointer to first node. ptrDeletedNode->ptrNext = NULL; iData = ptrDeletedNode->iData; //Stores the data value of the deleted node. ptrStart = ptrStart->ptrNext; //Now the second node is the first node of the list. free(ptrDeletedNode); //Return deleted node to heap. return(ptrStart); }//End of function void fnSLL_Delete_From_End (SLLNode *ptrStart) // Purpose : This function deletes the last node from the list // Input : ptrStart is the address of the first node in the list. // Output : None. // Comments: This function works if number of nodes in the list are more than one. If the list contains only one node, call the function to delete node from the beginning of the list. { int iTotal,iData; SLLNode *ptrNodeP, *ptrNodeQ; if(ptrStart == NULL) //If list is empty. { printf("There is nothing to delete\n"); return; } iTotal = fnSLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. if(iTotal > 1) //If list contains more than one nodes. { ptrNodeP = fnSLL_Find_Position(ptrStart, iTotal -1); //Get the address of node P. ptrNodeQ = fnSLL_Find_Position(ptrStart, iTotal); //Get the address of node Q. iData = fnSLL_Delete(ptrNodeP, ptrNodeQ); } }//End of function SLLNode *fnSLL_Delete_from_Specific_Position (SLLNode *ptrStart, int iPosition) // Purpose : This function deletes the last node from the list // Input : ptrStart is the address of the first node in the list. iPosition is the position from where the node is to be deleted. //Output : Returns the updated address of the first node in the list. { SLLNode *ptrNodeP, *ptrNodeQ; int iTotal,iData; iTotal = fnSLL_Count_Total_Nodes(ptrStart);

if(iPosition <= iTotal && iPosition > 0) //If valid position. { if(iPosition == 1) // If node is in the first position. ptrStart = fnSLL_Delete_From_Beginning (ptrStart); else { ptrNodeP = fnSLL_Find_Position(ptrStart, iPosition -1); //Get the address of node P. ptrNodeQ = fnSLL_Find_Position(ptrStart, iPosition); //Get the address of node Q. iData = fnSLL_Delete(ptrNodeP, ptrNodeQ); //Delete Q and get data value of Q. } } else printf("Not a valid position\n"); return ptrStart; }//End of function SLLNode *fnSLL_Reverse (SLLNode *ptrStart) // Purpose : This function reverse all the nodes in the list. // Input : ptrStart, the address of the first node in the list. // Output : The new value of ptrStart. { SLLNode *ptrTemp1,*ptrTemp2,*ptrTemp3; ptrTemp3 = ptrStart; ptrTemp2 = NULL; while( ptrTemp3 != NULL) { ptrTemp1 = ptrTemp2; ptrTemp2 = ptrTemp3; ptrTemp3 = ptrTemp3->ptrNext; ptrTemp2->ptrNext = ptrTemp1; } ptrStart = ptrTemp2; return ptrStart; }// End of Function void fnSLL_Display_Nodes(SLLNode *ptrStart) // Purpose : This function displays all the nodes in the list. // Input : ptrStart is the address of the first node in the list. // Output : None. { SLLNode *ptrTemp; ptrTemp = ptrStart; while(ptrTemp!=NULL) { printf("%d\n",ptrTemp->iData); ptrTemp = ptrTemp->ptrNext; } }//End of function void main(void) { SLLNode *ptrStart=NULL;

int choice,iData,iPosition; clrscr(); do { printf("1. Insert element at begining \n"); printf("2. Insert element at end positon\n"); printf("3. Insert at the specific position\n"); printf("4. Display\n"); printf("5. Delete from the begining\n"); printf("6. Delete from the last\n"); printf("7. Delete the element from the specific position\n"); printf("8. Reverse the list\n"); printf("9. Exit\n"); printf("Enter your choice\n"); scanf("%d",&choice); switch(choice) { case 1: printf("Enter the item\n"); scanf("%d",&iData); ptrStart = fnSLL_Insert_At_Beginning (ptrStart, iData); break; case 2: if(ptrStart!=NULL) { printf("Enter the item\n"); scanf("%d",&iData); fnSLL_Insert_At_End(ptrStart, iData); } else printf("For insertion into empty list choose option 1\n"); break; case 3: printf("Enter the specific position and the item\n " ); scanf("%d%d", &iPosition, &iData); ptrStart = fnSLL_Insert_At_Specific_Position (ptrStart,iData,iPosition); break; case 4: printf("\nThe Elements in the list are\n"); fnSLL_Display_Nodes(ptrStart); printf("\n*****************************\n"); break; case 5: ptrStart = fnSLL_Delete_From_Beginning (ptrStart); break; case 6: if(fnSLL_Count_Total_Nodes(ptrStart)==1)

{ printf("There is only one node in the list. Choose either option 5 or option 7\n"); break; } fnSLL_Delete_From_End(ptrStart); break; case 7: printf("\nEnter the position of the element to be deleted\n"); scanf("%d",&iPosition); ptrStart = fnSLL_Delete_from_Specific_Position (ptrStart,iPosition); break; case 8: ptrStart = fnSLL_Reverse(ptrStart); break; case 9: exit(); default: printf("\nWrong choice\n"); } }while(1); } /* C code to implement the operations on a Double linked list */ /* File name : DLL.c */ #include<stdio.h> #include<conio.h> struct Node { struct Node * ptrLeft; int iData; struct Node * ptrRight; }; typedef struct Node DLLNode;

//Pointer to the previous node. //Data value or information. //Pointer to the next node. //Rename the data type of each node as DLLNode.

//Prototype Declaration int fnDLL_Count_Total_Nodes(DLLNode *); DLLNode* fnDLL_Find_Position(DLLNode *, int ); void fnDLL_Insert (DLLNode *, int ); DLLNode *fnDLL_Insert_At_Beginning (DLLNode *, int ); void fnDLL_Insert_At_End (DLLNode *, int ); DLLNode *fnDLL_Insert_At_Specific_Position (DLLNode *, int , int); int fnDLL_Delete (DLLNode *); DLLNode *fnDLL_Delete_From_Beginning (DLLNode *); int fnDLL_Delete_From_End (DLLNode *); DLLNode *fnDLL_Delete_from_Specific_Position (DLLNode *, int); void fnDLL_Display_Nodes(DLLNode *); int fnDLL_Count_Total_Nodes(DLLNode *ptrStart) // Purpose : This function counts the total number of nodes in a double linked list.

// Input : ptrStart is the address of the first node in the list. // Output : This function returns total number of nodes iCount in the list. { int iCount=0; DLLNode *ptrTemp; //Create a new pointer ptrTemp of DLLNode type. ptrTemp = ptrStart; //Initialize pointer. iCount = 0; //Initialize counter. while(ptrTemp != NULL) //Repeat steps 11 and 12 until ptrTemp reaches NULL. { iCount = iCount + 1; //Increment value of iCount. ptrTemp = ptrTemp->ptrRight; //Advance ptrTemp to next node. } return(iCount); //Return total number of nodes. }//End of Function DLLNode *fnDLL_Find_Position(DLLNode *ptrStart, int iPosition) // Purpose : This function finds the address of a specific node. // Input : ptrStart is address of the first node and address of the node at iPosition in the list is required. // Output : This function returns the address of the desired node. { int iCounter; DLLNode *ptrTemp; ptrTemp = ptrStart; for(iCounter = 1; iCounter < iPosition && ptrTemp!=NULL; iCounter++) ptrTemp = ptrTemp->ptrRight; return ptrTemp; }// End of Function void fnDLL_Insert (DLLNode *ptrNodeB, int iData) //Purpose : This function inserts a new node C after node B in a double linked list. //Input : ptrNodeB is the address of node B. iData is data value of node C. //Output : None. { DLLNode *ptrNodeC; //ptrNodeC is the address of node C. ptrNodeC = (DLLNode*) malloc(sizeof(DLLNode)); //Create node C. ptrNodeC->iData = iData; // Set the data value into new node C. ptrNodeC->ptrLeft = ptrNodeB; //Set B as the left node of C. ptrNodeC->ptrRight = ptrNodeB->ptrRight; //Set D as the right node of C. ptrNodeB->ptrRight->ptrLeft = ptrNodeC; //Set C as the right node of D. ptrNodeB->ptrRight = ptrNodeC; //Set C as the left node of B. }// End of function. DLLNode *fnDLL_Insert_At_Beginning (DLLNode *ptrStart, int iData) //Purpose : Insert a new node P at the beginning of the list. //Input : ptrStart is the address of the first node and iData is the data value of new node P. //Output : This function returns the address of the first node (ptrStart). { DLLNode *ptrNodeP; //ptrNodeP is the address of node P. ptrNodeP = (DLLNode*) malloc(sizeof(DLLNode)); //Create the new node. ptrNodeP->iData = iData; //Copies the data value into new node. ptrNodeP->ptrLeft = NULL; //Set NULL to left link of P. ptrNodeP->ptrRight = ptrStart; //Set currently first node as the left node of P.

ptrStart->ptrLeft = ptrNodeP; ptrStart = ptrNodeP; return ptrStart; }// End of Function

//Set P as the left node of currently first node. //Now P is the first node in the list. //Return the updated address of the first node.

void fnDLL_Insert_At_End(DLLNode *ptrStart, int iData) // Purpose : This function inserts a node P at the end of the list. // Input : ptrStart is the address of the first node and iData is the data value of P. // Output : None. { int iTotalNodes; DLLNode *ptrPosition; iTotalNodes = fnDLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. if(iTotalNodes != 0) //If list is not empty. { ptrPosition = fnDLL_Find_Position(ptrStart, iTotalNodes); //Get the address of the last node. fnDLL_Insert(ptrPosition, iData); //Insert new node after the node ptrPosition. } }//End of Function DLLNode *fnDLL_Insert_At_Specific_Position (DLLNode *ptrStart, int iData, int iPosition) // Purpose : This function inserts a new node at a specific position of the list. // Input : ptrStart is address of the first node. iData is data value of new node. iPosition is the position where the new node is to be inserted. // Output : This function returns the address of the first node (ptrStart). { int iTotal; DLLNode *ptrNewNode, *ptrPosition; ptrPosition = fnDLL_Find_Position(ptrStart, iPosition -1); //Get the address of the node at position -1. iTotal = fnDLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. if(iPosition <= iTotal+1 && iPosition >= 0) //If position is valid. { if(iPosition == 1 || ptrStart == NULL) //If position is 1 or list is empty. ptrStart = fnDLL_Insert_At_Beginning (ptrStart, iData); //Insert node at beginning. else fnDLL_Insert(ptrPosition, iData); //Insert new node after the node ptrPosition. } else printf("\nNot a valid position"); return ptrStart; }//End of Function int fnDLL_Delete(DLLNode *ptrNodeB) //Purpose : This function deletes an existing node B from a double linked list. //Input : ptrNodeB is the address of node B. //Output : The data value of the deleted node. { int iData; iData = ptrNodeB->iData; //Stores the data value of the deleted node. ptrNodeB->ptrRight->ptrLeft = ptrNodeB->ptrLeft; ptrNodeB->ptrLeft->ptrRight = ptrNodeB->ptrRight; ptrNodeB->ptrLeft = ptrNodeB->ptrRight = NULL;

free(ptrNodeB); return(iData); }// End of function

//Returns the address of node Q to heap.

DLLNode *fnDLL_Delete_From_Beginning(DLLNode *ptrStart) // Purpose : This function deletes the first node from the list. // Input : ptrStart is the address of the first node in the list. //Output : Returns the address of the updated first node in the list. { int iData; DLLNode *ptrDeletedNode; if(ptrStart == NULL) //If the list is empty. { printf("\nThere is nothing to delete"); return; } ptrDeletedNode = ptrStart; //Set the pointer to first node. iData = ptrStart->iData; //Stores the data value of the deleted node. ptrStart = ptrStart->ptrRight; //Now the second node is the first node of the list. ptrStart->ptrLeft = NULL; ptrDeletedNode->ptrRight = NULL; free(ptrDeletedNode); //Return deleted node to heap. return(ptrStart); }//End of Function int fnDLL_Delete_From_End (DLLNode *ptrStart) // Purpose : This function deletes the last node from the list // Input : ptrStart is the address of the first node in the list. // Output : The data value of the last node. // Comments: This function works if number of nodes in the list are more than one. If the list contains only one node, call the function to delete node from the beginning of the list. { int iTotal,iData; DLLNode *ptrLastNode; if(ptrStart == NULL) //If list is empty. { printf("\nThere is nothing to delete"); return; } iTotal = fnDLL_Count_Total_Nodes(ptrStart); //Count total number of nodes. if(iTotal > 1) //If list contains more than one nodes. { ptrLastNode = fnDLL_Find_Position(ptrStart, iTotal); //Get the address of node Q. ptrLastNode->ptrLeft->ptrRight = NULL; //Set NULL to right link field of second last node. ptrLastNode->ptrLeft = NULL; //Set NULL to the left link field of last node. iData = ptrLastNode->iData; free(ptrLastNode); } return iData; }//End of Function DLLNode *fnDLL_Delete_from_Specific_Position (DLLNode *ptrStart, int iPosition)

// Purpose : This function deletes the last node of the list. // Input : ptrStart is the address of the first node in the list. iPosition is the position from where the node is to be deleted. //Output : Returns the updated address of first node in the list. { int iTotal,iData; DLLNode *ptrPosition; iTotal = fnDLL_Count_Total_Nodes(ptrStart); if(iPosition <= iTotal && iPosition > 0) //If valid position. { if(iPosition == 1) //If node is in the first position. ptrStart = fnDLL_Delete_From_Beginning(ptrStart); else if(iPosition == iTotal) //If the node is the last node. iData = fnDLL_Delete_From_End(ptrStart); else { ptrPosition = fnDLL_Find_Position(ptrStart, iPosition); //Get the address of the node. iData = fnDLL_Delete(ptrPosition); } } else printf("\nNot a valid position"); return ptrStart; }//End of Function void fnDLL_Display_Nodes(DLLNode *ptrStart) // Purpose : This function displays all the nodes in the list. // Input : ptrStart is the address of the first node in the list. //Output : None. { DLLNode *ptrTemp; ptrTemp = ptrStart; //Initialize pointer. while(ptrTemp!=NULL) //Repeat steps 10 and 11 until ptrTemp is NULL. { printf("%d\t",ptrTemp->iData); //Print the data. ptrTemp = ptrTemp->ptrRight; //Advance ptrTemp to next node. } }//End of Function void main(void) { DLLNode *ptrStart=NULL; int choice,iData,iPosition; clrscr(); do { printf("1. Insert element at begining \n"); printf("2. Insert element at end positon\n"); printf("3. Insert at the specific position\n"); printf("4. Display\n"); printf("5. Delete from the begining\n");

printf("6. Delete from the last\n"); printf("7. Delete the element from the specific position\n"); printf("8. Exit\n"); printf("Enter your choice\n"); scanf("%d",&choice); switch(choice) { case 1: printf("Enter the item\n"); scanf("%d",&iData); ptrStart = fnDLL_Insert_At_Beginning (ptrStart, iData); break; case 2: if(ptrStart!=NULL) { printf("Enter the item\n"); scanf("%d",&iData); fnDLL_Insert_At_End(ptrStart, iData); } else printf("For insertion into empty list choose option 1\n"); break; case 3: printf("Enter the specific position and the item\n " ); scanf("%d%d", &iPosition, &iData); ptrStart = fnDLL_Insert_At_Specific_Position (ptrStart,iData,iPosition); break; case 4: printf("\nThe Elements in the list are\n"); fnDLL_Display_Nodes(ptrStart); printf("\n*****************************\n"); break; case 5: ptrStart = fnDLL_Delete_From_Beginning (ptrStart); break; case 6: if(fnDLL_Count_Total_Nodes(ptrStart)==1) { printf("There is only one node in the list. Choose either option 5 or option 7\n"); break; } fnDLL_Delete_From_End(ptrStart); break; case 7: printf("\nEnter the position of the element to be deleted\n"); scanf("%d",&iPosition); ptrStart = fnDLL_Delete_from_Specific_Position (ptrStart,iPosition);

break; case 8: exit(1); default: printf("\nWrong choice\n"); } }while(1); } /* Program to add two polynomials maintained as linked lists. */ /* File name : PolyAdd.c */ #include <stdio.h> #include <conio.h> #include <alloc.h> /* structure representing a node of a linked list. The node can store term of a polynomial */ struct Polynomial { int iCoeff; int iExp; struct Polynomial *ptrNext; }; typedef struct Polynomial PolyNode; PolyNode * fnCreate_Polynomial(PolyNode *, int , int); void fnDisplayPoly ( PolyNode *); PolyNode * fnPolyADD(PolyNode *, PolyNode *, PolyNode *); PolyNode *fnCreate_Polynomial(PolyNode *ptrPoly, int iCoeff, int iExp) // Purpose : This function creates a node in a polynomial. // Input : ptrPoly is name of the polynomial. iCoeff and iExp are the coefficient and exponent of the node respectively. // Output : Returns the starting address (ptrPoly) of the polynomial. { PolyNode *ptrTemp,*ptrNewNode ; ptrTemp = ptrPoly; ptrNewNode = (PolyNode*) malloc(sizeof (PolyNode)) ; if ( ptrPoly == NULL ) ptrPoly = ptrNewNode; //Set new node as first node if list is empty. else { while ( ptrTemp -> ptrNext != NULL ) // traverse the entire linked list. ptrTemp = ptrTemp -> ptrNext; ptrTemp -> ptrNext = ptrNewNode; //Set new node as the next node of ptrTemp. } ptrNewNode -> iCoeff = iCoeff ; // Set coefficient in the new node. ptrNewNode -> iExp = iExp ; // Set exponent in the new node. ptrNewNode -> ptrNext = NULL ; // Set link field of new node to NULL as this is the last node. return ptrPoly; }// End of function

void fnDisplayPoly ( PolyNode *ptrPoly ) // Purpose : This function displays a polynomial. // Input : ptrPoly is the starting address of the list. // Output : None. { if(ptrPoly==NULL) { printf("List is empty"); return; } /* traverse till the end of the linked list */ while ( ptrPoly != NULL ) { printf ( "%dx^%d + ", ptrPoly -> iCoeff, ptrPoly -> iExp ) ; ptrPoly = ptrPoly -> ptrNext ; } printf ( "\b\b\b " ) ; /* erases the last colon */ }//End of Function PolyNode *fnPolyADD(PolyNode *ptrPolyA, PolyNode *ptrPolyB, PolyNode *ptrPolyC) //Purpose : This function adds two polynomials A and B and stores the result in C. //Input : ptrPolyA pointer to the first node of polynomial A. // : ptrPolyB pointer to the first node of polynomial B. // : ptrPolyC pointer to the first node of the resultant polynomial C. //Output : Returns None. { int iX; PolyNode *ptrTempA, *ptrTempB; ptrTempA = ptrPolyA; ptrTempB = ptrPolyB; while(ptrTempA != NULL && ptrTempB != NULL) { if(ptrTempA->iExp == ptrTempB->iExp) { iX = ptrTempA->iCoeff + ptrTempB->iCoeff; if(iX != 0) ptrPolyC = fnCreate_Polynomial(ptrPolyC, iX, ptrTempA->iExp); ptrTempA = ptrTempA->ptrNext; ptrTempB = ptrTempB->ptrNext; } else if(ptrTempA->iExp < ptrTempB->iExp) { ptrPolyC = fnCreate_Polynomial(ptrPolyC, ptrTempB->iCoeff, ptrTempB->iExp); ptrTempB = ptrTempB->ptrNext; } else { ptrPolyC = fnCreate_Polynomial(ptrPolyC, ptrTempA->iCoeff, ptrTempA->iExp); ptrTempA = ptrTempA->ptrNext; }

} while(ptrTempA != NULL) { ptrPolyC = fnCreate_Polynomial(ptrPolyC, ptrTempA->iCoeff, ptrTempA->iExp); ptrTempA = ptrTempA->ptrNext; } while(ptrTempB != NULL) { ptrPolyC = fnCreate_Polynomial(ptrPolyC, ptrTempB->iCoeff, ptrTempB->iExp); ptrTempB = ptrTempB->ptrNext; } return ptrPolyC; } // End of function void main( ) { PolyNode *ptrPolyA, *ptrPolyB, *ptrPolyC; int i = 0,iChoice,iCoeff,iExp ; ptrPolyA = ptrPolyB = ptrPolyC = NULL ; /* empty linked lists */ clrscr(); while(1) { printf("\n 1. Add term in first polynomial"); printf("\n 2. Show first polynomial"); printf("\n 3. Add term in second polynomial"); printf("\n 4. Show second polynomial"); printf("\n 5. Add two polynomials"); printf("\n 6. Exit"); printf("\nEnter your choice"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("\nEnter coefficient and exponent"); scanf("%d%d",&iCoeff,&iExp); ptrPolyA = fnCreate_Polynomial(ptrPolyA,iCoeff,iExp); break; case 2: fnDisplayPoly(ptrPolyA); break; case 3: printf("\nEnter coefficient and exponent"); scanf("%d%d",&iCoeff,&iExp); ptrPolyB = fnCreate_Polynomial(ptrPolyB,iCoeff,iExp); break; case 4: fnDisplayPoly(ptrPolyB); break; case 5: ptrPolyC=NULL; ptrPolyC = fnPolyADD(ptrPolyA,ptrPolyB,ptrPolyC); fnDisplayPoly (ptrPolyC) ; /* displays the resultant polynomial */

break; case 6: exit(1); default: printf("\nWrong choice"); } } } /* Program to add two polynomials maintained as linked lists. */ /* File name : PolyMul.c */ #include <stdio.h> #include <conio.h> #include <alloc.h> /* structure representing a node of a linked list. The node can store term of a polynomial */ struct Polynomial { int iCoeff; int iExp; struct Polynomial *ptrNext; }; typedef struct Polynomial PolyNode; PolyNode * fnCreate_Polynomial(PolyNode *, int , int); void fnDisplayPoly ( PolyNode *); PolyNode * fnPolyADD(PolyNode *, PolyNode *, PolyNode *); PolyNode *fnCreate_Polynomial(PolyNode *ptrPoly, int iCoeff, int iExp) // Purpose : This function creates a node in a polynomial. // Input : ptrPoly is name of the polynomial. iCoeff and iExp are the coefficient and exponent of the node respectively. // Output : Returns the starting address (ptrPoly) of the polynomial. { PolyNode *ptrTemp,*ptrNewNode; ptrTemp = ptrPoly; ptrNewNode = (PolyNode*) malloc(sizeof (PolyNode)) ; if ( ptrPoly == NULL ) ptrPoly = ptrNewNode; //Set new node as first node if list is empty. else { while ( ptrTemp -> ptrNext != NULL ) // traverse the entire linked list. ptrTemp = ptrTemp -> ptrNext; ptrTemp -> ptrNext = ptrNewNode; //Set new node as the next node of ptrTemp. } ptrNewNode -> iCoeff = iCoeff ; // Set coefficient in the new node. ptrNewNode -> iExp = iExp ; // Set exponent in the new node. ptrNewNode -> ptrNext = NULL ; // Set link field of new node to NULL as this is the last node. return ptrPoly; }// End of function

void fnDisplayPoly ( PolyNode *ptrPoly ) // Purpose : This function displays a polynomial. // Input : ptrPoly is the starting address of the list. // Output : None. { if(ptrPoly==NULL) { printf("List is empty"); return; } /* traverse till the end of the linked list */ while ( ptrPoly != NULL ) { printf ( "%dx^%d + ", ptrPoly -> iCoeff, ptrPoly -> iExp ) ; ptrPoly = ptrPoly -> ptrNext ; } printf ( "\b\b\b " ) ; /* erases the last colon */ }//End of Function PolyNode *fnPolyADD(PolyNode *ptrPolyA, PolyNode *ptrPolyB, PolyNode *ptrPolyC) //Purpose : This function adds two polynomials A and B and stores the result in C. //Input : ptrPolyA pointer to the first node of polynomial A. // : ptrPolyB pointer to the first node of polynomial B. // : ptrPolyC pointer to the first node of the resultant polynomial C. //Output : Returns None. { int iX; PolyNode *ptrTempA, *ptrTempB; ptrTempA = ptrPolyA; ptrTempB = ptrPolyB; while(ptrTempA != NULL && ptrTempB != NULL) { if(ptrTempA->iExp == ptrTempB->iExp) { iX = ptrTempA->iCoeff + ptrTempB->iCoeff; if(iX != 0) ptrPolyC = fnCreate_Polynomial(ptrPolyC, iX, ptrTempA->iExp); ptrTempA = ptrTempA->ptrNext; ptrTempB = ptrTempB->ptrNext; } else if(ptrTempA->iExp < ptrTempB->iExp) { ptrPolyC = fnCreate_Polynomial(ptrPolyC, ptrTempB->iCoeff, ptrTempB->iExp); ptrTempB = ptrTempB->ptrNext; } else { ptrPolyC = fnCreate_Polynomial(ptrPolyC, ptrTempA->iCoeff, ptrTempA->iExp); ptrTempA = ptrTempA->ptrNext; }

} while(ptrTempA != NULL) { ptrPolyC = fnCreate_Polynomial(ptrPolyC, ptrTempA->iCoeff, ptrTempA->iExp); ptrTempA = ptrTempA->ptrNext; } while(ptrTempB != NULL) { ptrPolyC = fnCreate_Polynomial(ptrPolyC, ptrTempB->iCoeff, ptrTempB->iExp); ptrTempB = ptrTempB->ptrNext; } return ptrPolyC; } // End of function PolyNode *fnPolyMUL (PolyNode *ptrPolyA, PolyNode *ptrPolyB, PolyNode *ptrPolyC) //Purpose : This function multiplies two polynomials A and B and stores the result in C. //Input : ptrPolyA pointer to the first node of polynomial A. // : ptrPolyB pointer to the first node of polynomial B. // : ptrPolyC pointer to the first node of the resultant polynomial C. //Output : Returns the pointer to polynomial C ptrPolyC. { PolyNode *ptrTempA, *ptrTempB,*ptrPolyC1=NULL,*ptrPolyC2; int coeff1,exp1; ptrTempA = ptrPolyA; ptrTempB = ptrPolyB; if(ptrPolyA == NULL) //If polynomial A is empty, then set polynomial C as B. return ptrPolyB; if(ptrPolyB == NULL) //If polynomial B is empty, then set polynomial C as A. return ptrPolyA; ptrPolyC1 = (PolyNode*)malloc(sizeof(PolyNode)); ptrPolyC1->ptrNext = NULL; while ( ptrTempA != NULL) // for each term of the first list A { while (ptrTempB != NULL) // multiply each term of the second list B with a term of A. { coeff1 = ptrTempA->iCoeff * ptrTempB->iCoeff; exp1 = ptrTempA->iExp + ptrTempB->iExp; ptrPolyC1->iCoeff = coeff1; ptrPolyC1->iExp = exp1; ptrTempB = ptrTempB->ptrNext; ptrPolyC2 = ptrPolyC; ptrPolyC = NULL; ptrPolyC = fnPolyADD (ptrPolyC2, ptrPolyC1, ptrPolyC); } ptrTempB = ptrPolyB; // reposition q to the starting of B. ptrTempA = ptrTempA->ptrNext; // go to the next node of A. } free(ptrPolyC1); return ptrPolyC; }//End of function

void main( void) { PolyNode *ptrPolyA, *ptrPolyB, *ptrPolyC; int iChoice,iCoeff,iExp ; ptrPolyA = ptrPolyB = ptrPolyC = NULL ; /* empty linked lists */ clrscr(); while(1) { printf("\n 1. Add term in first polynomial"); printf("\n 2. Show first polynomial"); printf("\n 3. Add term in second polynomial"); printf("\n 4. Show second polynomial"); printf("\n 5. Multiply two polynomials"); printf("\n 6. Exit"); printf("\nEnter your choice"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("\nEnter coefficient and exponent"); scanf("%d%d",&iCoeff,&iExp); ptrPolyA = fnCreate_Polynomial(ptrPolyA,iCoeff,iExp); break; case 2: fnDisplayPoly(ptrPolyA); break; case 3: printf("\nEnter coefficient and exponent"); scanf("%d%d",&iCoeff,&iExp); ptrPolyB = fnCreate_Polynomial(ptrPolyB,iCoeff,iExp); break; case 4: fnDisplayPoly(ptrPolyB); break; case 5: ptrPolyC = NULL; ptrPolyC = fnPolyMUL(ptrPolyA,ptrPolyB,ptrPolyC); fnDisplayPoly (ptrPolyC) ; /* displays the resultant polynomial */ break; case 6: exit(1); default: printf("\nWrong choice"); } } } 8.13 MCQ Chapter 8 Questions No. 1 to No. 2 are based on the following doubly linked list and its data structure.

1. What would be the correct statement(s), when the doubly linked list is empty? (a) Head.next=tail (b) Tail.prev=Head (c) Current=nill (d) All of these 2. Which of the following correctly describe(s) the steps to delete element b from the above doubly linked list? (a) Current.next.next.prev=Current.prev, Current.next = Current.next.next (b) Current.next = Current.prev.next, Current.next.next.prev=Current.prev (c)Current.next=Current.next.next, Current.next.next.prev=Current.next.prev (d) Current.next=tail, Head.next=Current.next.prev 3. In linked list, the logical order of elements (a) is same as their physical arrangement. (b). is determined by their physical m:rangement. (c) can not be determined from their physical arrangement (d) None of these. 4. Direct or random access of element is not possible in (a) linked list (b) array (c) string (d) None of these. 5. Odd man out (in linked list) (a) modify (c) to determine if it is empty

(b) (d)

replace None of these

6. The nth node in singly linked list, is accessed via (where n> 1) (a) the head node (b) the tail node (c) ( n - 1) nodes (d) None of these 7. In linked list, the successive elements (a) must occupy contiguous space in memory (b) need not occupy contiguous space in memory . (c) must not occupy contiguous space in memory (d) None of these 8. Linear order in linked list is provided through (a) index number

(b) the implied position of the node (c) pointer (d) None of these 9. Null pointer is used to tell (a) end of linked list (c) the linked list is empty

(b) empty pointer field of a structure (d) All of the above

10. List pointer variable in linked list contains address of the (a) following node in the list (b) current node in the list (c) first node in the list (d) None of these. 11. Underflow condition in linked list may occur when attempting to (a) insert a new node when there is no free space for it. (b) delete a non-existent node in the list. (c) delete a node in empty list (d) None of these 12. Overflow condition in linked list may occur when attempting to (a) create a node when free space pool is empty (b) traverse the nodes when free space pool is empty (c) create a node when linked list is empty (d) None of these 13. Because of linear structure of the linked list having natural linear ordering, there is similarity between linked list and array in (a) insertion of a node (b) deletion of a node (c) traversal of elements of list (d) none of these 14. Searching a linked list requires linked list be created (a) in sorted order only (b) in any order (c) without underflow condition (d) None of these 15. Copying all or part of linked list consists of (a) taking a null string and adding appropiate elements to the list one by one. (b) taking a null string and putting the address of the beginning of the list (c) changing only address of the address of beginning of the list. (d) None of these. 16. Deletion of a node in linked list involves keeping track of the address of the node(a) which immediately follows the node that is to be deleted. (b) which immediately precedes the node that is to be deleted (c) that is to be deleted (d) None of these 17. Header of a linked list is a special node at the

(a) end of the linked list (c) beginning of the linked list

(b) at the middle of the linked list (d) None of these

18. Header linked list in which last node contains the null pointer is called (a) grounded header list (b) circular header list (c) general header list (d) None of these 19. Header linked list in which last node points to the header node is called (a) grounded header list (b) circular header list (c) general header list (d) None of these 20. Polynomials in memory may be maintained through (a) linked list with header node (b) one dimensional array (c) stack (d) None of these 21. Having address of the node to be deleted in double linked list, the node can be deleted (a) without traversing the list (b) only after traversing the list from the head (c) only traversing the list (from the tail (d) None of these. 22. Representing the polynomial in memory using linked list requires each node having (a) two fields (b) three fields (c) more than three fields (d) None of these 23. The n - th node in a singly linked list is accessed via (a) The first n - 1 nodes . (b) 1 node - only the head (c) 1 node - only tlte tail (d) the first n + 1 nodes 24. It is required to insert a node at the end of a singly connected linked list having ",," nodes. How many nodes are to be traversed for this insertion? (a) 1 (b) n/2 (c) II (d) none of the above 25. There could be two representations of a circular linked list. In the first representation the list is identified by a pointer pointing to the beginning of the list as shown below. In the second representation, the list is identified by a pointer pointing to the end of the list as shown below. Which of the following statements is NOT true about these two representations? (a) In the first representation, insertion at the front (i.e. before the node X1) requires traversing the list. (b) In the first representation, insertion at the rear (i.e. after the node X3) requires traversing the list. (c) In the second representation, insertion only at the front (and not at the rear) does not require traversing the list. (d) In the second representation, insertion only at the front (and not at the rear) does not require traversing the list. 26. Linked lists are not suitable data structures for which one of the following problem (a) insertion sort (GATE-1994)

(b) binary search (c) radix sort (d) polynomial manipulation. 27. Consider the following statements. State false statement. (GATE-1996) (a) First-in-first-out types of the computations are efficiently supported by STACKS. (b) Implementing LISTS on linked lists is more efficient than implementing Lists on an array for almost all the basic LIST operations. (c) Implementing QUEUES on a circular array is more efficient than implementing QUEUES on a linear array with two indices. (d) Last-in-Last-out types of the computations are efficiently supported by QUEUES. 28. The concatenation of two lists is to be performed in O(1) time. Which of the following implementations of a list should be used? (GATE-1997) (a) singly linked list (b) doubly linked list (c) circular doubly linked list (d) array implementation of list 29. A polynomial p(x) is such that p (0) = 5, p(1) = 4, p(2) = 9 and p(3) = 20. The minimum degree it can have is' (GATE-1997) (a) 1 (c) 3 (b) 2 (d) 4 30. In which of the following cases, linked list implementation of sparse matrices consumes the same memory space as the conventional way of storing the entire array? (Assume all data-types need the same amount of storage.) (a) 5 x 6 matrix with 9 non-zero entries (b) 5 x 6 matrix with 8 non-zero entries (c) 6 x 5 matrix with 8 non-zero entries (d) 6 x 5 matrix with 9 non-zero entries 31. The linked list implementation of sparse matrices is superior to the generalized dope vector method because it is (a) conceptually easier (b) completely dynamic (c) efficient in accessing an entry (d) efficient if the sparse matrix is a band matrix 32. In a circularly linked list organization, insertion of a record involves the modification of (a) no pointer (b) 1 pointer (c) 2 pointers (d) 3 pointers 33. Linked lists are not suitable for implementing (a) insertion sort (b) binary search (c) radix sort (d) polynomial manipulation 34. The concatenation of two lists is to be performed in 0(1) time. Which of the following implementations of a list could be used? (a) Singly linked list (b) Doubly linked list (c) Circular doubly linked list (d) Array implementation of list 35. Write a statement to do the following "Make B point to the last node in the list" (a) B: = B^.Next; (b) B:=end; (c) B : = last; (d) B : = B^.Info.Key; 36. Write a statement to do the following "Make List point to an empty list" (a) List: = empty; (b) List: = NIL; (c) List: = Zero; (d) None of these.

37. In a linked list (a) each link contains a pointer to the next link (b) an array of pointers point to the links (c) each link contains data or a pointer to data (d) the links are stored in an array.

Solutions:
1. d 2. c 16. b 17. c 31. a,b 32. c 8.13 3. c 18. a 33. b 4. a 19. b 34. c 5. c 20. a 35. d 6. a 21. a 36. d 7. b 22. b 37. a 8. c 23. b 9. a 24. d 10. a 25. c 11. c 26. b 12. a 27. d 13. c 28. c 14. a 29. c 15. a 30. c

Exercise Chapter 8

1. What is a single Linked List? What are the advantages and disadvantages of single Linked List? 2. Consider a single Linked List contains the following elements: roll number: integer, name: string of max 25 char, avg no: float represent a single linked link with the elements above in C language. 3. Write down C functions to: (i) insert an element into (ii) delete an element from (a) beginning of a single linked list (b) middle position of a single linked list (a) beginning of a single linked list (b) middle position of a single linked list

4. Write an algorithm and a C function to reverse a single linked list. 5. How can you represent a polynomial using single linked list. State an polynomial example and show how can you represent the polynomial using linked list using a C program. 6. What is a Double Linked List? What are the advantages and disadvantages of Double Linked List? 7. Consider a Double Linked List contains the following elements: roll number: integer, name: string of max 25 char, avg no: float represent a double linked link with the elements above in C language. 8. Write down C functions to: (i) insert an element into (ii) delete an element from 9. Define and differentiate circular and double linked list. (a) beginning of a double linked list (b) middle position of a double linked list (a) beginning of a double linked list (b) middle position of a double linked list

CHAPTER 9 RECURSION
9.1 Definition
Recursion means process of solving a problem by reducing it to smaller versions of itself. More precisely, recursion is a technique that allows us to break down a problem into one or more sub problems that are similar

in form of original problem. It is a method for solving those problems which fall into the divide and conquer paradigm. Suppose P is a procedure containing either a call statement to itself or a call statement to a second procedure that may eventually result in a call statement back to the original procedure P. Then P is called a recursive procedure. So that the program will not continue to run indefinitely, a recursive procedure must have the following two properties: i) There must be certain criteria called base criteria for which the procedure does not call itself. ii) Each time the procedure does call itself (directly or indirectly) it must be closer to the base criteria. A recursive procedure with these two properties is said to be well-defined. The recursive programs though are small and easy to write but are not efficient in terms of the time and space complexity. Recursion may be implemented by means of stack.

9.2

Types of recursion

Recursion is of two types depending on whether a function calls itself from itself or whether two functions call one another mutually. The former is called direct recursion and latter is called indirect recursion. Thus two types of recursions are:(i) Direct recursion (ii) Indirect recursion Both types of recursions are shown diagrammatically below: fn1() { ; fn1(); } } fn2() { fn1(); ; } (a) Direct recursion (b) Indirect recursion fn1() { ; ; fn2();

Figure 9.1: Examples of direct and indirect recursion Depending on the number of recursive calls recursion can be further categorized as: (a) Linear recursion (b) Binary recursion (c) Multiple recursion

9.2.1 Linear recursion


Linear recursion can be defined as follows: (I) It performs a single recursive call. If there are more than one recursive calls in a procedure then only one of them should be chosen based on some tests or criteria. (II) Define each recursive call, so that it makes progress to base case.

9.2.2 Binary recursion


Binary recursion can be defined as follows: (I) It performs two recursive calls for each non-base case. (II) Define each recursive call, so that it makes progress to base case.

9.2.3 Multiple recursion


Multiple recursion can be defined as follows: (I) It performs many recursive calls. (II) Define each recursive call, so that it makes progress to base case.

9.3

Some recursive algorithms

In this section we will go through some recursive algorithms. But at first we will see how a recursive algorithm is written. Suppose a problem has the following recursive definition: F(x) = c for G(x) ------------- (1) = F(H(x)) for other values of x -------------(2) For the above problem condition 1 is the base case and condition 2 implies recursive call. So for x=G(x) return the value c to the calling function and for the other values of x calls the same function with the new argument H(x). Algorithm fnF(x) { if(x==G(x)) return c; else fnF(H(x); }// End of Algorithm Using the above said concept we can implement any recursive function for any recursive solution of a problem.

9.3.1 Factorial function


The recursive definition to calculate the factorial value of a number is as follows: Fact (n) = 1 for n=0 and n=1 = n*Fact (n-1) for n>1 Algorithm 9.1 1. Algorithm fnFact (n) 2. // Purpose : This algorithm calculates the factorial of a positive number. 3. // Input : n is the given number. 4. // Output : This function returns -1 for negative value of n, otherwise return factorial of n. 5. { 6. if(n<0) 7. return -1; 8. if(n == 1 || n == 0 ) // base criteria 9. return 1; 10. else 11. return (n*fnFact(n-1)); // recursive call 12. }// End of Algorithm In the algorithm 9.1, line 6 checks for a negative n. If n is negative then line 7 returns -1. Line 8 returns 1 if n is 0 or 1. Otherwise line 11 solves the problem recursively. Example 9.1 shows how the algorithm works. Example 9.1: Let us calculate the value of 4! using the above recursive algorithm: fnFact(4) = 4* fnFact(3) fnFact(3) = 3*fnFact(2)

fnFact(2) = 2*fnFact(1) fnFact(1) = 1 fnFact(2) = 2*1 = 2 fnFact(3) = 3*2 = 6 fnFact(4) = 4*6 = 24

9.3.2 G.C.D. of two numbers


According to Euclids algorithm, the recursive definition the gcd value of two positive integers x and y is as follows: Gcd(x,y) = Gcd(y,x) if x<y =y if xy and x%y=0 = Gcd(x,x%y) if x>y Algorithm 9.2 1. Algorithm fnGcd (x, y) 2. //Purpose : This algorithm calculates the gcd value of two numbers. 3. //Input : x and y are the two positive integers and x>y. 4. //Output : The calculated gcd value of x and y. 5. { 6. if (x>=y && x%y==0) //Base criteria 7. return y; 8. else 9. return(fnGcd(y, x%y)); //Recursive call 10. }// End of algorithm Example 9.2: Let us show the execution of above algorithm to calculate the gcd value of 10 and 6. fnGcd(10,6) fnGcd(6,10%6) fnGcd(4, 6%4) As here 4>2 and 4%2=0, fnGcd(4,2) returns 2. 2 2 2

9.3.3 Exponential power


We can define a recursive function to compute the value of x as follows: Power(x,n) = 1 if n = 0 =x if n = 1 = Power(x, n/2) * Power(x, n/2) if n is even and n > 1 = x * Power(x, (n-1)/2) * Power(x, (n-1)/2) if n is odd and n > 1 Algorithm 9.3 1. Algorithm fnPower(x,n) n 2. //Purpose : This algorithm computes x . 3. //Input : x is mantissa and n is exponent. n 4. //Output : The value of x . 5. { 6. if (n == 0) //Base criteria. 7. return 1; 8. if (n == 1) //Base criteria. 9. return x; 10. if (n>1 && n%2 == 0) 11. return(fnPower(x,n/2) * fnPower(x,n/2));
n

//Recursive call.

12. if (n>1 && n%2 == 1) 13. return(x * fnPower(x,(n-1)/2) * fnPower(x,(n-1)/2)); 14. }// End of algorithm.

//Recursive call.
6

Example 9.3: Figure 9.2 shows the execution of algorithm 9.3 to calculate the value of 2 and the order of the function calls are shown with each and every function call within third braces. 64 [1] fnPower(2,6)

[2] 8 fnPower(2,3)

8 [5] fnPower(2,3)

[3] 2 * fnPower(2,1)

2 [4] fnPower(2,1)

[6] 2 * fnPower(2,1)

[7] 2 fnPower(2,1)

return 2

return 2

return 2
6

return 2

Figure 9.2: A recursion tree to compute the value of 2 .

9.3.4 Fibonacci sequence


The celebrated Fibonacci sequence is as follows: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 The recursive definition to calculate the nth number in the above series can be stated as follows: Fib(n) = n for n= 0 or 1 = Fib(n-2) + Fib(n-1) for n>1 Algorithm 9.4 1. Algorithm fnFib(n) 2. // Purpose : This algorithm finds the nth number in the Fibonacci series. 3. // Input : n is an integer. 4. // Output : The value of nth number in the Fibonacci series. 5. { 6. if(n == 0 || n ==1 ) //Base criteria. 7. return n; 8. else 9. return(fnFib(n-2) + fnFib(n-1)); //Recursive call. 10. }//End of algorithm Example 9.4: The recursion tree to calculate the value of Fib(5) using the algorithm is shown in Figure 9.3 and the order of the function calls are shown with each and every function within third braces. 5 [1] fnFib(5)

[2] 2 fnFib(3) [3] 1 fnFib(1) 1 [4] fnFib(2)

+ [8] 1 fnFib(2) 0 [9] 1 [10] fnFib(0) + fnFib(1)

3 [7] fnFib(4) 2 [11] fnFib(3) 1 [13] fnFib(2)

return 1

[5] 0 1 [6] fnFib(0) + fnFib(1)

[12] 1 fnFib(1)

return 0 return 1

return 0

return 1

[14] 0 [15] 1 return 1 fnFib(0) + fnFib(1)

return 0 return 1 Figure 9.3: A recursion tree to find the n value in Fibonacci series
th

9.3.5 Towers of Hanoi


The Towers of Hanoi problem is a famous recursive problem, which is based on 3 pegs and a set of discs with different sizes. Suppose 3 pegs A, B and C are given and on peg A there are finite number of n discs in increasing order i.e. biggest one at the bottom and smallest one at the top. The object is to move the discs from peg A to peg C using peg B as an auxiliary peg. The rules are: a) Only one disc can be moved at a time. b) Only the top disc on any peg can be moved to any other peg. c) A larger disc cant be placed on a smaller one. If there is one disc on peg A then move the only disc to peg C (A C). For more than one disc on peg A, move top n-1 discs recursively to peg B using peg C as auxiliary according to the above stated rules. Now move remaining one disc from peg A to peg C. Then again move n-1 discs from peg B to peg C using peg A as auxiliary. The solution to the Towers of Hanoi problem for n = 3 appear in the figure 9.4. Move top disc from peg A to peg C. Move top disc from peg A to peg B. Move top disc from peg C to peg B. Move top disc from peg A to peg C. Move top disc from peg B to peg A. Move top disc from peg B to peg C. Move top disc from peg A to peg C. A B C A B C

(1) A

Initial B C

(2) A

A -> C B C

(3) A

A -> B B C

(4) A

C -> B B C

(5) A

A -> C B C

(6) A

B -> A B C

(7)

B -> C

(8)

A -> C

Figure 9.4: The moves to solve Tower of Hanoi problem for 3 discs. Algorithm 9.5 1. Algorithm fnTowers_of_Hanoi(A, B, C, N) 2. //Purpose : This algorithm finds the moves to solve the Tower of Hanoi problem for N discs. 3. //Input : N is total number of discs. 4. //Output : None. 5. { 6. if(N == 1) 7. { 8. Move top disc from A to C (A C); 9. return; 10. }

11. else 12. { 13. fnTowers_of_Hanoi(A, C, B, N-1); 14. fnTowers_of_Hanoi(A, B, C, 1); 15. fnTowers_of_Hanoi(B, A, C, N-1); 16. } 17. } // End of algorithm Let us find the moves for N = 3 using the above recursive algorithm:
TOH(A,B,C,3)

TOH(A,C,B,2)

TOH(A,B,C,1)

TOH(B,A,C,2)

A -> C

TOH(A,B,C ) TOH(A,C,B,1)

TOH(C,A,B,1)

TOH(B,C,A,1)

TOH(B,A,C,1) TOH(A,B,C,1)

A -> C A -> B C -> B B -> A B -> C Therefore the total moves required for 3 discs are: A -> C A -> B C -> B A -> C B -> A B -> C A -> C Number of moves required for N discs are: T(N) = 2T(N-1) + 1 = 22T(N-2) + 2 + 1 = ... = = 2iT(N-i) + 2i-1 + + 2 + 1 = 2N-1 + 2N-2 + ..+ 2 + 1 [Let N-i = 1. So T(1) = 1] = 2N 1

A -> C

9.4

Tail recursion

A recursive function is said to be tail recursive if there are no pending operations to be performed on return from a recursive call. Consider the function fnFact() of the algorithm 9.1. Notice that there is a pending operation, namely multiplication, to be performed on return from each recursive call. Whenever there is a pending operation, the function is called non-tail recursive. The fnFact() function can be rewritten in a tail recursive way as shown in algorithm 9.6. Algorithm 9.6 1. Algorithm fnFact1(n, result) 2. // Purpose : This algorithm finds the factorial of a number in a tail-recursive way. 3. // Input : result is the calculated factorial value of n 4. // Output : This function returns the value of result. 5. {

6. if(n == 0 || n == 1) 7. return result; 8. else 9. return fnFact1(n-1, n*result); 10. }// End of Algorithm Assume factorial value of 3 is to be calculated. Then the function fnFact1() described in Algorithm 9.6 is to be called as fnFact1(3,1). Now the question is how does the algorithm work? First the condition of line 6 executes. As n=3, so program control goes to line 9 of the else part. Now the function fnFact1(3,1) calls itself but with the different parameters like fnFact1(2,3). Similarly fnFact1(2,3) calls fnFact1(1,6). Now as n=1, the function fnFact1(1,6) returns 6 to fnFact1(2,3) and so on. Example 9.5 shows how the algorithm works. From the example it is clear that there is no pending operation after return from any function call. Thus we can implement a factorial function in a tail-recursive way. Example 9.5: Factorial of 3 is to be calculated. fnFact1(3,1) call return 6

fnFact1(2,3) call return 6

fnFact1(6,1)

9.5

Disadvantages of recursion

The disadvantages of recursion are as follows: (i) It consumes more storage space because the recursive calls along with automatic variables are stored on the stack. (ii) If proper precautions are not taken, recursion may result in non-terminating iterations. (iii) It also slows down execution speed, as function call require jumps, and saving the current state of program onto stack before jump.

9.6

Recursion Vs Iteration
Iteration It is a process of executing a statement or a set of statements repeatedly, until some specific condition is satisfied. Iteration involves four clear cut steps initialization, condition, execution, and updation. Any recursive problem can be solved iteratively. Iterative counterpart of a problem is more Recursion Recursion is the technique of defining anything in terms of itself. There must be an exclusive if statement inside the recursive functions, specifying stopping condition which indicates the base criteria. Not all problems have recursive solution. Recursion is generally a worse option to go for

Serial No. 1. 2. 3. 4.

efficient in terms of memory utilization and simple problems, or problems not recursive in execution speed. nature.

/****C code to solve some problems using recursion****/ /****File Name: recursion.c****/
#include<stdio.h> #include<conio.h> int iCount_total_moves_for_toh; int fnFact (int n) // Purpose : This function calculates the factorial of a positive number. // Input : n is the given number. // Output : This function returns -1 for negative value of n, otherwise return factorial of n. { if(n<0) return -1; if(n == 1 || n == 0 ) // base criteria return 1; else return (n*fnFact(n-1)); // recursive call }// End of function int fnGcd (int x, int y) //Purpose : This function calculates the gcd value of two numbers. //Input : x and y are the two positive integers and x>y. //Output : The calculated gcd value of x and y. { if (x>=y && x%y==0) //Base criteria return y; else return(fnGcd(y, x%y)); //Recursive call }// End of function int fnPower(int x,int n) //Purpose : This function computes xn. //Input : x is mantissa and n is exponent. //Output : The value of xn. { if (n == 0) //Base criteria. return 1; if (n == 1) //Base criteria. return x; if (n>1 && n%2 == 0) return(fnPower(x,n/2) * fnPower(x,n/2)); if (n>1 && n%2 == 1) return(x * fnPower(x,(n-1)/2) * fnPower(x,(n-1)/2)); }// End of function. int fnFib(int n) // Purpose : This function finds the nth number in the Fibonacci series.

//Recursive call. //Recursive call.

// Input: n is an integer. // Output : The value of nth number in the Fibonacci series. { if(n == 0 || n ==1 ) //Base criteria. return n; else return(fnFib(n-2) + fnFib(n-1)); //Recursive call. }//End of function void fnTowers_of_Hanoi(char A, char B, char C, int N) //Purpose : This function finds the moves to solve the Tower of Hanoi problem for N discs. //Input : N is total number of discs. //Output : None. { if(N == 1) { printf("%c->%c\n",A,C); iCount_total_moves_for_toh++; return; } else { fnTowers_of_Hanoi(A, C, B, N-1); fnTowers_of_Hanoi(A, B, C, 1); fnTowers_of_Hanoi(B, A, C, N-1); } } // End of function void main(void) { int n,iChoice,x,y; clrscr(); do { printf("1. Calculate factorial recursively\n"); printf("2. Calculate GCD recursively\n"); printf("3. Calculate x^n recursively\n"); printf("4. Calculate nth term of fibonacci series recursively\n"); printf("5. Tower of Hanoi problem\n"); printf("6. Exit\n"); printf("Enter your choice\n"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("Enter the number to calculate the factorial"); scanf("%d",&n); printf("The factorial of %d = %d\n",n,fnFact(n)); break; case 2: printf("Enter two numbers to calculate GCD value"); scanf("%d%d",&x,&y);

printf("The GCD value of %d and %d is %d\n",x,y,fnGcd(x,y)); break; case 3: printf("Enter the value of x and n"); scanf("%d%d",&x,&y); printf("The GCD value of %d and %d is %d\n",x,y,fnPower(x,y)); break; case 4: printf("Enter n"); scanf("%d",&n); printf("The %d term in fibonacci series is %d\n",n,fnFib(n)); break; case 5: printf("Enter the number of disks\n"); scanf("%d",&n); iCount_total_moves_for_toh=0; printf("The moves for %d disks are\n",n); fnTowers_of_Hanoi('A','B','C',n); printf("Total number of moves are %d\n\n",iCount_total_moves_for_toh); break; case 6: exit(1); default: printf("Wrong Choice\n"); } }while(1); } 9.7 MCQ Chapter 9 1. Tail recursion (a) occurs when the recursive call is the last statement executed in a recursive procedure or function (b) is a path that includes a recursive call to the routine, to solve a smaller version of the original problem (c) is a structure that keeps track of the activation records at run time, in order to preserve the values of parameters, return addresses, registers, and so on (d) refers to the point in the compile/execution cycle when variable names are associated with addresses in memory. 2. Recursive case (a) is a nonrecursive exit from the recursive routine (b) is a path that includes a recusive call to the routine, to solve a similer version of the original problem (c) is a structure that keeps track of the activation records at runtime, in order to preserve the values of parameters, return address, registers and so on. (d) refers to the point in the compile / execution cycle when variable names are associated with addresses in memory. 3. Base case (a) is a nonrecursive exit from the recursive routine (b) is a path that includes a recusive call to the routine, to solve a similer version of the original problem (c) is a structure that keeps track of the activation records at runtime, in order to preserve the values of parameters, return address, registers and so on.

(d) refers to the point in the compile / execution cycle when variable names are associated with addresses in memory. 4. Binding time (a) is a nonrecursive exit from the recursive routine (b) is a path that includes a recusive call to the routine, to solve a similer version of the original problem (c) is a structure that keeps track of the activation records at runtime, in order to preserve the values of parameters, return address, registers and so on. (d) refers to the point in the compile / execution cycle when variable names are associated with addresses in memory. 5. Run time stack (a) is a nonrecursive exit from the recursive routine (b) is a path that includes a recusive call to the routine, to solve a similer version of the original problem (c) is a structure that keeps track of the activation records at runtime, in order to preserve the values of parameters, return address, registers and so on. (d) refers to the point in the compile / execution cycle when variable names are associated with addresses in memory. 6. Infinite regress occurs when: (a) a base case is ommited (b) a base case is never reached (c) both (a) and (b) (d) none of these 7. Fibonacci function fib(n) = fib(n-1) + fib(n -2) is an example of (a) Direct recursion (b) Tree recursion (c) linear ecursion (d) both (a) and (b) 8. Any recursive function can be converted into an equivalent non recursive function (a) Always (b) never (c) sometimes (d) if and only if the function is tail recursive 9. Recursion is equivalent to (a) Greedy paradigm (c) both (a) and (b) 10. Advantage of recursion is (a) Coding complexity is less (c) Space complexity is less (b) Divide and Conquer paradigm (d) None of these (b) Time complexity is less (d) None of these

Solutions:
1. a 2. b 3. a 4. d 5. c 6. c 7. d 8. a 9. b 10. a

9.8 Exercise Chapter 9 1. What is Recursion? Write a C recursive algorithm to compute factorial of any given number. Check your algorithm with n=5.

2. Discuss and differentiate different types of recursions. 3. Write a C Program to determine GCD of given numbers. Show using a stack how your algorithm works. 4. Hence, Write a C Program to determine LCM of given numbers. Show using a stack how your algorithm works. 5. Write a C Program to determine nth element of a Fibonacci series. Show using a stack how your algorithm works. 6. What is iteration? How does it differs from recursion? What are the disadvantages of recursion. 7. Every recursive algorithm can be expressed as a iterative algorithm --- Justify the statement with a suitable example. 8. What is tail recursion? Explain with a suitable example and diagram.

Chapter 10 TREE
10.1 Definition
A tree is a finite set of vertices connected by edges such that (i) There is one specially designated vertex called root. (ii) The remaining vertices are partitioned into a collection of sub-trees T1, T2 Tn each of which is also a tree.

root A T2 T1 B C D T3

Figure 10.1: Example of a tree

10.2 Basic terminologies


Root: A node without any parent is called root node. In figure 10.1 A is the root node. Node (or Vertex): Each data item in a tree is called a node. The tree in figure10.1 has 11 nodes (A, B, C, D, E, F, G, H, I, J and K). Edge: The line drawn from one node to another node is called an edge. Internal node: A node with at least one child is called an internal node. In figure 10.1 A, B, C, D and F are the internal nodes. External node (or Leaf node): A node without any children is called an external node or leaf node. In figure 10.1 E, K, G, H, I and J are the leaf nodes. Ancestor and Descendant: A node n is an ancestor of node m (and m is descendant of n) if n is either the father of m or the father of some ancestors of m. In figure 10.1 node B is an ancestor of the nodes E, F and K. Degree of a node: The number of sub trees of a node is called its degree. In figure 10.1 degree of node A is 3, degree of node B is 2, degree of node E is 0, degree of node F is 1, and degree of node K is 0 and so on. Degree of a tree: It is the maximum degree of nodes in a given tree. In figure 10.1 node A has degree 3 and another node D is also having its degree 3. In all, this value is the maximum. So the degree of the above tree is 3. Siblings: The nodes who share the same parent are called siblings. In figure 10.1 H, I and J are siblings. Level: Number of ancestors of a node is called its level. Each node in a tree is assigned a level number as follows: 1) The root of tree is at level 0. 2) Level of other node = level of its parent + 1. In figure 10.1 node A is at level 0, nodes B, C and D are at level 1, nodes E, F, G, H, I and J are at level 2 and node K is at level 3. Depth (or height): The depth of a tree T can be defined as follows: depth of T = maximum level number of T + 1 In figure 10.1 depth of the tree is 4.

Path and Path length: It is a sequence of consecutive edges from the source node to the destination node. The number of edges in a path is path length. In figure 10.1, the path between A and H is A D H and the path length is 2. Branch: Path ending in a leaf node is called as branch. In figure 10.1 A B F K is a branch. Internal path length: Internal path length of a tree can be defined as the sum of the levels of all the internal nodes in the tree. In figure 10.1 the internal path length of the tree is 0+1+2+1+1 = 5. External path length: External path length of a tree is the sum of the levels of all the external nodes in the tree. The external path length of the tree in figure 10.1 is 2+3+2+2+2+2 = 13. Forest: A set of disjoint trees is called a forest. The following figure is a forest with three trees. Figure 10.2(d) shows an example of a forest.

10.3 Binary tree


A binary tree is a finite set of elements that is either empty or is partitioned into three disjoint subsets. 1) The first subset contains a single element called the root of the tree. 2) The other two subsets are themselves binary trees, called the left and right sub trees of the original tree. 3) Here each and every node can have at most two children.

(a)

(b)

(c)

(d)

(d) Figure 10.2: Example of some binary trees and forest

10.4 Types of binary trees


Complete binary tree: A binary 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.
A

Figure 10.3: Complete binary tree Full binary tree: A binary tree is called a full binary tree if it satisfies the following two conditions: 1) Each node has either zero or two children. 2) All the leaf nodes are in the same level (and obviously this is the last level).
A

Figure 10.4: Full binary tree Extended binary tree (or 2-tree): This is a binary tree where each node has either two or zero children. The node with zero children is called external node and the node with two children is called internal node. External nodes are represented by a square and internal nodes are represented by a circle. Frequently an algorithm can be represented by a 2-tree where internal nodes represent tests and external nodes represent actions.
6

5 0

Figure 10.5: Extended binary tree Here, NE = NI + 1 whereNE = number of external nodes NI = number of internal nodes

For the above 2-tree External path length = LE = 2 + 2 + 3 + 3 + 3 + 3 = 16 Internal path length = LI = 0 + 1 + 1 + 2 + 2 = 6 Observe thatLE = LI + 2NI

10.5 Properties of binary trees


The followings are the properties of a binary tree: 1. A binary tree with n nodes has exactly n-1 edges. Proof: This property can be proved by induction. Basis: Let n=1. That is there is only one node in the tree. So number of edge is 0. Hence the property is true for one node. Induction hypothesis: n = k: Assume the property is true for n = k i.e. for k nodes there is k-1 edges.

n = k+1: Now addition of one node includes one extra edge into edge set. So, now total number of edges are = (k-1) + 1 = k. Hence proved. 2. The maximum number of nodes on level i is 2i. Proof: By induction: Basis: Let i = 0. At level 0 there is only one node (root node). Hence the property is true for i = 0. Induction hypothesis: i = k: Assume the property is true for kth level. So at level k there is 2k nodes. i = k + 1: In a binary tree maximum number of children for each node is 2. Now at level k there is 2k nodes. Thus at level k+1 the maximum number of nodes can be 2*2k = 2k+1. Hence proved. 3. The number of nodes in a full binary tree with height h is 2h 1 (or the maximum number of nodes in a binary tree with height h is 2h 1). Proof: Total number of nodes in a full binary tree with height h is h-1 i 0 N = 2 = 2 + 21 + .. + 2h-1 = 2h -1 i=0 4. If n is the total number of nodes in a complete binary tree of height h, then h = log2 n + 1. Proof: From the definition of a complete binary tree of height h, it is full up to height h-1 and in the last level it may have some shortage of nodes. Hence we can write 2h-1 < n 2h 1 [As maximum number of nodes at height h-1 is 2h-1-1 and at height h is 2h-1] h-1 h 2 <n<2 h-1< log2 n < h So the value of log2 n lies between h and h-1. So if we take the floor value of log2 n then it will be h-1. Hence h = log2 n + 1. 5. If n0 be the total number of leaf nodes and n2 be the total number of nodes having two children in a binary tree, then prove that n2 = n0 - 1. Proof: Assume n1 is the total number of nodes with one child. Now the total number of nodes N = n0 + n1 + n2 And total number of edges E = n0 * 0 + n1 * 1 + n2 * 2 = n1 + 2n2. Again we know E = N 1 Hence, n1 + 2n2 = (n0 + n1 + n2) -1 which yields n2 = n0 1.

10.6 Representation of binary tree


There are two general ways to represent a binary tree in memory. 1) Linked representation: The most common and easiest way to represent a binary tree is using linked list. Each node in a binary tree has three fields. One field is used to store the data value and other two fields are used to store the address of the two children. struct Node { struct Node *ptrLeft; left info right char info; struct Node *ptrRight;

}; typedef struct Node TreeNode;


A

Figure 10.6: Example of a binary tree The linked representation of the binary tree in figure 10.6 is shown in figure 10.7. N represents the NULL value.
A

Figure 10.7: Linked representation of the binary tree in figure 10.6 Here we take a pointer variable ptrRoot to store the address of the root node in the tree. TreeNode *ptrRoot; The NULL value of ptrRoot indicates an empty tree. For a leaf node both the value of ptrLeft and ptrRight is NULL. 2) Sequential representation: Another method to represent a binary tree in memory is by array. This method is useful for a complete binary tree and is most efficient for a full binary tree. The rules to store the data values of a binary tree in an array are: i) The root R of tree T is stored in TREE [1]. ii) If a node N occupies TREE [k] then its left child is stored in TREE [2*k] and right child is stored in TREE [2*k + 1]. For a binary tree with height h, 2h 1 array spaces are required to store it. The sequential representation of the binary tree of figure 10.7 is

1
A

2
B

3
C

4
D

5
E

6
G

7
H

8
N

9
N

10
F

11
N

12
N

13
N

14
N

15
N

Now the question is how we can get the leaf nodes from the sequential representation. If the height of the binary tree is h, then all the nodes at level h-1 are obviously the leaf nodes. Now in the array the range of index of the nodes at level h-1 is from 2h-1 to 2h -1. So any node within this range in the array is a leaf node. For the other nodes N (that are above the last level) at index K, if TREE[2*K] and TREE[2*K + 1] both is NULL then N is leaf node.

10.7 Binary tree traversal methods


There are three standard ways of traversing a binary tree T with root R. A) Preorder traversal: 1. Process the root R 2. Traverse the left sub tree of R in preorder 3. Traverse the right sub tree of R in preorder
A

Figure 10.8: Example of a binary tree Preorder traversal of figure10.8: ABDECFG Algorithm 10.1 1. Algorithm fnRecursivePreorder (ptrRoot) 2. // Purpose : This algorithm finds the preorder traversal of a binary tree using recursion. 3. // Input : ptrRoot is the address of the root node of the tree. 4. //Output : None. 5. { 6. if(ptrRoot != NULL) 7. { 8. print(ptrRoot->info); 9. fnRecursivePreorder (ptrRoot->ptrLeft); 10. fnRecursivePreorder (ptrRoot->ptrRight); 11. } 12. }//End of algorithm Figure 10.9 shows the different steps of the recursive preorder algorithm for the binary tree of Figure 10.8. B D A print(A) print(B) print(D) fnRecurPreorder(NULL) fnRecurPreorder(NULL) print(E) print(F)

fnRecurPreorder(D) fnRecurPreorder(E)

fnRecurPreorder(B)

fnRecurPreorder(NULL) E

fnRecurPreorder(A) C print(C)

fnRecurPreorder(NULL) F

fnRecurPreorder(F)

fnRecurPreorder(C) G print(G)

fnRecurPreorder(G)

fnRecurPreorder(NULL) Figure 10.9: Tree of recursive calls for preorder of the tree in figure 10.8 Algorithm 10.2 1. Algorithm fnNon_Recursive_Preorder(ptrRoot) 2. // Purpose : This algorithm finds the preorder traversal of a binary tree in a non-recursive way. 3. // Input : ptrRoot is the address of the root node of the tree. 4. //Output : None. 5. { 6. TreeNode *N; 7. Initialize stack S; // initialize a stack s to be empty 8. fnPush(ptrRoot, S) // push root node onto the stack 9. while(!empty(S)) // while the stack is not empty 10. { 11. N = fnPop(S); // pop a node from the stack 12. if(N != NULL) // if the node is not NULL 13. { 14. print N->info; // print the contents of the node

15. fnPush(N->ptrRight, S); 16. fnPush(N->ptrLeft, S); 17. } 18. } 19. }//End of algorithm

// push the root of the right sub tree // push the root of the left sub tree

B) Inorder traversal: 1. Traverse the left sub tree of the root R in inorder. 2. Process the root R. 3. Traverse the right sub tree of the root R in inorder. Inorder traversal of figure 10.8 is DBEAFCG Algorithm 10.3 1. Algorithm fnRecursiveInorder(ptrRoot) 2. // Purpose : This algorithm finds the in-order traversal of a binary tree using recursion. 3. // Input : ptrRoot is the address of the root node of the tree. 4. //Output : None. 5. { 6. if(ptrRoot != NULL) 7. { 8. fnRecursiveInorder (ptrRoot->ptrLeft); 9. print(ptrRoot->info); 10. fnRecursiveInorder (ptrRoot->ptrRight); 11. } 12. }//End of algorithm Algorithm 10.4 1. Algorithm fnNon_Recursive_Inorder(ptrRoot) 2. // Purpose : This algorithm finds the in-order traversal of a binary tree in a non-recursive way. 3. // Input : ptrRoot is the address of the root node of the tree. 4. //Output : None. 5. { 6. TreeNode *P; 7. P = ptrRoot; //Assign the address of the root node to P. 8. Initialize stack S; // initialize a stack s to be empty. 9. while( !empty(S) || P != NULL) // while the stack is not empty or P is not NULL. 10. { 11. while(P != NULL) //While P is not NULL. 12. { 13. fnPush(S, P); //Push P to stack. 14. P = P->ptrLeft; //Traverse the left child of P. 15. } 16. if( !empty(S)) //If Stack is not empty. 17. { 18. P= fnPop(S); // Pop from stack and assign the address to P. 19. Print P->info; // Print the information field of P. 20. P = P->ptrRight; //Traverse the right child of P. 21. } 22. }

23. }//End of algorithm C) Postorder traversal: 1. Traverse the left sub tree of R in postorder 2. Traverse the right sub tree of R in postorder 3. Process the root R Postorder traversal of figure 10.8 is: DEBFGCA Algorithm 10.5 1. Algorithm fnRecursivePostorder(ptrRoot) 2. // Purpose : This algorithm finds the post-order traversal of a binary tree using recursion. 3. // Input : ptrRoot is the address of the root node of the tree. 4. //Output : None. 5. { 6. if(ptrRoot != NULL) 7. { 8. fnRecursivePostorder(ptrRoot->ptrLeft); 9. fnRecursivePostorder(ptrRoot->ptrRight); 10. print (ptrRoot->info); 11. } 12. }//End of algorithm

Algorithm 10.6 1. Algorithm fnNon_Recursive_Postorder(ptrRoot) 2. // Purpose : This algorithm finds the post-order traversal of a binary tree in a non-recursive way. 3. // Input : ptrRoot is the address of the root node of the tree. 4. //Output : None. 5. { 6. TreeNode *P, *N; 7. P = ptrRoot; //Assign the address of root node to P. 8. while( !empty(S)) //While stack is not empty. 9. { 10. while(P != NULL) //While P is not NULL. 11. { 12. fnPush(S, P); //Push P onto stack. 13. if(P->ptrRight != NULL); 14. fnPush(S, NULL); //Push NULL if right child of P exists. 15. P = P->ptrLeft; //Traverse to the left child of P. 16. } 17. N = fnPop(S); //Pop one node from stack assign it to N. 18. if(N != NULL) 19. print N->info; // If N is not null then print its information field. 20. else 21. { 22. N = fnPop(S); // Pop one node from stack assign it to N. 23. P = N->ptrRight; //Assign the address of the right child of N to P. 24. fnPush(S, N); //Push N onto stack.

25. } 26. } 27. }//End of algorithm

10.8 Creation of binary tree from preorder and inorder traversal


Consider the following preorder and inorder traversals of a binary tree. Preorder :ABDGHEICFJK Inorder :GDHBEIACJFK Now the different steps to create the binary tree are as follows: STEP 1: In preorder traversal, first we print the data and then move to left child and right child respectively. Therefore the first node in preorder traversal is obviously the root node and then the left sub tree and right sub tree of the root can be obtained from the inorder traversal. Preorder :ABDGHEICFJK Inorder :GDHBEIACJFK

A GDHBEI CJFK

STEP 2: Among the nodes of the left sub tree of A, node B comes first in the preorder traversal. So B is the immediate left child of A. Similarly C is the immediate right child of A. Preorder :ABDGHEICFJK Inorder :GDHBEIACJFK

GDH

EI

JFK

STEP 3: Preorder Inorder

:ABDGHEICFJK :GDHBEIACJFK

B G D H E I

C J F K

10.9 Creation of binary tree from postorder and inorder traversal


Consider the following post-order and in-order traversals of a binary tree. Postorder :HDIEBJFKLGCA Inorder :HDBIEAFJCKGL The steps to build the binary tree from two traversals are as follows: STEP 1: In post-order traversal, first we move to left child and right child respectively and then print the data. Therefore the last node in post-order traversal is obviously the root node and then the left sub tree and right sub tree of the root can be obtained from the in-order traversal. Postorder :HDIEBJFKLGCA Inorder :HDBIEAFJCKGL

HDBIE

FJCKGL

STEP 2: Now among the nodes of the left sub tree of root A, node B is traversed last in post-order traversal. Therefore node B is immediate left child of A. Similarly we get node C as the immediate right child of A. Postorder :HDIEBJFKLGCA Inorder :HDBIEAFJCKGL

DH

IE

IE

KGL

STEP 3: Postorder Inorder

:HDIEBJFKLGCA :HDBIEAFJCKGL
A

10.10

Expression tree

Expressions tree generally a 2-tree in which external nodes contain operands and internal nodes contain operators. But due to presence of unary operators into expressions it may not be a 2-tree. An expression tree does not contain parenthesis, the reason for this is that for evaluating an expression using expression tree, structure of tree itself decides the order of operations. An expression tree may not unique for an expression. But the result they produce must be correct. The preorder and post-order traversal of an expression tree gives the prefix and postfix notation of the arithmetic expression respectively. Example 1: Create expression tree for the arithmetic expression A+B-C.
+

Two expression trees for A+B-C Example 2: Create an expression tree for A + B * C D + logE / F. Step 1:
+

B*CD+logE/F

Step 2:
+

B*C

D+logE/F

Step 3:
+

logE/F

Step 4:
+

logE Step 5:
+

lo g

10.11

Binary search tree

A binary search tree is a binary tree in which each node has value greater than every node of its left sub tree and less than every node of its right sub tree. This type of tree does not contain duplicate value. The inorder traversal of a binary search tree gives a list in ascending order. An example of a binary search tree is shown in figure 10.10.
E

Figure 10.10: An example of a binary search tree Insertion into binary search tree: Consider the following items: ITEMS: M F P B I Q We have to create a binary search tree using the items. Solutions: Step 1: The first item in the list is M. As the tree is empty, insert M as the root node. insert M Step 2: The second item F is less than the root M. SoM insert F as the left child of M. insert F
M

Step 3: Next item P is greater than M, so insert P as the right child of M.

insert P
M

Step 4: Next item in the list is B. B is less than the root M. So next comparison takes place with F (the left child of M). Now B is less than F and left child of F is NULL. So insert B as the left child of F. insert B
M

Step 5: Next item I is less than the root M and greater than F (the left child of M). As the right child of F is NULL, insert I as the right child of F. insert I
M

Step 6: The last item Q is greater than the root M and its child P. So insert Q as the right child of P. insert Q
M

Algorithm for insertion into binary search tree: Algorithm 10.7 1. Algorithm fnInsert_BST(cData) 2. //Purpose : This algorithm inserts a new node into a binary search tree. 3. //Input : The item cData. 4. //Output : This algorithm returns 0 for a duplicate node and returns 1 for a successful insertion. 5. //Comments: Root of the tree is ptrRoot which is a global variable. 6. {

7. TreeNode *ptrNewNode, *ptrParent, *ptrChild; 8. ptrNewNode=(TreeNode *)malloc(sizeof(TreeNode)); 9. ptrNewNode->ptrLeft=ptrNewNode->ptrRight=NULL; 10. ptrNewNode->info=cData; 11. if(ptrRoot == NULL) 12. ptrRoot = ptrNewNode; 13. else 14. { 15. ptrChild = ptrRoot; 16. while(ptrChild != NULL) 17. { 18. ptrParent = ptrChild; 19. if(ptrChild->info > cData) ptrChild = ptrChild->ptrLeft; 20. else if(ptrChild->info < cData) ptrChild = ptrChild->ptrRight; 21. else return 0; 22. } 23. if(ptrParent->info > cData) ptrParent->ptrLeft = ptrNewNode; 24. else ptrParent->ptrRight = ptrNewNode; 25. } 26. return 1; 27. }//End of Algorithm Recursive algorithm for insertion into binary search tree: Algorithm 10.8 1. Algorithm TreeNode *fnInsert_BST( ptrRoot, iData) 2. //Purpose : This algorithm inserts a new node into a binary search tree in recursive way. 3. //Input : Root of the tree is ptrRoot and the item to be inserted is iData. 4. //Output : This algorithm returns address of a node. 5. { 6. if (ptrRoot == NULL) // position found. 7. { 8. ptrRoot = (TreeNode *) malloc(sizeof(TreeNode)); //Create new node. 9. ptrRoot ->ptrLeft = NULL; // Set its left child to NULL. 10. ptrRoot ->ptrRight = NULL; // Set its right child to NULL. 11. ptrRoot ->info = iData; //Insert the data value into the new node. 12. } 13. elseif ( iData < ptrRoot->info) // traverse to the left of p 14. ptrRoot->ptrLeft = fnInsert_BST(ptrRoot->ptrLeft, iData); 15. elseif( iData > ptrRoot->info) // traverse to the right of p 16. ptrRoot->ptrRight = fnInsert_BST(ptrRoot->ptrRight, iData); 17. else 18. print (Duplicate node); 19. return(ptrRoot); 20. }//End of algorithm Deletion from binary search tree: Rules: 1. If the node has no child then simply remove it.
M

Node I is deleted 2. If the node has one child then moves up the only child.
M

Node P is deleted 3. If the node has two children then replace its position by its inorder successor (or predecessor).
M P

Inorder : B F I M P Q M is to be deleted. As M has two children replace node M by its inorder successor P Node M is deleted Algorithm to delete a node from a BST: Algorithm 10.9 1. Algorithm fnDelete_BST(ptrDeleteNode) 2. //Purpose : This algorithm deletes a node from a binary search tree. 3. //Input : The node with the address ptrDeleteNode is to be deleted. 4. //Output : None. 5. { 6. if(ptrDeleteNode->ptrLeft == NULL && ptrDeleteNode->ptrRight == NULL)

7. { 8. free(ptrDeleteNode); 9. return; 10. } 11. else if(ptrDeleteNode->ptrLeft != NULL && ptrDeleteNode->ptrRight != NULL) 12. { 13. ptrSuccessor = Get_Inorder_Successor(ptrDeleteNode); 14. ptrSuccessor->ptrLeft = ptrDeleteNode->ptrLeft; 15. ptrSuccessor->ptrRight = ptrDeleteNode->ptrRight; 16. free(ptrDeletenode); 17. return; 18. } 19. else if(ptrDeleteNode->ptrLeft == NULL && ptrDeleteNode->ptrRight !=NULL) 20. { 21. ptrParent = Get_Parent(ptrDeleteNode); 22. if(ptrParent->ptrLeft == ptrDeleteNode) 23. ptrParent->ptrLeft = ptrDeleteNode->ptrRight; 24. else 25. ptrParent->ptrRight = ptrDeleteNode->ptrRight; 26. free(ptrDeleteNode); 27. return; 28. } 29. else 30. { 31. ptrParent = Get_Parent(ptrDeleteNode); 32. if(ptrParent->ptrLeft == ptrDeleteNode) 33. ptrParent->ptrLeft = ptrDeleteNode->ptrLeft; 34. else 35. ptrParent->ptrRight = ptrDeleteNode->ptrLeft; 36. free(ptrDeleteNode); 37. return; 38. } 39. }//End of algorithm

10.12

Heap

A heap is a complete binary tree and is implemented in array as sequential representation rather than linked representation. A heap is called max heap or descending heap if every node of heap has a value greater than or equal to the value of every child of that node. Similarly a heap is called min heap or ascending heap if every node of heap has a value less than or equal to the value of every child of that node. Insertion into max heap: M Step 1: insert node M
M

Step 2: insert node F

Step 3: insert node P

Reheap
P F

Step 4: insert node B


P P

Step 5: insert node I


P P

Reheap
F M I M

Step 6: insert node Q

Reheap
I M I P

Deletion from heap: 1. Assign the node to some variable ITEM 2. Replace ITEM by the last node L of the tree 3. Reheap

Q 1

I 2

P 3

B 4

F 5

M 6

Suppose ITEM = Q. Then replacing node Q by the last node (L=) M we get

Reheap
I P I M

10.13

Weight balanced binary tree

Huffman tree: Consider the extended binary tree shown in the figure 10.10.

Figure 10.10 Suppose every external node has some weight W, then the weighted path length for the external node will be where W denotes the weight and P denotes the path length of an external node. Let us take the weights 4, 7, 8, 12 and create three different 2-trees. P = W1P1 + W2P2 + .. + WnPn

12

12

Figure 10.11

Figure 10.12

12

Figure 10.13 Pa = 4 X 2 + 7 X 2 + 8 X 2 + 12 X 2 = 62 Pb = 4 X 1 + 7 X 3 + 12 X 3 + 8 X 2 = 77 Pc = 12 X 1 + 4 X 3 + 7 X 3 + 8 X 2 = 61 Now we can see that three different trees have different path lengths. So problem arises for obtaining a unique tree which has minimum weighted path length. This extended binary tree can be obtained by Huffman algorithm Huffman algorithm: 1. Let us take there are n weights W1, W2, . , Wn. 2. Take two minimum weights and create a sub tree. Suppose W1 and W2 are first two minimum weights then sub tree will be-

W1

W2

3. Now the remaining weights will be W1 + W2, W3, W4, . , Wn. 4. Create all sub trees at the last weight. Example: W1 16 STEP 1: W2 11 W3 7 W4 20 W5 25 W6 5 W7 16

STEP 2:

W1 16

W2 11

W3+W6 12 20

W4 25

W5 16

W7

11

STEP 3:

W1 16

W2 + W3+W6 23

W4 20

W5 25

W7 16

16

16

11

STEP 4:

W1 + W7 32

W2 + W3+W6 23

W4 20

W5 25

16

16

20

11

Step 5:

W1 + W7 32

W2 + W3+W6 + W4 43

W5 25

25

20

16

16

11

Step 6:

W1 + W7 + W5 57

W2 + W3+W6 + W4 43

25

20

16

16

11

10.14

AVL Search tree / Height balanced binary tree

An AVL tree is a binary search tree where the balance factor of any node can have only three values -1, 0 or 1. The name AVL comes from the Russian mathematics who developed this tree in 1962 : G. M. Adelson Velskii and E. M. Landis. Balance factor = height of left sub tree (hL) height of right sub tree (hR)
0 M 0 F 0 B 0 I -1 P 0 Q 0 B 1 F 0 M -1 P 0 Q

Figure 10.14: Examples of AVL tree Insertion into AVL tree: First insert an item into AVL tree according to the rules of insertion into binary search tree. Then check whether the tree is balanced or not. If it is unbalanced, then find the nearest ancestor node (P) to the inserted node with balance factor +2 or -2 on the path from inserted node to root node. P is called the pivot node. Then perform one of the following four rotations on the unbalanced tree to make it balanced depending on some criteria. 1. Left to left rotation: When the pivot node is left heavy and the new node is inserted in left sub tree of the left child of pivot node then the rotation performed is left to left rotation.
+1 P 0 1 Insert into AL +2 P 0 A 0

P R

h A L A R

P R

LL rotation

A L

h+1 A R P R

A L

A R

h+1

Balanced Unbalanced Balanced 2. Right to Right rotation: When the pivot node is right heavy and the new node is inserted in right sub tree of the right child of pivot node then the rotation performed is right to right rotation. -1 -2 0
A P P

0 P L A L h
A

-1 Insert into AR A R h P L
A

0
P

RR rotation A R P L A L

A R

A L h

h+1

h+1

Balanced Unbalanced Balanced 3. Left to Right rotation: When the pivot node is left heavy and the new node is inserted in right sub tree of the left child of pivot node then the rotation performed is left to right rotation. Here we insert a new node into BL (left sub tree of node B). But the new node can also be inserted into BR (i.e. the right sub tree of the node B). Actually left to right rotation is a kind of double rotation. First rotate the unbalanced tree right to right taking node A as pivot node and then perform left to left rotation on the resultant tree taking node P as pivot node.

0
P

+2
P

+0
B

0
A

-1 0
B

0 LR rotation
A

-1
A P

P h R

Insert into BL h P +1 R
B

A h L B h-1 L

h A L B R h-1 h B L

A h L B R h

B h L

B R h-1

P h R

h-1

Balanced

Unbalanced

Balanced

4. Right to Left rotation: When the pivot node is right heavy and the new node is inserted in left sub tree of the right child of pivot node then the rotation performed is right to left rotation. Here we insert a new node into BL (left sub tree of node B). Insertion can also take place into BR. Right to left rotation is also a double rotation. Here first we perform a left to left rotation taking the node A as pivot node and then a right to right rotation taking the node P as pivot node. The final balanced tree is shown in figure. -1
P

-2 0
P

0 h P L
B A

1 Insert into BL h A R h B L h P L
B A

RL rotation
P

-1

0 B R

1 B R

A R h h-1 h

h h-1

B L

P L

B L

B R h-1

A R

h-1 Balanced

Unbalanced

Balanced

Example: Construct an AVL search tree with the following elements in the order of their occurrence: G, B, A, K, M, C

Insert G, A:

Insert B:

LL Rotation
B A A

B G

Insert K, M:
A

RR Rotation
G K M G B A C K M A K M

Insert C:

B A G C K M

RL Rotation

Deletion from AVL tree: If deletion of a node from an AVL search tree makes the tree unbalanced, then first find the pivot node in the tree. Now perform the following rotations on the unbalanced tree to make it balance. 1. R0 rotation: If deletion takes place from the right sub tree of P and the balance factor of the left child (A) of P is 0, then perform R0 rotation. +1 +2 -1
P

Delete from PR

R0 rotation

0
A

0 P R A R h h A h L
A

1 P R A R h h-1 A L h A h R
P

A h L

P R

h-1

Balanced

Unbalanced

Balanced

2. R1 rotation: If deletion takes place from the right sub tree of P and the balance factor of the left child (A) of P is 1, then perform R1 rotation. +1
P

+2 Delete from PR +1 P R
P

0 R1 rotation P R A L
A

+1
A

0
A

h A h L

h-1

h h-1 A R

A h L

A R

h-1

A R

h-1

P R

h-1

Balanced

Unbalanced

Balanced

3. R-1 rotation: If deletion takes place from the right sub tree of P and the balance factor of the left child (A) of P is -1, then perform R2 rotation. +1
P

+2
P

0
B

-1
A

-1 0 P h R Delete from PR
A

0 0 P h-1 R R-1 rotation


A P

A h-1 L B L h-1

A h-1 L B R h-1 h-1 B L

B R h-1

h h-1

A L

B L h-1

B R h-1

P R h-1

Balanced

Unbalanced

Balanced

4. L0 rotation: If deletion takes place from the left sub tree of P and balance factor of the right child (A) of P is 0, then perform L0 rotation. 5. L1 rotation: If deletion takes place from the left sub tree of P and the balance factor of the right child (A) of P is 1, then perform L1 rotation.

6. L-1 rotation: If deletion takes place from the left sub tree of P and the balance factor of the right child (A) of P is -1, then perform R2 rotation.

10.15

Threaded binary tree

Consider the binary tree in figure 10.6 and its linked presentation in figure .10.17. In figure 10.17, the total number of links is 16 and the total number of NULL links is 9 where as the total number of nodes in the binary tree is 8. In fact if a binary tree has n number of nodes, then total number of links in the tree 2n and total number of NULL links is n+1. A.J. Perlis and C. Thornton jointly proposed an idea to make effective use of these NULL pointers. The basic idea is to replace the NULL pointers by some appropriate pointer values called thread. The general rule says to replace the NULL left pointer of a node with the address of the immediate predecessor of this node and replace the NULL right pointer of a node by the address of the immediate successor of this node. The threaded binary tree corresponding to the tree of figure 10.6 is shown in the figure 10.15. In the threaded binary tree still the left pointer of node D and right pointer of node H is NULL. To eliminate these two NULL values we take a header node. Left pointer of node D and right pointer of node H both contain the address of the header node. Now the question comes how we can differentiate between a thread pointer and normal pointer from memory representation of the tree. For this purpose we can include two more fields with each node to decide whether this is a thread pointer or normal pointer. Then the structure definition for a node is: struct Node { struct Node *ptrLeft; int lThread; left lThread info rThread right char info; struct Node *ptrRight; int rThread; }; typedef struct Node ThreadedTreeNode; The value 0 of lThread or rThread indicates that it is a normal pointer, otherwise a thread pointer. For any node p in a threaded binary tree: lThread(p) = 0 indicates left child of p is a normal pointer. lThread(p) = 1 indicates left child of p is a thread pointer. rThread(p) = 0 indicates right child of p is a normal pointer. rThread(p) = 1 indicates right child of p is a thread pointer. Now the algorithm to find the in-order successor of a node x in the tree is: Algorithm 10.10 1. Algorithm fnFind_Inorder_Successor(x) 2. // Purpose : This algorithm finds the successor node of x in in-order traversal of a threaded binary tree. 3. // Input : The address of the node for which the in-order successor node is required to find. 4. // Output : The address of the in-order successor node of x. 5. { 6. ThreadedTreeNode *p; 7. p = x->ptrRight; 8. if(x->rThread == 0) //if q has right child 9. while(p->lThread==0) 10. p = p->ptrLeft; 11. return p; 12. }//End of algorithm Now the algorithm to find the in-order predecessor of a node x in the tree is:

Algorithm 10.11 1. Algorithm fnFind_Inorder_Predecessor(x) 2. // Purpose : This algorithm finds the predecessor node of x in inorder traversal of a threaded binary tree. 3. // Input : The address of the node for which the inorder predecessor node is required to find. 4. // Output : The address of the inorder predecessor node of x. 5. { 6. ThreadedTreeNode *p; 7. p = x->ptrLeft; 8. if(x->lThread == 0) //if q has left child 9. while(p->rThread==0) 10. p = p->ptrRight; 11. return p; 12. }//End of algorithm Finally the algorithm for performing in-order traversal on the threaded binary tree is: Algorithm 10.12 1. Algorithm fnInorder(H) 2. // Purpose : This algorithm finds the inorder traversal of a threaded binary tree. 3. // Input : H is the address of the header node of the tree. 4. // Output : None. 5. { 6. ThreadedTreeNode *Header; 7. Header = H; // stores the address of the header node into the variable Header. 8. while(1) 9. { 10. H = fnFind_Inorder_Successor(H); 11. if(H==Header) // Traversing is finished. 12. return; 13. else 14. print(H->info); 15. } 16. }//End of Algorithm
A

Figure 10.15: Threaded binary tree corresponding to figure 10.6

Header node
1

Figure 10.16: Two way in-order threading using lThread and rThread fields with header node

10.16

M-way search tree

If we relax the restriction that each node can have only one key, we can reduce the height of the tree. An M-way search tree means a search tree with degree M. An M-way search tree T may be an empty tree. If T is nonempty, then it satisfies the following properties: 1. Each and every node of T has at least one key value and at most M-1 key values. 2. If in a node there are n keys k0, k1kn-1, then k0 < k1 < < kn-1. 3. All keys in left sub tree of a key are less than it and all keys in right sub tree of a key are greater than it. 4. Each of the sub trees of a node are also M-way search tree. The figure 10.17 shows an M-way search tree of order 3. The value N in the link part of every node denotes a NULL link. G M

N Figure 10.17: An M-way search tree with M=3

The height of an M-way search tree with n keys is logM(n + 1). The maximum number of keys in an M-way search tree of height h is Mh -1. Rules for insertion into M-way search tree: First find the position of the node for the new key in the tree. If the node is not full then insert the key into appropriate position in the node. For a full node create a new node in proper place. Example: Insert the following keys into an M-way search tree of order 3.

W, A, X, P, M, K, J Insert W, A: Insert X: N A N W N N A N W

N Insert P, M:

N Insert K:

N Insert J:

Rules for deletion from an M-way search tree: Suppose key Ki is to be deleted from a node of an M-way search tree.

..

Li-1

Ki

Li

..

Li-1 and Li are the addresses of the left child node and the right child node of K i respectively. Now the following four conditions may come into consideration. 1. Li-1 and Li both are NULL. Delete Ki simply. N A J Delete key B N A J

Before Deletion

After Deletion

2. Li-1 is not NULL and Li is NULL. Pick the largest key value (K) from the left child node of K i, delete key K and replace Ki with K. N A J Delete key N N A J

Before Deletion

After Deletion

3. Li-1 is NULL and Li is not NULL. Pick the smallest key value (K) from the right child node of Ki, delete key K and replace Ki with K. N A J Delete key A N B J

Before Deletion

After Deletion

4. Li-1 and Li both are NULL. Choose either the largest key value from the left child node of Ki or the smallest key value from the right child node of Ki, delete the key and replace Ki with the chosen key. J Delete key J

N OR Delete key J

Before Deletion N A J N A

After Deletion N

Before Deletion

After Deletion

10.17

B-Tree/Balanced M-way search tree

M-way search tree minimizes the time of file access due to its reduced height. But it is required to keep the height of the tree as low as possible and for this purpose we need to balance the height of the tree. This balanced M-way search tree is called B-Tree. Definition: A B-tree of order M is an M-way search tree in which 1. Root node has at least 1 key value and at most M-1 key values. 2. The other nodes except the root contain at least M/2 -1 key values and M-1 key values. 3. All leaf nodes are on the same level. 4. Keys are arranged in a defined order within the node. Rules for insertion a new key into a B-tree: 1. First search the tree to check the existence of new key value in the tree. If no duplicate key is found, then get the leaf node N where the new key is to be inserted. 2. If N is not full (a node is called full when it contains at most keys), then insert the new key in N and exit. 3. If N is full, then split the node into two new nodes at its median value and move the median key value to its parent node. Repeat step 3 until the tree is balanced. Rules for deletion a key from a B-tree: In B-tree deletion is done from the leaf node only. If we need to delete a key from an internal node then replace the node with its immediate successor (or predecessor) until a key from any leaf node is promoted to its upper level. Then balance the tree according to the given rules: 1. If the leaf node has enough key, then simply remove it. J N N N

N Delete I

2. If the leaf node contains less than minimum number of keys, then check its immediate left sibling node and immediate right sibling node.

i) If the left sibling node has more than the minimum number of key, then move the largest key value of this node to its parent and move down the intervening entry from the parent node to its right child node. J N N N

Delete O

ii) If the right sibling node has more than the minimum number of key, then move the smallest key value of this node to its parent and move down the intervening entry from the parent node to its left child node. J N N N

Delete A

iii) If both the sibling nodes have minimum number of keys, then create a new node merging the two leaf nodes and the intervening key value of their parent node. J P N N

Delete J

Example: Insert the following elements in a B-tree of order 3. W, A, X, P, M, Z, K, J, T, L Insert W, A: N A N W N Insert X, P, M, Z: N A W

Insert K:

K Insert J, T:

N M

N -

10.18

Red/Black tree:

A Red-Black tree is a colored binary search tree which has the following properties: 1. Every node is RED or BLACK. 2. Every leaf is a NULL node and colored BLACK. 3. If a node is RED, then both children are BLACK. 4. Every path from a node to a leaf contains the same number of BLACK nodes. Black Nodes Red Nodes

H D C E J K M

Figure 10.18: An example of a Red-Black tree /* Program to implement a Binary Search Tree */ /* File Name: BST.C */ #include<stdio.h> #include<conio.h> #include<alloc.h> struct Node { struct Node *ptrLeft; int iData; struct Node *ptrRight; }; typedef struct Node TreeNode;

//Prototype Declaration void fnRecursivePreorder (TreeNode *); void fnRecursiveInorder(TreeNode *); void fnRecursivePostorder(TreeNode *); TreeNode *fnInsert_BST( TreeNode *, int); void fnRecursivePreorder (TreeNode *ptrRoot) // Purpose : This function finds the preorder traversal of a binary tree using recursion. // Input: ptrRoot is the address of the root node of the tree. //Output : None. { if(ptrRoot != NULL) { printf("%d\t",ptrRoot->iData); fnRecursivePreorder (ptrRoot->ptrLeft); fnRecursivePreorder (ptrRoot->ptrRight); } }//End of function void fnRecursiveInorder(TreeNode *ptrRoot) // Purpose : This function finds the in-order traversal of a binary tree using recursion. // Input: ptrRoot is the address of the root node of the tree. //Output : None. { if(ptrRoot != NULL) { fnRecursiveInorder (ptrRoot->ptrLeft); printf("%d\t",ptrRoot->iData); fnRecursiveInorder (ptrRoot->ptrRight); } }//End of function void fnRecursivePostorder(TreeNode *ptrRoot) // Purpose : This function finds the post-order traversal of a binary tree using recursion. // Input: ptrRoot is the address of the root node of the tree. //Output : None. { if(ptrRoot != NULL) { fnRecursivePostorder(ptrRoot->ptrLeft); fnRecursivePostorder(ptrRoot->ptrRight); printf("%d\t",ptrRoot->iData); } }//End of function TreeNode *fnInsert_BST( TreeNode *ptrRoot, int iData) //Purpose : This function inserts a new node into a binary search tree in recursive way. //Input : Root of the tree is ptrRoot and the item to be inserted is iData. //Output : This function returns address of a node. { if (ptrRoot == NULL) // position found. {

ptrRoot = (TreeNode *) malloc(sizeof(TreeNode)); //Create new node. ptrRoot ->ptrLeft = NULL; // Set its left child to NULL. ptrRoot ->ptrRight = NULL; // Set its right child to NULL. ptrRoot ->iData = iData; //Insert the data value into the new node. } else if ( iData < ptrRoot->iData) // traverse to the left of p ptrRoot->ptrLeft = fnInsert_BST(ptrRoot->ptrLeft, iData); else if( iData > ptrRoot->iData) // traverse to the right of p ptrRoot->ptrRight = fnInsert_BST(ptrRoot->ptrRight, iData); else printf("\nDuplicate node"); return(ptrRoot); }//End of function void main(void) { TreeNode *ptrRoot; int iChoice; int iData; clrscr(); ptrRoot = NULL; while(1) { printf("\n1. Insert a node"); printf("\n2. Delete a node"); printf("\n3. Inorder Traversal"); printf("\n4. Postorder Traversal"); printf("\n5. Preorder Traversal"); printf("\n6. Exit"); printf("\n4. Enter your choice"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("\nEnter Node value"); scanf("%d",&iData); ptrRoot = fnInsert_BST(ptrRoot,iData); break; case 2: break; case 3: fnRecursiveInorder(ptrRoot); break; case 4: fnRecursivePostorder(ptrRoot); break; case 5: fnRecursivePreorder(ptrRoot); break; case 6: exit(1); }

} } /* Program to implement an AVL tree */ /* File Name: AVL.C */ #include <stdio.h> #include <conio.h> #include <alloc.h> #define FALSE 0 #define TRUE 1 struct Node { int iData ; int iBalanceFactor ; struct Node *ptrLeft ; struct Node *ptrRight ; }; typedef struct Node AVLNode; //Prototype Declaration AVLNode * fnBuild_Tree ( AVLNode *, int, int * ) ; AVLNode * fnDelete_Data ( AVLNode *, int, int * ) ; AVLNode * del ( AVLNode *, AVLNode *, int * ) ; AVLNode * fnBalance_Right ( AVLNode *, int * ) ; AVLNode * fnBalance_Left ( AVLNode *, int * ) ; void fnDisplay ( AVLNode * ) ; void fnDelete_Tree ( AVLNode * ) ; AVLNode * fnBuild_Tree ( AVLNode *ptrRoot, int iData, int *h ) // Purpose : This function inserts an element into an AVL tree. // Input: ptrRoot is root and iData is the data value to be inserted. // Output : Returns address of root node. { AVLNode *node1, *node2 ; if ( !ptrRoot ) { ptrRoot = ( AVLNode * ) malloc ( sizeof ( AVLNode ) ) ; ptrRoot -> iData = iData ; ptrRoot -> ptrLeft = NULL ; ptrRoot -> ptrRight = NULL ; ptrRoot -> iBalanceFactor = 0 ; *h = TRUE ; return ( ptrRoot ) ; } if ( iData < ptrRoot -> iData ) { ptrRoot -> ptrLeft = fnBuild_Tree ( ptrRoot -> ptrLeft, iData, h ) ; /* If ptrLeft subtree is higher */

if ( *h ) { switch ( ptrRoot -> iBalanceFactor ) { case 1: node1 = ptrRoot -> ptrLeft ; if ( node1 -> iBalanceFactor == 1 ) { printf ( "\nRight rotation along %d.", ptrRoot -> iData ) ; ptrRoot -> ptrLeft = node1 -> ptrRight ; node1 -> ptrRight = ptrRoot ; ptrRoot -> iBalanceFactor = 0 ; ptrRoot = node1 ; } else { printf ( "\nDouble rotation, Left along %d", node1 -> iData ) ; node2 = node1 -> ptrRight ; node1 -> ptrRight = node2 -> ptrLeft ; printf ( " then Right along %d.\n", ptrRoot -> iData ) ; node2 -> ptrLeft = node1 ; ptrRoot -> ptrLeft = node2 -> ptrRight ; node2 -> ptrRight = ptrRoot ; if ( node2 -> iBalanceFactor == 1 ) ptrRoot -> iBalanceFactor = -1 ; else ptrRoot -> iBalanceFactor = 0 ; if ( node2 -> iBalanceFactor == -1 ) node1 -> iBalanceFactor = 1 ; else node1 -> iBalanceFactor = 0 ; ptrRoot = node2 ; } ptrRoot -> iBalanceFactor = 0 ; *h = FALSE ; break ; case 0: ptrRoot -> iBalanceFactor = 1 ; break ; case -1: ptrRoot -> iBalanceFactor = 0 ; *h = FALSE ; } } } if ( iData > ptrRoot -> iData ) { ptrRoot -> ptrRight = fnBuild_Tree ( ptrRoot -> ptrRight, iData, h ) ;

/* If the ptrRight subtree is higher */ if ( *h ) { switch ( ptrRoot -> iBalanceFactor ) { case 1: ptrRoot -> iBalanceFactor = 0 ; *h = FALSE ; break ; case 0: ptrRoot -> iBalanceFactor = -1 ; break; case -1: node1 = ptrRoot -> ptrRight ; if ( node1 -> iBalanceFactor == -1 ) { printf ( "\nLeft rotation along %d.", ptrRoot -> iData ) ; ptrRoot -> ptrRight = node1 -> ptrLeft ; node1 -> ptrLeft = ptrRoot ; ptrRoot -> iBalanceFactor = 0 ; ptrRoot = node1 ; } else { printf ( "\nDouble rotation, Right along %d", node1 -> iData ) ; node2 = node1 -> ptrLeft ; node1 -> ptrLeft = node2 -> ptrRight ; node2 -> ptrRight = node1 ; printf ( " then Left along %d.\n", ptrRoot -> iData ) ; ptrRoot -> ptrRight = node2 -> ptrLeft ; node2 -> ptrLeft = ptrRoot ; if ( node2 -> iBalanceFactor == -1 ) ptrRoot -> iBalanceFactor = 1 ; else ptrRoot -> iBalanceFactor = 0 ; if ( node2 -> iBalanceFactor == 1 ) node1 -> iBalanceFactor = -1 ; else node1 -> iBalanceFactor = 0 ; ptrRoot = node2 ; } ptrRoot -> iBalanceFactor = 0 ; *h = FALSE ; } } } return ( ptrRoot ) ; }// End of function

AVLNode * fnDelete_Data ( AVLNode *ptrRoot, int iData, int *h ) // Purpose : This function deletes an item from the tree. // Input: ptrRoot is the adress of root and iData is the data to be inserted. // Output : Returns the address of the root. { AVLNode *node ; if ( !ptrRoot ) { printf ( "\nNo such Data." ) ; return ( ptrRoot ) ; } else { if ( iData < ptrRoot -> iData ) { ptrRoot -> ptrLeft = fnDelete_Data ( ptrRoot -> ptrLeft, iData, h ) ; if ( *h ) ptrRoot = fnBalance_Right ( ptrRoot, h ) ; } else { if ( iData > ptrRoot -> iData ) { ptrRoot -> ptrRight = fnDelete_Data ( ptrRoot -> ptrRight, iData, h ) ; if ( *h ) ptrRoot = fnBalance_Left ( ptrRoot, h ) ; } else { node = ptrRoot ; if ( node -> ptrRight == NULL ) { ptrRoot = node -> ptrLeft ; *h = TRUE ; free ( node ) ; } else { if ( node -> ptrLeft == NULL ) { ptrRoot = node -> ptrRight ; *h = TRUE ; free ( node ) ; } else { node -> ptrRight = del ( node -> ptrRight, node, h ) ; if ( *h ) ptrRoot = fnBalance_Left ( ptrRoot, h ) ; }

} } } } return ( ptrRoot ) ; } AVLNode * del ( AVLNode *succ, AVLNode *node, int *h ) { AVLNode *temp = succ ; if ( succ -> ptrLeft != NULL ) { succ -> ptrLeft = del ( succ -> ptrLeft, node, h ) ; if ( *h ) succ = fnBalance_Right ( succ, h ) ; } else { temp = succ ; node -> iData = succ -> iData ; succ = succ -> ptrRight ; free ( temp ) ; *h = TRUE ; } return ( succ ) ; } AVLNode * fnBalance_Right ( AVLNode *ptrRoot, int *h ) // Purpose :This function balances the tree, if right sub-tree is higher. // Input: ptrRoot is address of the root. // Output : Returns the address of root. { AVLNode *node1, *node2 ; switch ( ptrRoot -> iBalanceFactor ) { case 1: ptrRoot -> iBalanceFactor = 0 ; break; case 0: ptrRoot -> iBalanceFactor = -1 ; *h = FALSE ; break; case -1: node1 = ptrRoot -> ptrRight ; if ( node1 -> iBalanceFactor <= 0 ) { printf ( "\nptrLeft rotation along %d.", ptrRoot -> iData ) ; ptrRoot -> ptrRight = node1 -> ptrLeft ;

node1 -> ptrLeft = ptrRoot ; if ( node1 -> iBalanceFactor == 0 ) { ptrRoot -> iBalanceFactor = -1 ; node1 -> iBalanceFactor = 1 ; *h = FALSE ; } else { ptrRoot -> iBalanceFactor = node1 -> iBalanceFactor = 0 ; } ptrRoot = node1 ; } else { printf ( "\nDouble rotation, ptrRight along %d", node1 -> iData ); node2 = node1 -> ptrLeft ; node1 -> ptrLeft = node2 -> ptrRight ; node2 -> ptrRight = node1 ; printf ( " then ptrLeft along %d.\n", ptrRoot -> iData ); ptrRoot -> ptrRight = node2 -> ptrLeft ; node2 -> ptrLeft = ptrRoot ; if ( node2 -> iBalanceFactor == -1 ) ptrRoot -> iBalanceFactor = 1 ; else ptrRoot -> iBalanceFactor = 0 ; if ( node2 -> iBalanceFactor == 1 ) node1 -> iBalanceFactor = -1 ; else node1 -> iBalanceFactor = 0 ; ptrRoot = node2 ; node2 -> iBalanceFactor = 0 ; } } return ( ptrRoot ) ; }//End of function AVLNode * fnBalance_Left ( AVLNode *ptrRoot, int *h ) // Purpose : This function balances the tree, if left sub-tree is higher. // Input : ptrRoot is address of the root. // Output : Returns the address of root. { AVLNode *node1, *node2 ; switch ( ptrRoot -> iBalanceFactor ) { case -1: ptrRoot -> iBalanceFactor = 0 ; break ; case 0:

ptrRoot -> iBalanceFactor = 1 ; *h = FALSE ; break ; case 1: node1 = ptrRoot -> ptrLeft ; if ( node1 -> iBalanceFactor >= 0 ) { printf ( "\nptrRight rotation along %d.", ptrRoot -> iData ) ; ptrRoot -> ptrLeft = node1 -> ptrRight ; node1 -> ptrRight = ptrRoot ; if ( node1 -> iBalanceFactor == 0 ) { ptrRoot -> iBalanceFactor = 1 ; node1 -> iBalanceFactor = -1 ; *h = FALSE ; } else { ptrRoot -> iBalanceFactor = node1 -> iBalanceFactor = 0 ; } ptrRoot = node1 ; } else { printf ( "\nDouble rotation, ptrLeft along %d", node1 -> iData ) ; node2 = node1 -> ptrRight ; node1 -> ptrRight = node2 -> ptrLeft ; node2 -> ptrLeft = node1 ; printf ( " then ptrRight along %d.\n", ptrRoot -> iData ) ; ptrRoot -> ptrLeft = node2 -> ptrRight ; node2 -> ptrRight = ptrRoot ; if ( node2 -> iBalanceFactor == 1 ) ptrRoot -> iBalanceFactor = -1 ; else ptrRoot -> iBalanceFactor = 0 ; if ( node2-> iBalanceFactor == -1 ) node1 -> iBalanceFactor = 1 ; else node1 -> iBalanceFactor = 0 ; ptrRoot = node2 ; node2 -> iBalanceFactor = 0 ; } } return ( ptrRoot ) ; }//End of function void fnDisplay ( AVLNode *ptrRoot ) // Purpose : This function displays the nodes of the tree in in-order fashion. // Input: ptrRoot is address of the root. // Output : None.

{ if ( ptrRoot != NULL ) { fnDisplay ( ptrRoot -> ptrLeft ) ; printf ( "%d\t", ptrRoot -> iData ) ; fnDisplay ( ptrRoot -> ptrRight ) ; } }//End of function void main( ) { AVLNode *ptrRoot = NULL ; int h,iChoice,iData; clrscr( ) ; while(1) { printf("\n1. Enter new node in AVL tree"); printf("\n2. Delete a node from AVL tree"); printf("\n3. Display Tree"); printf("\n4. Exit"); printf("\nEnter your choice"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("\nEnter data"); scanf("%d",&iData); ptrRoot = fnBuild_Tree ( ptrRoot, iData, &h ); break; case 2: printf("\nEnter data"); scanf("%d",&iData); ptrRoot = fnDelete_Data ( ptrRoot, iData, &h ); break; case 3: printf ( "\nAVL tree:\n" ); fnDisplay (ptrRoot); break; case 4: exit(1); default: printf("\nWrong Choice"); } } }

10.19 MCQ Chapter 10

1. Consider the following paragraph. The traversal proceeds as far as possible to the left, then backs up until the first cross road, goes one step to the right and again as far as possible to the left. Repeat this process until all nodes are visited. What does the above paragraph describe? (a) Breadth first traversal (b) Critical path traversal (c) Shortest path traversal (d) Depth first traversal Questions No. 2 and No. 3 are based on the following tree structure.

2. Consider the following five statements. (i) It is a tree. (ii) It is a binary tree. (iii) It is an AVL tree. (iv) It is a binary search tree. (v) It is a strictly binary tree. Which of the above statements is correct with respect to the above tree? (a) (i) only (b) (i) and (ii) only (c) (i) , (ii) and (iii) only (d) (i), (ii) and (iv) only 3. Consider the following four statements. (i) Depth of the above tree is equal to 4. (ii) Cs proper descendants are I, J, K, L only. (iii) Bs siblings are C, D, E only. (iv) K, L, M, N are siblings. Which of the above statements is correct? (a) (i) and (ii) only (b) (iii) only (c) (ii) and (iii) only (d) (ii) only 4. Consider the following expression tree representation.

(e) All

(e) (ii), (iii) and (iv) only

Which of the following expressions is correct in relation to the above tree? (a) (A*B+C)^((A+B)*C) (b) (A+B*C)^((A+B)*C)

(c) (A+B*C)^( A+(B *C))

(d) (A+B^C)*((A+B)*C)

(e) (A+B*C)^( A+B*C)

5. Consider the following binary search tree algorithm. Public Bsnode search ( Bsnode p, int el) { While (p!=null) if(el==p.key) return p; else if (el < p.key) (i)..; else .(ii); (iii); } Identify suitable entries to fill in the blank positions labeled (i), (ii), (iii) of the above algorithm so that it will insert el to the binary search tree. (a) (i) p = p.right (ii) p =p.left; (iii) return null; (b) (i) p = p.left (ii) p =p.right; (iii) return null; (c) (i) p = p.left.next (ii) p =p.right.next; (iii) return null; (d) (i) p = p.right.next (ii) p =p.left.next; (iii) return null; (e) (i) p = p.right (ii) p =p.left; (iii) return null; 6. A Game tree (a) A special type of Binary tree (b) A special type of m-ary tree (c) A special type of binary search tree (d) None of these 7. Consider the following statement (Algorithm Segment). The method one uses to replace the node being deleted by the rightmost node in its left sub tree or leftmost node in its right sub tree. What does the above statement (algorithm segment) intend to do? (a) Deleting a node from an AVL, if deleting node has both a left and a right child (b) Deleting a node from a binary tree (c) Deleting a node from a binary search tree, if deleting node has both a left and a right child (d) Deleting a node from a binary search tree, if deleting node is a leaf node 8. Consider the following table.

Which of the above would be correct for inserting a new node into an AVL tree? (a) (i) only (b) (i) and (ii) only (c) (i), (ii) and (iii) only (d) (i) (ii) (iii) and (iv) only. (e) (i) (iii) and (iv) only 9. If a binary tree is threaded for an inorder traversal order, a NULL left link of any node is replaced by the address of its (a) successor (b) predecessor (c) root (d) None of these

10. Which of the following process is faster for threaded trees compared with their unthreaded counterparts ? (a) Insertion (b) Deletion (c) Traversal (d) None of these 11. Which of the following statement is TRUE in view of a threaded binary tree ? It can have (a) NULL links but no structural link. (b) only structural links but no NULL links (c) structural links and NULL links (d) None of these 12. Which of the following steps is performed first for inorder traversal of a binary tree ? (a) Traversal of the left subtree in postorder (b) Processing of "the root node. (c) Traversal of the left sub tree in inorder. (d) None of these 13. The preorder traversal of a binary tree begins with (a) processing of the root node (b) traversal of the right subtree in preorder (c) None of the above. 14. If a binary tree is threaded for an inorder traversal order, a NULL right link of any node is replaced by the address of its (a) successor (b) predecessor (c) root (d) None of these 15. The postorder traversal of a binary tree begins with (a) the post order traversal of the left subtree (b) processing of the root node II (c) the post order traversal of the right subtree (d) None of these 16. The inorder traversal of some binary tree produced the sequence DBEAFC, and the postorder traversal of the same tree produced the sequence DEBFCA. Which of the following is a correct preorder traversal sequence. ? (a) DBAECF (b) ABEDFC (c) ABDECF (d) None of these 17. The inorder traversal of some binary tree produced the sequence DBEAFC, and the postorder traversal of the same tree produced the sequence DEBFCA. What will be the total number of nodes in the left subtree of the given tree ? (a) 1 (c) 5 (b) 4 (d) None of these 18. The inorder traversal of some binary tree produced the sequence CBDAFE, and the postorder traversal of the same tree produced the sequence CDBFEA. What will be the total numrer of nodes in the right subtree of the given tree ? (a) 2 (b) 3 (c) 4 (d) None of these 19. The inorder traversal of some binary tree produced the sequence CBDAFE, and the postorder traversal of the same tree produced the sequence CDBFEA. What will be the total numrer of nodes in the left subtree

of the given tree ? (a) 2 (c) 5

(b) 3 (d) None of these

20. The inorder traversal of some binary tree produced the sequence CBDA, and the postorder traversal of the same tree produced the sequence CDBA. Which of the following statement is TRUE for the given tree ? Its : (a) left subtree is. empty, and the total number of nodes in its right subtree is 3. (b) right subtree is empty, and the total number of nodes in its left subtree is 3. (c) both left subtree and right subtree must be nonempty (d) None of the above 21. Which of the following statements is TRUE in view of the threaded binary trees ? (a) Insertion into a threaded tree is time-consuming but deletion from it is not (b) both insertions into and deletions from a threaded tree are more time-consuming. (c) deletion from a threaded tree is time-consuming but the insertion into it is not. (d) None of these 22. Searching of a node in lexical ordered binary tree is fast, because the number of comparisons needed to determine if an item exists in the tree will be : (a) 1 always (b) 2 always (c) 0 always (d) None of these 23. Which of the following statements is TRUE in view of the lexical ordered binary tree ? (a) Its left subtree contains nodes whose keys are lexically greater than the key associated with the root node. (b) Its left subtree contains nodes whose keys are lexically less than the key associated with the root node. (c) Its root node contains a key which is lexically greater than most of the keys associated with the nodes of the right subtree. (d) None of these 24. If for the given directed tree outdegree of every node is exactly equal to 2 or 0, and the number of nodes at I is 2 raised to the power of I-1, the tree is called: (a) binary tree (b) complete binary tree (c) m-ary tree (d) None of these 25. While performing inorder threading of a binary tree, if the left link represents a structural link, then it is: (a) replaced with a NULL link (b) left unchanged (c) replaced with a new thread link. (d) None of these 26. While performing inorder threading of a binary tree, if the right link represents a NULL link, then it is: (a) replaced with a structural link (b) left unchanged (c) replaced with a new thread link. (d) None of these 27. For which of the following trees, is the process of traversal is faster than others ? (a) Threaded binary trees (b) Lexical ordered binary tree (c) m-ary tree (d) None of these 28. Level of any node of a tree is:

(a) its distance from the root (b) height of its left subtree minus height of its right subtree (c) height of its right subtree minus height of its left subtree (d) None of these 29. Which of the following statements is TRUE in view of a threaded storage representation of a binary tree ? (a) Its inorder threading is different from its postorder threading (b) The number of NULL links in a threaded binary tree is very small, but it is non-zero. (c) The number of thread links is reduced to zero. (d) None of these 30. A dynamic data structure where we can search for desired records in 0 (log n) time is: (a) heap (b) binary search tree (c) cicularly linked list (d) array 31. Inorder traversal of the binary search tree implies visiting records in: (a) the order of increasing magnitude of their key (b) the order of decreasing magnitude of their key (c) arbitrary order (d) None of these 32. A threaded binary tree has following problem: (a) more time consuming tree traversal (b) nonsequential memory allocation (c) additional storage requirement (d) None of these 33. The tree traversal technique in which the root is traversed before its children is known as (a) post order traversal (b) pre-order traversal (c) inorder traversal (d) None of these 34. The maximum number of nodes in a binary tree of depth 5, is : (a) 31 (b) 16 (c) 32 (d) 15 35. Post order traversal of a binary tree starts with (a) postorder traversal of the left subtree (b) postorder traversal of the right subtree (c) processing of the root node (d) none of the above 36. How many cycles must be contained in a tree? (a) I (b) at least (c) zero (d) 2 37. Suppose that each node in a tree is represented by a self referential structure having two pointers to point to its left and right children. These pointers are set to NULL if the corresponding child is empty. How many

NULL pointers does a binary tree with "n" internal nodes have? (a) n (b) n + 1 (c) n 1 (d) The number depends on the shape of the tree. 38. If the inorder and preorder traversal of a binary tee are D, B, F, E, G, H, A, C and A, B, D, E, F, G, G, H, C respectively then the postorder traversal of that tree is : (a) D, F, G, A, B, C, H, E, (b) F, H, D, G, E, B, C, A (c) C, G, H, F, E, D, B, A (d) D, F, H, G, E, B, C, A 39. In a binary tree, the number of terminal or leaf nodes is 10. The number of nodes with two children is : (a) 9 (c) 15 (b) 11 (d) None of the above 40. Number of all possible binary trees with 4 nodes is (a) 14 (b) 34 (c) 24 (d) none of the above 41. Number of nodes in a complete binary tree of depth k is (a) 2k (b) 2k (c) 2k - 1 (d) None of the above 42. The number of rooted binary trees with n nodes is, (a) equal to the number of ways of multiplying (n + 1) matrices (b) equal to the number of ways of arranging n out of 2n distinct elements. (c) equal to 1/(n + 1)2n Cn (d) equal to n ! (GATE-1990)

43. The toral external path length, EPL, of a binary treewith n external nodes is, (EPL= L lw where lw is the path length of external node w), (GATE-1990) 2 (a) n always (b) n log2 n always 2 (c) equal to n always (d) O(n) for some special trees 44. A binary tree T has n leaf nodes. The number of nodes of degree 2 in T is (a) log2 n (c) n (b) n - 1 (d) 2n (GATE-1995)

45. In the balanced binary tree in Figure given below, how many nodes will become unbalanced when a node is inserted as a child of the node "g"? (GATE-1996)

(a) 1 (b) 3 (c) 7 (d) 8 46. Which of the following sequences denotes the post order traversal sequence of the tree of Question above? (a) f e g c d b a (b) g c b d a f e (c) g c d b f e a (d) f e d g c b a

(GATE-1996)

47. A binary search tree contains the values 1, 2, 3, 4, 5, 6, 7, 8. The tree is traversed in pre-order and the values are printed out. Which of the following sequences is a valid output? (GATE-1997) (a) 5 3 1 2 4 7 8 6 (c) 5 3 2 4 1 6 7 8 (b) 5 3 1 2 6 4 8 7 (d) 5 3 1 2 4 7 6 8 48. The minimum number of record movements required to merge five files A (with 10 records), B (with 20 records), C (with 15 records), D (with 5 records) and E (with 25 records) is: (GATE-1999) (a) 165 (c) 75 (b) 90 (d) 65 49. Consider the following nested representation of binary trees: (X Y Z) indicates Y and Z are the left and right subtrees, respectively, of node X. Note that Y and Z may be NULL, or further nested. Which of the following represents a valid binary tree? (GATE-2000) (a) (1 2 (4 5 6 7)) (b) (1 ( (2 3 4) 5 6) 7) (c) (1 (2 3 4)(5 6 7)) (d) (1 (2 3 NULL) (4 5)) 50. Let LASTPOST, LASTIN and LASTPRE denote the last vertex visited in a postorder, inorder and preorder traversal, respectively, of a complete binary tree. Which of the following is always true? (GATE-2000) (a) LASTIN=LASTPOST (b) LASTIN=LASTPRE (c) LASTPRE=LASTPOST (d) None of the above 51. The number of possible ordered trees with 3 nodes A, B , C is (a)16 (b) 12 (c) 6 (d) 10 52. A binary tree in which every non-leaf node has non-empty left and right subtrees is called a strictly binary tree. Such a tree with 10 leaves (a) cannot have more than 19 nodes (b) has exactly 19 nodes (c) has e~actly 17 nodes (d) cannot have more than 17 nodes 53. The depth of a complete binary tree with on' nodes is (log is to the base two) (a) log(n+l) 1 (b) log(n) (c) log (n-l) + 1 (d) log (n) + 1 54. Which of the following traversal techniques lists the nodes of a binary search tree in ascending order? (a) Post-order (b) In-order (c) Pre-order (d) None of the above

55. The number of possible binary trees with 3 nodes is (a) 12 (b) 13 c)5 (d) 15 56. The number of possible binary trees with 4 nodes is (a) 12 (b) 13 (c) 14 (d) 15 57. Which of the following remarks about Trie Indexing is not true? (a) It is efficient in dealing with strings of variable length. (b) It is efficient if there are few number of data items. (c) The number of disk accesses can't exceed the length of the particular string that is searched. (d) It can handle insertions and deletions, dynamically and efficiently. 58. Which of the following remarks about Trie Indexing is not true? (a) It is an m-ary tree. (b) It is a search tree of order m. (c) Successful searches should terminate in leaf nodes. (d) Unsuccessful searches may terminate at any level of the tree structure. 59. A binary tree has n leaf nodes. The number of nodes of degree 2 in this tree is (a) 10g2n (b) n-l (c) n (d) 2n 60. The number of binary trees with 3 nodes which when traversed in post-order gives the sequence A, B, C is (a) 3 (b) 9 (c) 7 (d) 5 61. A 3-ary tree is a tree in which every internal node has exactly 3 children. The number of leaf nodes in such a tree with 6 internal nodes will be (a) 10 (b) 23 (c) 17 (d) 13 62. A binary search tree contains the values - 1, 2, 3, 4, 5, 6, 7, and 8. The tree is traversed in preorder and the values are printed out. Which of the following sequences is a valid output? (a) 5 3 1 2 4 7 8 6 (b) 5 3 1 2 6 4 9 7 (c) 5 3 2 4 1 6 7 8 (d) 5 3 1 2 4 7 6 8 63. Which of the following need not be a binary tree? (a) Search tree (b) Heap (c) AVL-Tree (d) B-Tree

64. What is the maximum total number of nodes in a tree that has N levels? Note that the root is level (zero) (a) 22N (b) 2N+ 1-1 (c) 2N - 1 (d) 2N - 2N. 65. How many different binary search trees can be made from three nodes that contain the key values 1,2 and 3? (a) 30 (b) 20 (c) 10 (d) 15. 66. Tell whether the following tree is:

(a) complete

(b) full

(c) heap

(d) None of these

67. Tell whether the following tree is:

(a) complete and heap

(b) full (and complete)

(c) full

(d) heap.

68. Tell whether the following tree is:

(a) heap

(b) heap (and complete)

(c) full (and complete)

(d) None of these.

69. How many different binary trees can be made from three nodes that contain the key values 1,2 and 3? (a) 30 (b) 20 (c) 10 (d)5.

Solutions:
1. d 16. c 31. a 46. c 61. d 2. a 17. d 32. d 47. d 62. d 3. b 18. a 33. b 48. c 63. d 4. b 19. b 34. a 49. c 64. b 5. b 20. b 35. a 50. b 65. d 6. b 21. b 36. c 51. b 66. d 7. c 22. d 37. b 52. b 67. a 8. d 23. b 38. b 53. a 68. c 9. b 24. b 39. a 54. b 69. a 10. c 25. b 40. a 55. c 11. d 26. c 41. d 56. c 12. a 27. a 42. c 57. b 13. a 28. a 43. b 58. b 14. a 29. a 44. b 59. b 15. a 30. b 45. b 60. d

10.20 Exercise Chapter 10


1. What is a tree? Give a suitable example with a neat diagram. 2. What is a binary tree? Write a C program to insert new nodes to a binary tree and delete a given node from a binary tree. 3. State thw two different ways to represent binary tree and explain. 4. Write down the recursive algorithms and find their time complexity for (i) preorder traversal (ii) inorder traversal and (iii) postorder traversal of a binary tree

5. What is an expression tree? Create expression tree from the following expression and explain the construction process. a - b * c + d - log e / f 6. What is a binary search tree? Write a C program to insert new nodes to a binary search tree and delete a given node from a binary search tree. 7. What is a Heap? Write algorithms to insert new nodes to a heap and delete a given node from a heap. 8. What is a weight balanced binary tree? Briefly describe the Huffman tree and Huffman algorithm. 9. What is a height balanced binary tree? Write algorithms to insert new nodes to a height balanced binary tree and delete a given node from a height balanced binary tree. 10. What is a Threaded binary tree? Write algorithms to (i) find the inorder successor of a node for a threaded binary tree, (ii) find the inorder predecessor of a node for a threaded binary tree, (iii) perform the inorder traversal in a threaded binary tree. 11. What is a m way search tree? Write a application of m way search tree and explain. 12. What is a balanced m way search tree? Write algorithms to insert new nodes to a balanced m way search tree and delete a given node from a balanced m way search tree. 13. What is a Red-black tree?explain with a suitable diagram.

Chapter 11 GRAPH
11.1 Definition
Graph is non linear data structure. Mathematically graph can be defined by the pair G = (V, E) where, V = finite and non empty set of vertices. E = set of edges which are the pair of vertices. For the graph shown in figure 11.1, V = {1, 2, 3, 4} E = {<1, 2>, <1, 3>, <1, 4>, <2, 3>, <3, 4>}

11.2 Basic Terminologies


Undirected graph: A graph, which has unordered pair of vertices, is called undirected graph. In an undirected graph, pictorially the edges have no direction. Suppose there is an edge between two vertices V 0 and V1, then the edge can be represented as <V0, V1> or <V1, V0>. The graph shown in the figure 11.1 is an undirected graph. 1 2

Figure 11.1: An example of an undirected graph Directed graph: It is a graph in which each edge is represented by an ordered pair of vertices <V1, V2> where V1 is the tail and V2 is the head of the edge. In this type of graph each edge has direction, means <V1, V2> and <V2, V1> will represent different edges. Directed graph is also known as diagraph. The directed graph shown in figure 11.2 can be represented as: V = {1, 2, 3, 4} E = {<1, 2>, <2, 3>, <3, 1>, <3, 4>, <4, 1>} 1 2

Figure 11.2: An example of a directed graph Weighted graph: A graph is said to be weighted if every edge in the graph is assigned some non-negative value as weight. The weight may be the distance of the edge or the cost to travel along the edge or some other positive values depending on some parameters. A weighted graph is also known as network. The graph shown in the figure 11.3 is an example of a weighted graph. 1
10 8

2
12

Figure 11.3: An example of a weighted directed graph Adjacent nodes: A node V1 is adjacent to or neighbor of another node V 2 if there is an edge from node V 1 to node V2. In figure 11.1, node 1 is adjacent to node 2 and node 2 is adjacent to node 1 also. But in figure 11.2, node 1 is adjacent to node 2 and node 2 is adjacent from node 1. Incidence: In an undirected graph the edge <V0, V1> is incident on nodes V0 and V1. In a diagraph the edge <V0, V1> is incident from node V0 and is incident to node V1. Path and length of path: Path is a sequence of vertices and edges, in which any two consecutive edges are incident and no vertex is repeated. The total number of edges on a path is called its length. 2 e1 1 e7 6 Figure 11.4 In the graph shown in figure 11.4 some paths and their path lengths are given below. Path length 1 e1 2 e2 3 e3 4 e4 5 4 4 e3 3 e2 2 e11 3 1 e5 3 e8 6 2 Cycle and Hamiltonian cycle: A cycle is a path that starts and ends at the same vertex. A cycle is a path where the last vertex is adjacent to the first. A cycle in which no vertex repeats (such as 1-2-3-1 verus 1-2-3-21) is said to be simple. The shortest cycle in the graph defines its girth, while a simple cycle which passes through each vertex is said to be a Hamiltonian cycle. In Figur 11.1 the hamiltonian cycle is 1-2-3-4-1. But in figure 11.4 there is no hamiltonian cycle. Directed acyclic graph(DAG): A directed acyclic graph is a directd graph that has no cycle. For certain applications it is convenient to deal with graphs that contain no cycles. For example, a tree is a special kind of graph that contains no cycles. Obviously, all trees are DAGs. However, not all DAGs are trees. Figure 11.5 shows example of two directed acyclic graph. 1 1 e5 e8 e2 3 e3 4 e4 e6 5

Figure 11.5 Distance: Distance between two vertices V1 and V2 is the length of the shortest path between V1 and V2. In the figure 11.4, distance between vertices 1 and node 5 is 2. Walk: Walk is a sequence of vertices and edges, in which any two consecutive edges are incident and vertex and edge both can be repeated. In figure 11.4, 1 e1 2 e2 3 e6 5 e6 3 e8 6 e7 1 is a walk. Tour: Tour is a sequence of vertices and edges, in which any two consecutive edges are incident and no edge is repeated. In figure 11.4, 1 e1 2 e2 3 e3 4 e4 5 e6 3 e8 6 is a tour. Degree: Number of nodes adjacent from a vertex V is called degree of V and is represented as dg (V). In the figure 11.1, the degree of the vertices of the graph is: dg (1) = 3 dg (2) = 2 dg (3) = 3 dg (4) = 2 Indegree: The indegree of a vertex in a graph is the number of edges coming to that vertex or the number of edges incident to it. In figure 11.6, the indegrees of vertices 1, 2, 3, 4, 5 and 6 are 0, 1, 1, 2, 2 and 2 respectively. 2 1 3 5 4 6

Figure 11.6 Outdegree: The outdegree of a vertex in a graph is the number of edges going outside of that vertex or the number of edges incident from it. In figure 11.6, the outdegree of vertices 1, 2, 3, 4, 5 and 6 are 2, 3, 1, 1, 2 and 0 respectively. Source: In a graph, a vertex which has no incoming edges, but has outgoing edges, is called a source. The in degree of a source vertex is 0. In figure 11.6, node 1 is a source vertex. Sink: A vertex, which has no outgoing edges, but has incoming edges, is called a sink. The outdegree of a sink vertex is 0. In figure 11.6, node 6 is a sink vertex. Pendant Vertex: In a directed graph, a vertex is said to be pendant if its indegree is equal to 1 and outdegree is equal to 0. In an undirected graph, the degree of a pendant vertex is 1. In figure 11.7, node 5 is pendant vertex. 1 3 5

4 Figure 11.7

Complete graph: A graph G is complete iff every node U in G is adjacent to every other node V in G. Total number of edges in a complete graph = n(n-1)/2 where n = total number of vertices.
2

Figure 11.8: An example of a complete graph Multiple edge: Distinct edges e and e are called multiple edges if they connect the same end points i.e. e (U, V) and e (U, V). In figure 11.9, the edges e6 and e7 are multiple edges. Loop: An edge is called loop if it has identical endpoints i.e. e (U, U). In figure 11.9, e3 is a loop. Multigraph: A graph is said to be a multigraph if it contains either multiple edges or loops. Figure 11.9 shows a multi graph.
e3 e2 e4 e1 e6 e8 e7 e5

Figure 11.9: An example of a multi graph Chromatic number: The chromatic number of a graph is the least number of colors it takes to color its vertices so that adjacent vertices have different colors. For example, the graph of figure 11.6 has chromatic number two. Bipartite graph: A graph G is called bipartite if the vertex set V of G can be partitioned into two disjoint subsets V1 and V2 such that every edge joins a vertex in V1 with a vertex in V2. Figure 11.10 shows a bipartite graph.
V1 V2 V1

V2

Figure 11.10: Example of bipartite graph Planar graph: A graph is called planar if it can be drawn in the plane so that edges intersect only at their end points. Properties: 1. For any planar graph G with n 3 vertices has at most 3n 6 edges. 2. Four color theorem: Every planar graph can be colored using no more than four colors. 3. Kuratowskis theorem: Graph G is non-planar iff it has a sub graph K5 or K3, 3.

Figure 11.11(a): K5

Figure 11.11(b): K3, 3

Connected graph: An undirected graph is connected if there is a path from any node of graph to any other node, or any node is reachable from any other node. Figure 11.12(a) shows a connected graph. e2 1 2 1 2 e3 e6 3 e1 e4 3 5 e5 Figure 11.12(a): Connected graph Figure 11.12(b): Disconnected graph 4 5 4

Articulation point: Let G be a connected graph. An articulation point of G is a vertex whose removal (along with all adjacent edges) disconnects G. In a graph there can be more than one articulation points. In figure 11.12(a) vertex 2 is an articulation point. Bridge: Let G be a connected graph. A bridge of G is an edge whose removal disconnects G. In figure 11.12(a) edge e3 is a bridge. Euler tour: An Euler tour of a connected graph G is a cycle that traverses each edge of G exactly once, although it may visit a vertex more than once.

11.3 Representation of a graph


There are two ways; we can implement a graph in a computer program. These are: 1. Sequential representation: In sequential representation we use a 2-dimentional array of order n X n where n is the total number of vertices in the graph. This is a simple way to represent a graph into memory. A graph with n nodes takes n 2 space to represent it sequentially in memory and also takes O(n2) time to perform the graph operations and to solve the graph problems. Adjacency Matrix:

Suppose G is a graph with n nodes and suppose the vertices of graph G are ordered and are called 1, 2, 3 n. Then the adjacency matrix A of the graph G is an n X n matrix defined as follows: A [i][j] = 1 if there is an edge from node i to node j. =0 Otherwise Such a matrix which contains only 1 or 0 is called a bit matrix or Boolean matrix. The adjacency matrix of an undirected simple graph is a symmetric matrix. In the adjacency matrix of an undirected simple graph, the number of 1 is twice the number of edges in the graph. The adjacency matrix for the figure 11.13 is: is: 1 1 2 3 4 5 0 1 0 0 1 2 1 0 1 1 1 3 0 1 0 1 0 4 0 1 1 0 1 5 1 1 0 1 0 1 2 3 4 5 1 0 0 0 0 0 2 1 0 0 0 1 10 4 5 3 0 1 0 0 0 4 0 1 1 0 0 5 1 0 0 1 0 The adjacency matrix for the figure 11.14

2 3

2 3 2

2 7 4 8

6 3

4 Figure 11.13

4 Figure 11.14

5 Figure 11.15

Path Matrix: Suppose G is a directed graph with n nodes and suppose the vertices of graph G are ordered and are called 1, 2, 3 n. Then the path matrix P of the graph G is an n X n matrix defined as follows: P [i][j] = 1 if there is a path from vertex i to vertex j. =0 Otherwise The path matrix for the figure 11.14 is: 1 1 2 3 4 5 0 0 0 0 0 2 1 1 1 1 1 3 1 1 1 1 1 4 1 1 1 1 1 5 1 1 1 1 1

Weighted matrix: The weighted matrix W of a weighted graph G with n nodes is an n X n matrix which can be defined as: W [i][j] = weight of the edge <i, j> if there is an edge from i to j. = (i.e. a large value) Otherwise. The weighted matrix for the figure 11.15 is: 1 1 2 3 4 5 2 10 4 3 6 4 7 8 5 2 5

2. Linked representation: In the adjacency matrix it is difficult to insert new nodes or edges in the graph. First of all the time complexity for insertion or deletion of new nodes or edges in the adjacency matrix is high. More over as the array is static in nature; some spaces in the matrix defined to represent the graph, may be wasted or due to more vertices underflow in space may occur. Further more if the number of edges in the graph with n nodes is O(n) or O(nlog2n), then the matrix is sparse; hence a great deal of spaces will be wasted. If the adjacency matrix of the graph is sparse then it is more efficient to represent the graph through adjacency list. In adjacency list representation of graph, we will maintain two lists. First list will keep track of all the nodes in the graph and second list will maintain a list of adjacent nodes for each node to keep the track of the edges. Structure of a node in the vertex list: struct VNode { struct VNode *ptrNV; int VNo; struct ENode *ptrAV; }; typedef struct VNode Vertex;

// Pointer to next vertex in the list. // Name of the vertex. // Pointer to adjacent vertex to this node.

Name of the vertex ptrNV VNo ptrAV Pointer which points to the first vertex adjacent to this node.

Pointer which points to the next vertex in the list. Structure of an edge node in the edge list: struct ENode { int VNo; struct ENode *ptrAV;

// Name of the Vertex. // Pointer to the destination vertex of the next edge.

}; typedef struct ENode Edge; Pointer which points to the next adjacent node.

VNo

ptrAV

Destination node of the edge The adjacency list for the graph shown in figure 11.13 is: 1 2 3 4 N 5 2 1 2 2 1 5 3 4 3 2 N 5 4 N N N 4 5 N

The adjacency list for the graph shown in figure 11.14 is: 1 2 3 4 N 5 2 3 4 5 2 N N N 5 4 N N

11.4 Operations on a graph


Suppose a graph is maintained in memory by the linked representation. Now we will look for the following operations on a graph. 11.4.1 Searching in a graph: Suppose first we want to find a vertex in the graph. Algorithm 11.1 1. Algorithm fnFindVertex(V) 2. // Purpose : This algorithm finds whether a vertex is in the graph or not. 3. // Input : We have to search for the vertex V in the vertex list. 4. // Output : This algorithm returns the address of the vertex V for a successful search. Otherwise it returns //NULL. 5. {

6. 7.

Vertex *ptrTemp; ptrTemp = ptrStart;

// A global variable ptrStart contains the address of the first vertex in the vertex

list 8. while(ptrTemp != NULL) // Continue until vertex list is not empty. 9. { 10. if(ptrTemp->VNo == V) 11. return ptrTemp; 12. ptrTemp = ptrTemp->ptrNV; //Go to next vertex in the vertex list. 13. } 14. return NULL; //Return NULL if vertex does not exist. 15. }//End of algorithm Now we will find an edge (U, V) in the graph. For this purpose first find the vertex U in the vertex list and then search the edge list of U to find the vertex V. Algorithm 11.2 1. Algorithm fnFindEdge(U,V) 2. // Purpose : This algorithm finds whether an edge is in the graph or not. 3. // Input : We have to search for the edge (U, V) in the edge list. 4. // Output : This algorithm returns the address of the edge (U, V) for a successful search. Otherwise it returns //NULL. 5. { 6. Vertex *ptrTemp1; 7. Edge *ptrTemp2; 8. ptrTemp1 = ptrStart; // A global variable ptrStart contains the address of the first vertex in the vertex list 9. // Find the vertex U from the vertex list. 10. while(ptrTemp1 != NULL) // Continue until vertex list is not empty. 11. { 12. if(ptrTemp1->VNo == U) // Get the address of the node U from the vertex list. 13. break; 14. ptrTemp1 = ptrTemp1->ptrNV; // Go to next vertex in the vertex list. 15. } 16. // Find the vertex V from the edge list of U. 17. if(ptrTemp1 != NULL) // If U exists in the vertex list, then traverse the edge list of the vertex U. 18. { 19. ptrTemp2 = ptrTemp1->ptrAV; 20. while(ptrTemp2 != NULL) // Continue until the edge list of vertex U is NULL. 21. { 22. if(ptrTemp2->VNo == V) 23. return ptrTemp2; 24. ptrTemp2 = ptrTemp2->ptrAV; // Go to next vertex in the edge list. 25. } 26. } 27. return NULL; 28. }// End of algorithm Insertion in a graph: Insert new vertex To insert a new vertex V the following steps are performed: Step 1: First check whether V exists in the list or not. If V is already in the list then exit.

Step 2: Create new vertex. Step 3: Copy V to the data field of the newly created vertex. Step 4: Set NULL to the adjacent vertex list of it. Step 5: Set V as the first vertex in the list. Algorithm 11.3 describes the above steps. Algorithm 11.3 1. Algorithm fnInsertNode(V) 2. // Purpose : This algorithm inserts a new node at the first of the vertex list. 3. // Input : V is the vertex number to be inserted in the graph. 4. // Output : None. 5. { 6. Vertex ptrNewVertex; 7. if(fnFindVertex(V) != NULL) 8. { 9. print Duplicate Vertex; 10. exit(); 11. } 12. ptrNewVertex = (Vertex *)(malloc(sizeof(Vertex))); // Create a new vertex. 13. ptrNewVertex->VNo = V; //Copy V to data field of new vertex. 14. ptrNewVertex->ptrAV = NULL; //Set edge field of new vertex to NULL. 15. ptrNewVertex->ptrNV = ptrStart; //Set current first vertex as the next vertex of new one. 16. ptrStart = ptrNewVertex; //Now new vertex is the first vertex in the vertex list. 17. }// End of algorithm Inert new edge To insert a new edge (U, V) perform following steps: Step 1: If U is not in the vertex list or edge (U, V) already exist, then exit. Step 2: Create new edge. Step 3: Set V in the data field of new edge. Step 4: Inert newly created edge V at the beginning of the adjacency vertex list of U. Algorithm 11.4 describes the process. Algorithm 11.4 1. Algorithm fnInsertEdge(U, V) 2. // Purpose : This algorithm inserts a new edge at the last of the edge list of vertex U. 3. // Input : The edge is from vertex U to vertex V. 4. // Output : None. 5. { 6. Vertex *ptrLoc1; 7. Edge *ptrLoc2; 8. ptrLoc1 = fnFindVertex(U); //Get the address of the vertex U. 9. ptrLoc2 = fnFindEdge(U, V); // Get the address of the edge (U, V). 10. if(ptrLoc1 != NULL and ptrLoc2 === NULL) // If U is in the vertex list but V is not in the edge list of U. 11. { 12. ptrNewEdge = (Edge *)(malloc(sizeof(Edge))); //Create a new edge. 13. ptrNewEdge->VNo = V; //Copy V to the data field of new edge. 14. ptrNewEdge->ptrAV = ptrLoc1->ptrAV; //Insert V in the edge list of U. 15. ptrLoc1->ptrAV = ptrNewEdge; //Set vertex V as the first edge in the edge list of U. 16. } 17. } // End of algorithm 11.4.3 Deletion from graph:

Delete a vertex The steps to delete a vertex V are followings: Step 1: If V does not exist, then exit. Step 2: Delete all the edges ending at V. Step 3: Delete all the edges starting with V. Step 4: Delete V from the list. Algorithm 11.5 describes the steps to delete a vertex. Algorithm 11.5 1. Algorithm fnDeleteVertex(V) 2. // Purpose : This algorithm deletes an existing node from the vertex list of a graph. 3. // Input : V is the vertex number to be deleted from the vertex list. 4. // Output : None. 5. { 6. Vertex *ptrLoc1, *ptrTemp; 7. Edge *ptrLoc2, *ptrLoc3; 8. ptrLoc1 = fnFindVertex(V); //Get the address of the vertex V in the vertex list. 9. ptrLoc2 = ptrLoc1->ptrAV; //Get the address of the first vertex in the edge list of V. 10. while(ptrLoc2 NULL) 11. { 12. ptrLoc3 = ptrLoc2->ptrAV; //Delete all the edges from the edge list of V. 13. free(ptrLoc2); 14. ptrLoc2 = ptrLoc3; 15. } 16. ptrTemp = ptrStart; 17. while(ptrTemp NULL) 18. { 19. U = ptrTemp->VNo; 20. // Get the address of the edges ending at the vertex V. //Delete all the edges ending at V. 21. fnDeleteEdge(U,V); 22. ptrTemp = ptrTemp->ptrNV; 23. } 24. ptrTemp = ptrStart; 25. while(ptrTemp->ptrNV ptrLoc1) 26. ptrTemp = ptrTemp->ptrNV; //Delete the node V from the vertex list. 27. ptrTemp->ptrNV = ptrLoc1->ptrNV; 28. free(ptrLoc1); 29. }// End of algorithm Delete an edge The steps to delete an edge (U, V) are: Step 1: If the edge (U, V) does not exist, then exit. Step 2: First go the vertex U in the vertex list. Step 3: Delete vertex V from the adjacency vertex list of U. See algorithm 11.6. Algorithm 11.6 1. Algorithm fnDeleteEdge(U, V) 2. // Purpose : This algorithm deletes the edge <U, V> from the edge list of vertex U. 3. // Input : The edge is from vertex U to vertex V. 4. // Output : None. 5. {

6. Vertex *ptrLoc1; 7. Edge *ptrLoc2,*ptrTemp2; 8. ptrLoc1 = fnFindVertex(U); //Find the address of the vertex U in the vertex list. 9. ptrLoc2 = fnFindEdge(U, V); //Find the address of the edge to be deleted. 10. if(ptrLoc1!=NULL) // If the edge exists. 11. { 12. ptrTemp2 = ptrLoc1->ptrAV; 13. //Get the address of the previous edge of <U, V> in the edge list of U. 14. while(ptrTemp2->ptrAV!=ptrLoc2) 15. ptrTemp2 = ptrTemp2->ptrAV; 16. ptrTemp2->ptrAV = ptrLoc2->ptrAV; //Set the link. 17. free(ptrLoc2); 18. } 19. }//End of Algorithm

11.5 Traversal of a graph


There are two standard ways to traverse a graph. First one is Breadth first search (BFS) and the second one is Depth first search (DFS). The procedure BFS uses a queue to store the unvisited vertices and DFS uses a stack for this purpose. To distinguish between the visited vertices and unvisited vertices, we give the color to each and every node of the graph. Initially all the vertices have color WHITE to indicate that they are not yet visited. When a node is placed into a queue or stack, then the color is changed to BLACK to indicate that the vertex is now visited. 11.5.1 Breadth first search: In this traversal technique, first select a vertex S to start the traversal. Initially set the color of all the vertices to WHITE. Insert S into the queue and change its color to BLACK as now S is a visited vertex. Now delete the front node N from the queue. Visit all the adjacent vertices of N and insert to queue those neighbors of N that have the color WHITE. Change their colors to BLACK. Continue this procedure until queue is empty. Algorithm 11.7 1. Algorithm fnBFS(S) 2. // Purpose : This algorithm finds the BFS traversal of a given graph. 3. // Input : S is the starting node from which we have to start the traversal. 4. // Output : None. 5. { 6. for( all the vertices j of the graph) 7. Color[j] = WHITE. //Initialize the color of all nodes to WHITE. 8. fnInsert(S); //Put the starting node S into queue. 9. Color[S] = BLACK; //Change its color to BLACK. 10. while(queue is not empty) 11. { 12. N = fnDelete(); //Remove the front node N of queue. 13. for(each adjacent vertex j of N) //Visit all the adjacent vertex of N. 14. if(Color[j] == WHITE) 15. { 16. fnInsert(j); //Insert into queue all the neighbors of N of color WHITE. 17. Color[j] = BLACK; //Change their colors to BLACK. 18. } 19. } 20. }// End of algorithm

Time Complexity: If we represent the graph using adjacency list, then we have to traverse each edge at most twice (for an undirected graph). Hence time complexity is O (e) where e is the total number of edges. If we represent the graph using adjacency matrix, then we have to check n entries in the matrix for each vertex to find its adjacent vertices. As there are total n vertices, so time complexity is O (n2). Example 11.1: Consider the graph shown in the figure 11.16. Suppose we have to find the BFS traversal of this graph starting from vertex 1.
1

Figure 11.16 Initially: Color[1] = Color[2] = Color[3] = Color[4] = Color [5] = WHITE STEP 1: Insert starting node 1 into queue and change its Color to Black.

1
5

Status of the graph Traversal =

Content of the queue

STEP 2: Delete front node 1 from queue and insert into QUEUE nodes 2, 3 and 4 as their color is WHITE.

4 3 2

Status of the graph Traversal = 1

Content of the queue

STEP 3: Delete front node 2 from queue. The adjacent nodes of 2 are vertex 1 and vertex 5. But only the vertex 5 has color WHITE. So insert only vertex 5 into the queue.
1

5 4 3

Status of the graph

Content of the queue

Traversal = 1 2 STEP 4: Delete node 3 from queue. As all the neighbors of vertex 3 have color BLACK, so no more insertion into queue takes place.
1

5 4

Status of the graph Traversal = 1 2 3 STEP 5: Delete node 4 from queue without any insertion into queue.
1

Content of the queue

5
2 3 4

Status of the graph Traversal = 1 2 3 4 STEP 6: Delete node 5 from queue.


1

Content of the queue

Status of the graph Traversal = 1 2 3 4 5 As now the queue is empty, so traversal is finished.

Content of the queue

11.5.2 Depth first search: In this traversal technique, we use a stack instead of queue. Like the BFS traversal, initially set the color of all the vertices to WHITE. Insert starting vertex S into the stack and change its color to BLACK. Now delete the top node N from the stack. Visit all the adjacent vertices of N and insert to stack those neighbors of N that have the color WHITE. Change their colors to BLACK. Continue this procedure until stack is empty. Algorithm 11.8 1. Algorithm fnDFS(S) 2. // Purpose : This algorithm finds the DFS traversal of a given graph. 3. // Input : S is the starting node from which we have to start the traversal. 4. // Output : None. 5. { 6. for( all the vertices j of the graph) 7. Color[j] = WHITE; //Initialize the color of all nodes to WHITE. 8. fnPush(S); //Push the starting node S into stack. 9. Color[S] = BLACK; //Change its color to BLACK. 10. while(stack is not empty) 11. { 12. N = fnPop(); //Pop the top node N of the stack. 13. for(each adjacent vertex j of N) //Visit all the adjacent vertex of N. 14. if(Color[j] == WHITE) 15. { 16. fnPush(j); //Push into stack all the WHITE neighbors of N. 17. Color[j] = BLACK; //Change their colors to BLACK. 18. } 19. } 20. }// End of algorithm Example 11.2: Consider the graph shown in the figure 11.16. Suppose we have to find the DFS traversal of this graph starting from vertex 1. Initially: Color[1] = Color[2] = Color[3] = Color[4] = Color [5] = WHITE STEP 1: Insert starting node 1 into stack and change its color to Black.
1

1
5

Status of the graph Traversal =

Content of the stack

STEP 2: Delete top node 1 from stack and insert into stack nodes 2, 3 and 4 (the neighbors of 1) as their color is WHITE. Change the color of the vertices (2, 3 and 4) to BLACK.

4 3 2

Status of the graph Traversal = 1

Content of the stack

STEP 3: Delete top node 4 from stack. The adjacent nodes of 4 are vertex 1 and vertex 5. But only the vertex 5 has color WHITE. So insert only vertex 5 into the stack.

5 3 2

Status of the graph Traversal = 1 4

Content of the stack

STEP 4: Delete node 5 from queue. As all the neighbors of vertex 3 have color BLACK, so no more insertion into stack takes place.
1

3 2

Status of the graph

Content of the stack

Traversal = 1 4 5

STEP 5: Delete node 3 from stack without any insertion into stack.
1

2
5

Status of the graph Traversal = 1 4 5 3 STEP 6: Delete node 2 from stack.


1

Content of the stack

Status of the graph Traversal = 1 4 5 3 2 As now the stack is empty, so traversal is finished.

Content of the stack

11.6 Shortest path calculation (Dijkstras algorithm)


Dijkstra's algorithm is a greedy algorithm for solving the single-source, shortest-path problem on an edgeweighted graph in which all the weights are non-negative. It finds the shortest paths from some initial vertex, say s, to all the other vertices one-by-one. The essential feature of Dijkstra's algorithm is the order in which the paths are determined: The paths are discovered in the order of their weighted lengths, starting with the shortest, and proceeding to the longest. Dijkstras algorithm keeps two sets of vertices: U Vertices whose shortest paths have already been determined V-U Remainder vertices. To differentiate between these two sets, we maintain an array status []. Status[i] = Permanent denotes the vertex i is in set U and status[i] = Temporary indicates i is in V-U. Here we maintain two more arrays: dist[] and pred[]. Dist[i] is the length of the shortest path from source node (s) to node i. pred[i] denotes the node preceding node i in the shortest path. Initially for all the nodes i of the graph, dist[i] = and pred[i] = NULL and status[i] = Temporary.

Algorithm 11.9 1. Algorithm fnDijkstra (s, d, n) 2. // Purpose : This algorithm finds the shortest path between the vertex s and vertex d. 3. // Input : s is the starting vertex and d is the destination vertex. n is the total number of vertices in the graph. 4. // Output : None. 5. { 6. for (i = 1; i <= n; i++) 7. { 8. dist[i] = ; //Initialize the arrays. 9. pred[i] = NULL; 10. status[i] = Temporary; 11. } 12. status[s] = Permanent; 13. dist[s] = 0; 14. current = s; //current keeps the track of the last permanent vertex. 15. while(current != d) //Continue the procedure until the status of the destination vertex d is Permanent. 16. { 17. dc = dist[current]; 18. for(all vertices i, that are adjacent of current and status[i] = Temporary) 19. { 20. newdist = dc + W[current][i]; //W[][] is weighted matrix. 21. if(newdist < dist[i]) 22. { 23. dist[i] = newdist; 24. pred[i] = current; 25. } 26. } 27. Choose node k such that status[k] = Temporary and dist[k] is smallest; 28. current = k; 29. status[k] = Permanent; 30. } 31. }// End of algorithm

Example 11.3: 8
1 2

2 7
4

16
6

4 3 6 3
7

9 4 5 8

5 2

Figure 11.17 Suppose here s = 1 and d = 5 Step 1: Now current = 1. 8


1 2

2 7

16

4 3 6 3
7

9 4 5 8

5 2

NODE dist Pred status 1 0 NULL Permanent 2 NULL Temporary 3 NULL Temporary 4 NULL Temporary 5 NULL Temporary 6 NULL Temporary 7 NULL Temporary 8 NULL Temporary dc = dist[1] = 0 Vertices 2, 3 and 4 are adjacent to 1 and are Temporary. dist[2] > dc + weight[1][2] dist[3] > dc + weight[1][3] dist[4] > dc + weight[1][4] => > 0 + 8 re label 2 => > 0 + 2 re label 3 => > 0 + 7 re label 4 pred[2] = 1 pred[3] = 1 pred[4] = 1

NODE dist Pred status 1 0 NULL Permanent 2 8 1 Temporary 3 2 1 Temporary 4 7 1 Temporary 5 NULL Temporary 6 NULL Temporary 7 NULL Temporary 8 NULL Temporary STEP 2: dist[3] is smallest among the temporary nodes. So, now current = 3 and make it permanent. NODE dist Pred status

1 2 3 4 5 6 7 8 8
1

0 8 2 7

NULL 1 1 1 NULL NULL NULL NULL

Permanent Temporary Permanent Temporary Temporary Temporary Temporary Temporary

2 7

16

4 3 6 3
7

9 4 5 8

5 2

dc = dist[3] = 2 Here 4 and 7 are adjacent to 3 and are temporary. dist[4] > dc + weight[3][4] => 7 > 2 + 4 dist[7] > dc + weight[3][7] => > 2 + 3 re label 4 re label 7 pred[4] = 3 pred[7] = 3

NODE dist Pred status 1 0 NULL Permanent 2 8 1 Temporary 3 2 1 Permanent 4 6 3 Temporary 5 NULL Temporary 6 NULL Temporary 7 5 3 Temporary 8 NULL Temporary STEP 3: dist[7] is smallest among the temporary nodes. So, now current = vertex 7 and make it permanent. NODE dist Pred status 1 0 NULL Permanent 2 8 1 Temporary 3 2 1 Permanent 4 6 3 Temporary 5 NULL Temporary 6 NULL Temporary

7 8 8
1

3 Permanent NULL Temporary

2 7

16

4 3 6 3
7

9 4 5 8

5 2

dc = dist[7] = 5 Here vertices 4, 5 and 8 are temporary adjacent node to 7. dist[4] < dc + weight[7][4] dist[5] > dc + weight[7][5] dist[8] > dc + weight[7][8] 6<5+3 >5+4 >5+5 Dont re label vertex 4 re label vertex 5 re label vertex 8 pred[5] = 7 pred[8] = 7

NODE dist Pred status 1 0 NULL Permanent 2 8 1 Temporary 3 2 1 Permanent 4 6 3 Temporary 5 9 7 Temporary 6 NULL Temporary 7 5 3 Permanent 8 10 7 Temporary STEP 4: dist[4] is smallest among the temporary nodes. So, now current = vertex 4 and make it permanent. NODE dist Pred status 1 0 NULL Permanent 2 8 1 Temporary 3 2 1 Permanent 4 6 3 Permanent 5 9 7 Temporary 6 NULL Temporary 7 5 3 Permanent 8 10 7 Temporary

8
1 2

2 7

16

4 3 6 3
7

9 4 5 8

5 2

dc = dist[4] = 6 Here only vertex 5 is the temporary adjacent node to 4. dist[5] < dc + weight[4][5] 9<6+9 Dont re label vertex 5.

NODE dist Pred status 1 0 NULL Permanent 2 8 1 Temporary 3 2 1 Permanent 4 6 3 Permanent 5 9 7 Temporary 6 NULL Temporary 7 5 3 Permanent 8 10 7 Temporary STEP 5: dist[2] is smallest among the temporary nodes. So, now current = vertex 2 and make it permanent. NODE dist Pred status 1 0 NULL Permanent 2 8 1 Permanent 3 2 1 Permanent 4 6 3 Permanent 5 9 7 Temporary 6 NULL Temporary 7 5 3 Permanent 8 10 7 Temporary

8
1 2

2 7

16

4 3 6 3
7

9 4 5 8

5 2

dc = dist[2] = 8 Here vertex 6 is the temporary adjacent node to B. dist[6] > dc + weight[2][6] > 8 + 16 re label vertex 6 pred[6] = 2

NODE dist Pred status 1 0 NULL Permanent 2 8 1 Permanent 3 2 1 Permanent 4 6 3 Permanent 5 9 7 Temporary 6 24 2 Temporary 7 5 3 Permanent 8 10 7 Temporary STEP 6: dist[5] is smallest among the temporary nodes. So, now current = 5 and make it permanent. NODE dist Pred status 1 0 NULL Permanent 2 8 1 Permanent 3 2 1 Permanent 4 6 3 Permanent 5 9 7 Permanent 6 24 2 Temporary 7 5 3 Permanent 8 10 7 temp

8
1 2

2 7

16

4 3 6 3
7

9 4 8

5 2

5 As 5 is our destination node, the process will be stopped here. pred[5] = 7 pred[7] = 3 pred[3] = 1 So, the shortest path from 1 to 5 is 1375 And the cost of the shortest path from vertex 1 to 5 is 9.

11.7 Warshalls algorithm


Let G be a directed graph with n vertices 1, 2, 3n. Suppose we want to find the path matrix of G. Warshall gave an algorithm for this purpose. This algorithm is discussed in this section and a similar algorithm is used to calculate the shortest path for a weighted graph. Path Matrix: First we define n X n Boolean matrices P0, P1Pn as follows: Pk[i][j] = 1 If there is a simple path from i to j which does not use any other vertices except possibly 1.k. =0 Otherwise. To store the path matrix of the graph G we are taking a two dimensional array P[][] with order n X n. Initially the path matrix P is same as the adjacency matrix. Now Pk[i][j] means we want to traverse from vertex i to vertex j through vertex k. So Pk[i][j] is 1 if either we can go from vertex i to vertex j through vertex k or vertex j is already reachable from vertex i through some previous vertices. Hence Pk[i][j] = 1 can occur only if one of the following conditions is satisfied: 1) Pk-1[i][j] = 1 (i.e. i is reachable from j through some previous vertices) 2) Pk-1[i][k] = 1 and Pk-1[k][j] = 1 ( i.e vertex k is reachable from i and j is reachable from k) The algorithm to find the path matrix is shown in algorithm 11.10. Algorithm 11.10 1. Algorithm fnWarshallPM(n) 2. // Purpose : This algorithm finds the path matrix for a directed graph G. 3. // Input : n is the total number of vertices in G. 4. // Output : None. 5. { 6. //First copy the contents of adjacency matrix A to path matrix P. 7. for(i = 1;i<=n;i++) 8. for(j=1;j<=n;j++) 9. P[i][j] = A[i][j];

10. //Compute different iterations of P. 11. for(k=1;k<=n;k++) //k is the iteration number. 12. for(i=1;i<=n;i++) 13. for(j=1;j<=n;j++) 14. P[i][j] = P[i][j] || (P[i][k] && P[k][j]); 15. }// End of algorithm. 15 1 6 2 4 1 Figure 11.18 Example 11.4: The path matrix for the figure 11.18 can be calculated as follows: First find the adjacency matrix A for the graph. A= 1 0 0 1 1 0 0 0 0 1 0 1 0 0 1 0 3 2 2 1

Initially: P = A P= 1 0 0 1 1 0 0 0 0 1 0 1 0 0 1 0

Iteration 1: P1 = 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 0 1 0 1 1 1 0 1 1 1 0 1 0 0 1 0 0 0 1 0 1 1 1 1

Iteration 2: P =
2

Iteration 3: P =
3

Iteration 4: P =
4

1 1 1 1

1 1 1 1

1 1 1 1

1 1 1 1

The final path matrix of the graph shown in the figure 11.18 is P4. Shortest path: Consider a directed weighted graph G with n vertices. The weighted matrix for G is an n X n matrix. The path matrix of G tells us whether there is a path between two nodes. Now our goal is to find the path between two vertices i and j with minimum weight or cost and for this purpose a two dimensional array S[][] is used to store the weight of the shortest path between vertex i and vertex j. In path matrix the entry P k[i][j] is 1, if either Pk-1[i] [j] is 1 or (Pk-1[i][k] = 1 and Pk-1[k][j] = 1). Similarly, we can calculate the shortest weighted path from vertex i to vertex j as follows: Sk[i][j] = min {weight of Pk-1[i][j], weight of Pk-1[i][k] + weight of Pk-1[k][j]} = min {Sk-1[i][j], Sk-1[i][k] + Sk-1[k][j]} The algorithm to find the shortest path is shown in algorithm 11.11. Algorithm 11.11 1. Algorithm fnWarshallSP(n) 2. // Purpose : This algorithm finds the shortest path for all the pair of vertices of the graph G. 3. // Input : n is the total number of vertices in G. 4. // Output : None. 5. { 6. //First copy the weighted matrix or cost matrix W to shortest path matrix S. 7. for(i = 1;i<=n;i++) 8. for(j=1;j<=n;j++) 9. S[i][j] = W[i][j]; 10. //Compute different iterations of P. 11. for(k=1;k<=n;k++) //k is the iteration number. 12. for(i=1;i<=n;i++) 13. for(j=1;j<=n;j++) 14. S[i][j] = min{S[i][j] , (S[i][k] + S[k][j])}; //Compute new distance between i and j 15. }// End of algorithm Example 11.5: The shortest path matrix for the graph shown in figure 11.18 can be calculated as follows: First find the weighted matrix W for the graph. 15 W= 6 Initially: S = W 15 2 2 1 1 2

S0 =

1 1

2 Shortest path 2 2 5 3 2 3 5 3 2 3 1, 1 4, 1 1, 1 4, 1 1, 1 4, 1 1, 2 4, 1, 2 1, 2 4, 1, 2 1, 2 4, 1, 2 2, 3 4, 3 3, 4 -

Minimum weight Iteration 1: S =


1

15 6 15 6 15 6 11 9 8 6

2 8 2 8 2 8 2 11 10 8

1 1 3 1 1 3 1 1 3 1 3 1

Iteration 2: S =
2

1, 2, 3 2, 3 3, 4 4, 3 1, 2, 3 2, 3 4, 3 1, 2 2, 3, 4, 1, 2 3, 4, 1, 2 4, 1, 2 1, 2, 3, 4 2, 3, 4 3, 4 4, 3, 4 1, 2, 3 2, 3 3, 4, 3 4, 3 1, 2, 3, 4 2, 3, 4 3, 4 4, 3, 4

Iteration 3: S3 =

Iteration 4: S =
4

1, 2, 3, 4, 1 2, 3, 4, 1 3, 4, 1 4, 1

The final shortest path matrix of the graph shown in the figure 11.18 is S4. We indicate below how some of the highlighted entries are obtained: S1[4][2] = min {S0[4][2], S0[4][1] + S0[1][2]} = min {, 6 + 2} = 8 S2[1][3] = min {S1[1][3], S1[1][2] + S1[2][3]} = min {, 2 + 1} = 3 S3[1][4] = min {S2[1][4], S2[1][3] + S2[3][4]} = min {, 3 + 2} = 5 S3[4][4] = min {S2[4][4], S2[4][3] + S2[3][4]} = min {, 1 + 2} = 3 S4[1][1] = min {S3[1][1], S3[1][4] + S3[4][1]} = min {15, 5 + 6} = 11 [Path is 4, 1, 2] [Path is 1, 2, 3] [Path is 1 3, 4] => [Path is 1, 2, 3, 4] [Path is 4, 3, 4] [Path is 1 4, 1] => [Path is 1, 2, 3, 4, 1]

/*C Code to implement dijkstras function*/ /* File Name: Dijkstra.c */ #include<stdio.h> #include<conio.h> #define NULL -1 #define temp 0 #define perm 1 void dijkstra(int, int); int n;

int weight[20][20]; void dijkstra (int s, int d) { int i, k, step, dist[50], pred[20], status[20], current, dc, min, newdist; for (i = 0; i <= n-1; i++) { dist[i] = 9999; pred[i] = NULL; status[i] = temp; } status[s] = perm; dist[s] = 0; current = s; step=1; while(current != d) { dc = dist[current]; for(i=0;i<=n-1;i++) { if(weight[current][i] != 0 && status[i] == temp) { newdist = dc + weight[current][i]; if(newdist < dist[i]) { dist[i] = newdist; pred[i] = current; } } } min = 32766; for(i=0;i<=n-1;i++) { if(status[i]== temp && min > dist[i]) { min = dist[i]; k = i; } } current = k; status[k] = perm; // SHOW THE INTERMEDIATE STEPS printf("Step%d",step); printf("\nNode\tDist\tPred\tStatus\n"); for(i=0;i<=n-1;i++) { printf("%d\t",i); dist[i] >= 9999?printf("%c\t",224) : printf("%d\t", dist[i]); pred[i] == -1? printf("NULL\t") : printf("%d\t", pred[i]); status[i]==1?printf("PERM\n"): printf("TEMP\n"); } step++;

getch(); } printf("\n The cost is %d",dist[d]); printf("\n And the path is\t"); i = d; while(i != s) { printf("%d<-",i); i = pred[i]; } printf("%d",s); } void main() { int i, j, s, d; clrscr(); printf("\nEnter the number of nodes in the graph"); scanf("%d",&n); printf("\nEnter the weighted matrix"); for(i=0;i<=n-1;i++) for(j=0; j<=n-1; j++) scanf("%d",&weight[i][j]); printf("\nEnter the source and destination node"); scanf("%d%d",&s,&d); dijkstra(s,d); getch(); } /*C code to implement BFS traversal*/ /* File Name: BFS.c */ #include<stdio.h> #include<conio.h> #define MAX 49 #define WHITE 1 #define BLACK 2 void fnDfs(int); void cqinsert(int); int cqdelete(); int cqueueempty(); int n; int adjmat[20][20]; int front = 0, rear = 0, a[50]; void fnDfs(int s) // Purpose : This function finds the BFS traversal of a given graph. // Input : s is the starting node from which we have to start the traversal. // Output : None. { int i, n1, Color[20]; for(i = 0; i <= n-1; i++) Color[i] = WHITE; cqinsert(s);

Color[s] = BLACK; while ( !cqueueempty() ) { n1 = cqdelete(); printf("\t %d", n1); for(i = 0; i<= n -1; i++) { if(adjmat[n1][i] != 0 && Color[i] == WHITE) { cqinsert(i) ; Color[i] = BLACK; } } } }//End of function void cqinsert(int item) { if(front == (rear + 1) % MAX) { printf("\n Queue is full"); return; } else { rear = (rear + 1) % MAX; a[rear] = item; } } int cqdelete() { int item; if(front == rear) { printf("\n Queue is empty"); return; } else { front = (front + 1) % MAX; item = a[front]; } return item; } int cqueueempty() { int i; i = (front == rear)? 1 : 0; return i; } void main() { int i, j, s;

clrscr(); printf("\nEnter the number of nodes in the graph"); scanf("%d",&n); printf("\nEnter the adjacency matrix"); for(i=0;i<=n-1;i++) for(j=0; j<=n-1; j++) scanf("%d",&adjmat[i][j]); printf("\nEnter the starting node"); scanf("%d",&s); fnDfs(s); getch(); } /*C code to implement DFS traversal*/ /* File Name: DFS.c*/ #include<stdio.h> #include<conio.h> #define MAX 49 #define WHITE 1 #define BLACK 2 void fnDfs(int); void push(int); int pop(); int stackempty(); int n; int adjmat[20][20]; int top = -1, a[50]; void fnDfs(int s) // Purpose : This function finds the DFS traversal of a given graph. // Input: S is the starting node from which we have to start the traversal. // Output : None. { int i, n1, Color[20]; for(i = 0; i <= n-1; i++) Color[i] = 1; push(s); Color[s] = 2; while ( !stackempty() ) { n1 = pop(); printf("\t %d", n1); Color[n1] = 3; for(i = 0; i<= n -1; i++) { if(adjmat[n1][i] != 0 && Color[i] == 1) { push(i) ; Color[i] = 2; } } } }//End of Function

void push(int item) { if(top == MAX - 1) { printf("\n Stack is full"); return; } else { top++; a[top] = item; } } int pop() { int item; if(top == -1) { printf("\n Stack is empty"); return; } else { item = a[top]; top--; } return item; } int stackempty() { int i; i = (top == -1)? 1 : 0; return i; } void main() { int i, j, s; clrscr(); printf("\nEnter the number of nodes in the graph"); scanf("%d",&n); printf("\nEnter the adjacency matrix"); for(i=0;i<=n-1;i++) for(j=0; j<=n-1; j++) scanf("%d",&adjmat[i][j]); printf("\nEnter the starting node"); scanf("%d",&s); fnDfs(s); getch(); } /* C code to implement Warshalls path matrix and shortest path function */ /* File Name : warshall.c*/

#include<stdio.h> #include<conio.h> void fnWarshallPM(int[20][20],int[20][20], int); void fnWarshallSPM(int[20][20],int[20][20], int); void fnWarshallPM(int A[20][20],int P[20][20],int n) // Purpose : This function finds the path matrix for a directed graph G. // Input: n is the total number of vertices in G. // Output : None. { int i,j,k; //First copy the contents of adjacency matrix A to path matrix P. for(i = 1;i<=n;i++) { for(j=1;j<=n;j++) { P[i][j] = A[i][j]; printf("%d\t",A[i][j]); } printf("\n"); } //Compute different iterations of P. for(k=1;k<=n;k++) //k is the iteration number. for(i=1;i<=n;i++) for(j=1;j<=n;j++) P[i][j] = P[i][j] || (P[i][k] && P[k][j]); }// End of function void fnWarshallSPM(int A[20][20],int S[20][20],int n) // Purpose : This function finds the path matrix for a directed graph G. // Input : n is the total number of vertices in G. // Output : None. { int i,j,k; //First copy the contents of adjacency matrix A to path matrix P. for(i = 1;i<=n;i++) { for(j=1;j<=n;j++) { S[i][j] = A[i][j]; printf("%d\t",A[i][j]); } printf("\n"); } //Compute different iterations of P. for(k=1;k<=n;k++) //k is the iteration number. for(i=1;i<=n;i++) for(j=1;j<=n;j++) S[i][j] = S[i][j]<(S[i][k] + S[k][j])?S[i][j]:S[i][k] + S[k][j]; }// End of function

void main(void) { int i,j,n,iChoice; int P[20][20],A[20][20],S[20][20]; clrscr(); do { printf("1. Show Warshall's path matrix\n"); printf("2. Find shortst path matrix using Warshall's function\n"); printf("3. Exit\n"); printf("Enter your choice\n"); scanf("%d",&iChoice); switch(iChoice) { case 1: printf("Enter number of nodes in the graph\n"); scanf("%d",&n); printf("Enter the adjacency matrix\n"); for(i=1;i<=n;i++) for(j=1;j<=n;j++) scanf("%d",&A[i][j]); fnWarshallPM(A,P,n); printf("The path matrix is\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%d\t",P[i][j]); printf("\n"); } break; case 2: printf("Enter number of nodes in the graph\n"); scanf("%d",&n); printf("Enter the adjacency matrix\n"); for(i=1;i<=n;i++) for(j=1;j<=n;j++) scanf("%d",&A[i][j]); fnWarshallSPM(A,S,n); printf("The path matrix is\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%d\t",S[i][j]); printf("\n"); } break; case 3: exit(1); default: printf("Wrong Choice\n"); } }while(1);

} 11.8 MCQ Chapter 11 1. Adjacency matrix of a diagraph is: (a) Identity matrix (b) Symmetric Matrix (c) Asymmetric Matrix (d) None of these 2. What is not true for adjacency matrix of a graph (a) It is a symmetric matrix (b) It is a bit matrix (c) Diagonal has all zeroes (d) All are true statements. 3. Backtracking is another name for this method of traversal: (a) Depth-first (b) Breadth first (c) D-search (d) none of these. 4. A suitable structure for breadth-first and depth-first traversal of graphs : (a) Adjacency matrix (b) Edge listing (c) Adjacency list (d) None of these. S. Which method of traversal uses queue to hold nodes that are waiting to be processed : (a) Breadth-first (b) Depth-first (c) D-search (d) None of these. 6. Which method of traversal does not use stack to hold nodes that are waiting to be processed: (a) Breadth-first (b) Depth-first (c) D-search (d) None of these. 7. In which case adjacency list representation of graph is not useful (a) When number of edges is small (b) When number of vertices are changing due to insertion and deletion (c) In breadth-first traversal (d) It is useful in all above cases. 8. Breadth first search : (a) scans all incident edges before moving to other vertex (b) scans adjacent unvisited vertex as soon as possible (c) is same as backtracking (d) None of these. 9. If x is the adjacency matrix of a graph then (i,j)th entry of x represents : (a) No. of edge sequence of length k between i th and j th vertex (b) No. of path of length k between i th and j th vertex (c) No. of walks of length k betwen i th and j th vertex (d) None of these.

10. In an adjacency matrix parallel edges are given by : (a) Similar columns (b) Similar rows (c) Not representable (d) None of these. 11. A vertex with degree one in. a graph is called : (a) Leaf (b) Pendant vertex (c) End vertex (d) None of these. 12. A digraph in which ,outdegree is same as indegree is called : (a) balanced (b) symmetric (c) regular (d) None of these. 13. Graph structure is available in (a) C (b) C++ (c) Pascal (d) None of these 14. Depth first Traversal of a graph produces: (a) a spanning forest of the graph (b) a spanning tree of the graph (c) a minimal spanning tree' (d) None of the above. 15. Suppose that an undirected graph G is represented by an adjacency matrix A. Let B denote the matrix A x A x A x A x A. Which of the following is true if an element B (i,j) is non. zero? (a) Vertex j can be reached from vertex i in exactly 5 steps. (b) Vertex i can be reached from vertex j in exactly 5 steps. (c) Vertex j cannot be reached from vertex i. (d) None of the above. 16. The time complexity of Depth First Search algorithm to traverse a graph of n vertices and e edges is : (a) O(n) if the graph is represented by adjacency matrix (b) O (e) if the graph is represented by adjacency list (c) O (e) if the graph is represented by adjacency matrix (d) None of the above. 17. A graph is planar if and only if, (a) it does not contain subgraphs homomorphic to Ks and K3.3. (b) it does not contain subgraphs isomorphic to Ks or K3,3' (c) it does not contain subgraphs isomorphic to Ks and K3,3' (d) it does not contain subgraph (GATE-1990)

18. Kruskal's algorithm for finding a minimum spanning tree of a weighted graph G with n vertices and m edges has the time complexity of : (GATE-1991) (a) O(n2) (b) O(m n) (c) O(m + n) (d) O(m log n) 2 (e) O(m ).

19. Consider a simple connected graph G with n vertices and n edges(n > 2). Then, which of the following statements are true? (More than I choice may be correct) (GATE-1993) (a) G has no cycles (b) The graph obtained by removing any edge from G is not connected (c) G has at least one cycle (d) The graph obtained by removing any two edges from G is not connected. (e) None of the above. 20. Which of the following statements is false? (GATE-1994)

(a) Optimal binary search tree construction can be performed efficiently using dynamic programming. (b) Breadth first search can not be used to find component of a graph. (c) Give the prefix and postfix walks over 3 binary tree, the binary tree can not be uniquely constructed. (d) Depth first search can be used to find connected components of a graph. 21.The number of distinct simple graphs with up to 3 nodes is : (a) 15 (b) 10 (c) 7 (d) 9. 22. The minimum number of edges in a connected cyclic graph on n vertices is : (a) n - 1 (b) n (c) n + I (d) None of the above. 23. Let G be a graph with 100 vertices numbered 1 to 100. Two vertices i and j are adjacent iff |i j| = 8 or |i j| = 12. The number of connected components in G is : (GATE-1997) (a) 8 (b) 4 (c) 12 (d) 25 24. The number of articulation points of the following graph is : (a) 0 (b) 1 (c) 2 (d) 3. 25. Let G be an undirected graph. Consider a depth-first traversal of G, and let T be the resulting depth-first search tree. Let u be a vertex in G and let v be the first new (unvisited vertex) visited after visiting u in the traversal. Which of the following statements is always true? (GATE-2000) (a) {u, v} must be an edge in G. and u is a descendant of v in T (b) {u, v} must be an edge in G, and v is a descendant of u in T (c) If {u, v} is not an edge in G then It is a leaf in T (d) If {u, v}is not an edge in G then u and v must have the same parent in T. 26. The most appropriate matching for the following pairs : X: depth-first search 1: heap Y: breadth-first search 2: queue Z: sorting 3: stack (a) X-I, Y - 2, Z - 3 (b) X - 3, Y - 1, Z - 2 (c) X - 3, y,- 2, Z - 1 (d) X - 2, Y - 3, Z - 1. (GATE-1994)

27. Let G be an undirected connected graph with distinct edge weights. Let emax be the edge with maximum weight and emin the edge with minimum weight. Which of the following statements is false? (GATE-2000) (a) Every minimum spanning tree, of G must contain emin (b) If emax is in a minimum spanning tree, then its removal must disconnect G (c) No minimum spanning tree contains emax (d) G has a unique minimum spanning tree. 28. Consider an undirected un-weighted graph G. Let a breadth first traversal be done starting from a node r. Let d (r, u) and d (r, v) be the lengths of shortest paths from r to u and v respectively in G. If u is visited before v during the breadth first traversal, which of the following statement is correct? (GATE-2001) (a) d (r, u) < d (r, v) (b) d (r, u) > d (r, v) (c) d (r, u) d (r, v) (d) None of these. 29. Consider the graph below. The third row in the transitive closure of the above graph is 1 2 3

(a) 1,1,1 (c) 1,0,0

(b) 1,1,0 (d) 0,1,1

30. The eccentricity of node labeled 5 in the graph below 1

5 (a) 6 (c) 8 (b) 7 (d) 5

31. The center of the graph in previous question is the node labeled (a) 1 (b) 2 (c) 3 (d) 4

32. Consider the graph below. What should be the labels of nodes marked 1 and 2 if the breadth first traversal yields the list A B C D E?

B 1

2 (a) D and E (c) unpredictable

C (b) E and D (d) none of the above

33. Consider the graph below. Which of the following is a valid strong component? A B

D (a) a, c, d (c) b, c, d (b) a, b, d (d) a, b, c

34. Consider the undirected weighted graph below. The minimum cost spanning tree for this graph has the cost

(a) 18

(b) 20

(c) 24

(d) 22

35. The minimum number of edges in a connected cyclic graph on n vertices is (a) n-l (b) n (c) n+l (d) none of the above 36. The minimum number of colors needed to color a graph having n (>3) vertices and 2 edges is (a) 4 (b) 3 (c) 2 (d) 1 37. The maximum degree of any vertex in a simple graph with n vertices is (a) n (b) n-l (c) n+l (d) 2n-l 38. The number of edges in a regular graph of degree d and n vertices is .

(a) maximum of n, d

(b) n+d

(c) nd

(d) nd/2

Solutions:
1. d 16. d 31. d 2. d 17. b 32. a 3. a 18. c 33. d 4. c 19. c 34. b 5. a 20. b 35. b 6. a 21. a 36. c 7. d 22. b 37. b 8. a 23. a 38. d 9. c 24. d 10. c 25. b 11. b 26. c 12. d 27. c 13. d 28. c 14. b 29. d 15. b 30. b

11. Exercise Chapter 11


1. What is a graph? Differentiate between (i) Undirected and directed graph (ii) cycle and Hamiltonian cycle 2. How can you represent a graph. Discuss the two different ways of representation. 3. Write algorithms for (i) searching for a particular node in a graph (ii) searching for a particular edge in a graph (iii) insert a new node in a graph (iv) insert a new edge in a graph (v) delete a node from a graph (vi) delete a edge from a graph 4. Compare BFS and DFS. Write algorithms and compute time complexity for both BFS and DFS to search a graph for a node 5. How can you find shortest path between two nodes in a graph by Dijkstras algorithm, explain by suitable diagrams and algorithm. 6. Describe Warshalls algorithm to find shortest path between two nodes in a graph and explain it by suitable diagram and algorithm.

Chapter 12 SORTING
12.1 Definition
Sorting can be defined as arranging an unordered set of comparable data items in order. We will concentrate on sorting data that is stored in an array. Let P be an array of n elements P1 P2 P3..Pn in memory. Sorting P means arranging the contents of P in either increasing or decreasing order i.e. P1 P2 P3 P4 P5 .. Pn increasing order P1 P2 P3 P4 P5 .. Pn decreasing order In this chapter we will discuss about different sorting techniques to sort a set of data in increasing order.

12.2 The family of sorting methods


There are two main sorting themes: address-based sorting and comparison-based sorting. Sorting

Comparison-based Sorting

Address-based Sorting

Proxmap Sort

Radix Sort

Bubble Sort

Insertion Sort

Selection Sort

Merge Sort

Quick Sort

Heap Sort

Shell Sort

12.3 Bubble Sort


Bubble sort can be defined as a comparison based sort by checking each adjacent pair of items in a list in turn, swapping the items if necessary, and repeating the pass through the list until no swaps are done. Suppose the list of numbers P[1], P[2], , P[n] is in memory. The bubble sort algorithm works as follows: Iteration 1: Compare P[1] with P[2] and arrange them in ascending order such that P[1] < P[2]. Then compare P[2] with P[3] and arrange such that P[2] < P[3] and so on. Finally we arrange P[n-1] and P[n]. This step involves n-1 comparisons. During which the largest element bubbles up to its final position. That is the reason it is called as bubble sort. Iteration 2: Repeat step 1 with one less comparison. In this case the second largest element will occupy its proper position i.e. we stop after we compare and finally rearrange P[n-2] and P[n-1]. Iteration n-1: Finally we compare P[1] with P[2] and arrange them in such a way that P[1] < P[2]. Example: P[0] P[1] P[2] P[3] P[4] P[5] P[6] P[7] 33 86 57 92 37 48 25 12

During the first pass, the following comparisons are made: P[0] with P[1] P[1] with P[2] P[2] with P[3] P[3] with P[4] P[4] with P[5] P[5] with P[6] P[6] with P[7] 33 with 86 86 with 57 86 with 92 92 with 37 92 with 48 92 with 25 92 with 12 33 < 86 hence no interchange 86 > 57 hence interchange 86 < 92 hence no interchange 92 > 37 hence interchange 92 > 48 hence interchange 92 > 25 hence interchange 92 > 12 hence interchange 12 92

Thus at the end of first pass array will look like: 33 57 86 37 48 The complete set of iteration is depicted below: Iteration original Iteration=1 Iteration=2 Iteration=3 Iteration=4 Iteration=5 Iteration=6 Iteration=7 0 3 3 3 3 3 3 3 3 3 3 3 3 2 5 1 2 1 8 6 5 7 5 7 3 7 3 7 2 5 1 2 2 5

25

Array elements 2 3 4 5 5 9 3 4 7 2 7 8 8 3 4 2 6 7 8 5 3 4 2 1 7 8 5 2 4 2 1 5 8 5 2 7 2 1 4 5 5 2 8 7 1 3 4 5 2 7 8 7 3 3 4 5 3 7 8 7 3 3 4 5 3 7 8 7

6 2 5 1 2 8 6 8 6 8 6 8 6 8 6 8 6

7 12 92 92 92 92 92 92 92

For the implementation of algorithm, we have taken a Boolean variable swap to indicate whether the array is sorted or not and this variable improves the best case time complexity of algorithm from O(n2) to O(n). The initial value of swap is TRUE which means initially the array is not sorted. Before starting of each iteration, set the value of swap to FALSE. During execution of iteration, if at least one swap operation is performed between adjacent pair then set swap to true to indicate the array is not sorted till now and go for next iteration. Algorithm 12.1 1. Algorithm fnBubbleSort(P[],n) 2. //Purpose : This algorithm sorts a set of elements using the concept of bubble sort in ascending order. 3. //Input : P[0.n-1] is an array of n elements. 4. //Output : None. 5. // Comments: Here swap is a Boolean variable. After each iteration, TRUE value of swap indicates at least one interchange has taken place and FALSE value of swap signifies the array is now sorted. 6. { 7. swap = TRUE; 8. for(Iteration = 1; Iteration < n && swap == TRUE; Iteration++) 9. { 10. swap = FALSE; //Set swap to FALSE before starting of each iteration.

11. for(j = 0; j < n- Iteration; j++) 12. { 13. if(P[j] > P[j+1]) 14. { 15. swap = TRUE; 16. temp = P[j]; 17. P[j] = P[j+1]; 18. P[j+1] = temp; 19. } 20. } 21. } 22. } // End of algorithm Time complexity: 1. Best case: The array is in sorted order. Hence swap will remain false after first iteration and so the process ends. Therefore the total number of comparisons made will be decided by the inner loop for the first iteration which is n-1. Tb(n) = O(n) Average case: On the average the comparison made in the inner loop is (k-1)/2. f(n) = (n-1)/2 + (n-2)/2 + .. + 1/2 = n(n-1)/4 Therefore, Ta(n) = O(n2) 3. Worst case: The array P[] is in reverse order. So for the first iteration n-1 comparisons are needed, for the second iteration n-2 comparisons are needed and so on. Hence in the worst case f(n) = (n-1) + (n-2) + + 1 = n(n-1)/2 Therefore, Tw(n) = O(n2) Space Complexity: The space-complexity is O(1), just a few scalar variables. Here we do not count the size of the array being sorted because that is given, not created specifically for this algorithm.

12.4 Insertion Sort


Suppose an array P[] with n elements P[1], P[2], , P[n] is in memory. The insertion sort algorithm scans P from P[1] to P[n], inserting each element P[k] into its proper position in the previously sorted sub-array P[1], P[2], .., P[k-1]. Iteration 1:P[1] by itself is trivially sorted. Iteration 2:P[2] is inserted either before or after P[1] so that P[1] and P[2] is sorted. . Iteration n-1: P[n] is inserted into its proper place in P[1], P[2], .., P[n-1] so that P[1], P[2], ., P[n] is sorted. Iteration original Iteration=1 Iteration=2 Iteration=3 temp=86. temp > P[0], No interchange. temp=57. temp<P[1]. So insert temp at P[1]. temp=92. temp>P[2], No interchange Comments 0 3 3 3 3 3 3 3 1 8 6 8 6 5 7 5 Array elements 2 3 4 5 5 92 37 4 7 8 5 92 37 4 7 8 8 92 37 4 6 8 8 92 37 4 6 2 5 2 5 2 5 2 7 12 12 12 12

Iteration=4 Iteration=5 Iteration=6 Iteration=7

temp=37. temp<P[3]<P[2]<P[1]. So insert temp at P[1] temp=48. temp<P[4]<P[3]<P[2]. So insert temp at P[2] temp=25. temp<P[5]<P[4]<<P[0]. So insert temp at P[0]. temp=12. temp<P[6]<P[5]<<P[0]. So insert temp at P[0].

3 3 3 3 3 2 5 1 2

7 3 7 3 7 3 3 2 5

6 5 7 4 8 3 7 3 3

86 57 48 37

92 86 57 48

8 4 8 9 2 8 6 5 7

5 2 5 2 5 9 2 8 6

12 12 12 92

Algorithm 12.2 1. Algorithm fnInsertionSort(P[], n) 2. //Purpose : This algorithm sorts a set of elements using the concept of insertion sort. 3. //Input : P[0.n-1] is an array of n elements which is to be sorted in ascending order. 4. //Output : None. 5. { 6. for(Iteration = 1; Iteration < n; Iteration++) 7. { 8. temp = P[Iteration]; 9. for(j = Iteration-1; j >= 0 && temp < P[j]; j--) 10. P[j+1] = P[j]; 11. P[j+1] = temp; 12. } 13. }//End of algorithm Time complexity: 1. Best case: The array is in sorted order then only one comparison will be made in the inner loop. Therefore the total number of comparisons made will be decided by the outer loop which is n-1. f(n) = (n-1) X 1 Tb(n) = O(n) 2. Average case: If we were given a random permutation, the chances of the kth insertion requiring 0, 1, 2.k-1 comparisons are equal, and hence 1/k. The expected number of comparisons is for the kth insertion is: k

(i-1)/k = k(k-1)/2k = (k-1)/2 i=1 Therefore on the average, number of comparisons in the inner loop is (k-1)/2. Hence, f(n) = 1/2 + 2/2 + ..+ (n-1)/2 = n(n-1)/4 Therefore, Ta(n) = O(n2)
3. Worst case: The array is in reverse order. So for the first iteration 1 comparison is needed in the inner loop, for the second iteration 2 comparisons are needed and so on. Hence in the worst case f(n) = 1 + 2 +.. + (n-1) = n(n-1)/2 Therefore, Tw(n) = O(n2) Space Complexity:

The space-complexity is O(1). Here also we do not count the size of the array being sorted because that is given, not created specifically for this algorithm.

12.5 Selection Sort


Find the least value in the array, swap it into the leftmost component (where it belongs), and then forget the leftmost component. Do this repeatedly. Suppose an array P[] with n elements P[0], P[1], , P[n-1] is in memory. Iteration 0: Find the location loc of the smallest element in the list and then interchange P[loc] and P[0] if loc 0. Iteration 1: Find the location loc of the smallest element in the sub list of n-1 elements (P[1], P[2],.., P[n-1]) and then interchange P[loc] and P[1] if loc 1 . Then P[0] and P[1] is sorted. Iteration 2: Find the location loc of the smallest element in the sub list of n-2 elements (P[2], P[3],.., P[n-1]) and then interchange P[loc] and P[2] if loc 2. Then P[0], P[1] and P[2] is sorted. ... Iteration n-2: Find the location loc of the smaller element in the sub list of 2 elements (P[n-2] and P[n-1]) and then interchange P[loc] and P[n-2] if loc n-2 . Then P[0], P[1],.. P[n-1] is sorted. Iteratio n (k) original k=0 k=1 k=2 k=3 k=4 k=5 k=6 Comments 0 3 3 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 8 6 8 6 2 5 2 5 2 5 2 5 2 5 2 5 Array elements 2 3 4 5 5 7 5 7 5 7 3 3 3 3 3 3 3 3 3 3 9 2 9 2 9 2 9 2 3 7 3 7 3 7 3 7 3 7 3 7 3 7 3 7 9 2 4 8 4 8 4 8 4 8 4 8 4 8 4 8 4 8 9 2 5 7 5 7 6 2 5 2 5 8 6 8 6 8 6 8 6 8 6 8 6 7 12 33 33 57 57 57 92 92

Here loc = 7. loc k Interchange P[loc] and P[k]. Here loc = 6. loc k Interchange P[loc] and P[k]. Here loc = 7. loc k Interchange P[loc] and P[k]. Here loc = 4. loc k Interchange P[loc] and P[k]. Here loc = 5. loc k Interchange P[loc] and P[k]. Here loc = 7. loc k Interchange P[loc] and P[k]. Here loc = 6. As loc = k, no interchange.

Algorithm 12.3 1. Algorithm fnSelectionSort(P[], n) 2. //Purpose : This algorithm sorts a set of elements using the concept of selection sort. 3. //Input : P[0.n-1] is an array of n elements which is to be sorted in ascending order. 4. //Output : None. 5. { 6. for(k = 0; k < n-1; k++) 7. { 8. loc= k; //loc keeps the track of index of the minimum element in the list. 9. for(j = k+1; j < n; j++) 10. if(P[j] < P[loc]) 11. loc = j;

12. if(loc != k) 13. { 14. temp = P[k]; 15. P[k] = P[loc]; 16. P[loc] = temp; 17. } 18. } 19. }//End of algorithm Time complexity: 1. Best case: The best occurs when the list is in desired order. But unfortunately to get the minimum element from a set of n-k elements n-k comparisons are required. So for the first iteration we have to go through n-1 elements and hence n-1 comparisons. Similarly for the second iteration n-2 comparisons are needed and so on. Thus, f(n) = (n-1) + (n-2) + + 1 = n(n-1)/2 Therefore, T(n) = O(n2) 2. Average case: Here also in first iteration n-1 comparisons are required, in second iteration n-2 comparisons and so on. f(n) = (n-1) + (n-2) + + 1 = n(n-1)/2 Therefore, T(n) = O(n2) 3. Worst case: Like the best case and average case here also, f(n) = (n-1) + (n-2) + + 1 = n(n-1)/2 T(n) = O(n2)

Therefore,

Space Complexity: The space-complexity is O(1). Here also we do not count the size of the array being sorted because that is given, not created specifically for this algorithm.

12.6 Merge Sort


Divide and Conquer Sorting: In terms or algorithms, this method has three distinct steps: Divide: If the input size is too large to deal with in a straightforward manner, divide the data into two or more disjoint subsets. Recur: Use divide and conquer to solve the sub problems associated with the data subsets. Conquer: Take the solutions to the sub problems and merge these solutions into a solution for the original problem. Algorithm 12.4 1. Algorithm fnSort(list) 2. { 3. if(the list has length > 1) 4. { 5. Partition the list into two lists First half and Second half 6. fnSort(First half) 7. fnSort(Second half) 8. fnCombine(First half and Second half) 9. }

10. }//End of algorithm Divide: If S has at leas two elements (nothing needs to be done if S has zero or one elements), remove all the elements from S and put them into two sequences, S1 and S2, each containing about half of the elements of S. (i.e. S1 contains the first n/2 elements and S2 contains the remaining n/2 elements. Recur: Recursive sort sequences S1 and S2. Conquer: Put back the elements into S by merging the sorted sequences S1 and S2 into a unique sorted sequence. Algorithm 12.5 1. Algorithm fnMsort(low, high) 2. //Purpose : This algorithm sorts a set of elements using the concept of merge sort. 3. //Input : P[low.high] is an array of (high-low+1) elements which is to be sorted in ascending order. low is the lower index and high is the upper index of the array. 4. //Output : None. 5. { 6. if(low < high) 7. { 8. mid = (low + high)/2; 9. fnMsort(low, mid); 10. fnMsort(mid+1, high); 11. fnMerge(low, mid, high); 12. } 13. }//End of algorithm Algorithm 12.6 1. Algorithm fnMerge(low, mid, high) 2. //Purpose : This algorithm merges two sorted sub arrays. 3. //Input : The lower index and upper index of two sub arrays are low to mid and mid+1 to high respectively. 4. //Output : None. 5. { 6. h = low; 7. k = low; 8. j = mid + 1; 9. while(h <= mid && j <= high) 10. { 11. if(P[h] <= P[j]) 12. { 13. B[k] = P[h]; 14. h = h + 1; 15. } 16. else 17. { 18. B[k] = P[j]; 19. j = j+1; 20. } 21. k = k + 1; 22. } 23. if(h < mid) 24. {

25. for(i = h ; i <= mid; i++) 26. { 27. B[k] = P[i]; 28. k = k + 1; 29. } 30. } 31. else 32. { 33. for(i = j; i <= high; i++) 34. { 35. B[k] = P[i]; 36. k = k+1; 37. } 38. } 39. for(i = low; i <= high; i++) 40. P[i] = B[i]; 41. }//End of algorithm Consider the following array of 10 elements which is to be sorted using merge sortP[0] P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] 33 86 57 92 37 48 25 12 50 76 The tree of calls of merge sort (0, 9) is depicted in the figure 12.1.

33 fnMsort(0,0) fnMsort(0,1) fnMsort(0,2) 57 fnMsort(2,2) fnMsort(0,4) 92 fnMsort(3,3) fnMsort(3,4) 86 fnMsort(1,1)

37 fnMsort(4,4) 48

fnMsort(0,9) fnMsort(5,6)

fnMsort(5,5)

25 fnMsort(5,7) 12 fnMsort(7,7) fnMsort(5,9) fnMsort(6,6)

50 fnMsort(8,8) fnMsort(8,9) 76 fnMsort(9,9)

Figure 12.1: A tree of call of merge sort

The tree of calls of merging is shown in figure 12.2:33 86 fnMerge(0,1,1) 33 57 86 fnMerge(0,2,2) 33 37 57 86 92 fnMerge(0,3,4) 37 92 fnMerge(3,4,4) 25 48 fnMerge(5,6,6) 12 25 48 fnMerge(5,7,7) 12 25 48 50 76 50 76 fnMerge(8,9,9) Figure 12.2: A tree of call of merging node (a, b, c) represents the merging of the arrays (a, b-1) and (b, c). e.g. (0, 5, 9) means merging of (0, 5) and (6, 9). fnMerge(5,8,9) 12 25 33 37 48 50 57 76 86 92 fnMerge(0,5,9)

Example of merging: Merging can be performed on two sorted sub arrays. Here low = 0 mid = 5 high = 9 and k = 0 Iteratio n Pass = 1 Pass = 2 Pass = 3 Pass = 4 33 Pass = 5 33 Pass = 6 33 Pass = 7 33 Pass = 8 33 37 57 B[0 ] 12 37 37 37 P[0] h=0 33 h=0 33 h=0 33 P[1] 37 37 37 h=1 37 P[2] 57 57 57 57 h=2 57 h=2 57 h=2 57 Array elements P[3] P[4] P[5] P[6] j=5 86 92 12 25 j=6 86 92 12 25 86 86 86 86 86 h=3 86 B[1 ] 25 B[2 ] 33 92 92 92 92 92 92 B[3 ] 37 12 12 12 12 12 12 B[4 ] 48 25 25 25 25 25 25 B[5 ] 50 Comments P[7] 48 48 j=7 48 j=7 48 j=7 48 48 48 48 B[6 ] 57 P[8] 50 50 50 50 50 j=8 50 50 50 B[7 ] 76 P[9] 76 76 76 76 76 76 j=9 76 j=9 76 B[8 ] P[j] < P[h]. So, B[k] = P[j] j = j + 1 = 6, k = k + 1 = 1 P[j] < P[h]. So, B[k] = P[j] j = j + 1 = 7, k = k + 1 = 2 P[h] < P[j]. So, B[k] = P[h] h = h + 1 = 1, k = k + 1 = 3 P[h] < P[j]. So, B[k] = P[h] h = h + 1 = 2, k = k + 1 = 4 P[j] < P[h]. So, B[k] = P[j] j = j + 1 = 8, k = k + 1 = 5 P[j] < P[h]. So, B[k] = P[j] j = j + 1 = 9, k = k + 1 = 6 P[h] < P[j]. So, B[k] = P[h] h = h + 1 = 3, k = k + 1 = 7 P[j] < P[h]. So, B[k] = P[j] j = j + 1 = 10, k = k + 1 = 8

B[9]

Now j(value = 10) > high(value = 9), so program control goes outside the while loop. And as h(value =3) < mid(value = 5), P[h,.., mid] will be copied into array B[]. B[0 ] 12 B[1 ] 25 B[2 ] 33 B[3 ] 37 B[4 ] 48 B[5 ] 50 B[6 ] 57 B[7 ] 76 B[8 ] 86 B[9] 92

Finally the contents of array B[] will be copied into array P[]. P[0] P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] 12 25 33 37 48 50 57 76 86 92 Time complexity: Best case, Average case and Worst case: In the merge sort, the call of merge sort and merging does not depend on the order of the elements in the array. So the best case, average case and worst case time complexity are same in case of merge sort. Now let total T(n) time is needed to sort the array. That is the function fnMsort(low,high) takes T(n) time where n is the total number of elements in the array. So the statement fnMsort(low,mid) takes T(n/2) time to finish its execution as here we are dividing the array into two equal halves each having n/2 elements. Similarly fnMsort(mid+1,high) also needs T(n/2) time. Again the statement fnMerge(low,mid,high) for merge operation takes O(n) time. Hence the recurrence relation to compute the time complexity for the merge sort is: T(n) = 2T(n/2) + c*n c = some positive constant

= 2[2T(n/22) + c*n/2] + c*n = 22T(n/22) + 2c*n = = = 2kT(n/2k) + kcn = nT(1) + cnlogn [ Let 2k = n. So, k = logn] = n + cnlogn [ for n = 1, T(1) = 1] Therefore, T(n) = O(n logn) Space complexity: The space complexity of the recursive algorithm for merge sort is O(n+logn), n for the extra array B[] and log(n) for the stack space. Characteristics: 1. It is based on divide and conquer method. 2. Algorithms complexity will remain O(n logn) in the best, average and worst case. 3. To execute the algorithm, extra space required is equal to the number of elements in the array i.e. space complexity = O(n). 4. Time complexity of algorithm merge is O(n) where n = number of records being merged.

12.7 Quick Sort


It follows the Divide and Conquer method. It was devised by C.A.R. Hoare in 1962. Here the division into sub files is made such that the sorted sub files dont later to be merged. This is accomplished by rearranging the elements P[0:n-1] such that P[i] P[j] for all i, 0 i m and all j, m + 1 j n 1 for some m, 0 m n 1. Thus the elements in P[0:m-1] and P[m + 1 : n-1] may be independently sorted. The rearrangement of the elements is done by picking some element of P[], say t = P[s], and then reordering the other elements, so that all elements appearing before t in P[0:n-1] are less than or equal to t and all elements appearing after t are greater than or equal to t. This rearranging is referred to as partitioning. Example: Given array is:P[0] P[1] P[2] P[3] P[4] P[5] P[6] 33 37 57 86 92 27 25 Iteratio n Initial Step = 1 33 Step = 2 33 Step = 3 33 Step = 4 27 25 33 86 92 57 Sub list 1 P[0] P[1] 37 + Now the two sub lists are: 25 25 P[0] I=0 33 P[1] 37 I=1 37 P[2] 57 57 I=2 57 J=2 27 Array elements P[3] P[4] P[5] 86 86 86 I=3 86 92 92 92 92 27 12 J=5 27 57 Comments P[6] 25 J=6 25 37 37 P[7] J=7 + + 33 + 33 + 33 V 33 33 I<J interchange P[I] and P[J] I<J interchange P[I] and P[J] I>J interchange V and P[J] Now 33 has got its final position

27

25

Sub list 2 P[3] P[4] P[5] P[6] 86 92 57 37 The same process will be continued for sub lists and at the end all the elements of list will be at its proper position. Algorithm 12.7 1. Algorithm fnQsort(p, q) 2. //Purpose : This algorithm sorts an array using quick sort. 3. //Input : The lower index and upper index of the array are p and q respectively. 4. //Output : None. 5. { 6. if(p < q) 7. { 8. j = fnPartition(p, q+1); 9. fnQsort(p, j-1); 10. fnQsort(j+1, q); 11. } 12. }//End of algorithm Algorithm 12.8 1. Algorithm fnPartition(m, n) 2. //Purpose : This algorithm finds the proper place of the pivot element. 3. //Input : The lower index and upper index of the array are m and n respectively. 4. //Output : None. 5. { 6. P[n] = ; //Assign a large value at the end of the array. 7. v = P[m]; // v is the pivot element. 8. i = m; 9. j = n; 10. do 11. { 12. do 13. { 14. i = i+1; 15. } while(P[i] < v); 16. do 17. { 18. j = j-1; 19. }while(P[j] > v); 20. if(i < j) 21. fnInterchange (i, j); 22. }while(i < j); 23. P[m] = P[j]; 24. P[j] = v; 25. return j; //j is the actual position of the pivot element in the sorted array. 26. }//End of algorithm Algorithm 12.9

1. Algorithm fnInterchange(i, j) 2. { 3. temp = P[i]; 4. P[i] = P[j]; 5. P[j] = temp; 6. }//End of algorithm Time complexity: The time complexity of the above algorithm is given by :T(n) = P(n) + T(j LB) + T(UB j) where P(n) = time to partition the given sub file. T(j LB) = time to sort left sub file. T(UB j) = time to sort right sub file. 1. Best case: The best case occurs when the file is always partitioned in half i.e. j = (LB + UB)/2 Then the analysis becomes T(n) = P(n) + 2T(n/2) = cn + 2[2T(n/22) + c(n/2)] = 2cn + 22T(n/22) = = = kcn + 2kT(n/2k) = cnlogn + nT(1) [ Let 2k = n. So, k = logn] = cnlogn + n [for n = 1, T(1) = 1] Therefore, T(n) = O(n logn) 2. Average case: For the average case analysis, we make the following assumptions: The n elements to be sorted are distinct. The partitioning element is chosen using a random selection process. If RANDOM(i, j) is a function that generates a random integer in the interval [i, j], then the selection element is chosen by replacing the statements v = P[m] and i = m by i = RANDOM(LB, UB) and v = P[i]. Since the partitioning element v has an equal probability of being the ith smallest element 1 i (UB LB + 1) in P[LB, UB], hence the two sub files remaining to be sorted will be P[LB, j] and P[j + 1, UB] with probability 1/(UB LB + 1); LB j UB. Form this we obtain the recurrence:T(n) = (n + 1) + 1/n (k = 1..n)[T(k 1) + T(n k)] --------------------(1) Where n + 1 = number of comparisons required to partition the whole list by first time. Multiplying both sides of equation (1) by n we get nT(n) = n(n + 1) + 2[ T(0) + T(1) + + T(n 1)] --------------------(2) Replacing n by n-1 in equation (2) we get (n 1)T(n 1) = (n 1)n + 2[T(0) + T(1) + + T(n 2)] ---------(3) Subtracting equation (3) from equation (2) we get nT(n) (n 1)T(n 1) = 2n + 2T(n 1) or, nT(n) = 2n + (n + 1)T(n 1) or, T(n)/(n + 1) = 2/(n + 1) + T(n 1)/n -------------------------------- (4) Equation (4) is the required recurrence relation in average case. T(n)/(n + 1) = T(n 2)/(n 1) + 2/n + 2/(n + 1) = T(n 3)/(n 2) + 2/(n 1) + 2/n + 2/(n + 1) = = = T(0)/1 + 2 (k = 2.. n+1) 1/k = 2 (k = 2.. n+1) 1/k [ for n = 0, T(0) = 0]

n+1 2 1 Therefore, T(n) 2(n + 1) log(n + 1) Hence, T(n) = O(n logn) 3. Worst case: The worst case occurs when, at each invocation of the routine, the current file is partitioned into two sub files with one of them being empty(j = LB or j = UB) or key element being smallest or largest one. Then the analysis becomes T(n) = P(n) + T(0) + T(n 1) = cn + T(n 1) [ for n = 0, T(0) = 0] = cn + c(n 1) + T(n 2) = cn + c(n 1) + c(n 2) + T(n 3) = = = c(k = 1.. n) K + T(0) = cn(n + 1)/2 Therefore, T(n) = O(n2) dx/x = 2[log(n + 1) log1]

Space complexity: The space complexity of the recursive algorithm for quick sort is O(logn), log(n) for the stack space.

12.8 Heap Sort


Algorithm 12.10 1. Algorithm fnHeap_sort(length) 2. // Purpose : This algorithm sorts a set of element in ascending order using heap sort. 3. // Input : The elements are stored in the array P[1length]. 4. // Output : None. 5. { 6. heap_size = length; 7. fnBuild_max_heap(length); 8. for(i = length; i>=2; i--) 9. { 10. interchange(1, i); 11. heap_size--; 12. max_heapify(1, heap_size); 13. } 14. }//End of algorithm Algorithm 12.11 1. Algorithm fnBuild_max_heap(length) 2. //Purpose : This algorithm builds the max heap with the given data. 3. // Input : length of the array. 4. //Output : None. 5. { 6. for(i = length/2; i >=1; i--)

7. max_heapify(i, length); 8. }//End of algorithm Algorithm 12.12 1. Algorithm max_heapify(i, length) 2. // Purpose : This algorithm adjust the heap with size length in respect to the array position i. 3. // Input : i array position and length is the size. 4. //Output : None. 5. { 6. l = left(i); // index of the left child of i. 7. r = right(i); // index of the right child of i. 8. if(l heap_size) 9. { 10. if(l <= heap_size && P[l] >= P[i]) 11. largest = l; 12. else 13. largest = i; 14. if(r <= heap_size && P[r] > P[largest]) 15. largest = r; 16. if(largest != i) 17. { 18. interchange(i, largest); 19. max_heapify(largest, heap_size); 20. } 21. } 22. }//End of Algorithm Algorithm 12.13 1. Algorithm left( i) 2. // This algorithm calculates the index of the left child of i 3. { 4. return(2*i); 5. }//End of algorithm Algorithm 12.14 1. Algorithm right( i) 2. // This algorithm calculates the index of the right child of i. 3. { 4. return(2*i+1); 5. }//End of Algorithm Example: Consider the following arrayP[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] 54 56 46 29 72 65 32 64 48 Steps to create the heap: 1
5 6 5 4

4 6

2 i=4 8 5 9 6

3 7

2 9

7 2

6 5

3 2

6 4

4 8

Step 1:-max_heapify(4, 9) max_heapify(8, 9) 1 2 4 8


5 6 5 4

4 6

i=3 7

6 4

5 9

7 2

6 5

3 2

2 9

4 8

Step 2:-max_heapify(3, 9) max_heapify(6, 9) 1 i=2 4 8


5 6 5 4

6 5

3 7

6 4

5 9

7 2

4 5

3 2

2 9

4 8

Step 3:-max_heapify(2, 9) max_heapify(5,9) i= 1 2 4 8


7 2 5 4

6 5

3 7

6 4

5 9

5 6

4 5

3 2

2 9

4 8

Step 4:-max_heapify(1, 9) max_heapify(2,9) max_heapify(4, 9) 1 2 4 8


6 4 7 2

6 5

3 7

5 4

5 9

5 6

4 5

3 2

2 9

4 8

Step 5: Max heap is created Array elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Original 64 56 46 29 72 65 32 54 48 After creation of heap 72 64 65 54 56 45 32 29 48 Steps to delete the root and create the sorted array in ascending order: Step 1: Interchange P[1] and P[9] Array elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Step 1 48 64 65 54 56 45 32 29 72 Now heapify. 1 2 4 8
6 4 4 8

1 max_heapify(1, 8)
6 5

6 5

3 7 8 4

6 4

3 5 6

4 8

5 4

5 6

4 5

3 2

5 4

5 6

4 5

3 2

2 9

2 9

Step 2: Interchange P[1] and P[8] Array elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Step 2 29 64 48 54 56 45 32 65 72 Now heapify. 1
2 9

6 4

max_heapify(1, 7) max_heapify(2, 7) 2 4
6 4 4 8

2 max_heapify(5, 7)
3 2

5 6

3 5 6

4 8

5 4

5 6

4 5

5 4

2 9

4 5

3 2

Step 3: Interchange P[1] and P[7] Array elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Step 3 32 56 48 54 29 45 64 65 72 Now heapify. 1 2 4
5 6 3 2

1 max_heapify(1, 6) max_heapify(2, 6)
4 8

5 6

2 max_heapify(4, 6) 4
3 2

5 4

3 5 6

4 8

5 4

2 9

4 5

2 9

4 5

Step 4: Interchange P[1] and P[6] Array elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Step 4 45 54 48 32 29 56 64 65 72 Now heapify. 1 4 1 5 5 4 max_heapify(1, 5) max_heapify(2, 5) 2 4
5 4 4 8

3 4

4 5

3 5

4 8

3 2

2 9

3 2

2 9

Step 5: Interchange P[1] and P[5] Array elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Step 5 29 45 48 32 54 56 64 65 72 Now heapify. 1 2 1 4 9 8 max_heapify(1, 4) max_heapify(3, 4) 2
4 5 4 8

4 5

2 9

3 2

3 2

Step 6: Interchange P[1] and P[4] Array elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Step 6 32 45 29 48 54 56 64 65 72 Now heapify. 1 3 1 4 2 5 max_heapify(1, 3) max_heapify(3, 3) 2
4 5 2 9

3 2

2 9

Step 7: Interchange P[1] and P[3] Array elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Step 7 29 32 45 48 54 56 64 65 72

Now heapify. 1 2
3 2

2 9

1 max_heapify(1, 2) max_heapify(2, 2) 2
2 9

3 2

Step 8: Interchange P[1] and P[2] Array elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Step 8 29 32 45 48 54 56 64 65 72 Now heapify. 1 max_heapify(1, 1) 1
2 9 2 9

Now, the sorted array isArray elements P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] Finally 29 32 45 48 54 56 64 65 72

12.10 Radix Sort


Bucket sort or radix sort can be used to sort a list of elements. In case of decimal numbers, the base or radix is 10.

First of all the list of elements is sorted according to the first digit thus the elements are arranged in 10 buckets. In second pass, elements are arranged according to the second digit and so on. This process depends on the number of digits of the largest number. E.g. if the largest number is 1234 then 4 passes are needed. Example: Given array is:P[0] P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] P[10] 348 143 361 423 538 128 600 858 12 274 999 Pass 1: Elements 0 348 143 361 423 538 128 600 855 12 274 999 Pass 2: Elements 600 361 12 143 423 274 855 85 5 12 14 3 42 3 27 4 0 60 0 1 2 3 Buckets 4 5 6 36 1 7 8 9 1 2 27 4 999 60 0 85 5 36 1 42 3 53 8 12 8 14 3 1 2 3 Buckets 4 5 6 7 8 34 8 9

348 538 128 999 Pass 3: Elements 0 600 12 128 423 538 143 348 855 361 274 999 Therefore the sorted array is:27 4 36 1 14 3 34 8 1 2 12 8 1 2 3 12 8 53 8

34 8

999 Buckets 4 5

6 60 0

42 3 53 8

85 5

999

P[0] P[1] P[2] P[3] P[4] P[5] P[6] P[7] P[8] P[9] P[10] 12 128 143 274 348 361 423 538 600 855 999

Algorithm 12.15 1. Algorithm radix(P, n) 2. // Purpose : This algorithm sorts a set of elements in ascending order using radix sort. 3. // Input : P[0.n-1] is an array of n decimal numbers which is to be sorted in ascending order. 4. // Output : None. 5. //Comment: The one dimensional array buck[j] keeps the track of the index of the elements in jth bucket[j][]. 6. { 7. div = 1; 8. num = number of digits in the largest number of the array P[]. 9. for( pass = 1; pass num; pass++) 10. { 11. for(k = 0; k < 10; k++) 12. buck[k] = 0;

13. for(i = 0; i < n; i++) 14. { 15. j = (P[i]/div)%10; 16. bucket[j][buck[j]++] = P[i]; 17. } 18. i=0; 19. for(k = 0; k < 10; k++) 20. { 21. for(j = 0; j < buck[k]; j++) 22. P[i++] = bucket[k][j]; 23. } 24. div*=10; 25. } 26. }// End of Algorithm Time complexity: Radix sort is dependent on three things: R = Radix (10 for decimal, 26 for alphabets, and 2 for binary). D = Number of digits in the largest element. N = Size of array of elements. Therefore, the maximum number of comparisons will be F(N) = R * D * N 1. Best case: In best case the number of digits in largest element (D) will be logR N. So the maximum number of comparisons = R * logR N * N Which is of O(N log N). 2. Worst case: Suppose the number of digits in the largest element (D) is equal to number of elements in array(N). Which is of O(N2).

12.11 Comparison study of different sorting


Sorting Technique Best Case Average Case Worst Case Bubble O(n) O(n2) O(n2) Insertion O(n) O(n2) O(n2) 2 2 Selection O(n ) O(n ) O(n2) Merge O(nlogn) O(nlogn) O(nlogn) Quick O(n2) O(nlogn) O(nlogn) 2 Radix O(n ) O(nlogn) O(nlogn) Heap O(nlogn) O(nlogn) O(nlogn) /* Sorting using Bubble Sort*/ /* File Name: BUBBLE.C */ #include<stdio.h> #include<conio.h> #define TRUE 1 #define FALSE 0 void fnBubbleSort(int P[],int n)

//Purpose : This algorithm sorts a set of elements using the concept of bubble sort in ascending order. //Input : P[0...n-1] is an array of n elements. //Output : None. //Comments: Here swap is a Boolean variable. After each iteration, TRUE value of swap indicates at least one interchange has taken place and FALSE value of swap signifies the array is now sorted. { int swap,Iteration,j,temp; swap = TRUE; for(Iteration = 1; Iteration < n && swap == TRUE; Iteration++) { swap = FALSE; //Set swap to FALSE before starting of each iteration. for(j = 0; j < n- Iteration; j++) { if(P[j] > P[j+1]) { swap = TRUE; temp = P[j]; P[j] = P[j+1]; P[j+1] = temp; } } } } // End of algorithm void main(void) { int n,P[50],i; clrscr(); printf("Enter number of elements in the array\n"); scanf("%d",&n); printf("Enter the elements now\n"); for(i=0;i<n;i++) scanf("%d",&P[i]); fnBubbleSort(P,n); printf("After sorting:\n"); for(i=0;i<n;i++) printf("%d\t",P[i]); printf("\n"); getch(); } /*Sorting using insertion Sort*/ /* File Name: INSERTION.C */ #include<stdio.h> #include<conio.h> void fnInsertionSort(int P[], int n) //Purpose : This algorithm sorts a set of elements using the concept of insertion sort. //Input : P[0.n-1] is an array of n elements which is to be sorted in ascending order. //Output : None. { int Iteration,temp,j; for(Iteration = 1; Iteration < n; Iteration++) {

temp = P[Iteration]; for(j = Iteration-1; j >= 0 && temp < P[j]; j--) P[j+1] = P[j]; P[j+1] = temp; } }//End of algorithm void main(void) { int n,P[50],i; clrscr(); printf("Enter number of elements in the array\n"); scanf("%d",&n); printf("Enter the elements now\n"); for(i=0;i<n;i++) scanf("%d",&P[i]); fnInsertionSort(P,n); printf("After sorting:\n"); for(i=0;i<n;i++) printf("%d\t",P[i]); printf("\n"); getch(); } /* Sorting using Selection Sort*/ /* File Name: SELECTION.C */ #include<stdio.h> #include<conio.h> #define TRUE 1 #define FALSE 0 void fnSelectionSort(int P[], int n) //Purpose : This algorithm sorts a set of elements using the concept of selection sort. //Input : P[0.n-1] is an array of n elements which is to be sorted in ascending order. //Output : None. { int k,loc,j,temp; for(k = 0; k < n-1; k++) { loc= k; //loc keeps the track of index of the minimum element in the list. for(j = k+1; j < n; j++) if(P[j] < P[loc]) loc = j; if(loc != k) { temp = P[k]; P[k] = P[loc]; P[loc] = temp; } } }//End of algorithm void main(void) {

int n,P[50],i; clrscr(); printf("Enter number of elements in the array\n"); scanf("%d",&n); printf("Enter the elements now\n"); for(i=0;i<n;i++) scanf("%d",&P[i]); fnSelectionSort(P,n); printf("After sorting:\n"); for(i=0;i<n;i++) printf("%d\t",P[i]); printf("\n"); getch(); } /*Sorting using Merge Sort*/ /* File Name: MERGE.C */ #include<stdio.h> #include<conio.h> void fnMsort(int[],int,int); void fnMerge(int[],int,int,int); void fnMsort(int P[], int low, int high) //Purpose : This algorithm sorts a set of elements using the concept of merge sort. //Input : P[low.high] is an array of (high-low+1) elements which is to be sorted in ascending order. low is the lower index and high is the upper index of the array. //Output : None. { int mid; if(low < high) { mid = (low + high)/2; fnMsort(P,low, mid); fnMsort(P,mid+1, high); fnMerge(P,low, mid, high); } }//End of algoritm void fnMerge(int P[], int low, int mid, int high) //Purpose : This algorithm merges two sorted sub arrays. //Input : The lower index and upper index of two sub arrays are low to mid and mid+1 to high respectively. //Output : None. { int h,k,j,i,B[50]; h = low; k = low; j = mid + 1; while(h <= mid && j <= high) { if(P[h] <= P[j]) {

B[k] = P[h]; h = h + 1; } else { B[k] = P[j]; j = j+1; } k = k + 1; } if(h < mid) { for(i = h ; i <= mid; i++) { B[k] = P[i]; k = k + 1; } } else { for(i = j; i <= high; i++) { B[k] = P[i]; k = k+1; } } for(i = low; i <= high; i++) P[i] = B[i]; }//End of algorithm void main(void) { int n,P[50],i; clrscr(); printf("Enter number of elements in the array\n"); scanf("%d",&n); printf("Enter the elements now\n"); for(i=0;i<n;i++) scanf("%d",&P[i]); fnMsort(P,0,n-1); printf("After sorting:\n"); for(i=0;i<n;i++) printf("%d\t",P[i]); printf("\n"); getch(); } /*Sorting using Quick Sort*/ /* File Name: QUICK.C */ #include<stdio.h> #include<conio.h> void fnQsort(int[],int,int);

int fnPartition(int[],int,int); void fnInterchange(int[],int,int); void fnQsort(int P[], int p, int q) //Purpose : This algorithm sorts an array using quick sort. //Input : The lower index and upper index of the array are p and q respectively. //Output : None. { int j; if(p < q) { j = fnPartition(P,p, q+1); fnQsort(P, p, j-1); fnQsort(P, j+1, q); } }//End of algorithm int fnPartition(int P[], int m, int n) //Purpose : This algorithm finds the proper place of the pivot element. //Input : The lower index and upper index of the array are m and n respectively. //Output : None. { int v,i,j; P[n] = 32767; //Assign a large value at the end of the array. v = P[m]; // v is the pivot element. i = m; j = n; do { do { i = i+1; } while(P[i] < v); do { j = j-1; }while(P[j] > v); if(i < j) fnInterchange (P,i, j); }while(i < j); P[m] = P[j]; P[j] = v; return j; //j is the actual position of the pivot element in the sorted array. }//End of algorithm void fnInterchange(int P[],int i,int j) { int temp; temp = P[j]; P[j] = P[i]; P[i]=temp; }

void main(void) { int n,P[50],i; clrscr(); printf("Enter number of elements in the array\n"); scanf("%d",&n); printf("Enter the elements now\n"); for(i=0;i<n;i++) scanf("%d",&P[i]); fnQsort(P,0,n-1); printf("After sorting:\n"); for(i=0;i<n;i++) printf("%d\t",P[i]); printf("\n"); getch(); } /*Sorting using Heap Sort*/ /* File Name: HEAP.C */ #include<stdio.h> #include<conio.h> void fnHeap_sort(int[],int); void fnBuild_Max_Heap(int[],int); void fnMax_heapify(int[],int,int); void interchange(int[],int,int); void fnHeap_sort(int P[], int length) // Purpose : This algorithm sorts a set of element in ascending order using heap sort. // Input: The elements are stored in the array P[1length]. // Output : None. { int heap_size,i; heap_size = length; fnBuild_Max_Heap(P, length); for(i = length; i>=2; i--) { interchange(P, 1, i); heap_size--; fnMax_heapify(P, 1, heap_size); } } void fnBuild_Max_Heap(int P[], int length) //Purpose : This algorithm builds the max heap with the given data. //Input : length of the array. //Output : None. { int i; for(i = length/2; i >=1; i--) fnMax_heapify(P, i, length); }

void fnMax_heapify(int P[], int i, int heap_size) // Purpose : This algorithm adjust the heap with size length in respect to the array position i. // Input: i array position and length is the size. //Output : None. { int l,r,largest; l = left(i); // index of the left child of i. r = right(i); // index of the right child of i. if(l <= heap_size) { if(l <= heap_size && P[l] >= P[i]) largest = l; else largest = i; if(r <= heap_size && P[r] > P[largest]) largest = r; if(largest != i) { interchange(P,i, largest); fnMax_heapify(P, largest, heap_size); } } } int left(int i) { return(2*i); } int right(int i) { return(2*i+1); } void interchange(int P[],int i,int j) { int temp; temp = P[j]; P[j] = P[i]; P[i]=temp; } void main(void) { int n,P[50],i; clrscr(); printf("Enter number of elements in the array\n"); scanf("%d",&n); printf("Enter the elements now\n"); for(i=1;i<=n;i++) scanf("%d",&P[i]);

fnHeap_sort(P,n); printf("After sorting:\n"); for(i=1;i<=n;i++) printf("%d\t",P[i]); printf("\n"); getch(); } /* C CODE TO IMPLEMENT THE RADIX SORT*/ /* File Name: RADIX.C */ #include<stdio.h> #include<conio.h> void radix_sort(int); int P[100]; void radix_sort(int n) // Purpose : This algorithm sorts a set of elements in ascending order using radix sort. // Input: P[0.n-1] is an array of n decimal numbers which is to be sorted in ascending order. // Output : None. //Comment: The one dimensional array buck[j] keeps the track of the index of the elements in jth bucket[j][]. { int bucket[10][5],buck[10],b[10]; int i, j, k, l, num, div, large, passes; div=1; num=0; large=P[0]; for(i=1;i<n;i++) { if(P[i] > large) large=P[i]; } while(large > 0) { num++; large=large/10; } for(passes=0;passes < num; passes++) { for(k=0;k<10;k++) buck[k]=0; for(i=0;i<n;i++) { j=(P[i]/div)%10; bucket[j][buck[j]++]=P[i]; } i=0; for(k=0;k<10;k++) { for(j=0;j<buck[k];j++) P[i++]=bucket[k][j]; } div*=10; }

}//End of Function void main() { int n, i; clrscr(); printf("Enter number of elements"); scanf("%d",&n); printf("Enter the elements now"); for(i=0;i<=n-1;i++) scanf("%d",&P[i]); radix_sort(n); printf("After sorting\n"); for(i=0;i<=n-1;i++) printf("%d\t",P[i]); printf("\n"); getch(); }

12.12 MCQ Chapter 12


1. Name the sort for which time is not proportional to n2: (a) Selection sort (b) Bubble sort (c) Quick sort (d) None of these 2. The element at the root of the heap is (a) largest (b) smallest (c) depending on type of heap it may smallest or largest. (d) None of these 3. This sort does not use divide and conquer methodology (a) Merge sort (b) Quick sort (c) Bubble sort (d) None of these 4. Name the sort in which array to be sorted is partitioned again & again in such a way that all elements less than or equal to partitioning element appear before it and those which are greater appear after it (a) Merge sort (b) Quick sort (c) Selection sort (d) None of these. 5. This sort inserts each elements A (K) into proper position in the previously sorted sub array A (1) ...A (K-1). (a) Insertion sort (b) Radix sort (c) Merge sort (d) bubble sort 6. This sort finds location LOC of smallest element in A (K), ..., A (N) and then interchange A (LOC) with A (K) for K = 1, ..., N - 1.

(a) Shuffle sort (c) Heap sort

(b) Quick sort (d) None of these

7. In this sort, file is divided into subfiles which are to be independently sorted and then merged. (a) quick sort (b) heap sort (c) Bubble sort (d) None of these 8. This searching method requires that all keys must reside in internal memory (a) Binary search (b) sequential search (c) Hashing (d) None of these 9. Average case time complexity of the quick sort algorithm is more than (a) O(N log2 N) (b) O(N ln N) 2 (c) O(N ) (d) O(N3) 10. Worst case time complexity of the heap sort algorithm is (a) O(N log2 N) (b) O(N ln N) (c) O(N2) (d) O(N3) 11. For sorting contiguous list of records quicksort may be preferred over merge sort because: (a) it requires less time always (b) it does not require extra space for an auxiliary storage. (c) it requires less programming effort (d) some programming languages does not support recursion 12. In quicksort, a desirable choice for the partitioning element will be : (a) first element of the list (b) last element of the list (c) median of the list (d) a randomly chosen element of the list 13. Stability of a sorting algorithm is important for (a) sorting records on the basis of multiple keys (b) worst case performance of the sorting algorithm (c) sorting alphanumeric keys because they are likely to be same. (d) None of the above. 14. the heap (represented by an array) constructed from the list of numbers 30, 10, 80, 60, 15, 55, 17 is: (a) 60, 80, 55, 30, 10, 17, 15 (b) 80, 55, 60, 15, 10, 30, 17 (c) 80, 60, 30, 17, 55, 15, 10 (d) None of the above 15. Which of the following sorting algorithms has a worst case time complexity of 0 (n2) to sort n records? (a) Quick sort (b) Merge sort

(c) Heap sort (d) None of the above 16. The complexity of the comparison based sorting algorithm is: (a) (n log n) (b) (n) 2 (c) (n ) (d) (n n) 17. Merge sort uses (a) Divide and conquer strategy (b) Backtracking approach (c) Heuristic Search (d) Greedy approach 18. For merging two sorted lists of sizes m and n into a sorted list of size m + n, we require comparisons of (a) O(m) (b) O(n) (c) O(m+n) (d) O(log m + log n) 19. The minimum number of interchanges needed to convert the array 89, 19, 40, 17, 12, 10, 2, 5, 7, 11, 6, 9, 70 into a heap with the maximum element at the root is (a) 0 (b) 1 (c) 2 (d) 3 20. A sorting technique is called stable if: (a) It takes O(n log n) time . (b) It maintains the relative order of occurrences of non-distinct elements (c) It uses divide and conquer paradigm (d) It takes O(n) space (GATE-1990)

(GATE-1995)

(GATE-1996)

(GATE-1999)

21. Suppose we want to arrange the n numbers sorted in an array such that all negative values occur before all positive ones. Minimum number of exchanges required in the worst case is (GATE-1999) (a) n - 1 (b) n (c) n + 1 (d) None of the above 22. If one uses straight two-way merge sort algorithm to sort the following elements in ascending order 20, 47, 15, 8, 4, 40, 9, 30, 12, 17, then the order of these elements after the second pass of the algorithm is: (GATE-1999) (a) 8, 9, 15, 20, 47, 4, 12, 17, 30, 40 (b) 8, 15, 20, 47, 4, 9, 30, 40, 12, 17 (c) 15, 20, 47, 4, 8, 9, 12, 30, 40, 17 (d) 4, 8, 9, 15, 20, 47, 12, 17, 30, 40 23. Let s be a sorted array of n integers. Let t(n) denote the time taken for the most efficient algorithm to determine if there are two elements with sum less than 1000 in s. Which of the following statements is true? (GATE-2000)

(a) t (n) is O(1) (b) n t (n) n log2 n (c) n log2 n t (n) < n C2 (d) t (n) = nC2 24. The number of swappings needed to sort the numbers 8, 22, 7, 9, 31, 19, 5, 13 in ascending order, using bubble sort is (a) 11 (b) 12 (c) 13 (d) 14

25. Given two sorted list of size m and n respectively. The number of comparisons needed in the worst case by the merge sort algorithm will be (a) m x n (b) maximum of m, n (c) minimum of m, n (d) m + n - 1 26. Sorting is not useful for (a) report generation (b) minimizing the storage needed (c) responding to queries easily (c) making searching easier and efficient 27. Choose the correct statements. (a) Internal sorting is used if the number of items to be sorted is very large. (b) External sorting is used if the number of items to be sorted is very large. (c) External sorting needs auxiliary storage. (d) Internal sorting needs auxiliary storage. 28. A sorting technique that guarantees, that records with the same primary key occurs iti the same order in the sorted list as in the original unsorted list is said to be (a) stable (b) consistent (c) external (d) linear 29. The way a card game player arranges his cards as he picks them up one by one, is an example of (a) bubble sort (b) selection sort (c) insertion sort (d) merge sort 30. You want to check whether a given set of items is sorted. Which of the following sorting methods will be the most efficient if it is already in sorted order? (a) Bubble sort (b) Selection sort (c) Insertion sort (d) Merge sort 31. The average number of comparisons performed by the merge sort algorithm, in merging two sorted lists of length 2 is (a) 8/3 (b) 8/5 (c) 11/7 (d) 11/6 32. Which of the following sorting methods will be the best if number of swappings done, is the only measure of efficiency? (a) Bubble sort (b) Selection sort (c) Insertion sort (d) Quick sort 33. You are asked to sort 15 randomly generated numbers. You should prefer (a) bubb1esort (b) quick sort (c) merge sort (d) heap sort 34. As part of the maintenance work, you are entrusted with the work of rearranging the library books in a shelf in proper order, at the end of each day. The ideal choice will be (a) bubble sort (b) insertion sort (c) selection sort (d) heap sort

35. The maximum number of comparisons needed to sort 7 items using radix sort is (assume each item is a 4 digit decimal number) (a) 280 (b) 40 (c) 47 (d) 38 36. Which of the following algorithms exhibits the unnatural behavior that, minimum number of comparisons are needed if the list to be sorted is in the reverse order and maximum number of comparisons are needed if they are already in sorted order? (a) Heap sort (b) Radix sort (c) Binary insertion sort (d) There can't be any such sorting method 37. Which of the following sorting algorithm has the worst time complexity of nlog (n)? (a) Heap sort (b) Quick sort (c) Insertion sort (d) Selection sort 38. Which of the following sorting methods sorts a given set of items that is already in sorted order or in reverse sorted order with equal speed? (a) Heap sort (b) Quick sort (c) Insertion sort (d) Selection sort 39. Merge sort uses (a) divide and conquer strategy (b) backtracking approach (c) heuristic search (d) greedy approach 40. For merging two sorted lists of sizes m and n into a sorted list of size m+n, we require comparisons of (a) O(m) (b) O(n) (c) O(m+n) (d) O(log(m) + log(n)) 41. A machine needs a minimum of 100 see to sort 1000 names by quick sort. The minimum time needed to sort 100 names will be approximately (a) 50.2 see (b) 6.7 see (c) 72.7 see (d) 11.2 see 42. A machine took 200 see to sort 200 names, using bubble sort. In 800 see, it can approximately sort . (a) 400 names (b) 800 names (c) 750 names (d) 800 names 43. The correct order of arrangement of the names Bradman, Lamb, May, Boon, Border, Underwood and Boycott, so that the quicksort algorithm makes the least number of comparisons is (a) Bradman, Border, Boon, Boycott, May, Lamb, Underwood (b) Bradman, Border, Boycott, Boon, May, Underwood, Lamb (c) Underwood, Border, Boon Boycott, May, Lamb, Bradman (d) Bradman, May, Lamb, Border, Boon, Boycott, Underwood 44. Which of the following is useful in implementing quick sort? (a) Stack (b) Set (c) List (d) Queue

45. Which of the following algorithm design technique is used in the quick sort algorithm? (a) Dynamic programming (b) Backtracking (c) Divide and conquer (d) Greedy method 46. Assume 5 buffer pages are available to sort a file of 105 pages. The cost of sorting using m-way merge sort is (a) 206 (b) 618 (c) 840 (d) 926

47. A sorting procedure is called to sort a list of 100 integers that have been read from a file. If all 100 values are zero, what would the execution requirements (in terms of Big-O) be if the sort used was MergeSort? (a) O(N) (b) O(N2) (c) O(N log2 N) (d) None of these. 48. A list is ordered from smallest to largest when a sort is called. Which sort would take the longest time to execute? (a) HeapSort (b) Bubble sort . (c) QuickSort (with the first element as the split value) (d) SelectionSort. 49. A list is ordered from smallest to largest when a sort is called. Which sort would take the shortest time to execute? (a) Heapsort (b) ShortBubble (c) QuickSort (d) SelectionSort. 50. Identify one or more correct answers: Sorting an array of pointers to list elements, rather than sorting the elements themselves, is a good idea when (a) the number of elements is vary large (b) the individual elements are large in size (c) the sort is recursive (d) there are multiple keys on which to sort the elements.

Solutions:
1. b 16. a 31. a 46. c 2. c 17. a 32. b 47. c 3. c 18. c 33. a 48. c 4. b 19. b 34. b 49. d 5. a 20. b 35. a 50. b 6. d 21. a 36. c 7. d 22. b 37. a 8. a 23. b 38. b 9. b 24. d 39. a 10. a 25. d 40. c 11. b 26. b 41. b 12. c 13. c 14. b,c 15. a 27. b,c 28. a 29. c 30. c 42. a 43. a,b 44. a 45. c

12.13 Exercise Chapter 12


1. What is a sorting? State different types of sorting method and compare between them. 2. Write down the algorithm for bubble sort and explain how you can sort a unsorted array of integers by using bubble sort. Find out the time complexity of your algorithm. 3. Write down the algorithm for bubble sort and explain how you can sort a unsorted array of integers by using insertion sort. Find out the time complexity of your algorithm. 4. Write down the algorithm for bubble sort and explain how you can sort a unsorted array of integers by using selection sort. Find out the time complexity of your algorithm. 5. Write down the algorithm for bubble sort and explain how you can sort a unsorted array of integers by using merge sort. Find out the time complexity of your algorithm. 6. Write down the algorithm for bubble sort and explain how you can sort a unsorted array of integers by using quick sort. Find out the time complexity of your algorithm. 7. Write down the algorithm for bubble sort and explain how you can sort a unsorted array of integers by using heap sort. Find out the time complexity of your algorithm. 8. Write down the algorithm for bubble sort and explain how you can sort a unsorted array of integers by using radix sort. Find out the time complexity of your algorithm.

Chapter - 13 SEARCHING
13.1 INTRODUCTION
Searching is a process to find an element from a set of elements stored in an order or randomly. In this chapter we will discuss about two popular searching techniques 1) Linear/Sequential search and 2) Binary search. 13.2 SEQUENTIAL SEARCH Sequential search is nothing but searching an element in linear way. This can be in array or in linked list. The array or linked list may be in sorted or unsorted order. We have a need to start the search from beginning and scan the elements one by one until the end of array or linked list. For successful search it returns the location of the element, otherwise returns the failure notification. 1. Sequential search in array: Algorithm 13.1 describes the procedure. Algorithm 13.1 1. Algorithm fnSequential_search_in_Array(arrData, item, n) 2. // Purpose : This algorithm finds an element from an array of elements. 3. // Input : n is the number of elements in the array arrData and item is the data to be searched. 4. // Output : For successful search it returns position of the item in the array, otherwise returns -1. 5. { 6. for(i = 0; i <= n-1; i++) 7. { 8. if(arrData[i] == item) 9. { 10. return i; 11. break; 12. } 13. } 14. return -1; 15. }// End of Algorithm. Consider the following array arrData[0:4]. arrData[0] arrData[1] arrData[2] arrData[3] arrData[4] 10 15 8 9 25 If the item = 9 then the above algorithm returns 3. If the item be 20 then the above algorithm returns -1. 2. Sequential search in linked list: Algorithm 13.2 describes the procedure. Algorithm 13.2 1. Algorithm fnSequential_search_in_LinkedList(ptrStart, item) 2. // Purpose : This algorithm finds an element from a linked list. 3. // Input : ptrStart is the starting address of the list and item is the data to be searched. 4. // Output : For successful search it returns position of the item in the array, otherwise returns -1. 5. { 6. SLLNode *ptrTemp;

7. ptrTemp = ptrStart; 8. iPosition = 1; 9. while(ptrTemp != NULL) 10. { 11. if(ptrTemp->iData == item) 12. return iPosition; 13. ptrTemp = ptrTmp->ptrNext; 14. iPosition = iPosition + 1; 15. } 16. return -1; 17. }//End of Algorithm. Consider the following linked list: ptrStart
10 15 8 9 25 NULL

If item = 9 then the above algorithm returns 4. If item be 20 then the above algorithm returns -1. 3. Time complexity 4. Best case: If the desired record is present in the first position of the search table, then only one comparison is made. Therefore, Tb(n) = O(1) 5. Average case: The element can be found at any position in the list and the probability is 1/n. Now if the element is found at first position then only one comparison is required, for the second position two comparisons are required and so on. Hence, f(n) = 1/n + 2/n + + n/n = (1 + 2 + ...+ n)/n = n(n + 1)/2n = (n + 1)/2 Therefore, Ta(n) = O(n) 6. Worst case: For an unsuccessful search, the search table will be visited till the last record. Therefore, Tw(n) = O(n)

13.3 BINARY SEARCH


Binary search is performed over a sorted array of elements. The logic behind this technique is given below: 1. First find the middle element of the array. 2. Compare the middle element with the item. 3. There are three cases. a) If it is the desired element then search is successful. b) If it is less than the desired element then search only the first half of the array. c) If it is greater than the desired element search in the second half of the array. Now in linked list random access is not possible. So first we have to calculate the position of the middle element and then sequentially we have to traverse to the middle position. Thats why it is hard to implement the binary search over a linked list.

Example: Consider the following sorted array: arrData[0] arrData [1] arrData [2] arrData [3] arrData [4] arrData [5] arrData [6] 19 21 27 30 35 40 43 And the item to be searched is = 27. Iteratio n original [0] [1 ] Array elements [2] [3 [4 ] ] 35 35 35 Comments [5 ] [6] mid = (6+0)/2 = 3. item < arrData[mid] So, now search in arrData[l:mid-1] i.e. arrData[0:2]. mid = (2+0)/2 = 1. item > arrData[mid] So, now search in arrData[mid+1:h]i.e. arrData[2:2]. mid = (2+2)/2 = 2. item = arrData[mid]. So, successful search. Return the index of the item in the array i.e. here 2. h=6 40 43 40 40 43 43

l=0 19 21 27 30 Step = 1 l=0 h=2 19 21 27 30 Step = 2 l=h=2 19 21 27 30

Now, let the item = 42 Iteratio n original Step = 1 19 Step = 2 19 Step = 3 19 21 27 30 35 21 27 30 35 21 27 [0] [1 ] Array elements [2 [3 [4] [5] ] ] 27 30 35 l=4 30 35 40 Comments [6]

l=0 19 21

h=6 mid = (6+0)/2 = 3. item > arrData[mid] So, now search in arrData[mid+1 : h] i.e. arrData[4:6]. 43 h=6 mid = (4+6)/2 = 5. item > arrData[mid] So, now search in arrData[mid+1:h]i.e. arrData[6:6]. 40 43 l=h=6 mid = (6+6)/2 = 6. item < arrData[mid]. So, now search in arrData[l:mid-1]i.e. arrData[6:5]. 40 43 h=5 l=6 As now l > h the process is stopped and this is an unsuccessful search. So, return -1. 40 43

Algorithm 13.3 1. Algorithm fnBinarySearch_NonRecursive(arrData, low, high, item) 2. // Purpose : This algorithm finds an element from a sorted array in a non recursive approach. 3. // Input : low and high are the lower and upper index of array arrData and item is the data to be searched. 4. // Output : For successful search it returns position of the item, otherwise returns -1. 5. { 6. while(low > high) 7. { 8. mid = (low + high)/2; 9. if(item == arrData[mid]) 10. return mid; 11. else if(item < arrData[mid]) 12. high = mid 1; 13. else 14. low = mid + 1; 15. } 16. return -1;

17. }// End of Algorithm

Algorithm 12.4 1. Algorithm fnBinarySearch_Recursive(arrData, low, high, item) 2. // Purpose : This algorithm finds an element from a sorted array in a recursive approach. 3. // Input: low and high are the lower and upper index of array arrData and item is the data to be searched. 4. // Output : For successful search it returns position of the item, otherwise returns -1. 5. { 6. if(low > high) 7. return -1; 8. else 9. { 10. mid = (low + high)/2; 11. if(item == arrData[mid]) 12. return mid; 13. else if(item < arrData[mid]) 14. fnBinarySearch_Recursive(arrData, low, mid 1, item); 15. else 16. fnBinarySearch_Recursive(arrData, mid + 1, high, item); 17. } 18. }// End of Algorithm Time complexity: 1. Best case: If the desired record is present in the mid position of the search table, then only one comparison is made. Therefore, Tb(n) = O(1) 2. Average case and Worst case: Suppose T(n) unit of time is required to search the array of n elements. Now in the second iteration the array is halved. So, in the second iteration T(n/2) time is required and so on. Hence we get the following recurrence relation: T(n) = c + T(n/2) c = some constant time required to find the middle element. 2 = c + c + T(n/2 ) = . = . = kc + T(n/2k) = c logn + T(1) [ Let 2k = n. So, k = logn] Therefore, T(n) = O(log n)

13.4 MCQ Chapter 13


1. Which of the following is not possible as a balance factor of any node of an AVL tree? (a) 1 (b) 2 (c) 0 (d) none of the above 2. A B-tree of order n is also called (a) (n - n) - 1 tree (c) (n - 1) - n tree

(b) n - (n - 2) tree (d) none of these

3. A balanced search tree (B-tree) is one in which every node has between: (a) t - 1 and 2t - 1 children (b) t and 2t children (c) t / 2 and t children (d) None of these Where t is a constant. 4. What is the maximum number of nodes in a heap with 8 leaf nodes? (a) 15 (b) 16 (c) 17 (d) 31 5. How many distinct binary search tree can be formed which contains the integers 1, 2, 3 ? (a) 6 (b) 5 (c) 4 (d) 3 6. Maximum possible height of an AVL Tree with 7 nodes is : (a) 3 (b) 4 (c) 5 (d) none of the above 7. Which of the following is a hash function ? (a) Quadratic probing (b) Chaining (c) Open addressing (d) Folding 8. The average number of key comparisons done in successful sequential search in a list of length n is (GATE-1996) (a) log n (b) (n - 1)/2 (c) nl2 (d) (n + 1)/2 9. A binary search tree is generated by inserting in order the following integers: 50. 15, 62, 5. 20.. 58, 91. 3. 8, 37. 60. 24 The number of nodes in the left subtree and right subtree of the root respectively is (a) (4. 7) (b) (7. 4) (c) (8, 3) (d) (3. 8) 10. An advantage of chained hash table (external sorting) over the open addressing scheme is (a) Worst case complexity of search operations is less (b) Space used is less (c) Deletion is easier (d) None of the above

(GATE-1996)

(GATE-1996)

11. Which of the following statements is false? (GATE-1998) (a) A tree with n nodes has n - 1 edges. (b) A labeled rooted binary tree can be uniquely constructed given its postorder and preorder results. (c) A complete binary tree with n internal nodes has (n + 1) leaves. (d) The maximum number of nodes in a binary tree of height h is (2h + 1 - 1). 12. A complete n-ary tree is one in which every node has 0 or n sons. If x is the number of internal nodes of a complete n-ary tree, the number of leaves in it is given by: (a) x (n - 1) + 1 (b) xn - 1 (GATE-199B)

(c) xn + 1

(d) x (n + 1) (GATE-1999)

13. Which of the following is correct? (a) B-trees are for sorting data on disk and B+-trees are for main memory (b) Range queries are faster on B+ trees (c) B-trees are primary indexes and B+ trees are for secondary indexes (d) The height of a B+ tree is independent of the number of records. 14. B+ -trees are preferred to binary trees in database because (a) Disk capacities are greater than memory capacities (b) Disk access is much slower than memory access (c) Disk data transfer rates are much less than memory data transfer rates (d) Disks are more reliable than memory. 15. The average successful search time for sequential search on 'n' items is (a) n / 2 (b) (n-1) / 2 (c) (n+l) / 2 (d) log (n + 1) 16. The order of the binary search algorithm is (a) n (b) n2 (c) nlog(n) (d) log(n) 17. Which is the best? (a) Binary search (b) Sequential search (c) Indexing (d) Cant say as all are best in their own application

18. The element being searched for is not in an array of 100 elements. What is the average number of comparisons needed in a sequential search to determine that the element is not there if the elements are completely unordered? (a) 100 (b) 50 (c) 75 (d) 25 19. The element being searched for is not in an array of 100 elements. What is the average number of comparisons needed in a sequential search to determine that the element is not there if the elements are ordered from smallest to largest? (a) 100 (b) 50 (c) 75 (d) 25 20. The element being searched for is not in an array of 100 elements. What is the average number of comparisons needed in a sequential search to determine that the element is not there if the elements are ordered from largest to smallest? (a) 100 (b) 50 (c) 75 (d) 25 21. The element being searched for is in an array of 100 elements. What is the average number of comparisons needed in a sequential search to determine the position of the element if the elements are completely unordered? (a) 100 (b) 50 (c) 75 (d) 25

Solutions:
1. b 16. d 2. d 17. d 3. a 18. a 4. a 19. b 5. b 20. b 6. b 21. c 7. d 8. d 9. b 10. b 11. b 12. a 13. b 14. a 15. a

13.5 Exercise Chapter 13


1. What is a searching? What are thedifferent types of searching? Briefly describe them with a suitable example. 2. Write an algorithm to find an element in a sorted array by sequential search and explain it by a suitable example and diagram. Find out the time complexity. 3. Write an algorithm to find an element in a single linked list by sequential search and explain it by a suitable example and diagram. Find out the time complexity. 4. Write an algorithm to find an element in a double linked list by sequential search and explain it by a suitable example and diagram. Find out the time complexity. 5. Write an algorithm to find an element in a sorted array by binary search and explain it by a suitable example and diagram. Find out the time complexity. 6. Write an algorithm to find an element in a single linked list by binary search and explain it by a suitable example and diagram. Find out the time complexity. 7. Write an algorithm to find an element in a double linked list by binary search and explain it by a suitable example and diagram. Find out the time complexity.

CHAPTER 14 HASHING
14.1 Introduction
In this chapter we examine data structures which are designed specifically with the objective of providing efficient insertion and find operations. In order to meet the design objective, certain concessions are made. Specifically, we do not require that there be any specific ordering of the items in the list. In addition, while we still require the ability to remove items from the list, it is not our primary objective to make removal as efficient as the insertion and find operations. Ideally we would build a data structure for which both the insertion and find operations are O(1) in the worst case. In the case of an ordered list, the cost of an insertion is O(1) and the cost of the find operation using linear search is O(n). For a sorted list, the cost of insertion is O(n) and the cost of the find operation using binary search is O(n logn) for the array implementation. In order to meet the performance objective of constant time insert and find operations, we need a way to do them without performing a search. I.e., given an item k, we need to be able to determine directly from k the array position where it is to be stored. Therefore, hashing is a searching technique which takes constant time (O(1)) to find an item from a list.

14.2 Hash table


A hash table is an array of m buckets, together with a hash function H(k) that translates each key k to a bucket index (in the range 0m1). A bucket has number of slots and each slot is capable to hold one record. Hash table (array of buckets) Hash function key k1 k2 k3 k4 value 2 m-2 2 5 0 1 2 3 4 5

kn

4 Hashing (translating keys to bucket indices)

m2 m1

Figure 14.1 Principles of Hash tables: 1. Each key k has a home bucket in the hash table, namely the bucket with index H(k). 2. To insert a new entry with key k into the hash table, assign that entry to ks home bucket. 3. To search for an entry with key k in the hash table, look in ks home bucket. 4. To delete an entry with key k from the hash table, look in ks home bucket. 5. The hash function must be consistent:

k1 = k2 => H(k1) = H(k2). 6. In general, the hash function is many-to-one. 7. Always prefer a hash function that makes collisions relatively infrequent.

14.3 Hash functions


In this section we will discuss about several hashing functions. Now we assume that we are dealing with integer valued keys. A hash function can be defined as a function which takes key (k) as input and produces a bucket index as output. H(k) = L Where,H is a hash function. k is a set of keys. L is bucket indices (or set of memory addresses). Some of the popular hash functions are described below: 1. Division method: Perhaps the simplest of all the methods of hashing a key k is to divide k by M and then to use the remainder as the bucket index. This is called the division method of hashing. In this case, the hash function is H(k) = k mod M In certain situations, some extra care is needed in the selection of a suitable value for M. For example, it is often convenient to make M an even number. But this means that H(k) is even if k is even; and H(k) is odd if k is odd. If all possible keys are equiprobable, then this is not a problem. However if, say, even keys are more likely than odd keys, the function H(k) = k mod M will not spread the hashed values of those keys evenly. Similar problem may occur for odd value of M. Similarly, it is often tempting to let M be a power of two. E.g., M = 2q for some integer q>1. In this case, the hash function H(k) = k mod M simply extracts the bottom q bits of the binary representation of k. While this hash function is quite easy to compute, it is not a desirable function because it does not depend on all the bits in the binary representation of k. For these reasons M is often chosen to be a prime number. For example, suppose there is a bias in the way the keys are created that makes it more likely for a key to be a multiple of some small constant, say two or three. Then making M a prime increases the likelihood that those keys are spread out evenly. Also, if M is a prime number, the division of k by that prime number depends on all the bits of k, not just the bottom q bits, for some small constant q. Example 14.1: Consider a hash table with 7 slots and the keys are 49, 123, 68, and 90. Suppose we are taking the value of M = 7. Then, H(49) = 49 mod 7 = 0 H(123) = 123 mod 7 = 4 H(68) = 68 mod 7 = 5 H(90) = 90 mod 7 = 6 The hash table will look like:

Division method key 49 123 68 90 value 0 4 5 6

Hash table (array of buckets) 49

0 1 2 3 4 5 6

123 68 90

Algorithm 14.1 describes an algorithm which creates a bucket index according to division method. Algorithm 14.1 1. Algorithm fnDivision(k, M) 2. // Purpose : This algorithm produces the bucket index for a key using division method. 3. // Input : The key k and the prime number M. 4. // Output : The bucket address L. 5. { 6. L = k%M; //Get the remainder. 7. return L; 8. }// End of algorithm Disadvantages: A potential disadvantage of the division method is due to the property that consecutive keys map to consecutive hash values. While this ensures that consecutive keys do not collide, it does mean that consecutive array locations will be occupied. In certain implementations this can lead to degradation in performance. In the following sections we consider hashing methods that tend to scatter consecutive keys. 2. Middle square method: In middle square method the key k is squared and then some particulars bits from the middle of k2 (for example 3rd and 4th LSB) are extracted to get the desired key. The positions of these particular bits are same for all the keys. For example, if the 3rd and 4th LSB are considered for the square of key k1, then for k2 also 3rd and 4th LSB are considered. Therefore the hash function is: H(k) = some bits of (k2) Example 14.2: Consider a hash table with 10 slots and the keys are 49, 123, 68, and 100. Here we are taking the 3rd LSB from the square of each key. Then, H(49) = 3rd LSB(2401) = 4 H(123) = 3rd LSB(15129) = 1 H(68) = 3rd LSB(4624) = 6 H(100) = 3rd LSB(10000) = 0 The hash table looks like:

Middle square method key 49 123 68 100 value 4 1 6 0

Hash table (array of buckets) 100 123

0 1 2 3 4 5 6 7 8 9

49

68

Algorithm 14.2 describes an algorithm which creates a bucket index according to middle square method. Algorithm 14.2 1. Algorithm fnMidSquare(k, b1, b2) 2. // Purpose : This algorithm produces the bucket index for a key using middle square method. 3. // Input : k is the key and from b1 to b2 bits are to be extracted from k2. 4. // Output : The bucket index L. 5. { 6. k1 = k*k/ 10(b1-1); //Discard the left most bits and store the number in k1. (b2-b1+1) 7. L = k1 % 10 ; //Extract leftmost (b2-b1) bits from k1. 8. return L; 9. }// End of algorithm 3. Folding method: In folding method the key k is partitioned into a number of parts k1, k2, k3.kr, where each part except possibly the last has the same number of digits as the required address. Then the parts are added together ignoring the last carry. Therefore the hash function is: H(k) = k1 + k2 + k3 + . + kr where the carry is ignored. Example 14.3: Consider a hash table with 10 slots and the keys are 49, 123, 68, and 90. Here we are partitioning the key with size one. Then, H(49) = 4 + 9 = 3 ignoring the carry. H(123) = 1 + 2 + 3 = 6 H(68) = 6 + 8 = 4 ignoring the carry. H(90) = 9 + 0 = 9 The hash table looks like:

Folding method key 49 123 68 90 value 3 6 4 9

Hash table (array of buckets)

0 1 2 3 4 5 6 7 8 9 90 123 49 68

The algorithm 14.3 illustrates the folding method of hashing: Algorithm 14.3 1. Algorithm fnFolding(k) 2. // Purpose : This algorithm produces the bucket index for a key using folding method. 3. // Input : The key k. 4. // Output : The bucket address L. 5. { 6. q = 1; 7. p = 0; 8. L = 0; 9. while(k!=0) 10. { 11. p = p + 10q * (k%10); 12. k = k/10; 13. q=q+1; 14. if(q=size of each partition) 15. { 16. L = L + p; 17. p = 0; 18. q = 0; 19. } 20. } 21. if(length of L > size of each partition) 22. Discard the MSB from L. 23. return L 24. }// End of algorithm

14.4 Collision
If two keys k1 and k2 produce same bucket index L for some hash function, then it is called collision. To resolute the collision different collision resolution techniques are available. The collision resolution techniques can be classified into two broad categories: 1. Open addressing 2. Chaining They are discussed in next section in details.

14.5 Clustering
Clustering occurs when keys group together in the hash table and many collisions. Clustering causes collision and collisions make the clustering problem worse.

14.6 Collision resolution techniques


1. Open addressing: The general procedure for open addressing can be stated as: i) All keys are stored in hash table only. ii) When collisions occur, use a systematic (consistent) procedure to store elements in free slots of the hash table. Depending on the systematic procedure open addressing can be of the following types: A) Linear probing: Suppose two keys k1 and k2 yield same memory location (or bucket index) L. H(k1) = L and H(k2) = L Now to resolve this collision, search the memory for an available place like: L, L+1 L + i . Store the key in the first available place. Example 14.4: Consider a hash table with 7 slots and the keys are 49, 123, 68, and 90. Suppose we are using division method to produce bucket index and we are taking the value of M = 7. The hash table is circular i.e. bucket index 0 comes after bucket index 6. Collision resolution technique is linear probing. Now, H(49) = 0 Store 49 at 0th location in hash table. H(123) = 5 Store 124 at 5th location in hash table. H(68) = 5 Cell 5 in hash table is already occupied. So check next cell. Cell 6 is empty. Store 68 in 6th cell. H(90) = 5 Cell 5, 6 and 0 are already occupied. So store 90 at cell 1 in the hash table. The hash table will look like:

Division method key 49 123 68 90 value 0 5 5 5

Hash table (array of buckets) 49 90

0 1 2 3 4 5 6

123 68

B) Quadratic probing: Suppose two keys k1 and k2 yield same memory location (or bucket index) L. H(k1) = L and H(k2) = L Now to resolve this collision, search the memory for an available place like: L, L+12, L+22 L + i2 . Store the key in the first available place. Example 14.4: Consider a hash table with 7 slots and the keys are 49, 123, 68, and 90. Suppose we are using division method to produce bucket index and we are taking the value of M = 7. The hash table is circular i.e. bucket index 0 comes after bucket index 6. Collision resolution technique is quadratic probing. Now, H(49) = 0 Store 49 at 0th location in hash table. H(123) = 5 Store 124 at 5th location in hash table. H(68) = 5 Cell 5 in hash table is already occupied. So check 5+12 cell. Cell 6 is empty. Store 68 in 6th cell H(90) = 5 Cell 5 and 5 + 12 are already occupied. Now check cell 5 + 22. As the list is circular 5+ 22 or 9 is interpreted as cell 2 (9%7). Cell 2 is empty. So store 90 at cell 2 in the hash table. The hash table will look like: Division method key 49 123 68 90 value 0 5 5 5 Hash table (array of buckets) 49

0 1 2 3 4 5 6

90

123 68

C) Double hashing: Here a second hash function is used to resolve the collisions. If two keys k1 and k2 yield the same memory location, then search the memory linearly for an available place like: L, L+H, L+2H..L + iH Store the key at the first available memory location. D) Rehashing: If at any stage the hash table becomes full or overflow then it will be very difficult to find the free slot for new key. In such a situation create a new hash table which is double in length than the previous one. Then scan the previous hash table and for each key calculate the new bucket index and store them into newly created hash table. 2. Chaining: The general procedure for chaining is: i) Store all elements that hash to the same slot in a linked list. ii) Store a pointer to the head of the linked list in the hash table slot. Example 14.5: Consider a hash table with 7 slots and the keys are 49, 123, 68, and 87. Suppose we are using division method to produce bucket index and we are taking the value of M = 7. The hash table is circular i.e. bucket index 0 comes after bucket index 6. Collision resolution technique is chaining. Now, H(49) = 0 Store 49 at 0th location in hash table. H(123) = 5 Store 124 at 5th location in hash table. H(68) = 5 Cell 5 in hash table is already occupied. So check 5+12 cell. Cell 6 is empty. Store 68 in 6th cell. H(87) = 3 Cell 5 and 5 + 12 are already occupied. Now check cell 5 + 22. As the list is circular 5+ 22 or 9 is interpreted as cell 2 (9%7). Cell 2 is empty. So store 90 at cell 2 in the hash table. The hash table will look like: Division method key 49 123 68 87 value 0 5 5 3 Hash table (array of addresses) 49 N

0 1 2 3 4 5 6

87

123

68

Comparison study of open addressing and chaining: Collision resolution technique Open addressing Advantages 1. Save space since we dont have pointers. 2. Fast rehashing. 3. Fast access through use of main Disadvantages 1. Clustering problem. 2. Priory knowledge is required about maximum number of elements.

Chaining

1. 2. 3. 4.

table space. Unlimited number of elements Unlimited number of collisions No clustering problem. Deletion is simpler

3. Multiple collisions may occur. Overhead of multiple linked lists.

14.7 Load factor


Suppose there are n items in the bucket and bucket size is m. Then the load factor can be defined as Load factor = occupied space/total space = n/m. =0 indicates an empty bucket. = 0.5 signifies that the bucket is half full. Similarly =1 means full bucket. For open addressing can never exceed 1, but for chaining there is no bar on the value of .

14.8 Analysis of open addressing and chaining


In our analysis, we will count the number of probes (or search) required for a successful search and the number of probes (or search) required for an unsuccessful search. U() = number of probes required for an unsuccessful search. S() = number of probes required for a successful search. The probability (P(h)) that a probe hits an occupied space is and the probability (P(m)) that a probe hits an empty space is 1- . P(h) = and P(m) = 1- Now the probability that one probe is required for an unsuccessful search is (1- ). The probability that the unsuccessful search terminates after two probes is (1- ). Similarly the probability that the exactly k probes are required for an unsuccessful search is k-1(1- ). For linear probing, the average number of probe required for an unsuccessful search is: U() = 1/2(1+1/(1- )2) And for a successful search, average number of probe is: S() = 1/2(1+1/(1- )) For chaining, the average numbers of probes required for an unsuccessful search and for a successful search are approximately: U() = e- + and S() = 1 + /2 /* Program to implement some hash functions */ /* File Name: Hash_Function.C */ #include<stdio.h> #include<conio.h> #include<math.h> int fnDivision(int k, int M) // Purpose : This algorithm produces the bucket index for a key using division method. // Input: The key k and the prime number M. // Output : The bucket address L. { int L; L = k%M; //Get the remainder. return L; }// End of algorithm int fnMidSquare(int k, int b1, int b2)

// Purpose : This algorithm produces the bucket index for a key using middle square method. // Input: k is the key and from b1 to b2 bits are to be extracted from k2. // Output : The bucket index L. { int k1,L; k1 = (k*k)/ fnpow(10,(b1)); //Discard the left most bits and store the number in k1. L = k1 % fnpow(10,(b2-b1+1)); //Extract leftmost (b2-b1) bits from k1. return L; }// End of algorithm int fnpow(int x, int y) { int i,x1=1; for(i=1;i<=y;i++) x1=x1*x; return x1; } void main(void) { clrscr(); printf("Division Method = %d",fnDivision(20,9)); printf("Mid Square of %d = %d",99*99,fnMidSquare(99,2,2)); getch(); }

14.9 MCQ Chapter 14


1. Consider the figure below 0 S7 1 S1 2 3 S4 4 S2 5 6 S5 7 8 S6 9 S3 A hash table with 10 buckets with one slot per bucket is depicted in the above Fig. The symbols, S 1 to S7 are initially entered using a hashing function with linear probing. The maximum number of comparisons needed in searching an item that is not present is (a) 4 (b) 5 (c) 6 (d) 3 2. A hash function f defined as f (key) = key mod 7, with linear probing, is used to insert the keys 37, 38, 72, 48, 98 , 11, 56, into a table indexed from 0 to 6. What will be the location of key II? (a) 3 (b) 4 (c) 5 (d) 6

3. The average search time of hashing, with linear probing will be less if the load factor (a) is far less than one (b) equals one (c) is far greater than one (d) none of the above 4. A hash table can store a maximum of 10 records. Currently there are records in locations 1,3, 4, 7, 8, 9, 10. The probability of a new record going into location 2, with a hash function resolving collisions by linear probing is (a) 0.6 (b) 0.1 (c) 0.2 (d) 0.5 5. Consider a hashing function that resolves collision by quadratic probing. Assume the address space is indexed from 1 to 8. Which of the following locations will never be probed if a collision occurs at position 4? (a) 4 (b) 5 (c) 8 (d) 2 6. A hash table has space for 100 records. What is the probability of collision before the table is 10% full? (a) 0.45 (b) 0.5 (c) 0.3 (d) 0.34 (approximately) 7. A hash function randomly distributes records one by one in a space that can hold x number of records. The probability that the mth record is the first record to result in collision is (a) (x-1) (x-2)...(x-(m-2))(m-l) / xm-l (b) (x-1) (x-2)... (x-(m-l))(m-l) / xm-l (c) (x-1) (x-2)... (x-(m-2))(m-l) / xm (d) (x-1) (x-2)... (x-(m-l))(m-l) / xm 8. If the hashing function is the remainder on division, then clustering is more likely to occur if the storage space is divided into 40 sectors rather than 41. This conclusion is (a) more likely to be false (b) more likely to be true (c) is always false (d) none of the above

Solutions:
1. b 2. c 3. a 4. a 5. d 6. a 7. a 8. b

14.5 Exercise Chapter 14


1. What is hashing? Define hash table and explain by a suitable diagram. State the principles of hash table. 2. Define hash function. State different types of hash functions, give their algorithms and explain them by suitable diagrams. 3. What are collision and clustering? What is collision resolution technique? Compare between different types of collision resolution technique. 4. Discuss different types of collision resolution technique with suitable diagrams. 5. What is load factor? Analyse different types of collision resolution technique with help of load factor.

CHAPTER 15 FILES
Introduction
In our present banking systems only ATM card is sufficient to operate all operations of an account like (i) To see the balance (ii) To withdraw amount (iii) To generate mini statement to see previous transactions both deposit and withdrawal etc. . But one can have access if ATM card is placed on an ATM machine and with typing the proper password. Thus no paper, no cheque book etc are required and at the same time no intervention and permission of bank officials are also required for operating a bank account. This is possible only because all data are stored electronically in non volatile ( Tape, Disk, Drum, CD, DVD etc.) devices with the help of computers and brought back the same when necessary. Not only in banking but all most all spheres of life nowadays have been driven by computers, where storing of data, program etc. are very essential for future and repeated usage, which leads to evolve various areas of computer sc. like Data Base Management System, Data Mining, Image Processing, Bio-Informatics etc. In context of data structure we will discuss very limitied types of files and mechanism how to handle these files for various operations.

15.2 Definition
(1) Collection of data in non-volatile memory of a computer. (2) Collection of related records in non-volatile memory of a computer. The definition (1) suggests to put any characters, numbers and symbols to put together and to store in secondary storage device with a specific name. To retriev back the same in future with supply of the

specified name the computer will display the content i.e. what was stored in the secondary storage for the same. This type of file is called non-structured file, i.e. no particular structure or the format is required to store data in such type of file. The usage of such file is generally in the area of text processing, image processing and in program development. But the essential areas of computing like Banking, Hospitals, Insurance and Universisties need data to be stored in secondary storage device in the structured format for processing. Where the basic format is record, i.e. each data should belong to a particular heading termed as fields and when a group of headings are defined together for a particular entity or things a record structure is created. Whenever all the relevant data are written under the corresponding headings or fields a record is created and when all the possible records are collected together a structured file is formed. e.g. an account of any bank is represented as 100100 Sourav Ganguly 25000

where the first number is account number, the second one is name and the third one is balance, like wise all the accounts of the bank could be represented as File: Account Account Number 100100 100101 100102 Name Sourav Ganguly Ashoke Mukherjee Suman Gupta Balance 25000 12000 13000

15.3 File Classification:

Non Structured file:


Text File A text file is a kind of computer file that is structured as a sequence of lines. A text file exists within a computer file system. The end of a text file is denoted by placing one or more special characters, known as an end-of-file marker, after the last line in a text file. (.txt) is a filename extension for files consisting of text usually contain very little formatting (ex: only bolding or italics). The precise definition of the .txt format is not specified, but typically matches the format accepted by the system terminal or simple text editor. Files with the .txt extension can easily be read or opened by any program that reads text and, for that reason, are considered universal (or platform independent).

The ASCII character set is the most common format for English-language text files, and is generally assumed to be the default file format in many situations. For accented and other non-ASCII characters, it is necessary to choose a character encoding. In many systems, this is chosen on the basis of the default locale setting on the computer it is read on. Common character encodings include ISO 8859-1 for many European languages. Because many encodings have only a limited repertoire of characters, they are often only usable to represent text in a limited subset of human languages. Unicode is an attempt to create a common standard for representing all known languages, and most known character sets are subsets of the very large Unicode character set. Although there are multiple character encodings available for Unicode, the most common is UTF-8, which has the advantage of being backwards-compatible with ASCII: that is, every ASCII text file is also a UTF-8 text file with identical meaning. System File A system file is a computer file important to the operating system. More specifically, it refers to the files which are used to build the operating system unning on a computer system. Examples of system files are: command file, Executable file, Dynamic Link laboratory file etc. Command File (.COM): The command file having file name extension (.com) has been used in various computer systems for different purposes. Originally, the term stood for "Command file" and was a text file containing commands to be issued to the operating system. In MS-DOS and compatible DOSes, and in 8-bit CP/M, a COM file is a simple type of executable file. The COM format is perhaps the simplest executable format of all; it contains no metadata, only code and data, and is loaded at offset 0x0100 of some segment of memory and executed. Because of how the segmentation model works, there is no need for relocation. In Intel 86(Early version of x86) CPU architecture, only 65,536 bytes of memory could be addressed (address range 0x0000 to 0xFFFF). Under CP/M, the first page of this memory, from 0x0000 to 0x00FF was reserved for system use, and any user program had to be loaded at exactly 0x0100 to be executed. COM files fit this model perfectly. Note that there was no possibility of running more than one program or command at a time: the program loaded at 0x0100 was run, and no other. Although the file format is the same in MS-DOS and CP/M, this does not mean that CP/M programs can be directly executed under MS-DOS or vice versa; MS-DOS COM files contain x86 instructions, while CP/M COM files contain 8080, 8085 or Z80 instructions. Additionally, MS-DOS COM files often depend on operating system traps supplied exclusively by MS-DOS via interrupt 21h. It is possible to construct a fat COM file which both processor families can execute. Files may have names ending in .COM, but not be in the simple format described above; this is indicated by a magic number at the start of the file. For example, the COMMAND.COM file in DR-DOS 6 is actually in DOS executable format, indicated by the first two bytes being MZ (0x4D 0x5A). Under CP/M 3, if the first byte of a COM file is 0xC9 then this indicates the presence of a 256-byte header; since 0xC9 corresponds to the 8080 instruction RET, this means that the COM file will immediately terminate if run on an earlier version of CP/M that does not support this extension. Executable File (.EXE) EXE is the common filename extension denoting an executable file (a program) in the OpenVMS, DOS, Microsoft Windows, ReactOS, and OS/2 operating systems. Besides the executable program itself, many EXE files contain other components called resources, such as bitmaps and icons which the executable program may use for its graphical user interface. The DOS executable file format differs from the COM executable, which is limited to slightly less than 64 KB in size and since it lacks relocation information, can only contain one code segment. The DOS executable header contains such relocation information, which allows multiple segments to be loaded at arbitrary memory addresses, and support executables larger than 64 KB. There are several main EXE file formats. (i) DOS executable: These can only be identified by the ASCII string "MZ" or the hexadecimal 4D 5A at the beginning of the file (the "magic number"), although, in fact, "ZM" or the hexadecimal 5A 4D works as well.

These executables can be run from DOS, and most Windows versions can execute them using a sort of emulation. (ii) 16-bit New Executable: Introduced with Multitasking MS-DOS 4.0, these can be identified by the "NE" in ASCII. These cannot be run by any other version of DOS but can be run by all Windows and OS/2 versions. (iii) Mixed 16/32-bit Linear Executable: Introduced with OS/2 2.0, these can be identified by the "LE" in ASCII. This format is not used for OS/2 applications anymore, but instead for VxD drivers under Windows 3.x and Windows 9x, and by some DOS extenders. (iv) 32-bit Linear Executable: Introduced with OS/2 2.0, these can be identified by the "LX" in ASCII. These can only be run by OS/2 2.0 and higher. They are also used by some DOS extenders. (v) 32-bit Portable Executable: Introduced with Windows NT, these are the most complex and can be identified by the "PE" in ASCII. These can be run by all versions of Windows NT, and also Windows 95 and higher, partially also in DOS using HX DOS Extender. They are also used in BeOS R3, however the format used by BeOS somewhat violates the PE specification as it doesn't specify a correct subsystem. (vi) 64-bit Portable Executable: Introduced by 64-bit versions of Windows, these are PE files with a CPU type corresponding to a 64-bit instruction set such as x86-64 or IA-64. These can only be run by 64-bit editions of Microsoft Windows, such as Windows XP 64-Bit Edition or Windows Server 2003 64-Bit Edition, running on machines with the CPU type specified in the file. Dynamic Link Laboratory File(.DLL, .OCX, .DRV) Dynamic-link library file or DLL file, is Microsoft's implementation of the shared library concept in the Microsoft Windows and OS/2 operating systems. These libraries usually have the file extension DLL, OCX (for libraries containing ActiveX controls), or DRV (for legacy system drivers). The file formats for DLLs are the same as for Windows EXE files that is, Portable Executable (PE) for 32-bit Windows, and New Executable (NE) for 16-bit Windows. As with EXEs, DLLs can contain code, data, and resources, in any combination. It can also have icon libraries, sometimes having the extension ICL, and font files, having the extensions FON and FOT.

Structured File
Sequential File Definition: When records are stored in seceondary storage media as per the ordering of a search key field of every records. Example: file: ClassGrade Roll No. 001 002 003 Name Sourav Ganguly Ashoke Mukherjee Suman Gupta Grade A B B

The file ClassGrade is a sequential file where each record is ordered on Roll No. Realization of Sequential file in Computer: Though to create and to access any data file programming is required in any language but the underlying operating system(OS) has only the low level system calls to create and to access files in secondary storage devices, since OS maintains blocks, clusters, sectors etc. in file allocation table for providing spaces to create any data file. There are following things to be remembered always to work with file. 1) First of all a file must be opened in workspace for either putting data into it or to retrieve data out from the file. There are following modes on which a file can be opened. i) Mode: Output: Which is essential to open a file at first time creation.

ii) iii)

Mode: Input: Which is essential to open a file for retrieval of data. Mode: Input/Output : Which is essential for updation purpose.

Once a data file has been created with Output mode and dumping of reords on it have been done the file must be closed. After closing the file if by mistake the file is reopened in Output mode all the data written earlier will be lost. Thus, to access next the Input mode is essential. In C language the modes are represented as: (i) Output: R (Read only) (ii) Input: A (Append only), W (Write only) (iii) Input/ Output:W+ (Read and write), R+ (Read and write), A+ (Append and write) Secondly, in C language File Pointer is very essential to keep track of positions of records as bellow: Address 2200 2201 2202 2203 Record A A D T Pointer 2203 EOF 2200 2201

File pointer Start(BOF)

Index Sequential File The retrieval of a record from a sequential file, on average, requires traverse of half of the total file. This makes the enquiries not only inefficientbut very time consuming for a large file. To improve this, a type of indexing technique ia added to sequential file and is named as index sequential file. Definition: A sequential file that is indexed for faster search is called index sequential file. Example: Index for starting page numbers of a dictionary file Words Starting with letters A B C D X Y Z Page # 3 42 86 158 807 809 812

An index is a set of <key, address> pairs. Indexing associates a set of objects to a set of ordered quantities, which are usually smaller in number, giving us a mechanism to search faster.Indexes are created from a sequential or sorted set of primary keys from a sequential file. The indexes provides random access to the record fields of the file, while the sequential nature of the file provides easy access to the subsequent records as well as sequential processing. Index can be of three types: (iv) Implicit Index: (v) Limit Index (vi) Multilevel Index

15.4 Example Programs:


15.4.1 Non Structured file (Read and Write a Text File) #include<stdio.h> #include<conio.h> void fnReadFile(FILE*); void fnWriteFile(FILE*); void main(void) { FILE *fp1,*fp2; int Choice; clrscr(); while(1) { printf("\nMENU"); printf("\n1. Read from file"); printf("\n2. Write into file"); printf("\n3. Exit"); printf("\nEnter your choise"); scanf("%d",&Choice); switch(Choice) { case 1: fp1=fopen("test1.txt","r"); fnReadFile(fp1); fclose(fp1); break; case 2: fp1=fopen("test1.txt","a+"); fnWriteFile(fp1); fclose(fp1); break; case 3: exit(); } } getch(); } void fnReadFile(FILE *Source) { char c; while((c=getc(Source))!=EOF) printf("%c",c); } void fnWriteFile(FILE *Source) { char c; while((c=getche())!='q') putc(c,Source);

} Structured File (Read and Write a Sequential File) #include<stdio.h> #include<conio.h> struct student { int iRoll; char cName[50]; char cStream[20]; }; typedef struct student student; void fnReadFile(FILE*); void fnWriteFile(FILE*); void main(void) { FILE *fp1,*fp2; int Choice; clrscr(); while(1) { printf("\nMENU"); printf("\n1. Read from file"); printf("\n2. Write into file"); printf("\n3. Exit"); printf("\nEnter your choise"); scanf("%d",&Choice); switch(Choice) { case 1: fp1=fopen("test1.txt","r"); fnReadFile(fp1); fclose(fp1); break; case 2: fp1=fopen("test1.txt","a+"); fnWriteFile(fp1); fclose(fp1); break; case 3: exit(); } } } void fnReadFile(FILE *Source) { student s; while(fread(&s,sizeof(student),1,Source)) printf("\n%d\t%s\t%s",s.iRoll,s.cName,s.cStream); } void fnWriteFile(FILE *Source)

{ student s; printf("\nEnter Student's roll, name and stream"); fflush(stdin); scanf("%d",&s.iRoll); fflush(stdin); gets(s.cName); fflush(stdin); gets(s.cStream); fwrite(&s,sizeof(student),1,Source); }

15.4 MCQ Chapter 15


1. Six files F1, F2, F3 , F4, F5 and F6 have 100, 200, 50, SO, 120, 150 number of records respectively. In what order should they be stored so as to optimize access time? Assume each file is accessed with the same frequency. (a) F3, F4, F1, F5, F6, F2 (b) F2, F.6, F5, F1, F4, F3 (c) F1, F2, F3, F4, F5, F6 (d) Ordering is immaterial as all files are accessed with the same frequency. 2. Pick the correct statements. (a) Sequential file organization is suitable for batch processing. (b) Sequential file organization is suitable for interactive processing. (c) Indexed sequential file organization supports both batch and interactive processing. (d) Relative file can't be accessed sequentially. 3. Which of the following file organizations is preferred for secondary key processing? (a) Indexed sequential file organization (b) Two-way linked list (c) Inverted file organization (d) Sequential file organization

Solutions:
1. a 2. a,c 3. c

Exercise Chapter 15
1. Discuss the differences between the following file organizations: (a) (i) Sequential (ii) Index sequential (b) (i) Text (ii) System (c) (i) Command file (ii) Executable file Compare their storage and access efficiencies. State one applications o each file organisations. 2. What is index sequential file organisations? What are the advantages and disadvantages of index sequential files?

3. Write a C program to append some text after rhe contents of a simple text file text.txt. 4. Give algorithms for insertion and deletion records from a Sequential file. 5. What is index? What are the various types of indexing? State the advantages of using indexing over a sequential file. Can this indexing be used in non structured file?

VARIOUS QUESTIONS ON DATA STRUCTURE AND ALGORITHM

(As because size of interger datatype in different C compiler is different, we have used size_t instead of int datatype. For simplicity Let size_t as int datatype with 4 byte size wherever mentioned) 1. Describe one good method for precisely specifying what a function must do, without indicating how the function accomplishes its work. Provide an example, using a small function. 2. What is a precondition? What is a postcondition? 3. It's recommended that the precondition be checked at the start of a function. What's a good approach if a function discovers that its precondition is not true? 4. Is it always possible for a function to check that its precondition is true? 5. Suppose that you accidently call a correctly implemented function, but the precondition is false. Is the function guaranteed to halt with a nice message? Is the function allowed to erase everything on your hard drive? 6. Write the first few lines of this function so that it uses the assert facility to check its precondition: void exam(int i) // Precondition: i is not equal to 42. ... 7. Give a concise formula that gives the approximate number of digits in a positive integer. The integer is written in base 10. 8. Why is the order of an algorithm generally more important than the speed of the processor? 9. Convert each time formula to the best possible big-O notation. Do not include any spurious constants in your big-O answer. Time Formula 10n 2n 3 times log (base 2) of n 2n + 10n 10. Write the simplest big-O expression to describe the number of operations required for the following algorithm: for (i = 1; i < N; ++i) { ...statements that require exactly i operations... } 11. You are at a computer science cocktail party and you meet a student who has just started working with a debugger. With about three or four sentences, explain the basic features of your debugger and how they help you find bugs. 12. Suppose that p is an int variable. Write several lines of code that will make p point to an array of 100 integers, place the numbers 0 through 99 into the array components, and return the array to the heap. Do not use malloc. 13. What happens if you call new but the heap is out of memory?

14. Draw a picture of memory after these statements: int i = 42; int k = 80; int* p1; int* p2; p1 = &i; p2 = &k; 15. Suppose that we execute the statements from the previous question, and then we execute these statements: *p1 = *p2; Draw a picture of memory after this additional statement. 16. Draw a picture of memory after these statements: int i = 42; int k = 80; int* p1; int* p2; p1 = &i; p2 = &k; p1 = p2; 17. Here is a function prototype: void exam(const double data[]); Write two or three sentences to tell all the information that you know about the parameter. 18. Consider the following prototype: function foo (const int * p); What restriction does the const keyword provide within the implementation of foo? 18. Consider a bag structure that stores the items in a dynamic array. Use an example with pictures to explain what goes wrong if the automatic assignment operator is used for this bag. 19. Consider the bag from Question no 18 (using a dynamic array). If the insert function is activated, and the bag is full, then the bag is resized with the statement: reserve(used + 1); Describe a problem with this approach, and provide a better solution. 20. Consider the bag from Question no 18 (using a dynamic array). The pointer to the dynamic array is called data, and the size of the dynamic array is stored in a private member variable called capacity. Write the following new member function: void bag::triple_capacity( ) // Postcondition: The capacity of the bag's dynamic array has been // tripled. The bag still contains the same items that it previously // had. Do not use the reserve function, do not use realloc, and do not cause a heap leak. Do make sure that both data and capacity have new values that correctly indicate the new larger array. 21. Implement functions to meet the following specificaions. In each case, you should make an appropriate choice for the kind of parameter to use A. A void function with one parameter (a polynomial p). The function prints the value of p evaluated at x=1, x=2, x=3,..., up to x=10.

B. A void function with one parameter (a polynomial p). The function deletes the largest term in p, and this change should affect the actual argument. 22. This question is relevant if you implemented the polynomial class with a dynamic array. Which of the polynomial operators needed a nested loop in its implementation? 23. This question is relevant if you implemented the polynomial class with a dynamic array. Draw a picture of the values in the first five elements of your dynamic array and tell me the value of the current degree after these statements: polynomial p(3); p.assign_coef(4.1, 4); p.assign_coef(2.1, 2); 24. Suppose that a main function has executed the three statements shown in the previous problem. Give an example of one statement that can now be executed by the main function, causing the degree of p to drop to 2. 25. This question is relevant if you implemented a polynomial that included some calculus-based operations such as derivative and integral. Write a new function that meets the following specification: double slope(const polynomial& p, double x) // POSTCONDITION: The return value is equal to the slope of the // polynomial p, evaluated at the point x. 26. What is the one statement that can be used to insert a new node at the head of a linked list. Assume that the list's head_pointer is called head_ptr and the that the data for the new node is called entry. 27. Suppose that p is a pointer to a node in a linked list, and *p is not the tail node. What are the steps to removing the node after *p? Use one short English sentence for each step. 28. Suppose we are using the usual node definition (with member functions called data and link). Your program is using a node* variable called head_ptr to point to the first node of a linked list (or head_ptr == NULL for the empty list). Write a few lines of C++ code that will print all the double numbers on the list. 29. Suppose we are using the usual node definition (with member functions called data and link), and that locate_ptr is pointing to a node in a linked list. Write a statement that will make locate_ptr point to the next node in the list (if there is one). If there is no next node, then your statement should set locate_ptr to NULL. 30. Implement the following function as a new function for the linked list toolkit. (Use the usual node definition with member variables called data and link.) size_t count_42s(const node* head_ptr); // Precondition: head_ptr is the head pointer of a linked list. // The list might be empty or it might be non-empty. // Postcondition: The return value is the number of occurrences // of 42 in the data field of a node on the linked list. // The list itself is unchanged. 31. Implement the following function as a new function for the linked list toolkit. (Use the usual node definition with member variables called data and link.) bool has_42(const node* head_ptr); // Precondition: head_ptr is the head pointer of a linked list. // The list might be empty or it might be non-empty. // Postcondition: The return value is true if the list has at least // one occurrence of the number 42 in the data part of a node.

32. Implement the following function as a new function for the linked list toolkit. (Use the usual node definition with member variables called data and link.) bool all_42s(const node* head_ptr); // Precondition: head_ptr is the head pointer of a linked list. // The list might be empty or it might be non-empty. // Postcondition: The return value is true if every node in the // list contains 42 in the data part. NOTE: If the list is empty, // then the function returns true. 33. Implement the following function as a new function for the linked list toolkit. (Use the usual node definition with member variables called data and link. The data field is an int.) int sum(const node* head_ptr); // Precondition: head_ptr is the head pointer of a linked list. // The list might be empty or it might be non-empty. // Postcondition: The return value is the sum of all the data components // of all the nodes. NOTE: If the list is empty, the function returns 0. 34. Implement the following function as a new function for the linked list toolkit. (Use the usual node definition with member variables called data and link. The data field is an int.) int product(const node* head_ptr); // Precondition: head_ptr is the head pointer of a linked list. // The list might be empty or it might be non-empty. // Postcondition: The return value is the product of all the data components // of all the nodes. NOTE: If the list is empty, the function returns 1. 35. Implement the following function as a new function for the linked list toolkit. (Use the usual node definition with member variables called data and link.) void list_tail_insert(node* head_ptr, const node::value_type& entry); // Precondition: head_ptr is the head pointer of a non-empty // linked list. // Postcondition: A new node has been added at the tail end // of the list. The data in the new node is taken from the // parameter called entry. 36. Implement the following function as a new function for the linked list toolkit. (Use the usual node definition with member variables called data and link.) bool data_is_on(const node* head_ptr, const node* p); // Precondition: head_ptr is the head pointer of a linked list // (which might be empty, or might be non-empty). The pointer p // is a non-NULL pointer to some node on some linked list. // Postcondition: The return value is true if the data in *p // appears somewhere in a data field of a node in head_ptr's // linked list. Otherwise the return value is false. // None of the nodes on any lists are changed. 37. Implement the following function as a new function for the linked list toolkit. (Use the usual node definition with member variables called data and link.) bool is_on(const node* head_ptr, const node* p); // Precondition: head_ptr is the head pointer of a linked list // (which might be empty, or might be non-empty). The pointer p // is a non-NULL pointer to some node on some linked list.

// Postcondition: The return value is true if p actually points to // one of the nodes in the head_ptr's linked list. For example, // p might point to the head node of this list, or the second node, // or the third node, and so on. Otherwise the return value is // false. None of the nodes on any lists are changed. 38. Implement the following function as a new function for the linked list toolkit. Do not call any of the other toolkit functions from within your implementation. (Use the usual node definition with member variables called data and link.) void list_insert_zero(node* previous_ptr); // Precondition: previous_ptr is a pointer to a node on a linked list. // Postcondition: A new node has been added to the list after // the node that previous_ptr points to. The new node contains 0. 39. Suppose that p, q, and r are all pointers to nodes in a linked list with 15 nodes. The pointer p points to the first node, q points to the 8th node, and r points to the last node. Write a few lines of code that will make a new copy of the list. You code should set THREE new pointers called x, y, and z so that: x points to the first node of the copy, y points to the 8th node of the copy, and z points to the last node of the copy. Your code may NOT contain any loops, but it can use the toolkit functions. 40. Suppose that a_ptr and b_ptr are node pointers. Write one clear sentence to tell me when the expression (a_ptr==b_ptr) will be true. 41. Compare the worst-case big-O time analysis for these two functions: The insert function for the bag that is implemented using a fixed-sized array, and the insert function for the bag that is implemented using a linked list. 42. Compare the worst-case big-O time analysis for these two functions: The erase_one function for the bag that is implemented using a fixed-sized array, and the erase_one function for the bag that is implemented using a linked list. 43. Tell about one of the sequence operations that is more efficient because the class keeps a tail pointer as a private member variable. Provide a specific example showing why the operation would be less efficient without the tail pointer. 44. Tell about one of the sequence operations that is easier to program because the class keeps a precursor (rather than just a cursor). Provide a specific example showing why the operation would be harder to program without the precursor. 45. Compare the worst-case big-O time analysis for these two functions: The insert function for the sequence that is implemented using a fixed-sized array, and the insert function for the sequence that is implemented using a linked list. 46. Compare the worst-case big-O time analysis for these two functions: The remove function for the sequence that is implemented using a fixed-sized array, and the remove function for the sequence that is implemented using a linked list. 47. Describe a situation where storing items in an array is clearly better than storing items on a linked list. 48. Consider the code below which is supposed to shift the values from data[0] through data[n-1] rightward one position (so these values now reside in data[1] through data[n]).

size_t i; for (i = 0; i < n; ++i) data[i+1] = data[i]; There is a bug in the above code. Write one sentence to describe the bug and write new code that does the job correctly. 49. Explain what modifications would be needed to make the parenthesis matching algorithm check expressions with different kinds of parentheses such as (), [] and {}'s. 50. Complete the body of this function. You do not need to check the precondition. You may use the stack template class. bool balanced(const char p[ ], size_t n) // Precondition: p[0]...p[n-1] contains n characters, each of which // is '(', ')', '{' or '}'. // Postcondition: The function returns true if the characters form a // sequence of correctly balanced parentheses with each '(' matching // a ')' and each '{' matching a '}'. Note that a sequence such as // ( { ) } is NOT balanced because when we draw lines to match the // parentheses to their partners, the lines cross each other. On the // other hand, ( { } ) amd { ( ) } are both balanced. 51. The following code is going to be executed with THREE pushes and ONE pop: stack<int> s; s.push(1); s.push(2); s.push(3); s.pop( ); Suppose that s is represented by a partially filled array. Draw the state of the elements of s after the above code: 52. Suppose that s in te above question is represented by a linked list. Draw the state of the private member variables of s after the above code: 53. Implement the following function. You may use the stack template class and the stack operations of push, pop, peek, is_empty, and size. You may also use cin.peek( ) and use "cin>>i" to read an integer. int evaluate_postfix_from_cin( ) // Precondition (Which is not checked): The next input line of cin is a // properly formed postfix expression consisting of integers, // the binary operations + and -, and spaces. // Postcondition: The function has read the next input line (including // the newline) and returned the value of the postfix expression. { int i; stack<int> s; 54. Complete the body of this function. Use a queue of characters to store the input line as it is being read. size_t counter( ) // Precondition: There is a line of input waiting to be read from cin.

// Postcondition: A line of input has been read from cin, up to but not // including the newline character. The return value of the function // is the number of times that the LAST character of the line appeared // somewhere in this line. // EXAMPLE Input: ABBXDXXZX // The value returned by counter would be 4 for this input since there // are 4 X's in the input line. { size_t answer = 0; queue q; 55. I am going to execute this code with THREE inserts and ONE get_front: queue<int> s; s.insert(1); s.insert(2); s.insert(3); s.get_front( ); Suppose that s is represented by a circular array. Draw the state of the elemets of s after the above code: 56. I am going to execute this code with THREE insert and ONE get_front: queue<int> s; s.insert(1); s.insert(2); s.insert(3); s.get_front( ); Suppose that s is represented by a linked list. Draw the state of the elemets of s after the above code: 57. Describe why it is a bad idea to implement a linked list version a queue which uses the head of the list as the rear of the queue. 58. Write a recursive function that has one parameter which is a size_t value called x. The function prints x asterisks, followed by x exclamation points. Do NOT use any loops. Do NOT use any variables other than x. 59. Implement the following function. Do not use any local variables or loops. void pattern(unsigned int n) // Precondition: n > 0; // Postcondition: The output consists of lines of integers. The first line // is the number n. The next line is the number 2n. The next line is // the number 4n, and so on until you reach a number that is larger than // 4242. This list of numbers is then repeated backward until you get back // to n. /* Example output with n = 840: 840 1680 3360 6720 6720 3360 1680 840

60. Write a recursive function with two unsigned int parameters, m and n. The precondition requires 0 <= m and m <= n. The function prints a line of m asterisks, then a line of m+1 asterisks, and so on up to a line of n asterisks. Then the same pattern is repeated backward: a line of n asterisks, then n-1, and so on down to n. The only loop allowed in your implementation is a loop to print a line of m asterisks. You may have two copies of this loop in different places of the implementation. 61. This question involves a game with teddy bears. The game starts when I give you some bears. You can then give back some bears, but you must follow these rules (where n is the number of bears that you have): If n is even, then you may give back exactly n/2 bears. If n is divisible by 3 or 4, then you may multiply the last two digits of n and give back this many bears. (By the way, the last digit of n is n%10, and the next-to-last digit is ((n%100)/10). If n is divisible by 5, then you may give back exactly 42 bears. The goal of the game is to end up with EXACTLY 42 bears. For example, suppose that you start with 250 bears. Then you could make these moves: --Start with 250 bears. --Since 250 is divisible by 5, you may return 42 of the bears, leaving you with 208 bears. --Since 208 is even, you may return half of the bears, leaving you with 104 bears. --Since 104 is even, you may return half of the bears, leaving you with 52 bears. --Since 52 is divisible by 4, you may multiply the last two digits (resulting in 10) and return these 10 bears. This leaves you with 42 bears. --You have reached the goal! 62. Write a recursive function to meet this specification: bool bears(int n) // Postcondition: A true return value means that it is possible to win // the bear game by starting with n bears. A false return value means that // it is not possible to win the bear game by starting with n bears. // Examples: // bear(250) is true (as shown above) // bear(42) is true // bear(84) is true // bear(53) is false // bear(41) is false // Hint: To test whether n is even, use the expression ((n % 2) == 0). 63. Write a recursive function with this prototype: void permute(string first, string second) // Postcondition: The output consists of lines of Strings. Each String // is made by writing some rearrangement of first followed by all // of second. // For example, if first is "CAT" and second is "MAN", then there will // be six lines of output: // CATMAN // ACTMAN // TCAMAN // CTAMAN // ATCMAN // TACMAN Hints: The stopping case occurs when the length of first is zero (in which case the function prints second). Some string member functions that will help you for a String s:

s[s.length( ) - 1] is a copy of the last character of s. s.length( ) is the length of s. cout << s << endl; will print s. s = c + s; will insert the character c at the front of s. s += c; will append the character c to the end of s. s.erase(0, 1); will remove one character from the front of s. s.erase(s.length( )-1, 1) will remove the last character from s. 64. Write a function with this prototype: void numbers(ostream& outs, const string& prefix, unsigned int levels); The function prints output to the ostream outs. The output consists of the string prefix followed by "section numbers" of the form 1.1., 1.2., 1.3., and so on. The levels argument determines how may levels the section numbers have. For example, if levels is 2, then the section numbers have the form x.y. If levels is 3, then section numbers have the form x.y.z. The digits permitted in each level are always '1' through '9'. As an example, if prefix is the string "THERBLIG" and levels is 2, then the function would print 81 strings. In this case, the function starts by printing: THERBLIG1.1. THERBLIG1.2. THERBLIG1.3. and ends by printing: THERBLIG9.7. THERBLIG9.8. THERBLIG9.9. The stopping case occurs when levels reaches zero (in which case the prefix is printed once by itself followed by nothing else). 65. Write a function with one positive int parameter called n. The function will write 2^n-1 integers (where ^ is the exponentiation operation). Here are the patterns of output for various values of n: n=1: Output is: 1 n=2: Output is: 1 2 1 n=3: Output is: 1 2 1 3 1 2 1 n=4: Output is: 1 2 1 3 1 2 1 4 1 2 1 3 1 2 1 And so on. Note that the output for n always consists of the output for n-1, followed by n itself, followed by a second copy of the output for n-1. 66. The first step of the maze search algorithm was to step forward and write your name on the ground. What is the importance of writing your name on the ground? 67. Consider the following function: bool dead_end() // Postcondition: The return value is true if the direction directly // in front is a dead end (i.e., a direction that cannot contain the // tapestry). // Library facilities used: useful.h (from Appendix 1). { return inquire("Are you facing a wall?") || inquire("Is your name written in front of you?"); } Explain why the function dead_end sometimes asks 2 questions and sometimes asks only 1. 68. What two properties must a variant expression have to guarantee that a recursive function terminates?

Here is a small binary tree: 14 / \ 2 11 /\ /\ 1 3 10 30 / / 7 40 Circle all the leaves. Put a square box around the root. Draw a star around each ancestor of the node that contains 10. Put a big X through every descendant of the node the contains 10. 69. Draw a full binary tree with at least 6 nodes. 70. Draw a complete binary tree with exactly six nodes. Put a different value in each node. Then draw an array with six components and show where each of the six node values would be placed in the array (using the usual array representation of a complete binary tree). 71. Write the structure for a new node definition that could be used for a node in a tree where: (1) Each node contains int data, (2) Each node has up to four children, and (3) Each node also has a pointer to its parent. Store the pointers to the children in an array of four pointers. 72. Draw a binary taxonomy tree that can be used for these four animals: Rabbit, Horse, Whale, Snake. 73. Using the binary_tree_node from the above example, write a function to meet the following specification. Check as much of the precondition as possible. No recursion is needed. void subswap(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a non-empty binary tree. // Postcondition: The original left subtree has been moved and is now the right // subtree, and the original right subtree is now the left subtree. // Example original tree: Example new tree: // 1 1 // /\ /\ // 2 3 3 2 // /\ /\ // 4 5 4 5 74. Here is a small binary tree: 14 / \ 2 11 /\ /\ 1 3 10 30 / / 7 40 Write the order of the nodes visited in: A. An in-order traversal: B. A pre-order traversal: C. A post-order traversal: 75. Using the binary_tree_node from above example, Write a recursive function to meet the following specification. You do not need to check the precondition.

void increase(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a binary tree. // Postcondition: Every node of the tree has had its data increased by one. 76. Using the binary_tree_node from the above example, write a recursive function to meet the following specification. You do not need to check the precondition. size_t many_nodes(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a binary tree. // Postcondition: The return value is the number of nodes in the tree. // NOTES: The empty tree has 0 nodes, and a tree with just a root has // 1 node. 77. Using the binary_tree_node from the above example, write a recursive function to meet the following specification. You do not need to check the precondition. int tree_depth(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a binary tree. // Postcondition: The return value is the depth of the binary tree. // NOTES: The empty tree has a depth of -1 and a tree with just a root // has a depth of 0. 78. Using the binary_tree_node from the above example, write a function to meet the following specification. You do not need to check the precondition. size_t count42(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a binary tree (but // NOT NECESSARILY a search tree). // Postcondition: The return value indicates how many times 42 appears // in the tree. NOTE: If the tree is empty, the function returns zero. 79. Using the binary_tree_node from the above example, write a function to meet the following specification. You do not need to check the precondition. bool has_42(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a binary tree (but // NOT NECESSARILY a search tree). // Postcondition: The return value indicates whether 42 appears somewhere // in the tree. NOTE: If the tree is empty, the function returns false. 80. Using the binary_tree_node from the above example, write a function to meet the following specification. You do not need to check the precondition. bool all_42(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a binary tree (but // NOT NECESSARILY a search tree). // Postcondition: The return value is true if every node in the tree // contains 42. NOTE: If the tree is empty, the function returns true. 81. Using the binary_tree_node from the above example, write a recursive function to meet the following specification. You do not need to check the precondition. int sum_all(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a binary tree. // Postcondition: The return value is the sum of all the data in all the nodes. // NOTES: The return value for the empty tree is zero.

82. Suppose that we want to create a binary search tree where each node contains information of some data type called Item (which has a default constructor and a correct value semantics). What additional factor is required for the Item data type? 83. Suppose that a binary search tree contains the number 42 at a node with two children. Write two or three clear sentences to describe the process required to delete the 42 from the tree. 84. Using the binary_tree_node from page 465, write a function to meet the following specification. You do not need to check the precondition. Make the function as efficient as possible (do not visit nodes unnecessarily): size_t count42(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a binary SEARCH tree. // Postcondition: The return value indicates how many times 42 appears // in the tree. 85. Using the binary_tree_node from the above example, write a function to meet the following specification. You do not need to check the precondition. Make the function as efficient as possible (do not visit nodes unnecessarily): int max(binary_tree_node<Item>* root_ptr) // Precondition: root_ptr is the root pointer of a nonempty binary SEARCH // tree. // Postcondition: The return value is the largest value in the tree. 86. Using the binary_tree_node from the above example, write a function to meet the following specification. You do not need to check the precondition. void insert_one_42(binary_tree_node<Item>*& root_ptr) // Precondition: root_ptr is the root pointer of a binary SEARCH tree. // Postcondition: One copy of the number 42 has been added to the binary // search tree. 87. Suppose that we want to create a heap where each node contains information of some data type called Item (which has a default constructor and a correct value semantics). What additional factor is required for the Item data type? 88. A heap is a binary tree where the entries can be compared using the usual six comparison operations (that form a total order semantics). Write the two rules that the binary tree must follow in order for the structure to actually be a heap. 89. Give two different reasons to explain why the following binary tree is not a heap: 91 / \ 77 46 / \ \ 68 81 11

90. Draw a new heap that is created by inserting 82 into the following heap: 91 / \ 77 66 / \ / \

68

1 3

11

91. Draw a new heap that is created by removing one item from the following heap: 91 / \ 77 66 / \ / \ 68 1 3 11 92. Suppose that you are performing a reheapification downward. Write a precise condition that describes the situation that causes the reheapification to stop. 93. Suppose that you are performing a reheapification upward. Write a precise condition that describes the situation that causes the reheapification to stop. 94. Suppose that a non-leaf node in a B-tree contains 42 entries. How many children does the node have? 95. Draw an example of a B-tree with four nodes and seven integer entries. The value of MINIMUM is 1 for this tree. 96. Draw a new B-tree that is created by inserting 82 into the following B-tree. For this example, the minimum number of items in each node is 1. Note that the rightmost leaf starts with two entries, 71 and 93. 56 / \ 7 66 / \ / \ 2 8 63 71 and 93 97. Draw a new B-tree that is created by deleting 63 from the following B-tree. For this example, the minimum number of items in each node is 1. Note that the rightmost leaf starts with two entries, 71 and 93. 56 / \ 7 66 / \ / \ 2 8 63 71 and 93 98. Suppose that a B-tree is declared so that MAXIMUM (the maximum number of items in a node) is 84. What is the value of MINIMUM (the minimum number of items in a non-root node)? Suppose that a B-tree is declared so that MAXIMUM (the maximum number of items in a node) is 84. Write one clear sentence to describe why each node's data array is set up to hold up to 85 items (one more than MAXIMUM). 99. Suppose that a and b are two positive integers and n is some non-negative number. Write an equation to show the relationship between log base a of n and log base b of n. Give a derivation to show that the relationship is valid. 100. Here is an array with exactly 15 elements: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Suppose that we are doing a serial search for an element. Circle any elements that will be found by examining two or fewer numbers from the array.

101. Here is an array with exactly 15 elements: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Suppose that we are doing a binary search for an element. Circle any elements that will be found by examining two or fewer numbers from the array. 102. Implement the body of the following function using a binary search of the array. You do not need to check the precondition. All your local variables must be size_t variables. bool has_42(const int data[ ], size_t n) // Precondition: The elements data[0]...data[n-1] are sorted from smallest // to largest. The value of n might be zero (indicating an empty // array). // Postcondition: A true return value indicates that the number 42 appears in // data[0]...data[n-1]. A false return value indicates that 42 doesnt appear. 103. Draw a hash table with open addressing and a size of 9. Use the hash function "k%9". Insert the keys: 5, 29, 20, 0, 27 and 18 into your table (in that order). 104. Suppose you are building an open address hash table with double hashing. The hash table capacity is n, so that the valid hash table indexes range from 0 to n. Fill in the blanks: In order to ensure that every array position is examined, the value returned by the second hash function must be ________________________ with respect to n. 105. One way to ensure this good behavior is to make n be _______________, and have the return value of the second hash function range from _________ to _________ (including the end points). 106. Draw a hash table with chaining and a size of 9. Use the hash function "k%9" to insert the keys 5, 29, 20, 0, and 18 into your table. 107. Suppose that I have the following record_type definition for a record in a hash table: struct record_type { int key; ... other stuff may also appear here ... }; The hash table uses open addressing with linear probing. The table size is a global constant called CAPACITY. Locations of the table that have NEVER been used will contain the key -1. Locations of the table that were once used but are now vacant will contain the key -2. All valid keys will be non-negative, and the hash function is: size_t hash(int key) { return (key % CAPACITY); } Complete the implementation of the following function. There is no need to check the precondition, but your code must be as efficient as possible. bool key_occurs(const record_type data[ ], int search_key) // Precondition: data[0]...data[CAPACITY-1] is an open address hash table // as described above. // Postcondition: If search_key occurs as a key of a record in the table, then // the function returns true; otherwise the function returns false. 108. Suppose that an open-address hash table has a capacity of 81 and it contains 81 elements. What is the table's load factor? (An appoximation is fine.)

109. I plan to put 1000 items in a hash table, and I want the average number of accesses in a successful search to be about 2.0 A. About how big should the array be if I use open addressing with linear probing? NOTE: For a load factor of A, the average number of accesses is generally (1+1/(1-A)). B. About how big should the array be if I use chained hashing? NOTE: For a load factor of A, the average number of accesses is generally (1+A/2). 110. Here is an array of ten integers: 5 3 8 9 1 7 0 2 6 4 Draw this array after the FIRST iteration of the large loop in a selection sort (sorting from smallest to largest). 111. Here is an array of ten integers: 5 3 8 9 1 7 0 2 6 4 Draw this array after the FIRST iteration of the large loop in an insertion sort (sorting from smallest to largest). This iteration has shifted at least one item in the array! 112. Suppose that you are writing a program that has the usual selectionsort function available: void selectionsort(int data[ ], size_t n); Your program also has an integer array called x, with 10 elements. Write two function calls: The first call uses selectionsort to sort all of x; the second call uses selectionsort to sort x[3]..x[9]. 113. Describe a case where quicksort will result in quadratic behavior. 114. Here is an array which has just been partitioned by the first step of quicksort: 3, 0, 2, 4, 5, 8, 7, 6, 9 Which of these elements could be the pivot? (There may be more than one possibility!) 115. Give a concise accurate description of a good way for quicksort to choose a pivot element. Your approach should be better than "use the entry at location [0]". 116. Give a concise accurate description of a good way for quicksort to improve its performance by using insertionsort. 117. Here is an array of ten integers: 5 3 8 9 1 7 0 2 6 4 Suppose we partition this array using quicksort's partition function and using 5 for the pivot. Draw the resulting array after the partition finishes. 118. Here is an array of ten integers: 5 3 8 9 1 7 0 2 6 4 Draw this array after the TWO recursive calls of merge sort are completed, and before the final merge step has occured. 119. Write two or three clear sentences to describe how a heapsort works. 120. Suppose that you are writing a program that has these two functions available: int compare_ints(const void* p1, const void* p2); // Precondition: p1 and p2 are really pointers to integers. // Postcondition: The return value is: // (a) negative if *p1 < *p2 // (b) zero if *p1 == *p2 // (c) positive if *p1 > *p2

void qsort( void* base, size_t n, size_t bytes, int compar(const void*, const void*) ); // Same specification as the standard library qsort function. Your program also has an integer array called x, with 10 elements. Write two function calls: The first call uses qsort to sort all of x; the second call uses qsort to sort x[3]..x[9]. 121. Suppose that you implement quicksort nonrecursively using a stack, as in your last programming assignment. You use your algorithm to sort an array of 100 items, and at the start of the final iteration of the while loop, the stack contains just two numbers: 10 (on top) and 90 (on bottom). Write one or two clear sentences to describe which parts of the array are sorted at this point, and which parts of the array remain to be sorted. 122. Draw a directed graph with five vertices and seven edges. Exactly one of the edges should be a loop, and do not have any multiple edges. 123. Draw an undirected graph with five edges and four vertices. The vertices should be called v1, v2, v3 and v4--and there must be a path of length three from v1 to v4. Draw a squiggly line along this path from v1 to v4. 124. Draw the directed graph that corresponds to this adjacency matrix: 0 1 2 3 0 | true false true false | 1 | true false false false | 2 | false false false true | 3 | true false true false | 125. Draw the edge lists that correspond to the graph from the previous question. 126. What are the benefits of using an external iterator as opposed to an internal iterator? 127. How may Djikstra's algorithm be modified to determine if it is possible to get from a given vertex to all other vertices in the graph? 128. In Djikstra's shortest path algorithm, what technique is used to choose the next vertex to process?

You might also like