DSALab UTCN
DSALab UTCN
Iosif Ignat
Preface
A collection of values that share a common set of operations is called a data type. Structured or composite data types are collections of individual data items of the same or different data types. Data structures are collections of variables, possibly of different data types, connected in various ways. High-level languages prior to Pascal usually limited their concepts of data types to those provided directly by hardware (integers, reals, double precision integers and reals, and blocks of contiguous locations). Two objects had different types if it was necessary to generate different code to manipulate them. Pascal and later languages have taken a rather different approach, based on the concept of abstract data types. An abstract data type is a programming language facility for organizing programs into modules using criteria that are based on the data structures of the program. Also, an abstract data type can be denes as a set of values and a set of procedures that manipulate those values. The specication of the module should provide all information required for using the type, including the allowable values of the data and the effects of the operations. However, details about the implementation, such as data representations and algorithms for implementing the operations, are hidden within the module. This separation of specication from implementation is a key idea of abstract data types. This laboratory guide is intended to facilitate understanding of the widely used data structures such as lists, trees, graphs, hash tables and the operations associated with them. Also, the main general algorithms development methods greedy, backtracking, branch-and-bound, divide and conquer, dynamic programming and some heuristics form the object of this guide, as well as some sorting algorithms. The language used for implementations is C. In implementing the assignments a good programming style is very important, therefore some guide is given in Appendix A. This guide is intended to be used by the students of the rst year of the Automation and Computer Science Faculty of the Technical University of Cluj-Napoca, who study in English.
Contents
1. Singly Linked Lists 1.1. Purpose . . . . . . . . . . . . . . 1.2. Brief Theory Reminder . . . . . . 1.3. Operations on a Singly-linked List 1.4. Lab.01 Assignments . . . . . . . . 1 1 1 1 5 8 8 8 8 10 14 14 14 14 17 22 22 22 22 24 26 27 29 29 29 32 39 43 43 43 45 47 47 47 50 52 52 52 54 55 57 57 57 63 66
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
2. Circular Singly Linked Lists 2.1. Purpose . . . . . . . . . . . . . . . . . . . 2.2. Brief Theory Reminder . . . . . . . . . . . 2.3. Operations on a Circular Singly-linked List 2.4. Lab.02 Assignments . . . . . . . . . . . . . 3. Doubly Linked Lists 3.1. Purpose . . . . . . . . . . . . . . . 3.2. Brief Theory Reminder . . . . . . . 3.3. Operations on a Doubly Linked List 3.4. Lab.03 Assignments . . . . . . . . . 4. Trees 4.1. Purpose . . . . . . . . . . . . . . 4.2. Brief Theory Reminder . . . . . . 4.3. Operations on Binary Trees . . . . 4.4. Completely Balanced Binary Trees 4.5. Arbitrary Trees . . . . . . . . . . 4.6. Lab.04 Assignments . . . . . . . . 5. Binary Search Trees 5.1. Purpose . . . . . . . . 5.2. Brief Theory Reminder 5.3. Code Samples . . . . . 5.4. Lab.05 Assignments . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
6. Hash Tables 6.1. Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2. Brief Theory Reminder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3. Lab.06 Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7. Graph Representations and Traversals 7.1. Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2. Brief Theory Reminder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3. Lab.07 Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8. Graph Processing Algorithms 8.1. Purpose . . . . . . . . . . . 8.2. Brief Theory Reminder . . . 8.3. Minimum Spanning Tree . . 8.4. Lab.08 Assignments . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
9. Algorithm Design. Greedy and Backtrack 9.1. Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2. Brief Theory Reminder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.3. Lab.09 Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10. Algorithm Design. Divide and Conquer and Branch and Bound
10.1. Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2. Brief Theory Reminder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3. Lab.10 Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11. Algorithm Design. Dynamic Programming and Heuristics 11.1. Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2. Brief Theory Reminder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.3. Lab.11 Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12. Fundamental Sorting Algorithms 12.1. Purpose . . . . . . . . . . . . . . . . . . . . 12.2. Brief Theory Reminder . . . . . . . . . . . . 12.3. Sorting Algorithms Example Implementations 12.4. Laboratory Assignments . . . . . . . . . . . Bibliography A. Programming Style Guide A.1. Why Need a Style Guide? . . . . . A.2. Code Documentation . . . . . . . A.3. General Naming Conventions . . . A.4. Preprocessor Statements . . . . . A.5. Portability Issues . . . . . . . . . A.6. Macros . . . . . . . . . . . . . . A.7. General Construction Conventions B. C Language Reference Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
66 66 69 73 73 73 77 80 80 80 81 84 86 88 88 88 89 89 89 89 90 93 99
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
ii
A Singly-linked List Model A singly-linked list may be depicted as in Figure 1.1. f irst last
000 000
000 000
000 000
0 ...
000 000
000
NULL
Then place the data into the node addressed by p. The implementation depends on the type of data the node holds. For primitive types, you can use an assignment such as p = data. 3. Make the necessary connections:
p>next = NULL; / * node i s appended to the l i s t * / i f ( last != NULL ) / * l i s t i s not empty * / last>next = p ; else f i r s t = p ; / * f i r s t node * / last = p ;
Inserting a Node in a Singly-linked List The node to be inserted may be created as shown at 1.3. We will assume here that the node to insert is pointed to by p. If the list was empty then there would be only one node, i.e.
i f ( f i r s t == NULL ) { f i r s t = p; last = p ; p>next = NULL; }
If the list was not empty, then insertion follows different patterns depending on the position where the node is to be inserted. Thus, we have the following cases: 1. Insertion before the rst node of the list:
i f ( f i r s t != NULL ) { p>next = f i r s t ; f i r s t = p; }
2. Insertion after the last node of the list (this operation is also called append):
i f ( last != NULL ) { p>next = NULL; last>next = p ; last = p ; }
3. Insertion before a node given by its key. There are two steps to execute: a) Search for the node containing the givenKey:
NodeT *q , *q1 ; q1 = NULL; / * i n i t i a l i z e * / q = first ; while ( q != NULL ) { i f ( q>key == givenKey ) break ; q1 = q ; q = q>next ; }
4. Insertion after a node given by its key. Again, there are two steps to execute: a) Search for the node containing the givenKey:
NodeT *q , *q1 ; q1 = NULL; / * i n i t i a l i z e * / q = first ; while ( q != NULL ) { i f ( q>key == givenKey ) break ; q1 = q ; q = q>next ; }
Removing a Node of a Singly-linked List When we are to remove a node from a list, there are some aspects to take into account: (i) list may be empty; (ii) list may contain a single node; (iii) list has more than one node. And, also, deletion of the rst, the last or a node given by its key may be required. Thus we have the following cases: 1. Removing the rst node of a list
NodeT *p ; i f ( f i r s t != NULL ) { / * nonempty l i s t * / p = first ; f i r s t = f i r s t >next ; free ( p ) ; / * free up memory * / i f ( f i r s t == NULL ) / * l i s t i s now empty * / last = NULL; }
Complete Deletion of a Singly-linked List For a complete deletion of a list, we have to remove each node of that list, i.e.
NodeT *p ; while ( f i r s t != NULL ) { p = first ; f i r s t = f i r s t >next ; free ( p ) ; } last = NULL;
Stacks A stack is a special case of a singly-linked list which works according to LIFO1 algorithm. Access is restricted to one end, called the top of the stack. A stack may be depicted as in Figure 1.2. Its operations are: push push an element onto the top of the stack; pop pop an element from the top of the stack; top retrieve the element at the top of the stack; delete delete the whole stack. This can be done as explained in the previuos paragraph.
Queues A queue is also a special case of a singly-linked list, which works according to FIFO2 algorithm. Of the two ends of the queue, one is designated as the front where elements are extracted (operation called dequeue), and another is the rear, where elements are inserted (operation called enqueue). A queue may be depicted as in Figure 1.3. The main operations are:
1 L[ast] 2 F[irst]
pop
push
000 000
000 000
0 ...
000 000
000
NULL
enqueue place an element at the tail of the queue; dequeue take out an element form the front of the queue; delete delete the whole queue. This can be done as explained at 1.3. f irst 000 000 Elements inserted (enqueued) at front 000 000 000 000 0 ... 000 000 last 000 000 Elements extracted (dequeued) at rear
If an attempt is made to get out a truck which is not the closest to the garage entry, the error message Truck x not near garage door. 1.2. Same problem, but with a circular road and two entries: one for entry, another for exit. Trucks can get out only in the order they got in. . I/O description. Input:
On_road(2) On_road(5) On_road(10) On_road(9) On_road(22) Show_trucks(road) Show_trucks(garage) Enter_garage(2) Show_trucks(road) Show_trucks(garage) Enter_garage(10) Enter_garage(25) Exit_garage(10) Exit_garage(2) show_trucks(road) show_trucks(garage)
Output:
1.3. Dene and implement functions to operate on a list cell structure dened as follows:
typedef struct node { struct node *next ; void *data ; } NodeT;
using the model given in Figure 1.4. Data cells should contain a numeric key and a string, e.g. a students name and id number. . I/O description. Operations should be coded as: cre=create empty list, del key =delete node with key from list, dst=delete the rst node (not the sentinel), dla=delete the last node (not the sentinel), ins data=insert data element in ascending order of keys, ist data=insert a node with data as rst, ila data=insert a node with data as last, prt=print list.
0 NULL sentinel
0 0 0 0
0 0 0 0
0 0 0 0
0 ... 0
0 0 0 0
0 0 0 0
Figure 1.4.: A model of a list for problem 1.3. 1.4. Implement a list as a static array containing pointers to information nodes allocated on the heap, using the model of Figure 1.5 . I/O description. Operations should be coded as: cre=create empty list, del key =delete node with key from list, ist data=insert node with data as rst node, ila data=insert node with data as last node, dst=delete rst node, dla=delete last node, prt=print list. The data nodes could be records containing for instance a product id, product name and amount. ... ... ... zzz ...
000
000
000
...
000
Figure 1.5.: A model of a list for problem 1.4. 1.5. Topological sort. The elements of a set M are denoted by strings. Read pairs of elements (x, y ), x, y M meaning "element x precedes element y . List the elements of the set such that if an element precedes another, then it should be listed before its successors. Your algorithm should only make use of lists (i.e. no separate sorting). In case that conicting pairs are given, the message Pair x, y ignored due to conflict should be issued.. I/O description. Input:
alpha,delta alpha,epsilon epsilon,delta
1.6. Create a singly linked list, ordered alphabetically, containing words and their occurrence frequency. The input is paragraphs read from a le given as an argument to the program. Output the list alphabetically to another le, given
Figure 1.6.: Example input and output for problem 1.4.6. 1.7. Write a program which creates two ordered list, based on an integer key, and then merges them together. . I/O description. Input:
i1 23 47 52 30 2 5 -2 i2 -5 -11 33 7 90 p1 p2 m p1 p2
Output:
1: 2: 1: 2: -2 2 5 23 30 47 52 -11 -5 7 33 90 -11 5 2 2 5 7 23 ... empty
Thus, "commands" accepted are: in=insert onto list n {1, 2}, pn=print contents of list n, m=merge lists. 1.8. Imagine an effective dynamic structure for storing sparse matrices. Write operations for addition, and multiplication of such matrices. . I/O description. Input:
m1 40 40 (3, 3, 30) (25, 15, 2) m2 40 20 (5, 12 1) (7 14 22) m1+m2 m1*m2 , where m1=read elements for matrix m1, and the following triples are (row, col, value) for that matrix. Reading ends when either another command or the end of le marker is encountered. E.g. m1+m2=add matrix 1 to matrix 2, and m1*m2=multiply matrix m1 with matrix m2. Output should be given in similar format, i.e triplets.
1.9. Imagine an effective dynamic structure for storing polynomials. Write operations for addition, subtraction, and multiplication of polynomials. . I/O description. Input:
p1=3x^7+5x^6+22.5x^5+0.35x-2 p2=0.25x^3+.33x^2-.01 p1+p2 p1-p2 p1*p2 , Output: <list of sum polynomial> <list of difference polynomial> <list of product polynomial>
Figure 2.1.: A model of a circular singly-linked list. The structure of a node may be:
typedef struct nodetype { int key ; / * an optional f i e l d * / / * other useful data f i e l d s * / struct nodetype *next ; / * link to next node * / } NodeT;
Another choice is to look for a key, say givenKey . Code for such list scan is given below:
NodeT *p ; p = pNode ; i f ( p != NULL ) do { i f ( p>key = givenKey ) { / * key found at address p * / return p ; } p = p>next ; } while ( p != NULL ) ; return NULL; / * not found * /
Inserting a Node of a Circular Singly-linked List A node may be inserted before a node containing a given key, or after it. Both cases imply searching for that key, and, if that key exists, creating the node to insert, and adjusting links accordingly. Inserting Before a node with key givenKey There are two steps to execute: 1. Find the node with key givenKey :
NodeT *p , *q , *q1 ; q1 = NULL; / * i n i t i a l i z e * / q = pNode ; do { q1 = q ; q = q>next ; i f ( q>key == givenKey ) break ; } while ( q != pNode ) ;
Inserting after a node with key givenKey Again, there are two steps to execute: 1. Find the node with key givenKey :
Removing a Node from a Circular Singly-linked List Again there are two steps to take: 1. Find the node with key givenKey :
NodeT *p , *q , *q1 ; q = pNode ; do { q1 = q ; q = q>next ; i f ( q>key == givenKey ) break ; } while ( q != pNode ) ;
2. Delete the node pointed to by q . If that node is pN ode then we adjust pN ode to point to its previous.
i f ( q>key == givenKey ) { / * node with key givenKey has address q * / i f ( q == q>next ) { / * l i s t now empty * / } else { q1>next = q>next ; i f ( q == pNode ) pNode = q1 ; } free ( q ) ; }
Complete Deletion of a Circular Singly-linked List For complete deletion of a list, we have to remove each node of that list, i.e.
NodeT *p , *p1 ; p = pNode ; do { p1 = p ; p = p>next ; free ( p1 ) ; } while ( p != pNode ) ; pNode = NULL;
10
Operations should be coded as: ins data=insert data in the list if it is not already there (check the key part of data area for that purpose), del key =delete data containing key from list (if it is on the list), ist data=insert node with data as rst node, dst=delete rst node, prt=print list, fnd key =nd data node containing key in its data area. length f irst
000 000
000 000
000 000
0 ...
000 000
000 000
data
data
data
data
data
Figure 2.2.: Another model of a circular singly-linked list. 2.2. Dene and implement functions for operating on the data structure given below and the model of gure 2.3.
struct circularList { int length ; NodeT * current ; }
The eld current changes to indicate: the last inserted node if the last operation was insert, the found node for nd, the next node for delete, Operations should be coded as: ins data=insert a node containing data in the list if an element with the same key is not already there, fnd key =nd data node containing key in its data area (check the key part of data area for that purpose), del key =delete node with key in ints data area from list (if it is on the list), icr data=insert after current position, dcr=delete current node, prt=print list. length current
000 000
000 000
000 000
0 ...
000 000
000 000
data
data
data
data
data
Figure 2.3.: Yet another model of a circular singly-linked list. 2.3. Implement a circular buffer1 to hold records containing student-related data. A producer-consumer principle is to be implemented according to the following: a) Records are accepted as they are produced. b) If there are no records in the buffer, consumer is postponed till producer places one. c) If the buffer lls up, producer is postponed till consumer takes one out. Use a limit of 1o for queue size, so it lls up fast. . I/O description. Input:
p227,Ionescu I. Ion, 3071, DSA, 10 p231,Vasilescu T. Traian, 3087, DSA, 5 lq p555,John E. Doe, 3031, DSA, 9 p213,King K. Kong, 3011, DSA, 2 p522,Mickey M. Mouse, 3122, ART, 10
1 buffer=memory
11
Output:
227,Ionescu I. Ion, 3071, DSA, 10 231,Vasilescu T. Traian, 3087, DSA, 5 eoq 227,Ionescu I. Ion, 3071, DSA, 10 231,Vasilescu T. Traian, 3087, DSA, 5 555,John E. Doe, 3031, DSA, 9 213,King K. Kong, 3011, DSA, 2 522,Mickey M. Mouse, 3122, ART, 10 ... eoq queue full. postponed 573 queue full, postponed 257 522,Mickey M. Mouse, 3122, ART, 10 ... 573,Curtis W. Anthony, 3012, ART, 10 257,Bugs R. Bunny, 3000, GYM, 10 eoq
where 227=student id, Ionescu I. Ion=name, 3071=group id, DSA= 3-letter course code, 10=mark; eoq=endof-queue, p=record from producer, c=record is consumed, lq=list queue contents [command]. 2.4. Implement two dynamically allocated circular lists to hold solve the following problem: A number of person names (given as last name, rst name) are inserted in the rst list. A count is read Count in a circle, left to right, and move each nth person to the second list, till a single person remains in the rst list. Counting continues with the person following the moved one. Output the contents of both lists. . I/O description. Input:
Ionescu Ion Vasilescu Traian Doe John Kong King Mickey Mouse Curtis Anthony Bugs Bunny 7
Output:
First list: ----------Mickey Mouse Second list: -----------Bugs Bunny Ionescu Ion Doe John Curtis Anthony Vasilescu Traian Kong King
2.5. Use a stack to reverse the order of the elements in a circular list sorted in ascending order. The data (which is also the key) in the list are person names like in the previous problem. . I/O description. Input:
12
Output:
Vasilescu Traian Mickey Mouse Kong King Ionescu Ion Doe John Curtis Anthony Bugs Bunny
2.6. Use a stack to reverse the order of the elements in a circular list sorted in descending order. The data (which is also the key) in the list are person names like in the previous problem. . I/O description. Input:
Ionescu Ion Vasilescu Traian Doe John Kong King Mickey Mouse Curtis Anthony Bugs Bunny
Output:
Bugs Bunny Curtis Anthony Doe John Ionescu Ion Kong King Mickey Mouse Vasilescu Traian
13
000 000
NULL
0 . . .0 0
000
NULL
000
As we have seen when discussing singly-linked lists, the main operations for a doubly-linked list are: creating a cell; accessing a cell; inserting a new cell; deleting a cell; deleting the whole list.
Creating a Doubly Linked List We shall take the list to be initially empty, i.e.
L> f i r s t = L>last = NULL;
After allocating space for a new node and lling in the data (the value eld), insertion of this node, pointed by p, is done as follows:
14
Accessing a Node of a Doubly Linked List Stating at a certain position (i.e. at a certain node) we can access a list: In sequential forward direction
for ( p = L> f i r s t ; p != NULL; p = p>next ) { / * some operation o current c e l l * / }
Based on a key. Finding a node based on a given key may be achieved as we did at Lab. 1, 1.3. Inserting a Node of a Doubly Linked List We can insert a node before the rst node in a list, after the last one, or at a position specied by a given key: before the rst node:
i f ( L> f i r s t == NULL ) { / * empty l i s t * / L> f i r s t = L>last = p ; p_>next = p>prev = NULL; } else { / * nonempty l i s t * / p>next = L> f i r s t ; p>prev = NULL; L> f i r s t >prev = p ; L> f i r s t = p ; }
15
Here we assumed that the node with the given key is present on list L and it we found it and placed a pointer to it in variable q . Deleting a Node of a Doubly Linked List When deleting a node we meet the following cases: Deleting the rst node:
p = L> f i r s t ; L> f i r s t = L> f i r s t >next ; / * nonempty l i s t assumed * / free ( p ) ; / * release memory taken by node * / i f ( L> f i r s t == NULL ) L>last == NULL; / * l i s t became empty * / else L> f i r s t >prev = NULL;
Deleting a node given by its key. We will assume that the node of key givenKey exists and it is pointed to by p (as a result of searching for it)
i f ( L> f i r s t == p && L>last == p ) { / * l i s t has a single node * / L> f i r s t = NULL; L>last = NULL; / * l i s t became empty * / free ( p ) ; } else i f ( p == L> f i r s t ) { / * deletion of f i r s t node * / L> f i r s t = L> f i r s t >next ; L> f i r s t >prev = NULL; free ( p ) ; } else { / * deletion of an inner node * / p>next>prev = p>prev ; p>prev>next = p>next ; free ( p ) ; }
Deleting a Doubly Linked List Deleting a list completely means deleting each of its nodes, one by one.
NodeT *p ; while ( L> f i r s t != NULL ) { p = L> f i r s t ; L> f i r s t = L> f i r s t >next ; free ( p ) ; } L>last = NULL;
16
and the model of Figure 3.2. Now assume that data is records with two elds: a name and a birthdate (see example input below). Header is of type ListT length f irst last
0 0. . . 0
data
000
data
data
Figure 3.2.: A list model for problem 3.3.1. . I/O description. Operations should be coded as: ins<record>=insert <record> in the list, del<birthdate>=delete record(s) containing <birthdate> from list, fnd<birthdate>=nd record containing <birthdate>, ist<number>=insert <record> as rst, dst=delete rst, prt=print list, ita<number>=insert record as last, dta=delete last node. An example follows: Input: Output:
19850303 19870101 19880202 -20000101 -19870101 19880202 -19870101 -19870101 19550505 -19870101 -Vasile Vlad Ionescu Ion Marin Maria not found Ionescu Ion Marin Maria Ionescu Ion Ionescu Ion Zaharia Zicu Ionescu Ion
ins ins ist prt fnd dst prt dta prt ita prt del prt
"Ionescu Ion" 19870101 "Marin Maria" 19880202 "Vasile Vlad" 19850303 20000101
Note that after each output a line with - is printed. 3.3.2. Implement the same operations as in the previous problem, using the same data for the list model given in Figure 3.3.
17
l L
0 0. . . 0 000
000 000 Cells are of type NodeT 000 data Data is of any type
Sentinel
3.3.3. Read a paragraph containing words from an input le. Then create a doubly-linked list containing the distinct words read, and their occurrence frequency. This list should have the structure as depicted in Figure 3.4. The data structures are dened as follows:
typedef struct { int length ; NodeT * f i r s t , * last ; } ListT ; typedef struct { char *word ; double frequency ; } DataT ; typedef struct node { struct node *prev , *next ; DataT data ; } NodeT;
f irst
last
0 0. . . 0
The list should be alphabetically ordered. Print the words and their frequencies in alphabetical ascending and descending order, case insensitive. Example data: Input:
Everything LaTeX numbers for you has a counter associated with it. The name of the counter is the same as the name of the environment or command that produces the number, except with no \. Below is a list of some of the counters used in LaTeXs standard document styles to control numbering.
18
3.3.4. Simulate a game, using two circular doubly linked list. The game involves a number of children, and you are supposed to read their names from a le. The children whose names are on lines numbered by prime numbers should be placed on the rst list, and the others on the second list. Starting with the child whose name is on the line in the middle (or numberOf Children/2) of the second list, children on that list are counted clockwise. Every mth child, where m is the number of elements in the rst list is eliminated from that list. Counting goes on with the next child. Repeat this counting m times or till the second list gets empty. Your program should output the initial lists and the nal second list. Example. Input:
John James Ann George Amy Fanny Winston Bill Suzanna Hannah Alex Gaby Thomas
Output:
19
Prime [7 children]: -----John James Ann Amy Winston Alex Thomas Non-prime [6 children]: ---------George Fanny Bill Suzanna Hannah Thomas Starter: Bill Count: 7 Empty list
3.3.5. The same problem as the one before, but counting counter-clockwise. 3.3.6. Read a paragraph containing words from an input le. Then create a doubly-linked list containing the distinct words read, where the words of the same length are placed in the same list, in ascending order. The data structures are dened as follows:
typedef struct { int length ; NodeT * f i r s t , * last ; } ListT ; typedef struct data { char *word ; struct data *next ; } DataT ; typedef struct node { int wordLength ; struct node *prev , *next ; DataT *words ; } NodeT;
Print the lists in descending order of their word length, case insensitive. Example data: Input:
Everything LaTeX numbers for you has a counter associated with it. The name of the counter is the same as the name of the environment or command that produces the number, except with no \. Below is a list of some of the counters used in LaTeXs standard document styles to control numbering.
Output:
1: a 2: \. as in is no of or to 3: for has it. the you 4: list name same some that used with 5: Below LaTeX 6: except styles 7: command control counter LaTeXs number, numbers 8: counters document produces standard 10: associated Everything numbering.
20
21
4. Trees
4.1. Purpose
In this lab session we will work on basic operations for binary trees, completely balanced binary trees and arbitrary trees.
The construction of a binary tree may be achieved with a function containing the following code:
Binary Tree Traversals There are three kinds of traversals: preorder, inorder, and postorder. Listing 4.1 shows the C implementation for the operations of construction and traversal of a binary tree. Listing 4.1: Construction and traversals of binary trees
#include <stdio . h>
22
23
4. Trees
/ * read node id * / i f ( branch == 0 ) p r i n t f ( "Root i d e n t i f i e r [0 to end ] =" ) ; else i f ( branch == 1 ) p r i n t f ( " Left child of %d [0 to end ] =" , parent>id ) ; else p r i n t f ( "Right child of %d [0 to end ] =" , parent>id ) ; scanf ( "%d" , &id ) ; i f ( id == 0 ) return NULL; else { / * build node pointed to by p * / p = ( NodeT * ) malloc ( sizeof ( NodeT ) ) ; i f ( p == NULL ) fatalError ( "Out of space in createBinTree " ) ; / * f i l l in node * / p>id = id ; p> l e f t = createBinTree ( 1 , p ) ; p>right = createBinTree ( 2 , p ) ; } return p ; } int main ( ) { NodeT * root ; root = createBinTree ( 0 , NULL ) ; while ( \ n != getc ( stdin ) ) ; p r i n t f ( " \ nPreorder l i s t i n g \ n" ) ; preorder ( root , 0 ) ; p r i n t f ( " Press Enter to continue . " ) ; while ( \ n != getc ( stdin ) ) ; p r i n t f ( " \ nInorder l i s t i n g \ n" ) ; inorder ( root , 0 ) ; p r i n t f ( " Press Enter to continue . " ) ; while ( \ n != getc ( stdin ) ) ; p r i n t f ( " \ nPostorder l i s t i n g \ n" ) ; postorder ( root , 0 ) ; p r i n t f ( " Press Enter to continue . " ) ; while ( \ n != getc ( stdin ) ) ; return 0; }
24
25
4. Trees
nLeft = nbOfNodes / 2; nRight = nLeft 1; p = ( NodeT * ) malloc ( sizeof ( NodeT ) ) ; p r i n t f ( " \nNode i d e n t i f i e r=" ) ; scanf ( "%d" , &( p>id ) ) ; p> l e f t = creBalBinTree ( nLeft ) ; p>right = creBalBinTree ( nRight ) ; } return p ; } int main ( ) { NodeT * root ; int nbOfNodes = 0; p r i n t f ( " \nNumber of nodes in the tree=" ) ; scanf ( "%d" , &nbOfNodes ) ; creBalBinTree ( nbOfNodes ) ; while ( \ n != getc ( stdin ) ) ; p r i n t f ( " \ nPreorder l i s t i n g \ n" ) ; preorder ( root , 0 ) ; p r i n t f ( " Press Enter to continue . " ) ; while ( \ n != getc ( stdin ) ) ; p r i n t f ( " \ nInorder l i s t i n g \ n" ) ; inorder ( root , 0 ) ; p r i n t f ( " Press Enter to continue . " ) ; while ( \ n != getc ( stdin ) ) ; p r i n t f ( " \ nPostorder l i s t i n g \ n" ) ; postorder ( root , 0 ) ; p r i n t f ( " Press Enter to continue . " ) ; while ( \ n != getc ( stdin ) ) ; return 0; }
To build such a tree: 1. Read for each node, in postorder, elds: id, other useful info1 , and the number of children and push this info onto a stack. 2. When parent node is read, pop children nodes of the stack, ll children pointers in parent node, and then push a pointer to parent onto stack. Finally the only pointer on the stack will be a pointer to the root node, and the process stops. Tree traversal is achieved in level order, according to the following:
1 if
Use a queue to keep pointers to nodes to be processed. The queue is initially empty. Enqueue a root node pointer Dequeue a node, process node information, and enqueue pointers to the children of the currently processed node. Repeat previous step till the queue becomes empty.
dened
26
where i:=signals the expression follows on input, p:=print the expression. Output: a pretty print the tree. E.g.
* / \ / \ + / a \ c / \ d # e
4.6.2. Read a mathematical expression, in prex form, as a character string, from a text le input. The allowed operators are the usual binary additive (+, -), multiplicative (*, /), and unary sign change (+, -). Build the tree for that expression. Every node should contain either an operator or an operand. A missing operand (for unary operators) is signaled by a # character in the input. Operands can be arbitrary numbers, separated by spaces. Example input. For the expression ((1.05-(-55+22)))*10.3, the tree representing the expression is:
* / \ / 1.05 10.3 \ + / \ -55 22 -
where i:=signals the expression follows on input, e?=evaluate the expression. Output: the value of the expression. 4.6.3. Construct a family tree organized as follows: the root node holds a parentss name (names must be unique) as a key; each name is unique. Then, after reading pairs of names from a le of your choice, output the relations between pairs or persons. .6 . I/O description. Input. (< parentN ame > (< childN ame > < childN ame > ... < childN ame >)) ... ?< name1 >, < name2 > The angle brackets (<, >) do not exist in the input, they just mark changeable amounts. All names (i.e. < childN ame >, < parentN ame >, < name1 >, < name2 > are alphabetic strings with no spaces. (, ) are delimiters. ? signals query about persons. Output is relation degree (i.e, child, parent, cousin, etc.). For the example tree in Figure 4.2 below, a part of the input would be:
(Matilde (Sebastian Leonor)) (Sebastian (Philip Mary)) (Philip (Frederick Ethel)) (Raul (Joaquim Julia)) ... ? Matilde, Agnes ? Agnes, Matilde ? Joaquim Julia
27
4. Trees
Matilde Sebastian Philip Frederick Ethel Mary Lionel Agnes Ral Joaquim Jlia Leonor Amlia lvaro Augusta
Figure 4.2.: Example Family Tree 4.6.4. Consider the inverse of Problem 4.3. Given the output data of Problem 4.3(i.e. relations among relatives) construct its input. 4.6.5. Transform a binary tree holding character strings into a doubly-linked list such a way that you can reconstruct the tree. The input is specied as for Problem 4.3. Output is prex and inx traversals of the tree on two separate lines. 4.6.6. Write the code for representing trees as lists of children. Input is as for Problem 4.3. Output is prex, postx, and inx traversals of the tree on three separate lines. 4.6.7. Write the code for representing trees as lists of children. Input is as for Problem 4.3. Output is the tree in level order, i.e. the rst line is the root alone; the second is the root identication, followed by a colon, and all its children, separated one space each; the third line is the identication of the leftmost child of the root, followed by a colon, and then all its children, separated one space each; the fourth line is the second child of the root followed by a colon, and then all its children, separated one space each, and so on up to the lowest level. .6 4.6.8. Implement AVL trees with character string keys. Input are commands for insertion, deletion, and searching the tree. Commands are always specied in the rst column. Use i for insertion, d for deletion f for nd. Each command is followed by a space and an argument a key. Output of insert and delete are the prex and inx traversals of the tree. Output for f ind is the input key followed by a space and found or not found as suitable..6 4.6.9. Implement 2-3 trees holding birthday records. (Records contain a persons name and birthday). The key here is the birthday (given as yyyymmdd, e.g. 19991011). Input and output is similar to Problem 4.8. 4.6.10. Implement binary trees holding books in a library. The data for each book is: ISBN (this is the key), Author, Title, Status (borrowed, on-shelf). Operations are marked in the input as follows: a for adding a new book; b to borrow a book; r for returning a book; d for removing a book form the library tree; l for printing the contents of the whole library. Command a is followed by three elements: the ISBN, authors name enclosed in double quotes, and boot title enclosed in double quotes, all separated by commas. Borrow and return commands require the ISBN alone. Listing of shelves contents must print ISBN, author, title and status for each book, in a format similar to input. 4.6.11. Implement the priority queue ADT using with a POT using an array as presented in Lecture 3. Input data are numbers. Operations are specied by letters i for insert, and d for deleteM in. Your program must output a printout of the contents of the tree after each operation, in level order similar to that of Problem 4.8. 4.6.12. Implement the heap ADT using an array as presented in Lecture 3. Input data are strings. Operations are specied by letters i for insert, and d for deleteM ax. Your program must output a printout of the contents of the tree after each operation, in level order similar to that of Problem 4.8.
28
The resulting structure of a BST depends upon the order of node insertions. Inserting a Node in a BST A BST is build by inserting new nodes into it. This can be done by performing the following steps: 5.2.1. If the tree is empty, then create a new node, the root, with key value key, and empty left and right subtrees. 5.2.2. If root key is key, then insertion is not possible, as the keys must be distinct nothing to do. 5.2.3. If the given key, say key, is less than the key stored at the root node, then continue with the rst step, for the left subtree. 5.2.4. If the given key, say key, is bigger than the key stored at the root node, then continue with the rst step, for the right subtree. Insertion can be achieved non-recursively using the procedure presented below:
typedef int KeyType ; typedef int ElementType; / * for s i m p l i c i t y . Both should occur before NodeT declaration * / void nInsert ( KeyType givenKey ) { NodeT *p , *q ; / * build node * / p = ( NodeT * ) malloc ( sizeof ( NodeT ) ) ; p>key = givenKey ; / * the info f i e l d should be f i l l e d in here * / p> l e f t = p> right = NULL; / * leaf node * / i f ( root == NULL ) { / * empty tree * / root = p ; return ; } / * i f we reach here then the tree i s not empty ; look for parent node of node pointed to by p * / q = root ;
29
Finding a Node of a Given Key in a BST The algorithm for nding a node is quite similar to the one for insertion. One important aspect when looking for information in a BST node is the amount of time taken. The number of comparisons would be optimal if there would be at most log2 n where n is the number of nodes stored. The worst case is encountered when insertion is executed with the nodes sorted in either ascending or descending order. In that case the tree turn out to be a list. A nd function may be implemented as1 :
NodeT * find ( NodeT * root , KeyType givenKey ) { NodeT *p ; i f ( root == NULL ) return NULL; / * empty tree * / for ( p = root ; p != NULL; ) { i f ( givenKey == p>key ) return p ; else i f ( givenKey < p>key ) p = p> l e f t ; else p = p>right ; } return NULL; / * not found * / } / * invoke with : q = find ( root , key ) ; * / }
Deleting a Node in a BST When a node is deleted, the following situations are met: 5.2.1. The node to delete is a leaf. Child node pointer in parent node (left or right) must be set to zero (NULL). 5.2.2. The node to delete has only one child. In this case, in parent node of the one to be deleted its address is replaced by the address of its child.
1 see
30
BST Traversals As with any tree, there are three systematic traversals: preorder, inorder and postorder traversals. Here is some code for traversals:
void preorder ( NodeT *p ) { i f ( p != NULL ) { / * code for info r e t r i e v a l here * / preorder ( p> l e f t ) ; preorder ( p>right ) ; } } void inorder ( NodeT *p ) { i f ( p != NULL ) { inorder ( p> l e f t ) ; / * code for info r e t r i e v a l here * / inorder ( p>right ) ; } } void postorder ( NodeT *p ) { i f ( p != NULL ) { postorder ( p> l e f t ) ; postorder ( p>right ) ; / * code for info r e t r i e v a l here * / } }
Optimal BSTs The length of the path traversed when looking for a node of key x, in a BST, is: length of path = hf hnf + 1 if successful if not found
where hf is the level of the node containing the given key, and hnf is the level of the last node met before deciding that the node is not present. Let S = {c1 , c2 , . . . , cn } be the set of keys leading to success, in ascending order, i.e. c1 < c2 < . . . < cn and let pi be the probability of looking for key ci , for i = 1, 2, . . . , n. If we denote C the set of possible keys, the C S is the set of keys which lead to unsuccessful search. We can partition this set into subsets:
31
Let now qi be the probability of looking for a key in the set ki . Each key ki involves the same search path; then the length of this path will be h i . If we denote L the average search path and call it the cost of the tree, we have:
n n
L=
i=1
p i hi +
j =0
qj h i
provided that:
n n
pi +
i=1 j =0
qj = 1
An optimal tree is a BST in which for given pi , qj the cost L is minimal. Optimal BSTs are not subject to insertions and deletions. Occurrence frequencies should be used instead of pi and qj when minimizing the cost function L. Those frequencies will be found by using test data runs. If we denote by Ai,j an optimal tree with nodes ci+1 , ci+2 , . . . , cj , then the weight wi,j of Ai,j is:
j j
wi,j =
k=i+1
pk +
k =i
qk
gi,j can be calculated as: wi,i = qi wi,j = wi,j 1 for 0 i n for 0 i < j n
It follows that the cost of the optimal tree Ai,j can be evaluated as: ci,i = wi,i for 0 i n ci,j = wi,j + min (ci,k1 + ck,j ) for 0 i < j n
i<kj
Let ri,j be the value of k for which a minimal ci,j is obtained. The node of key c[ri,j ] will be the root of the sub-optimal tree Ai,j , with subtrees Ai,k1 and Ak,j , where k = ri,j . Computing the values of the matrix C is O(n3 ), but it has been demonstrated that the running time can be reduced to O(n2 ). In order to build an optimal tree you can use function buildOptTree, given below:
NodeT *buildOptTree ( int i , int j ) { NodeT *p ; i f ( i == j ) p = NULL; else { p = ( NodeT * ) malloc ( sizeof ( NodeT ) ) ; p> l e f t = buildOptTree ( i , r [ i ] [ j ] 1 ) ; p>key = keys [ roots [ i ] [ j ] ] ; p>right = buildOptTree ( r [ i ] [ j ] , j ) ; } return p ; }
32
typedef struct node { int key ; struct node * l e f t , right ; } NodeT; NodeT root ; void fatalError ( const char *msg ) { p r i n t f ( msg ) ; p r i n t f ( " \ n" ) ; exit ( 1 ) ; } void preorder ( NodeT *p , int level ) /* * p = current node ; * l e v e l = used for nice printing */ { int i ; i f ( p != NULL ) { for ( i = 0; i <= level ; i ++ ) p r i n t f ( " " ) ; / * for nice l i s t i n g * / p r i n t f ( "%2.2d \ n" , p>key ) ; preorder ( p>l e f t , level + 1 ) ; preorder ( p>right , level + 1 ) ; } } void inorder ( NodeT *p , int level ) { int i ; i f ( p != NULL ) { inorder ( p>l e f t , level + 1 ) ; for ( i = 0; i <= level ; i ++ ) p r i n t f ( " " ) ; / * for nice l i s t i n g * / p r i n t f ( "%2.2d \ n" , p>key ) ; inorder ( p>right , level + 1 ) ; } } void postorder ( NodeT *p , int level ) { int i ; i f ( p != NULL ) { postorder ( p>l e f t , level + 1 ) ; postorder ( p>right , level + 1 ) ; for ( i = 0; i <= level ; i ++ ) p r i n t f ( " " ) ; / * for nice l i s t i n g * / p r i n t f ( "%2.2d \ n" , p>key ) ; } } void insert ( int key ) / * non recursive version of i n s e r t * / { NodeT *p , *q ; p = ( NodeT * ) malloc ( sizeof ( NodeT ) ) ; p>key = key ; p> l e f t = p>right = NULL; i f ( root == NULL )
33
34
35
36
37
38
If le stock.data exists, your program must automatically load its contents. . I/O description. This program should be interactive. You have to maintain information for school classes. For each of the students in a class the following information is kept: a unique code, student name, birth date, home address, id of class he is currently in, date of enrollment, status (undergrad, graduate). For keeping track of the students, the school secretary would use a computer program based
39
If le student.data exists, your program must automatically load its contents. . I/O description. This program should be interactive. 5.4.2. Build an optimal BST (binary search tree) to hold C language keywords in its nodes. In order to determine frequencies pi and qi use code samples from the lab book. . I/O description. Keywords and frequencies may be read from a le with a name of your choice (e.g. keyword.data). The menu must include the following: Printing the tree in preorder. Listing keyword, f requency pairs for each node. Finding a keyword. If found/not found your program should output the path traversed in determining the answer, followed by yes if found or no if not found. Hint. To determine frequencies you may use a list sorted in frequency order. 5.4.3. Build an optimal BST (binary search tree) to hold unextended Pascal language keywords in its nodes. The keyword list can be found e.g. at http://pascal-central.com/ppl/chapter3.html#Unextended. In order to determine frequencies pi and qi use code samples downloaded from the Internet. . I/O description. Keywords and frequencies may be read from a le with a name of your choice (e.g. keyword.data). The menu must include the following: Printing the tree in postorder. Listing keyword, f requency pairs for each node. Finding a keyword. If found/not found your program should output the path traversed in determining the answer, followed by yes if found or no if not found. Hint. To determine frequencies you may use a list sorted in frequency order. 5.4.4. Write a program to illustrate operations on a BST holding numeric keys. The menu must include: Insert Delete Find Show Allow a maximum depth of 5 (note that root is at depth 0), and a maximum number of nodes on the last level of 32. After each step a print of the tree should be shown as below:
I. Insert > i 20 I. Insert > i 10 I. Insert > i 11 I. Insert > i 1 I. Insert > i 23 I. Insert > i 26 I. Insert > s 20 / \ 10 23 / \ \ D. Delete D. Delete D. Delete D. Delete D. Delete D. Delete D. Delete F. Find F. Find F. Find F. Find F. Find F. Find F. Find S. Show S. Show S. Show S. Show S. Show S. Show S. Show Q. Quit Q. Quit Q. Quit Q. Quit Q. Quit Q. Quit Q. Quit
40
F. Find
S. Show
Q. Quit
F. Find
S. Show
Q. Quit
F. Find
S. Show
Q. Quit
5.4.5. Write a program to illustrate operations on a BST holding alphabetic uppercase letter keys. The menu must include: Insert Delete Find Show
Allow a maximum depth of 5 (note that root is at depth 0), and a maximum number of nodes on the last level of 32. After each step a print of the tree should be shown as below:
I. Insert D. > i V I. Insert D. > i J I. Insert D. > i K I. Insert D. > i A I. Insert D. > i X I. Insert D. > i Z I. Insert D. > s V / \ J X / \ \ A K Z I. Insert D. > d M M not found I. Insert D. > d V X / \ J Z / \ A K I. Insert D. > d J X / \ K Z / A Delete Delete Delete Delete Delete Delete Delete F. Find F. Find F. Find F. Find F. Find F. Find F. Find S. Show S. Show S. Show S. Show S. Show S. Show S. Show Q. Quit Q. Quit Q. Quit Q. Quit Q. Quit Q. Quit Q. Quit
Delete
F. Find
S. Show
Q. Quit
Delete
F. Find
S. Show
Q. Quit
Delete
F. Find
S. Show
Q. Quit
41
42
6. Hash Tables
6.1. Purpose
In this lab session we will work with hash tables. We will study the main operations on hash tables, i.e. create, insert, nd and retrieve items.
43
6. Hash Tables
Collision Resolution Collision resolution may be achieved as follows: insert all records with colliding keys in a singly-linked list. There will be a number of lists, called buckets one for each hash value. So, an open hash table is modelled as gure 6.1 shows. A cell in a bucket may be described by:
typedef struct cell { char *key ; / * other useful info * / struct cell *next ; } NodeT;
0 0 ... 0
0 0
0 0
0 0
The steps needed to nd a record of hash value key in a hash table are: 6.2.1. Determine the hash value for the key h = f (key ). 6.2.2. Linearly search the bucket for hash value h:
p = BucketTable[ h ] ; while ( p != NULL ) { i f ( strcmp ( key , p>key ) == 0 ) return p ; p = p>next ; } return NULL; / * not found * /
Insertion in a hash table takes the following steps: 6.2.1. Make a new node pointed to by p and ll it:
p = ( NodeT * ) malloc ( sizeof ( NodeT ) ) ; fillNode ( p ) ;
6.2.3. If the bucket table header entry is empty, insert as rst list element:
i f ( BucketTable[ h ] == NULL ) { BucketTable[ h ] = p ; p>next = NULL;
Otherwise check if the record to insert is not already present. If already there we have two choices: to apply some operation such as delete or update or signal a "double key" error. If the record is not present, insertion as rst node in the bucket may be effected:
q = find ( p>key ) ; i f ( q == 0 ) { / * not found . Insert i t * / p > next = BucketTable[ h ] ; Buckettable [ h ] = p ; } else / * double key * / processRec ( p , q ) ;
A hash table is build by repeatedly insertion nodes. A complete list of a hast table contents may be achieved with:
for ( i = 0; i < B; i ++ ) i f ( BucketTable[ i ] != NULL ) {
44
6.3.2. Write a program to manage an hash table, using open addressing, where the keys are book ISBNs. Your code should provide create, insert, nd and delete operations on that table. . I/O description. Input:
i<name> d<name> f<name> l i<name>=insert <name>, d<name>=delete <name>, f<name>=nd <name> in the table; l=list table contents. Note that the characters <, and > are not part of the input. Use a hash function suitable for character strings and motivate your answer in a comment stored in the heading area of your hash table processing routines. Output for nd should be yes, followed by the table index if found or no if not found.
6.3.3. Write a program to manage an hash table, using collision resolution by chaining, where the keys are student full names. Your code should provide create, insert, nd and delete operations on that table. . I/O description. Input:
i<name> d<name> f<name> l i<name>=insert <name>, d<name>=delete <name>, f<name>=nd <name> in the table; l=list table contents. Note that the characters <, and > are not part of the input. Use a hash function suitable for character strings and motivate your answer in a comment stored in the heading area of your hash table processing routines. Output for nd should be yes, followed by the table index if found or no if not found.
6.3.4. Write a program to manage an hash table, using open addressing, with character string keys, where the hash function should be selectable before each run. The methods you should use in building your has functions are linear, quadratic and double hashing (cf. Lecture 3). Your code should provide create, insert, nd and delete operations on that table. . I/O description. Input:
f<number> i<name>
45
6. Hash Tables
d<name> f<name> l f<number>=select the hash function numbered with <number>, i<name>=insert <name>, d<name>=delete <name>, f<name>=nd <name> in the table; l=list table contents. Note that the characters <, and > are not part of the input. Output for nd should be yes, followed by the table index if found or no if not found.
6.3.5. Write a program to manage an hash table, using open addressing, with numeric long integer keys, where the hash function should be selectable before each run. The methods you should use in building your has functions are linear, quadratic and double hashing (cf. Lecture 3). Your code should provide create, insert, nd and delete operations on that table. . I/O description. Input:
f<number> i<name> d<name> f<name> l f<number>=select the hash function numbered with <number>, i<name>=insert <name>, d<name>=delete <name>, f<name>=nd <name> in the table; l=list table contents. Note that the characters <, and > are not part of the input. Output for nd should be yes, followed by the table index if found or no if not found.
6.3.6. Write a program to manage an hash table, using collision resolution by chaining, with numeric long integer keys, where the hash function should be selectable before each run. The methods you should use in building your has functions are memory address, integer cast, and component sum (cf. Lecture 3). Your code should provide create, insert, nd and delete operations on that table. . I/O description. Input:
f<number> i<name> d<name> f<name> l f<number>=select the hash function numbered with <number>, i<name>=insert <name>, d<name>=delete <name>, f<name>=nd <name> in the table; l=list table contents. Note that the characters <, and > are not part of the input. Output for nd should be yes, followed by the table index if found or no if not found.
6.3.7. Write a program to manage an hash table, using collision resolution by chaining, with numeric long integer and string keys, where the hash function should use polynomial accumulation (cf. Lecture 3). Your code should provide create, insert, nd and delete operations on that table. . I/O description. Input:
i<name> d<name> f<name> l i<name>=insert <name>, d<name>=delete <name>, f<name>=nd <name> in the table; l=list table contents. Note that the characters <, and > are not part of the input. Output for nd should be yes, followed by the table index if found or no if not found.
46
A labelled adjacency matrix, A, also called a cost matrix is dened as A[i][j ] = label of arc (i, j ) if (i, j ) E an arbitrary symbol if (i, j ) /E
The adjacency matrix is symmetric for undirected graphs. Which of the two representations is best? The adjacency matrix is useful when the presence or absence of an arc is frequently tested. It performs badly when the number of arcs if much less than the square of the number of nodes (i.e. |E | << |V | |v |). The adjacency list makes better use of memory, but looking for arcs is more difcult. In an adjacency list the list of arcs for adjacent nodes is kept. The whole graph may be represented by a table indexed by nodes, each entry for a node i in the table containing the address of the list of nodes adjacent to i. That list may be a static or a dynamic list. Figure 7.1 shows a graph and its adjacency matrix representation. Figure 7.2 shows static/dynamic adjacency lists representations. Traversals Breadth-rst Search Breadth rst search involves the following actions:
47
1 2
0 1 2 3 4
0 0 0 0 0 1
1 1 0 0 0 0
2 1 1 0 0 0
3 0 1 1 0 0
4 0 1 0 1 0
1. Enqueue the start vertex in an empty queue. 2. Dequeue one node to process and enqueue all its unvisited adjacent nodes. 3. Repeat step 2 until the queue becomes empty. A sketch of the algorithm is:
enum { UNVISITED, VISITED }; void bfs ( int nbOfNodes, int srcNode ) { int mark[ nbOfNodes ] ; / * for marking v i s i t e d nodes * / QueueT Q; / * queue of nodes integers * / int i , v , w; / * nodes * / makeNull( Q ) ; for ( i = 0; i < nbOfNodes; i ++ ) / * mark a l l nodes unvisited * / mark[ i ] = UNVISITED; mark[ srcNode ] = VISITED ; / * mark source node v i s i t e d * / process info for srcNode ; enqueue( srcNode , Q ) ; / * srcNode will be the f i r s t node dequeued in the loop below * / while ( ! empty ( Q ) ) { v = dequeue( Q ) ; for ( each node w adjacent to v ) i f ( mark[ w ] == UNVISITED ) { mark[ w ] = VISITED ; process info for w; enqueue( w, Q ) ; } }
0 1 2 3 4
0 3 7 9 11
2 3 0 3 4 5 0 4 0 5 0 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
2 0 1 2 3 4 3 4 5 2
3 4
Figure 7.2.: Adjacency list representations for the graph of gure 7.1(a)
48
49
s e d f g a c b d f e g a
s b d c Q : c, d, e
(c) After processing nodes adjacent to a.
e g f
Q : a, c, d, e
(b) After processing nodes adjacent to b.
s b a c Q : d, e, f
(d) After processing nodes adjacent to c.
s e d f g a c Q:g
(e) After processing nodes adjacent to d and e.
s b d f e g a c Q:g
(f) After processing nodes adjacent to f .
b d
e g f
Depth-rst Search When searching a graph in depth-rst search mode, the source node is marked visited rst. Then every adjacent node is visited, recursively. After visiting all the nodes reachable form a given source, the traversal ends. If there still are unvisited nodes, another node is chosen as a source an the steps are repeated. The algorithm sketch is:
enum { UNVISITED, VISITED }; void dfs ( int nbOfNodes, int srcNode ) { int mark[ nbOfNodes ] ; / * for marking v i s i t e d nodes * / StackT S; / * stack of nodes * / int i , v , w; / * nodes * / for ( i = 0; i < nbOfNodes; i ++ ) / * mark source node v i s i t e d * / mark[ i ] = UNVISITED; mark[ srcNode ] = VISITED ; / * mark source node v i s i t e d * / process info for srcNode ; push ( srcNode , S ) ; while ( ! empty ( S ) ) { v = top ( S ) ; l e t w be the next unvisited node on AdjList ( v ) ; i f ( w exists ) { mark[ w ] = VISITED ; process info for w; push ( w, S ) ; } else pop( S ) ; }
Note that this algorithm uses a stack to eliminate recursion. A trace of the relevant steps in breadth-rst search is shown in Figure 7.4.
50
s e d a c S : a, b
(b) After processing node b.
s b d e a c S : c, a, b
(c) After processing node a.
b d
s b a c S : d, c, a, b
(d) After processing node c.
s e d a c S : e, d, c, a, b
(e) After processing node d.
b d
Figure 7.4.: A trace of depth-rst search on a graph. 7.7.1. Let G be a digraph, i.e. G = (V, E ) and let V be one of its subgraphs. If the nodes are represented by integers which are to be read from a text le, output the induced subgraph G = (V , E ) to a text le. Implement the necessary code. Input is given as follows:.7
V nodes 1 3 5 6 7 V arcs (1 3) (1 5) (1 7) (3 6) (3 7) ... V nodes 1 3 5
Output should look like this: V nodes 1 3 5 V arcs (1 3) (1 5) ... Hint. Use fgets and sscanf to read input. E.g. reading a pair of arcs can be accomplished with sscanf(&buffer[i], "(%d%d)", &v, &w) where v and w are integers. 7.7.2. Implement breadth-rst and depth-rst traversals for a graph represented by an adjacency matrix as presented in Lectures 5 and 6. Input is as for Problem 7.1. Output the nodes traversed in sequence to a text le of your choice. 7.7.3. For an undirected graph G = (V, E ), where node names are positive integers, implement a graph construction method, and a function named havePath(v, w), where v V and w V are nodes, to check if there is a path between the two given nodes, say v and w of graph G = (V, E ). Graphs will be read from a le as before, and path will be printed to a text le as a sequence of nodes (i.e integer numbers). The invocation of function havePath(v, w) will be specied in the input le after graph specication input as a question mark and two node names (i.e. numbers), e.g.
V nodes 1 3 5 6 7 V arcs (1 3) (1 5) (1 7) (3 6) (3 7) ... ?1,5 ?1,6 ?5,1 ...
7.7.4. For an undirected graph G = (V, E ), where node names are positive integers, implement a graph construction method, and a function which returns the longest simple path, named longestPath(a, b), where a V and b V are nodes. Input as before, output is a space separated list of nodes along the path. 7.7.5. For an undirected graph G = (V, E ), where node names are positive integers, implement a graph construction method, and a function named isConnected to check whether or not the graph is strongly connected. Input as before, output {yes, no}.
51
The vector parent holds the vertices which are accessible from the source vertices. It allows for path reconstruction from a source to any accessible node. For nodes inaccessible from a source node S [i] = 0 and dist[i] = .
52
All Pairs Shortest Paths By repeatedly applying Dijkstras algorithm with each node as a source, in turn, we can obtain the minimum paths for all the pairs of vertices in a graph. Another choice is R. W. Floyds algorithm. Floyds algorithm was developed for solving the all pairs shortest paths problem. The algorithm maintains the minimal costs in an array, say A. Initially, A is identical to the cost matrix, cost. The minimal distances computation is accomplished in n iterations, where n is the number of nodes. At iteration k , A[i][j ] holds the minimum distance between nodes i and j using paths which do not contain nodes numbered higher than k , except possibly for the end nodes, i and j . A is calculated with the following formula: Aij (k) = min(Aij (k1) , Aik (k1) + Akj (k1) ) Because Aik (k) = Aik (k1) and Akj (k) = Akj (k1) we may use a single copy of array A. Floyds is then:
#define N M A X ? / * max no . of nodes * / double cost [ NMAX ] [ NMAX ] ; double A[ NMAX ] [ NMAX ] ; void Floyd ( int nbOfNodes ) { int i , j , k ; /* i n i t i a l i z e A */ for ( i = 1; i <= n ; i ++ ) for ( j = 1; j <= n ; j ++ ) A[ i ] [ j ] = cost [ i ] [ j ] ; for ( i = 1; i <= n ; i ++ ) A[ i ] [ i ] = 0; for ( k = 1; k <= n ; k++ ) / * a l l nodes * / for ( i = 1; i <= n ; i ++ ) / * a l l l i n e s * / for ( j = 1; j <= n ; j ++ ) / * a l l columns * / i f ( A[ i ] [ k ] + A[ k ] [ j ] < A[ i ] [ j ] ) A[ i ] [ j ] = A[ i ] [ k ] + A[ k ] [ j ] ; }
For keeping track of the shortest paths, we can make use of an additional table, p, where p[i, j ] will hold the vertex k which lead to the minimum distance A[i, j ]. If p[i, j ] = 0, then the edge (i, j ) is the shortest from i to j . In order to be able to display the intermediate vertices along the path from i to j , we can use the following scheme:
void path ( int i , int j ) { int k ; k = p[ i ] [ j ] ; i f ( k != 0 ) { path ( i , k ) ; l i s t node k ; path ( k , j ) ;
53
Another solution to this problem was given by Kruskal. Kruskal maintains the edges in the order of ascending costs. The spanning tree will have n 1 edges. At each step, a minimum cost edge is selected, such a way that it does not form a cycle together with the rest of the edges contained in T . The sketch for Kruskals is:
void Kruskal ( int n ) { T = {}; while ( T is not a spanning tree ) { select edge of min . cost (w, u ) from V; delete edge (w, u ) from V; i f ( (w, u ) does not close a cycle in T ) add (w, u ) to T ; } }
The difcult part in Kruskals is checking for a cycle. In order to implement Kruskals algorithm, a data structure which supports union (or merge) and nd operations on disjoint sets is useful. Three operations are necessary: CreateSet(u) create a set containing a single item u. FindSet(u) nd the set that contains a given item u. Union(u, v ) merge the set containing u and the set containing v into a set containing both items. Thus the sketch for Kruskals algorithm becomes:
SetT Kruskal ( GraphT G, VertexT v ) / * G=(V, E) , where V i s the set of vertices , and E i s the set of edges for graph G. */ { SetT A; makeEmpty(A ) ; / * A i s i n i t i a l l y empty * / for ( each u in V ) CreateSet ( u ) ; / * create set for each vertex * / Sort E in increasing order by weight w; i f ( FindSet( u ) ) != FindSet( v ) ) { / * i f u and v are in d i f f e r e n t trees * / Add( u , v ) to A;
54
4 a
6 g a
6 g
4 a 8
b 9 c
10 8 d 2 1 9 7
e 5 f
6 g 2 a
b 9
10 8 d 2 1 9 7
e 5 f
6 g 2
4 a 8
b 9 c
10 8 d 2 1 9 7
e 5 f
6 g 2 a
b 9
10 8 d 2 1 9 7
e 5 f
6 g 2
55
8.8.2. 8.8.3.
8.8.4.
8.8.5.
56
Here, it is the job of select to set the criterion used in obtaining the nal solution. 9.2.2. First set the order used to consider the elements of set A. Then take elements of A one by one using the established order and check if, by adding it to the partial solution stored in set B we can get a possible solution. If so, add it to B . A sketch of this is:
#define M A X N ? / * suitable value * / void greedy2 ( int A[ MAXN ] , int n , int B[ MAXN ] , int *k ) / * A i s the set of candidate n elements ; B i s the set of k elements solution * / { int x , v , i ; *k = 0; / * empty solution set * / process ( A, n ) ; / * rearrange A * / for ( i = 0; i < n ; i ++ ) { x = A[ i ] ; checkIfSolution ( B, x , v ) ; / * v = 1 i f by adding x we get a solution and v = 0 otherwise * /
57
Greedy Example. Prims Algorithm This problem was stated in 8.3. We will briey remind how this algorithm works: 9.2.1. Initially, include only one node in the tree T . ( It does not matter which, so we can safely start with node 1. ) The set of arcs in the tree is empty. 9.2.2. Select a minimum cost arc, having one end in the tree and the other in the rest of vertices. Repeat this step n 1 times. To avoid passing through all arcs at each step, we can use an n-component vector v dened as: 0 if vertex i T k if vertex i / T; Ui = k T is a node such that (i, k ) is a minimum cost edge Initially, v [1] = 0, and v [2] = v [3] = . . . = v [n] = 1, i.e initially the tree is A = ({1}, ). Here is an implementation:
#include <stdio . h> #define M A X N 10 #define INFTY 0x7fff void Prim2 ( int n , int c [ MAXN ] [ MAXN ] , int e [ MAXN ] [ 2 ] , int * cost ) / * n = number of vertices ; c = cost matrix ; e = edges of the MST; c = cost of MST * / { int v [ MAXN ] ; / * v[ i ] = 0 i f i i s in the MST; v[ i ] = j i f i i s not in the MST; j i s a node of the tree such that ( i , j ) i s a minimum cost edge * / int i , j , k , min ; * cost = 0; v [ 1 ]=0; for ( i = 2; i <= n ; i ++ ) v [ i ] = 1; / * tree i s ({1} ,{}) * / / * find the rest of edges * / for ( i = 1; i <= n 1; i ++ ) { / * find an edge to add to the tree * / min = INFTY ; for ( k = 1; k <= n ; k++ ) i f ( v [ k ] != 0 ) i f ( c [ k ] [ v [ k ] ] < min ) { j = k; min = c [ k ] [ v [ k ] ] ; } e[ i ] [ 0 ] = v [ j ] ; e[ i ] [ 1 ] = j ; * cost += c [ j ] [ v [ j ] ] ; / * update vector v * / v [ j ]= 0; for ( k = 1; k <= n ; k++ ) i f ( v [ k ] != 0 && c[ k ] [ v[ k ] ] > c[ k ] [ j ] ) v[ k ] = j ; }
58
Backtracking Backtracking is used in developing algorithms for the following type of problems where n sets, say S1 , S2 , . . . , Sn are given, each of ni components. We are required to nd the elements of a vector X = (x1 , x2 , . . . , xn ) S = S1 S2 . . . Sn such that a relation (x1 , x2 , . . . , xn ) holds for the elements of vector X . The relation is called an internal relation, the set S = S1 S2 . . . Sn is called the space of possible solutions, and the vector X = (x1 , x2 , . . . , xn ) is called a result. Backtracking nds all the possible results of the problem. Out of all these, we can pick up one that satises an additional condition. n Backtracking eliminates the need to generate all the i=1 nS possible solutions. To achieve that, in generating vector X we have to obey the following conditions: 9.2.1. xk gets values only if x1 , x2 , . . . , xk1 have already got values. 9.2.2. After setting xk to a value, check the continuation relation (x1 , x2 , . . . , xk ) establishing if it makes sense to evaluate xk+1 . If condition (x1 , x2 , . . . , xk ) is not met, then we pick up a new value for xk Sk and check again. If the set of choices for xk becomes empty, we restart by selecting another value for xk1 and so forth. These reduction in the value of k is the origin of the name of the method. It suggests that when no advance is possible, we back-track the sequence of the current solution.
59
*/ )
l i s t or process solution * /
To process the whole space invoke recBackT rack (1). Backtracking Example. The Queen Placement Problem The problem is stated as follows: Find all the arrangements of n queens on an n n chessboard such that no queen would threaten another, i.e. no two queens are on the same line or diagonal. As on each line there should be only one queen, the solution may be presented as a vector X = (x1 , x2 , . . . , xn ), where xi is the column where a queen is placed on line i. The continuation conditions are:
60
61
62
0 2 1 3 4 5 0
a Here,
output is just an example of a Hamiltonian cycle, but not a Hamiltonian cycle of minimum length
9.9.3. Find the smallest sum of n integer numbers taken from different diagonals parallel with the main diagonal, and including the main diagonal of a matrix A of size n n. . I/O description. Input: number of lines and columns in matrix A, followed by the rows of the matrix, e.g. 3 1 7 2
63
Output: 0 The elements taken were: -2, 0, and 2. 9.9.4. Find the smallest difference of n integer numbers taken from different diagonals parallel with the secondary diagonal, and including the secondary diagonal of a matrix A of size n n. . I/O description. Input: number of lines and columns in matrix A, followed by the rows of the matrix, e.g. 3 1 4 10 7 5 -2 2 3 0
Output: -19 The elements taken were: -2, 7, and 10 (i.e. 2 7 10). 9.9.5. A maze is coded using a n m matrix with corridors represented by 1s situated in consecutive positions on the same line or column, and the rest of the elements are 0. A person is in position (i, j ) inside the labyrinth. List all the exit routes which do not pass the same place twice. . I/O description. Input: n and m on one line, followed by the rows of matrix A, the coordinates (row, column) of the exit, and the coordinates of the person (row, column), e.g. 25 30 000000000000000000000000000000 001111110111111111011111111100 001000010100000000000001000100 ... Output is a sequence of rowcolumn pairs indicating the successive position of the person. 9.9.6. A set of integer numbers is given. Generate all the subsets of this set which sum up to exactly S . . I/O description. Input: enumeration of elements in the set, on one line, then sum on one line e.g. 1 -3 5 -7 2 6 6 Output: enumeration of elements summing up to the given sum, e.g. 1 -3 2 6 5 -7 2 6 ... 9.9.7. A set of natural numbers is given. Generate all the subsets of this set joined by alternating + and - operators which sum up to exactly S . . I/O description. Input: enumeration of elements in the set, on one line, then sum on one line e.g. 1 3 5 7 2 6 0 Output: enumeration of elements resulting in the given sum, e.g. 1-3+2 1+3-6 5-7+2 1+5-6 ... 9.9.8. A ball is placed on a sand dune of varying height, situated in a plane area. The dune height (natural number 255 is coded using a n m matrix with discrete heights represented by natural numbers. The height of the plane area is 1 less than the lowest dune point. The initial position of the ball is given as a rowcolumn pair i, j in the matrix.
64
65
10. Algorithm Design. Divide and Conquer and Branch and Bound
10.1. Purpose
In this lab session we will experiment with divide and conquer and branch and bound algorithm development methods.
10.2.1. c( x)=level of vertex x + distance(current state, f inal state), or 10.2.2. c(x)=cost(parent(x)) + distance(current state, f inal state), where by "level of vertex x" we mean the number of decision made to reach the current conguration. The form of the "distance" function is problem specic. E.g., for the game known as PERSPICO (see laboratory assignments), the distance is the number of tiles which are displaced. Here are some remarks regarding the algorithm for branch and bound, given below: List L holds the nodes storing the state conguration. The parent of current vertex i is stored in vector parent, which allows rebuilding the path from the root node to the nal node. root holds a pointer to the start vertex i.e initial state.
NodeT * root ; root = ( NodeT * ) malloc ( sizeof ( NodeT ) ) ; / * place i n i t i a l configuration at location pointed to by root * / void BranchAndBound( ) { NodeT * i , * j ;
66
The functions used above have the following meanings: makeN ull(L) makes L the empty list; isEmpty (L) returns 1 if L is empty and 0 otherwise; LCelement(L) returns an element on list L having the lowest cost c , to support a LC traversal of the state tree; append(j, L) appends node j to list L. Note that when a node i from list L becomes the current node, then all its descendants are generated, and placed on list L. One of these will be selected as current, based on its cost c , and the process goes on till the nal node is reached. Divide and Conquer Divide and conquer means repeatedly dividing a problem into two or more subproblems of the same type, and then recombining the solved subproblems in order to get a solution of the problem. Let A = (a1 , a2 , . . . , an ) be a vector whose components are processed. Divide an conquer is applicable if for any p and q , natural numbers such that 1 p < q n we can nd a number m [p + 1, q 1] such that processing the sequence ap , ap+1 , . . . , aq can be achieved by processing sequences ap , ap+1 , . . . , am and am , am+1 , . . . , aq , and then combining the results. Briey, divide and conquer may be sketched as follows:
void DivideAndConquer ( int p , int q , SolutionT ) / * p and q are indices in the processed sequence ; i s the solution * / { int , m; SolutionT , ; i f ( abs ( q p ) ) process ( p , q , ) ; else { Divide ( p , q , m ) ; DivideAndConquer ( p , m, ) ; DivideAndConquer ( m + 1 , q , ) ; Combine ( , , ) ; } }
Invocation:
DivideAndConquer ( 1 , n , )
67
10. Algorithm Design. Divide and Conquer and Branch and Bound
is the maximum length of a sequence ap , ap+1 , . . . , aq which can be directly processed; m is the intermediate index where we can split the sequence ap , ap+1 , . . . , aq ; beta and are the intermediate results obtained by processing sequences ap , ap+1 , . . . , am and am , am+1 , . . . , aq , respectively; is the result of combining intermediate solutions and ; split splits the sequence ap , ap+1 , . . . , aq into sequences ap , ap+1 , . . . , am and am , am+1 , . . . , aq ; combine combines solutions and obtaining the nal solution, . E.g. Mergesort a vector of n elements:
#include <stdio . h> #define M A X N 100 int A[ MAXN ] ; / * vector to sort * / void printVector ( int n ) / * print vector elements 10 on one line * / { int i ; p r i n t f ( " \ n" ) ; for ( i = 0; i < n ; i ++ ) { p r i n t f ( "%5d" , A[ i ] ) ; i f ( ( i + 1) % 10 == 0 ) p r i n t f ( " \ n" ) ; } p r i n t f ( " \ n" ) ; } void merge( int lBound , int mid , int rBound) { int i , j , k , l ; int B[ MAXN ] ; / * B = auxiliary vector * / i = lBound ; j = mid + 1; k = lBound ; while ( i <= mid && j <= rBound ) { i f ( A[ i ] <= A[ j ] ) { B[ k ] = A[ i ] ; i ++; } else { B[ k ] = A[ j ] ; j ++; } k++; } for ( l = i ; l <= mid ; l ++ ) { / * there are elements on the l e f t * / B[ k ] = A[ l ] ; k++; } for ( l = j ; l <= rBound ; l ++ ) { / * there are elements on the right * / B[ k ] = A[ l ] ; k++; } / * sequence from index lBound to rBound i s now sorted * / for ( l = lBound ; l <= rBound ; l ++ ) A[ l ] = B[ l ] ; }
68
2 6 14
Figure 10.1.: Example positions for the 15-puzzle. I/O description. Input: tile numbers for initial position starting with the top line, e.g. the position in the example should be (actually one space is enough for separation): 1 0 3 4 5 2 7 8 9 6 10 11 13 14 15 12 Output: consecutive board positions in the same format as the input.
69
10. Algorithm Design. Divide and Conquer and Branch and Bound
10.3.2. There are n cars1 on a rail track, numbered with distinct values from the set {1, 2, . . . , n}. A crane available at that location may pick k cars from the rail track and place them at the end of the sequence of cars (imagine this as being on the right hand side). Then, this cars are pushed to form a continuous line of cars. Given the initial order of the cars, you are required to nd (if possible) the minimum number of operations which the crane must execute to get the cars in sorted order, i.e 1, 2, . . . , n. . I/O description. Input: sequence of car numbers, separated by spaces all on one line. Output: sequence of congurations, one on a line, each line beginning with the number of operation, a colon and the list of cars. E.g. Input: 9 9 2 5 8 1 5 7 6 3 4 Output (given just for how it looks, unchecked): 1: 9 5 8 1 5 7 6 3 4 2
10.3.3. There are 2n natives on a river bank. Out of these, n are man eaters. They wish to cross the river using a boat which can take at most k persons in one trip. If on either the bank or in the boat there are more cannibals than normal people, then the cannibals will eat the others. Find a way to take them all on the opposite bank without losing people to the cannibals and without changing numbers (i.e. use other people than the initial ones). . I/O description. Input: n k , n = number of cannibals, k = boat capacity, e.g. for n = 12, and k = 5: 12 5 Output (initial situation for the same values): L: 12c 12n B: 0c 0n R: 0c 0n Here L means left bank, R means right bank, and B means boat. Other letters mean c=cannibal, n=normal. 10.3.4. There are n goats lined on a bridge, going all in the same direction. From the opposite direction, other n goats are coming towards them. The goats cannot go around each other, but each goat can skip over one single goat of the opposite group and can advance if it there is empty space ahead. Using these two possibilities for a move, make the goat groups cross the bridge. . I/O description. Input: number of goats in one line. Output: sequence of congurations, e.g. initially: [0] Op: none a5 a4 a3 a2 a1 __ b1 b2 b3 b4 b5 [1] Op: advance a1 a5 a4 a3 a2 __ a1 b1 b2 b3 b4 b5 [2] Op: jump b1 a5 a4 a3 a2 b1 a1 __ b2 b3 b4 b5 Here [0] is the step number, a5 a4 a3 a2 a1 is the line of goats going from left to right __ denotes an empty position, and Op: indicates the operation one of none, advance, jump
1 not
automobiles!
70
10.3.6. Consider the design of a round robin chess tournament schedule (works for other contests as well, like volleyball, tennis, etc.), for n = 2k players. Each player (or team) must play every other player and each of them must play for n 1 days the minimum number of days needed to complete the tournament. We get a tournament table of n row per n 1 column, whose entry in row i and column j is the player i must contend with on the j th day. . I/O description. Input: the number of players. Output. The game table as shown below. Note that line 1 contains player numbers, as well as rst column E.g. For eight players, the input is:
1 2 3 4 5 6 7 8
1 2 1 4 3 6 5 8 7
2 3 4 1 2 7 8 5 6
3 4 3 2 1 8 7 6 5
4 5 6 7 8 1 2 3 4
5 6 7 8 5 4 1 2 3
6 7 8 5 6 3 3 1 2
7 8 5 6 7 2 4 4 1
The problem is to tile a board of size 2k 2k with one single tile and 22k 1 Lshaped groups of 3 tiles. A divide-and-conquer approach can recursively divide the board into four, and place a L-grouped set of 3 tiles in the center at the parts that have no extra tile, as shown in Figure 10.3. . 10.3.7. I/O description. Input k 5. Output: board tiling. Different colors should be symbolized with different lowercase letters. Thus, color 1 sould be marked with a, color 2 with b, etc.
Figure 10.3.: Example tiling.
71
10. Algorithm Design. Divide and Conquer and Branch and Bound
Given a set of n points (xi , yi ) the problem asks what is the distance between the two closest points. A brute force approach in which the distances for all the pairs are measured takes O(n2 ) time. A divide-and-conquer algorithm can sort the points along the x-axis, partition the region into two parts Rlef t and Rright having equal number of points, recursively apply the algorithm on the sub-regions, and then derive the minimal distance in the original region. The closest pair resides in the left region, the right region, or across the borderline. The last case needs to deal only with points at distance d = min(dlef t , dright ) from the dividing line, where dlef t and dright are the minimal distances for the left and right Figure 10.4.: Example closest pair problem. 10.3.8. regions, respectively. The points in the region around the boundary line are sorted along the y coordinate, and processed in that order. The processing consists of comparing each of these points with points that are ahead at most d in their y coordinate. Since a window of size d 2d can contain at most 6 points, at most ve distances need to be evaluated for each of these points (see Figure 10.4). The sorting of the points along the x and y coordinates can be done before applying the recursive divide-andconquer algorithm. . I/O description. Input: n, the number of points, followed by point coordinates, one point per line (xi , yi ). Output: coordinates of closest pair (xi , yi )(xj , yj ) on one line. Use parenthesis to mark pairs.
72
Figure 11.1.: States and decisions. If the sequence of decisions di , di+1 , . . . , dj which changes state si1 into state sj (with intermediate states si , si+1 , . . . , sj 1 ) is optimal, and if for all i k j 1 both di , di+1 , . . . , dk and dk+1 , dk+2 , . . . , dj are optimal sequences of decisions which transform state si1 into sk , and state sk into sj respectively, then the principle of optimality is satised. To apply this method we may proceed as follows: Check that the principle of optimality is satised. Write the recurrence equations which result from the rules which govern the state changes and solve them. E.g. Consider the multiplication of a sequence of matrices R = A1 A2 . . . An , where Ai with 1 i n is of size di di+1 . The resulting matrix, R, will be of size d1 dn+1 . It is common knowledge that when multiplying two matrices, say Ai and Ai+1 we must effect di di+1 di+2 multiplications. If the matrices to be multiplied have different numbers of rows/columns, then the number of operations used to get R depends on the order of performing the multiplications. What we would like to nd is the order of performing these multiplications which results in a minimum number of operations. Let Cij be the minimum number of multiplications for calculating the product Ai Ai+1 . . . Aj for 1 j n. Note that: Ci,i = 0, i.e., multiplication for a chain with only one matrix is done at no cost. Ci,i+1 = di di+1 di+2 . C1,n will be the minimum value. The principle of optimality is observed, i.e. Ci,j = min[Ci,k + Ck+1,j + di dk+1 dj +1 , for i k < j , and the associations are (Ai Ai+1 . . . Ak ) (Ak+1 Ak+2 . . . Aj ).
We have to compute Ci,i+d for each level d till we get C1,n . In order to build the binary tree which describes the order of multiplications, we will also retain the value of k which was used in obtaining the minimum, which enables us to show how the matrices are associated. The vertices of this tree will contain the limits of the matrix subsequence for which the matrices are associated. The root will be (1, n), and a subtree rooted at i, j ) will have (i, k ) and (k + 1, j ) as children, where k is the value for which the optimum is obtained. Here is the C code implementing optimal matrix multiplication:
73
74
Heuristics An heuristic algorithm is an algorithm which gives an approximate solution, not necessarily optimal, but which can be easily implemented and has a polynomial growth rate. Heuristics are used in solving problems where a global optimum is not known to exist and where results close to optimum are acceptable. One way to cope with such problems is to decompose the process of nding a solution into successive processes for which the optimum is searched. Generally, this does not lead to a global optimum, as local optima do not imply the global optimum is reached. In practice, a number of approximate solutions are found, and the best of them is then selected. E.g. Let us now consider the traveling salesman problem. Let G = (X, ) be an undirected complete1 graph with a strictly positive cost associated to each edge. We are required to nd a cycle, starting with vertex i, which passes through all nodes exactly once, and ends at i. It is possible to nd an optimal solution, using backtracking, but that will take exponential time. The heuristic for which the code is given below, uses the greedy method and requires polynomial time. The steps to obtain the solution are: If (v1 , v2 , . . . , vk ) is the already built path then If {v1 , v2 , . . . , vk } = X then add edge (vk , V1 ) and the cycle is completed. If {v1 , v2 , . . . , vk } = X then add the minimum cost edge which connects vk to a vertex in X not included yet. As a cycle is a closed path, we may start at any one node. So we could pick up each of 1, 2, . . . , n, in turn, as a start vertex, nd the cycle, and retain the minimum of them all. Here is the code for beginning with node i:
#include <stdio . h> #include <l i m i t s . h>
1 remember:
75
76
77
78
79
a0 , ah , a2h , . . . a1 , a1+h , a1+2h , . . . . . . ah , a2h , a3h , . . . h is called an increment. Then, shellsort involves the selection of k increments, in descending order, i.e. h 1 > h 2 > h 3 > . . . > hk = 1 and performing a h1 sort, then a h2 sort, and so on, and nally a 1sort. The performance of the method is tightly connected to the selection of the increments. In the example code given in Listing 12.1, the function directInsertSort implements sorting by direct insertion, and the function shellSort implements shellsort. The running time for direct insertion sort is O(n2 ), and the growth rate of shellsort is O(n log n). A similar growth rate O(n log n) is found for binary insertion sort. Swap Sort Swap sort means to execute successive swaps of elements, i.e. ai aj till all the elements of the sequence are arranged in ascending order. Methods based on swaps are bubblesort and quicksort. Bubblesort is achieved by comparing elements ai and ai+1 and, if ai ai+1 proceeds further to comparing ai+1 and ai+2 . If ai > ai+1 then ai and ai+1 are swapped. Then ai+1 is compared to ai+2 . After the rst scan of the vector, the element of highest value gets in the last position; after the second scan the second highest gets in second-last position, and so on. The running time is O(n2 ). Quicksort is attributed to C.A.R. Hoare and uses the divide-and-conquer approach. The principle of quicksort is: pick
80
81
for ( i = 0; i < n ; i ++ ) { c [ i ] =0; / * i n i t i a l i z e counting vector * / b [ i ] =a [ i ] ; / * copy vector a to b * / } / * count * / for ( j = 1; j < n ; j ++ ) for ( i = 0; i <= j 1; i ++) i f ( a [ i ] < a [ j ] ) c [ j ]++; else c [ i ]++; / * rearrange vector a * / for ( i = 0; i < n ; i ++ ) a [ c [ i ] ] = b [ i ] ; } void directInsertionSort ( int n , float a [ MAXN ] ) / * implements direct insertion sort * / { int i , j ; float x ; for ( j = 1; j < n ; j ++ ) { x = a[ j ] ; i = j 1; while ( i >= 0 && x <a [ i ] ) { a[ i + 1 ] = a[ i ] ; i = i 1; } a [ i + 1] = x ; } } void shellSort ( int n , float a [ MAXN ] ) / * implements s h e l l s o r t * / { int i , j , incr ; float x ; incr = 1; while ( incr < n ) incr = incr * 3 + 1; while ( incr >= 1 ) { incr /= 3; for ( i = incr ; i < n ; i ++ ) { x = a[ i ] ; j = i; while ( a [ j incr ] > x ) { a [ j ] = a [ j incr ] ; j = j incr ; i f ( j < incr ) break ; } a[ j ] = x ; } } } void bubbleSort ( int n , float a [ MAXN ] ) / * implements bubblesort * / { int i , j , f i n ; float x ; j = 0; do {
82
83
12.2.
12.3.
84
85
Bibliography
[***99] ***. Programming in C. WWW: http://www.lysator.liu.se/c/index.html, 1999. Lynkoeping University, Sweden.
[AHU87] Alfred V. Aho, John E. Hopcroft, and Jeffrey D. Ullman. Data Structures and Algorithms. Addison-Wesley Publishing Company, 1987. [AS96] Harold Abelson and Gerald Jay Sussman. Structure and interpretation of computer programs. MIT Press, USA, second edition, 1996. WWW: http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-1.html.
[Buh02] Jeremy Buhler. CS241 Algorithms and Data Structures. Handouts. WWW: http://students.cec.wustl.edu/ cs241/handouts, 2002. Washington University in St. Louis, USA, Center for Engineering Computing. [Del02] [Dev97] DJ Delorie. DJGPP - A free 32-bit development system for DOS. WWW: http://www.delorie.com, 2002. Luc Devroye. Data Structures and Algorithms. Class Notes Compiled by Students. http://cgm.cs.mcgill.ca/ luc/1997notes.html, 1997. School of Computer Science, McGill University, Montreal, Canada.
[Dow02] Allen B. Downey. How To Think Like A Computer Scientist. Learning with C++. WWW:http://www.ibiblio.org/obp/thinkCScpp/, 2002. [GBY90] Gaston H. Gonnet and Ricardo Baeza-Yates. Handbook of Algorithms and Data Structures. Addison-Wesley Publishing Co. Inc., 1990. WWW: http://www.dcc.uchile.cl/ rbaeza/handbook/hbook.html. [Haa99] Johan Haastad. Advanced Algorithms. Lecture Notes. WWW: http://www.oopweb.com/Algorithms/Download/algnotes.zip, 1999.
[Hoh02] Robert Hohne. Interactive development environment - RHIDE. WWW: http://www.rhide.com, 2002. [Hol95] Steve Holmes. C Programming. WWW: http://www.strath.ac.uk/IT/Docs/Ccourse/, 1995. University of Strathclyde Computer Center, Glasgwo, UK.
[Knu73] Donald E. Knuth. The Art of Computer Programming, volume 1. Fundamental Algorithms. Addison Wesley, Reading, Mass., USA, second edition, 1973. [Koe89] [KR78] [Les96] Andrew Koenig. C Traps and Pitfalls. Addison-Wesley Publishing Company, 1989. PDF version: http://www.programmersheaven.com/search/download.asp?FileID=391. Brian W. Kernighan and Dennis M. Ritchie. The C Programming Language. Prentice-Hall, Englewood Cliffs, New Jersey, USA, rst edition, 1978. Martin Leslie. C Programming Reference. Release 1.06. WWW: http://mip.ups-tlse.fr/C_ref/C/cref.html, 1996.
[Mor98] John Morris. Data Structures and Algorithms. Lecture Notes. WWW: http://www.oopweb.com/Algorithms/Documents/PLDS210/VolumeFrames.html, 1998. Electrical and Electronic Engineering, University of Western Australia. [Mou99] David M. Mount. Data Structures CMSC420. Lecture Notes. WWW: http://www.oopweb.com/Algorithms/Download/420lects.zip, 1999. [Nav] [NIS00] Jacob Navia. LCC-Win32: a free compiler system for Windows. WWW: http://www.cs.virginia.edu/ lcc-win32/. NIST. Dictionary of Algorithms and Data Structures. National Institute of Standards and Technology, USA, 2000. WWW: http://www.nist.gov/dads.
[Oua92] Steve Oualline. C Elements of Style. M&T Books, 1992. WWW: http://www.oualline.com/style.
86
Bibliography
[Oua97] Steve Oualline. Practical C Programming. OReilly & Associates, 1997. Book examples: http://examples.oreilly.com/pcp3. [PB01] [Ski98] [Sof02] P.J. Plauger and Jim Brodie. Dinkum C99 Library. Dinkumware Ltd., Concord MA, USA, 2001. WWW: http://www.dinkumware.com/refxc.html. Steven S. Skiena. The Stony Brook Algorithm Repository. WWW: http://www.cs.sunysb.edu/ algorithm, 1998. Computer Science, State University of New York. Bloodshed Software. The Dev-C++ Resource Site. WWW:http://www.bloodshed.net/dev/devcpp.html, 2002.
[Sum95] Steve Summit. comp.lang.c Frequently Asked Questions. WWW: http://www.eskimo.com/ scs/C-faq/top.html, 1995. [Sym99] Antonios Symvonis. Algorithms COMP 3001. Lecture Notes. WWW: http://www.oopweb.com/Algorithms/Download/Algorithms.zip, 1999. [Ven02] [Wir76] Andreas Veneris. ECE242: Algorithms and Data Structures. Lecture Notes. WWW: http://www.ecf.utoronto.ca/apsc/courses/ece242f/, 2002. University of Toronto, Canada. Niklaus Wirth. Algorithms + Data Structures = Programs. Prentice Hall, Englewood Cliffs, NJ, USA, 1976.
87
Please use the template of listing A.1 for your descriptive box: Listing A.1: Descriptive box.
/****************************************************************** * list.h * File Name: * To supply prototypes and other * Purpose: declarations for the list module. * * DSAL.105.AC.01.04-B2/2003 * Course ID: * Student Name: I. V. Ionescu [email protected] * E-mail: * ****************************************************************** * Module History * Description * Date
88
A.6. Macros
Macro names will always be in uppercase. Whenever possible, the body of a macro must be enclosed in parentheses, like this:
#define MAX_SIZE (200)
An instance of an argument in a macro expansion must be enclosed in parentheses whenever possible, as in the following:
#define SQ_ROOT( n ) (pow( (n ) , .5 ) )
89
Compound statements will be terminated with the closing brace on a line by itself, aligned directly underneath the opening brace. This is an example:
static void processFile ( FILE * f i l e ) { while ( ! feof ( f i l e ) ) { . . . } . . . }
Leave one space from the parentheses as you probably notices in all the supplied examples. This improves readability. Listing A.3 shows the template to use for *.h les, and listing A.2 shows the template to use for *.c les: Listing A.2: Template for *.c les
/****************************************************************** * <file name here> * File Name: * <purpose here> * Purpose: * DSAL.105.AC.01.04-B2/2002 * Course ID: * Student Name: <your name here> <your email here> * E-mail: * ******************************************************************* * Module History * Description * Date ------------------------* ------First submission of Project #1 * dd-lll-aa ******************************************************************/ #include <system includes> #include "<project includes specified relative to project base directory eg. inc/list.h>
90
/* Global variables ---------------*/ <variable declarations. Same as in .h except without extern and with initializers> /* Module types -----------*/ <declarations for types which are only used within the module. Should be storage class static> /* Module variables ---------------*/ <declarations for variables which are global to this module only. Should be storage class static> /* Module functions ---------------*/ <definitions for functions which are used in this module only. Should be storage class static> /* Global functions ---------------*/ <definitions for the functions prototyped in .h>
91
92
B. C Language Reference
Program Structure and Functions
type f nc(type1 , . . .); type name; main() { declarations statements } type f nc(arg1 , . . .) { declarations statements return value; } /* */ main(int args, char *argv[]) exit(arg ) function declarations external variable declarations main routine local variable declarations
C Preprocessor
include library le #include < f ilename > include user le #include f ilename replacement text #define$ name text replacement macro #define$ name(var) text E.g. #define max(A,B) ((A)>(B) ? (A) : (B)) undene #undef name quoted string replace # concatenate args and rescan ## conditional execution #if, #else, #elif, #endif is name dened, not dened? #ifdef, #ifndef name dened? defined(name) line continuation char \
93
B. C Language Reference
Initialization
initialize variable initialize array initialize char string type name = value; type name[] = {value1 , . . .}; char name[] = string ;
Constants
long (sufx) oat (sufx) exponential form octal (prex 0) hexadecimal (prex zero-ex) character constant (char, octal, hex) newline, cr, tab, backspace special characters L or l F or f e 0 0x or 0X a, \ooo, \xhh \n, \r, \t, \b \\, \?, \, \"
94
Flow of Control
statement terminator block delimiters exit from switch, while, do, for next iteration of while, do, for goto label return value from function Flow Constructions if statement ; { } break continue goto label : return expr if (expr) statement else if (expr) statement else statement while (expr) statement for (expr1 ; expr2 ; expr3 ) statement do statement while (expr) switch (expr) { case const1 : statement1 break case const2 : statement2 break default: statement }
95
B. C Language Reference
compare n chars of cs with ct pointer to rst c in rst n ofcs put c into rst n chars of s memcmp(s.ct,n) memchr(cs,c,n) memset(s.c,n)
Input/Output <stdio.h>
Standard I/O standard input stream standard output stream standard error stream end of le get a character print a character print formatted data print to string s read formatted data read from string s read line to string s(<max chars) print string s File I/O declare le pointer pointer to named le stdin stdout stderr EOF getchar() putchar(chr) printf("f ormat",arg1 , . . .) sprintf(s,"f ormat",arg1 , . . .) scanf("f ormat",&name1, . . .) sscanf(s,"f ormat",&name1, . . .) gets(s,max) puts(s)
FILE *f p fopen("name","mode") modes: r (read), w (write), a (append) get a character getc(f p) write a character getc(chr, f p) write to le fprintf(f p,"f ormat",arg1, . . .) read from le fscanf(f p,"f ormat",&name1, . . .) close le fclose(f p) non-zero if error ferror(f p) non-zero if EOF feof(f p) read line to string s(<max chars) fgets(s,max,f p) Codes for Formatted I/O: "%-+ 0w.pmc left justify + print with sign space print space if no sign 0 pad with leading zeros w min eld width p precision m conversion character: h short, l long, L long double
d,i integer u unsigned c single char s char string f oat e,E exponential o octal x,X hexadecimal p pointer n number of chars written g,G same as f or e,E depending on exponent
96
absolute value of long n labs(n) quotient and remainder of ints n,d div(n,d) returns structure with div_t.quot and div_t.rem quotient and remainder of longs n,d div(n,d) returns structure with ldiv_t.quot and ldiv_t.rem pseudo-random integer [0,RAND_MAX] rand() set random seed to n srand(n) terminate program execution exit(status) pass string s to system for execution system(s) Conversions convert string s to double convert string s to integer convert string s to long convert prex of s to double convert prex of s (base b) to long same, but unsigned long Storage Allocation allocate storage change size of object deallocate space Array Functions search unsortedarray for key search sortedarray for key sort array ascending order
97
B. C Language Reference
98
Index
abstract data type, 3 adjacency list, 47 adjacency matrix, 47 algorithm Dijkstras, 52 Floyds, 53 heuristic, 75 Kruskals, 54 Prims, 58 algorithm development backtracking method, 59 branch and bound method, 66 divide-and-conquer method, 67 dynamic programming method, 73 greedy method, 57 arc head, 47 tail, 47 cost, 52 matrix, 52 cycle, 47 digraph, 47 labelled, 47 strongly connected, 47 graph arc, 47 directed, see digraph edge, 47 minimum spanning tree, 54 traversal breadth-rst, 47 depth-rst, 50 undirected, 47 hashing collision resolution, 44 hash function, 43 hash value, 43 list, 1 doubly-linked, 14 singly-linked, 1 singly-linked, circular, 8 mergesort, 68 optimal solution, 73 path in a graph, 47 simple, 47 queue, 4 solution space, 59 sort bubblesort, 80 by counting, 80 by direct selection, 81 by insertion, 80 by swapping, 80 quicksort, 80 stack, 4 subgraph, 47 induced, 47 table, 43 tree arbitrary, 26 binary, 22 binary search tree, 29 optimal binary search tree, 31
99