Anda di halaman 1dari 103

Data structure Course Outline

Part I : The dynamic array

(1)  Introduction to data structures
(2)  Creating our first data structure with functional support
(3)  The dynamic array
(4)  Design
(5)  Operations
(6)  Pseudocode: core functions
(7)  Pseudocode: utility functions
(8)  The DArray definition (DARRAY.H)
(9)  The DArray operations (DARRAY.C)
(10) Code to test the DArray (DATEST.C)
(11) Packaging the DArray
(12) An application of the DArray
(13) Memory map
(14) Application pseudocode
(15) Selected application code
(16) Choosing the best DArray size, growth
(17) Selected exercises

Part II : The dynamic sorted array

(18) A dynamic sorted array
(19) Sorting algorithms
(20) Algorithm performance
(21) Quicksort
(22) A partition algorithm
(23) Quicksort pseudocode
(24) Binary search
(25) Packaging the dynamic sorted array
(26) DSArray pseudocode
(27) The DSArray definition (DSARRAY.H)
(28) The DSArray operations (DSARRAY.C)
(29) Code to test the DSARRY(DSTEST.C)
(30) A fast record search application
(31) Memory map
(32) Application pseudocode
(33) Selected exercises

Part III : The linked list

(34) The linked list
(35) What is a linked list?
(36) Inserting/deleting elements
(37) Searching for elements
(38) Design
(39) Linked list traversal
(40) Formal definition
(41) Pseudocode for the operations
(42) The linked list definition (LL.H)
(43) The linked list operations (LL.C)
(44) Code to test the linked list (LLTEST.C_
(45) A linked list of heterogeneous data
(46) Design
(47) Data definition
(48) Memory map
(49) Pseudocode
(50) Selected application code
(51) Doubly linked lists
(52) Sorted linked lists
(53) Selected exercises
Part IV : The hash table

(54) What is a hash table?
(55) How does the hash table provide fast search/add?
(56) Rehashing
(57) Choosing the number of buckets
(58) Formal definition
(59) Pseudocode for the operations
(60) The HashTable definition (HT.H)
(61) The HashTable operations (HT.C)
(62) Code to test the hash table (HTTEST.C)
(63) Output of HTTEST.C
(64) Testing the rehash() function
(65) Code to test rehashing (HTTEST2.C)
(66) Output of HTEST2.C
(67) Car dealership search
(68) Design
(69) AUTO.H
(70) AUTOTEST.C
(71) Output of AUTOTEST.C
(72) Designing hash functions
(73) Which fields to hash on?
(74) Multiple hashing
(75) Analysis of the multiple hashing example
(76) A better solution...
(77) Multiple hashing on ForestryRegister
(78) How to implement multiple hashing
(79) Selected exercises

Part V : The binary tree

(80)  The binary tree
(81)  Searching for elements in a binary tree
(82)  Search performance
(83)  Adding elements to a binary tree
(84)  How is the tree organized in memory
(85)  Binary tree definition
(86)  Ordering elements in the tree
(87)  Deleting elements in the tree
(88)  A deletion algorithm
(89)  The binary tree definition (BT.H)
(90)  Pseudocode for the operations
(91)  The binary tree definition (BT.C)
(92)  Code to test the binary tree (BTTEST.C)
(93)  Output of BTTEST.C
(94)  Expression trees
(95)  Expression tree evaluator (EXP.H)
(96)  Expression tree evaluator (EXP.C)
(97)  Cross­referencing with trees
(98)  Company­employee original database
(99)  Employee history generated tree
(100) Performing queries
(101) COMPANY.DAT input file
(102) Employee cross­referencer (COMPANY.H)
(103) Employee cross­referencer (COMPANY.C)
(104) Output of COMPANY.C
(105) Expanding a BTNode's definition
(106) Supporting complex BTNodes
(107) Inorder, Postorder, Preorder traversals
(108) Balancing binary trees
(109) Balancing algorithm pseudocode
(110) B­Trees
(111) Selected exercises
Introduction to data structures

After having studied the basics of the C language it is now possible to study more advanced 
programming techniques.  If we are to write fast and efficient programs we will have need of 
optimal programming building blocks.  These we can call data structures.

A data structure is a packaged unit of functions that works on specific data to produce some 
useful result.
  

To fully appreciate the worth of a data structure, it is necessary to study in detail the design and 
implementation of some standard ones from first principles.  Four core data structures will be 
discussed in detail:

Dynamic Array
Linked  List
  Hash    Table
     Binary  Tree

Even after having designed these data structures from scratch, the other aspect of the art 
of designing good programs is to decide, when given a problem, how to apply data structures to 
solve the problem at hand.  This will be addressed in the latter portion of this data structure unit.
Advantages of data structures

I.   Promotes re­use of important functions on data.

II.  Implementation is hidden from the user to allow
     concentration on high level solutions of the problem at
     hand.

III. Prepackaged data structures by third party vendors can be
     guaranteed to be terribly efficient, probably the best
     solution for low level data manipulation.  The more
     optimized libraries that we use in our software, the
     greater the potential exists for faster, more powerful
     results with the minimum of coding.

IV.  A standard set of functions provides a separation from
     low­level implementation to the high level application,
     giving room for enhancing/modifying the data structure
     functions to other methodologies if the need be without
     touching the application code.
I ­ Creating our first data structure with functional support

The standard C arrays int i[5]; float f[10]; ... can be considered data structures but with minimal 
built­in support.  We have to write the functions from scratch to do basic but well­defined 
operations: add an element, remove an element, search for an element, display an element, edit 
an element, ...

If we had a pre­packaged array type which included the above
operations for free, we could concentrate on how to use
this black box to solve higher applications, like a record
manager, an attendance monitor, a graphics application, etc.

Note that we don't have such a black box shipped with standard C so we have to write our own 
code from scratch which unfortunately, ultimately involves writing the same kind of logic again 
and again for each and every application that requires comprehensive array capabilities.

To avoid this tedious scenario, why not create an array package, or data structure, general 
enough to be used in many programs.  We can extend its capabilities from the standard C array 
type to include the following aspects:

. stores arbitrary types of data
. resizes itself automatically
       as data is added/deleted over time
. range checks index accesses to preserve integrity  

We can call our a DARRAY, or dynamic array, a vector list implementation whose internal size 
varies over time. 
The Dynamic array

We have described the general qualities of a DArray, but what does it look like internally?

Physically, we can picture a vector, or contiguous list of pointers to data of some type:

                ­­­­­
            [0]|     | ­­­­­­­­­­­­>  Data
            [1]|     | ­­­­­­­­­­­­>  Data
            [2]|     | ­­­­­­­­­­­­>  Data
            [3]|     | ­­­­­­­­­­­­>  Data
             . |     |   . 
             . |     |   .
             . |     |   .
  [nElements­1]|     | ­­­­­­­­­­­­>  Data
             . |     |
             . |     |
             . |     |
       [size­1]|     |   
                ­­­­­    ­­
                           |
               |     |     | possible future growth
                           | of DArray by <growthSize>
               |     |     | if necessary
                           |
               |     |     |
                ­ ­ ­    ­­ 
               |     |    
                         
               |     |    
                          
               |     |    
                ­ ­ ­  
                 .
                 .
                 .
Design

I..Indirect or direct storage?

We can base our design on a list of pointers to data instead of a list of raw data in this case 
because we really don't know what type of data users of the DArray will be using, so we could 
never guess the size of each cell beforehand.  Also as we have seen, it is much more memory 
efficient to allocate pointers to data initially, and at run­time point them to real­life data that is 
needed and created via malloc. 

Along this line, since the application of interest knows best about the data it is working on, we 
will leave it up to the application designer to manage the creation/deletion of the data.  The 
DArray's sole concern is to manage the pointers to the data.

II..Internal variables?

Note that we have two important variables that describe the contents of the DArray, 
<nElements> and <size>.

nElements   describes the actual number of pieces of data stored
            in the list.

size        describes the actual size of the vector of pointers
            which changes over time.

   <size> is always >= <nElements>

growthSize  describes the extension cell size of the array
            when the <size> is exhausted
III..Dynamic paradigm?

Adding elements to the DArray is not too difficult if we foresee ourselves patching items to the 
end of the list.  But removing elements presents difficulties.  What do we do when a hole is 
created by deleting a pointer to a piece of data in the middle of the list?  One paradigm, or 
method is to shrink, or shift the data downwards to absorb the hole:

 ­­­                      ­­­
|   | ­>  T              |   |  ­> T
|   | ­>  X              |   |  ­> X
| x | ­>  Y      ==>     |   |  ­> Q
|   | ­>  Q              |   |  ­> Z
|   | ­>  Z              |   |
|...| ­>                 |...|
 ­­­                      ­­­

Before deleting "Y"      After deleting "Y"

The advantage of this method is that we keep the pointer list tight, a one­to­one snapshot of the 
data in the list.  The disadvantage is that each memory shift operation is time expensive and may 
slow down the performance appreciably in large lists.
An alternate paradigm may reduce the overall number of memory shifts, that is to keep holes 
vacant, or in more technical terms, keep holes as holes and only do memory shifts when 
necessary, at a certain user­defined tolerance, say when the number of holes exceeds a certain 
absolute tolerance or a certain percentage of the size of the DArray.

Ex: size = 4, Tolerance = 50 % of size

 ­­­         ­­­         ­­­         ­­­            ­­­
|   | ­> A  | x |       |   |       |   |          |   | ­> B
|   | ­> B  |   | ­> B  |   | ­> B  |   | ­> B     |   | ­> D
|   | ­> C  |   | ­> C  |   | ­> C  | x |          |...|
|...|       |...|       |   | ­> D  |   | ­> D     |   |
 ­­­         ­­­         ­­­         ­­­            ­­­

            Delete "A"   Add "D"    Delete "C"     Result
                                       /\            /\
                                       ||   hole     ||
                                       || adjustment ||
                                       || triggered  ||
                                        ­­­­­­­­­­­­­­

Using this paradigm, we definitely reduce the number of overall memory shifts, but another 
contingency crops up.  What if the user of the data structure refers to a particular cell, expecting 
a particular piece of data, assuming a tight representation of the data, only to find that at some 
magical time, namely at hole adjustment time, the data has been shifted beyond his knowledge?

Which dynamic model to choose?

There are many other paradigms, too numerous to discuss at this time.  For the sake of 
simplicity, and considering that the DArray is our first data structure, let's opt for the first 
solution, the tight model.
Operations

Let's propose a series of specific functions on our DArray that application designers would like 
to have.

     (1) init(darray,initialSize)
     (2) add(element,darray)
     (3) remove(index,darray,shouldDelete)
     (4) flush(darray,shouldDelete)
     (5) nElementsIn(darray)
     (6) get(index,darray)
     (7) searchFor(element,darray,comparisonFunction)
(8) wrapup(darray)

Armed with a DArray supporting these functions we could solve many useful problems.  This is 
great, but where do we start?

We have already begun by identifying the memory model and the
operations on the data of interest.  Now we must be sure to pseudocode the operations and walk 
through the first round logic with several test cases before launching into the code.

Why?  Because if we don't, we are sure to make mistakes, if not a mess and regret jumping into 
the code too quickly, especially faulty code.  Don't be alarmed!  Even experienced programmers 
suffer from this nasty habit!
Pseudocode: core functions

********************************************************
* Initialize <array> to <initialSize>

init(array,initialSize,growthDelta)
  nElements   = 0
  size        = initialSize;
  growthSize  = growthDelta
  array       = new array of size [initialSize]
 
********************************************************
* add a piece of <data> to the <array>

add(data,array)
  if (nElements >= size)
     grow(array)

  array[nElements] = data
  nElements = nElements + 1

********************************************************
* remove a <index>'th piece of data from the <array>
* if <shouldDelete> is set, the data is deallocated

remove(index,array,shouldDelete)
  if (!inRange(index,array)
return ERROR
  else
if (shouldDelete)
      deallocate(array[index])
removeHole(index,array)
     nElements = nElements ­ 1
     return NO_ERROR

********************************************************
* Remove all pieces of data from the array
* if <shouldDelete> is set, the data is deallocated

flush(array,shouldDelete)
   if (shouldDelete)
for (i = 0 to nElements­1)
        remove(i,array,shouldDelete)

   nElements = 0
********************************************************
* Return the number of elements in <array>

nElementsIn(array)
  return nElements

********************************************************
* Access the <index>'th piece of data in <array>
  and return a reference (address) to the data

get(index,array)
   if (index >= 0 AND index < nElements)
    return array[index]
   else
     return errorReference

********************************************************
* Linear search to check if <searchElement> matches
* any element in <array> recognized by <comparisonFunction> 
* Returns the index of the matched item

searchFor(searchElement,array,comparisonFunction)
   for (i = 0 to nElements­1)
  if (comparisonFunction(array[i],searchElement)
        return (i)
   return(NOT_FOUND)

********************************************************
* Deallocate all memory associated with array
* if <shouldDelete> is set, the items in the array
* are automatically deleted

wrapup(array,shouldDelete)
   flush(array,shouldDelete)
   delete array
Pseudocode : utility functions

********************************************************
* Check to see if <index> is a valid index into <array>
* Returns <TRUE> or <FALSE>

inRange(index,array)
if (index < 0 OR index >= nElements)
      return FALSE
else
return TRUE

********************************************************
* Expand an <array> by <growthSize>
*  and recopy the original contents to new area of memory

grow(array)
newSize = size + growthSize
     newArray = a new area of memory of <newSize>
i = 0
while (i < size)
newArray[i] = array[i]

deallocate <array>

array = newArray
size  = newSize

********************************************************
* Remove the hole at array[index] by shifting elements
* in <array> from <index> to <nElements> down one cell

removeHole(index,array)
for (i = index + 1 to nElements­1)
array[i­1] = array[i];
The DArray definition (DARRAY.H)

#ifndef DARRAYH
#define DARRAYH

/* Constants */

enum {FALSE,TRUE};
enum {ERROR,NO_ERROR};

#define NOT_FOUND ­1

/* Data description */

typedef struct
{
void **array;
int nElements;
int size;
int growthSize;
} DArray;

typedef int (*CompareFunction)(void *data1,void *data2);

/* Function prototypes */

int init(DArray *d,int initialSize,int growthDelta);
void wrapup(DArray *d,int shouldDelete);
int add(DArray *d,void *data);
void reMove(DArray *d,int index,int shouldDelete);
void flush(DArray *d,int shouldDelete);
int search(DArray *d,void *data,CompareFunction cmp);
void *get(DArray *d,int index);
int nElements(DArray *d);

#endif

Notes:

We can generalize the type of data pointed to by declaring it as the generic void*.  The type of 
data that <array> can point to is now arbitrary.  It could be int*, float*, char* or any user defined 
structure type like Student*, Record*, etc.  void* is ideal for the purposes of creating a DArray 
data structure that can handle any type of data.

We defer creating the actual elements of the internal array until run­time when the user of the 
DArray has specified what the initial size of the list should be.
The DArray operations (DARRAY.C)

#include "darray.h"
#include <stdlib.h>

/* Utility functions */

static void display(DArray *d)
{
printf("DA:nElements,size,growthSize=%d,%d,%d\n",
d­>nElements,d­>size,d­>growthSize);
}

static int inRange(DArray *d,int index)
{
if ((index < 0) || (index >= d­>nElements))
{
printf("Index not in range %d\n",index);
display(d);
return(FALSE);
}
else
return(TRUE);
}

static int grow(DArray *d)
{
int newSize;

if (d­>growthSize <= 0)
return(ERROR);

newSize = d­>size + d­>growthSize;
d­>array = (void**)realloc(d­>array,sizeof(void*)*newSize);
if (!d­>array)
return(ERROR);
d­>size = newSize;
}

static void removeHole(DArray *d,int index)
{
int i;
for (i = index+1; i < d­>nElements­1; i++)
d­>array[i­1] = d­>array[i];
}
/* ­­ Core functions ­­ */

int init(DArray *d,int initialSize,int growthDelta)
{
if (initialSize <= 0)
initialSize = initialSize;

d­>nElements   = 0;
d­>size        = initialSize;
d­>growthSize  = growthDelta;
d­>array       = (void**)malloc(sizeof(void*)*initialSize);

return(d­>array ? NO_ERROR : ERROR);
}

void wrapup(DArray *d,int shouldDelete)
{
flush(d,shouldDelete);
free(d­>array);
}

int nElements(DArray *d)
{
return(d­>nElements);
}

int add(DArray *d,void *data)
{
if (d­>nElements >= d­>size)
if (ERROR == grow(d))
return(ERROR);
d­>array[d­>nElements] = data;
d­>nElements++;

return(NO_ERROR);
}

void reMove(DArray *d,int index,int shouldDelete)
{
if (!inRange(d,index))
return(ERROR);
if (shouldDelete)
free(d­>array[index]);
removeHole(d,index);
d­>nElements­­;
return(NO_ERROR);
}

void flush(DArray *d,int shouldDelete)
{
int i;
if (shouldDelete)
for (i = 0; i < nElements; i++)
remove(d,i,shouldDelete);
d­>nElements = 0;
}

int search(DArray *d,void *data,CompareFunction cmp)
{
int i;
for (i = 0; i < d­>nElements; i++)
if ((*cmp)(d­>array[i],data))
return(i);
return(NOT_FOUND);
}

void *get(DArray *d,int index)
{
if (!inRange(d,index))
return(0);
return(d­>array[index]);
}
Code to test the DArray (DATEST.C)

#include <stdio.h>
#include <string.h>
#include "darray.h"

/* ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ */
/* APPLICATION TEST CODE on integer data  */
/*  to verify the basic operations of the */
/*  DARRAY, dynamic array data structure  */
/* ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ */

static void displayData(DArray *d)
{
int i;
printf("[");
for (i = 0; i < nElements(d); i++)
{
int *intData = (int*)get(d,i);
printf("%d ",*intData);
}
printf("]\n");
}

static int compareData(void *d1,void *d2)
{
int *id1 = (int*)d1,*id2 = (int*)d2;
return( (*id1 == *id2) ? TRUE : FALSE);
}

void main()
{
int i=1,j=2,k=3,m=4,n=5;
int index,*idata;
DArray da,*d = &da;

init(d,2,2);
add(d,(void*)&i);
add(d,(void*)&j);
add(d,(void*)&k);
add(d,(void*)&m);
displayData(d);
reMove(d,2,FALSE);
add(d,(void*)&n);
displayData(d);
index = search(d,(void*)&j,compareData);
if (index != NOT_FOUND)
printf("<j> found at index <%d>\n",index);
flush(d,FALSE);
displayData(d);
idata = (int*)get(d,89);
wrapup(d,FALSE);
}

Output                                   Memory  map
 
                                              ­­­
1 2 3 4                                 d ­>0| . | ­> | i |    Initial size is 2 cells 
1 2 3 5                                     1| . | ­> | j |       
<j> found at index <1>                      2| . | ­> | k |    Cells 2,3 are added
[]                                          3| . | ­> | n |    upon third add(..) call
Index not in range 89                         ­­­              
DA:nElements,size,growthSize=0,4,2 
Packaging the DArray

Note the separation between the application code and the generic data structure code.  Two 
independent teams are working here.  The data structure designers have created the DArray 
independently, without prior knowledge, of what application designers will use it for.

                                   |
                                   |
                                   |
  ­­­­­­­­­­­­­­­­­­­­­­­­­        |         ­­­­­­­­­­­­­­­
 |  Data structure support |       |        | Application   |
  ­­­­­­­­­­­­­­­­­­­­­­­­­    <=======>     ­­­­­­­­­­­­­­­
                              Communication
                              occurs via the
                              operations or
                              public interface
   DARRAY.H                        |
   DARRAY.C                        |           APP.C
                                   |

This separation is important.  The application should not demand to know the internal details of 
the data structure, nor should the data structure demand to know the nature of every application 
that will use it.

If in any way a data structure depends on specifics of the application then we question the quality
of its design.  For example, if we had hard­coded the DArray to work only with string data, then 
we seriously restrict its application in the real world.

Vice­verse, if the application designer modifies the DArray search function to perform correctly 
for the specific data it works on, then we have the sense that the application is overstepping its 
bounds.
An application of the DArray

The test application was worthy of demonstrating the usage of the DArray functions, but a small 
application is worthy of demonstrating its real world application.  Particularly, we should 
investigate the functioning of the DArray when records are allocated on the fly, stored in the 
DArray and deleted when processing is completed.

Consider a personal telephone directory application that reads name/telephone pairs from a file 
and allows the user to search for a particular person's phone number.  Here is a sample run:

1 ­ Read phone list
2 ­ Search for a phone number
3 ­ Quit

     => 1

Enter phone file: => phone.dat
21 records read...

=> 2
Enter person's name: => karen t
Karen Telford, 567­8901

=> 2
Enter person's name: => joe crow
Sorry, no directory for joe crow
...

phone.dat

  Larry_Radon   345­5671
  Susan_Verlin  431­7788
  Marla_Hawkens 245­4566
  ...
 
Memory map

Before getting to the code, let's first consider how the data could be stored in memory, assuming 
that pointers to the data are managed by a DArray, d:

d:

            ­­­       ­­­
 array ­>  | . | ­>  | . | ­> "Larry Radon"
           |   |     | . | ­> "345­5671"
           |   |      ­­­
            ­­­       ­­­
           | . | ­>  | . | ­> "Susan Verlin"
           |   |     | . | ­> "431­7788"
           |   |      ­­­
            ­­­       ­­­
           | . | ­>  | . | ­> "Marla Hawkens"
           |   |     | . | ­> "245­4566"
           |   |      ­­­
            ­­­
             .
             .
             .
Application pseudocode

TelephoneStructure: TelRec
  pointer to name
  pointer to telephone

********************************************************
* MAIN: display menu to user and decode the options:
*   1 ­ Read phone list
*   2 ­ Search for a record 
*   3 ­ Quit

initialSize = 50
growthSize  = 10
       
main()

   DArray telList
   TelRec searchRecord

   init(telList,initialSize,growthSize)
   repeat
     userChoice = displayMenu()
     if (userChoice is READ)
        input filename
 flush(telList,TRUE)     * Note we must delete any existing records
                                * before allocating new ones
        readRecords(filename,telList)
     else if (userChoice is SEARCH)
        input personsName
        searchRecord.name = personsName
        index = search(telList,recMatch,searchRecord)
        if (index is FOUND)
display personsName,telephoneNumber
    until (userChoice is QUIT)
    deallocateRecords(telList)
    wrapup(telList,TRUE)
********************************************************
* Checks if <rec1.name> matches <rec2.name>
* up to the minimum number of characters
* between the two names
* Returns TRUE upon a match otherwise FALSE

recMatch(rec1,rec2)
if (matchesMinimally(rec1.name,rec2.name))
return TRUE
else
return FALSE

********************************************************
* Read telephone records from <fileName>
* and store in heap records  
* and add to <telList>

readRecords(fileName,telList)
open fileName
if (successful)
while (not end of file)
read name, telephone
rec = new TelRec            * create a heap record to be
                                                * deleted by the caller
rec.name      = name
rec.telephone = telephone
add(telList,rec)
close fileName

********************************************************
* Cycle through all records in <telList> and deallocate
* memory previously allocated for each record

deallocateRecords(telList)
for (i = 0 to nElements()­1)
record = get(telList,i)
deallocateRecord(record)

deallocateRecord(record)
deallocate name
deallocate telephone
Selected application code

...
typedef struct
{
char *name;
char *telephone;
} TelRec;
...

#define MAX_CHARS 80

int readRecords(char *fileName,DArray *d)
{
FILE *fp;
char name[MAX_CHARS],telephone[MAX_CHARS];

if (!(fp = fopen(fileName,"r")))
return(ERROR);

/* read the first record from the file */

fscanf(fp,"%s %s",name,telephone);

/* loop for the whole file */

while (!feof(fp))
{
/* allocate a telephone record */

tRec = (TelRec*)malloc(sizeof(TelRec));

/* allocate space for <name,telephone> fields */

tRec­>name = (char*)malloc(strlen(name)+1);
tRec­>telephone = (char*)malloc(strlen(telephone)+1);

/* copy the <name,telephone> fields into the telephone record */

strcpy(tRec­>name,name);
strcpy(tRec­>telephone,telephone);

/* add the completed telephone record to the DArray */

add(d,(void*)tRec);

/* read the next record from the file */

fscanf(fp,"%s %s",name,telephone);
}
fclose(fp);
return(NO_ERROR);
}

void deallocateRecord(TelRec *record)
{
free(record­>name);
free(record­>telephone);
}

void deallocateRecords(DArray *d)
{
int i;
TelRec *record;
for (i = 0; i < d­>nElements(); i++)
{
record = (TelRec*)get(d,i);
deallocateRecord(record);
}
}
Choosing the best DArray size, growth

The DArray is user configurable upon creation via the two parameters initialSize and growthSize.
These two parameters are important and can affect overall application execution time.

If we are doing many adds to the DArray and our growthSize is too small, many unnecessary 
reallocations of memory will occur thus slowing down execution.

Bad case scenarios:

 Case 1                           Case 2

                                   ­­­
  ­­­                             |   |  initial
 |   |                            |   |   ||
 |   |  initial                   |   |   ||
 |   |                            |   |   \/
 |   |                            |   |
 | ­ |  realloc                   |   |
 |   |  realloc                   |   |
 |   |  realloc                   |   |
  ...    ...                      |   |
  ­­­                             |   |  Large initialSize,
                                  |   |  growthSize hogs
  Small growthSize,initialSize     ...   memory
  Run­time slow down              |   |
                                  |   |
                                   ­­­

On the other hand, if we make initialSize too large and we only end up using a small portion of 
it, our application ends up hogging memory which can be used for other programs (and our 
own).

The best choice of initialSize and growthSize depends on the expected amount of data used in 
the DArray and how often we expect the DArray to grow in size.

It is almost impossible to give hard and fast rules for choosing these parameters; it depends 
totally on the application and how important is the tradeoff of memory versus execution time.

Tips

Try to snapshot the size of the data structure at key times in the program, to get a feel for the the 
expected case.

Try experimenting with different initialSize, growthSize settings and observe if there is any 
noticeable execution performance. 
Unit 1 : Exercises

(1) ABC Tune­up corporation has need of a system which records information on their customers
for a daily period only.  The services they offer in their drive­in, drive­out garage are:

Engine oil       change    $25
Transmission oil change    $50
Radiator fluid   add       $5

At the end of the day, they want a report of all the people who required servicing: Customer 
name, vehicle license, year, make, and a breakdown of the services requested.  Some days are 
slack, others are busy, so as there is really no way of knowing how many customers may come, a
DArray is ideal for recording the information in memory.

To get a better idea of which services are popular, you are also requested to submit at day's end a
report of the totals and percentages of each service requested by customers and the revenue 
gained for each service.  Example:

                  Engine oil   Transmission oil   Radiator fluid
                 ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Customers:           16             2                  4
Percent:             80%            10%                16%
Revenue:            $400           $100               $20
                                  
Total customers:     20               
Total sales    :   $520
(2)(a)  You are asked to add a function to the DArray class that allows inserting an item into the 
array at a given index.  Pictorally:

  ­­               ­­
0|  | ­> A       0|  | ­> A
1|  | ­> B       1|  | ­> B
2|  | ­> C       2|  | ­> C
3|  | ­> D       3|  | ­> Z
 |..|            4|  | ­> D
  ­­              |..|
                   ­­
 before       after inserting "Z" at index 3

Pseudocode your solution first then code, test and debug it.  

(b) Text writers often have need of editing their table of contents regularly; adding, removing 
and editing chapter titles and subsections are the common operations.  Using your enhanced 
version of the DArray, you are asked to develop an application that allows writers to do this.  A 
sample run may appear something like:

1..Display contents
2..Insert a chapter
3..Insert a chapter item
4..Remove a chapter
5..Remove a chapter item

...
=> 1

1..  Computer architecture
1.1   Memory
1.2   CPU
2..  Programming languages
2.1   C
2.2   Ada
  ...  

=> 3
Insert item in what chapter,item?  => 1,1.2
Insert what text into chapter 3? => I/O Devices

=> 1

1..  Computer architecture
1.1   Memory
1.2   I/O Devices
1.3   CPU
2..  Programming languages
2.1   C
2.2   Ada
A Dynamic sorted array

Often the data we wish to store should be ordered in a particular way, say sorted by name, or 
ordered by increasing age or salary.  This way, the data is nicer to display, it is easier to produce 
reports on the data, and perhaps the greatest advantage, is that searches on the key or sorted data 
can be performed practically instantaneously.

How would we enhance the DArray to support an ordering feature?

Certainly we could add a function sort() to the list of operations that would transform the 
existing list of records from random or scattered order to sorted order.  This function could be 
called at the discretion of the application designer whenever necessary.

We could even go so far as to support another add function, addSorted() which would insert a 
new element.  This way the list is sorted on the fly as data is added to the list:

DArray nameList, sorted by name

  ­­              ­­                 ­­                 ­­
 |  | ­> Zoey    |  | ­> Alfred     |  | ­> Alfred     |  | ­> Alfred
 |  |            |  | ­> Zoey       |  | ­> Bertha     |  | ­> Bertha
 |  |            |  |               |  | ­> Zoey       |  | ­> Joey
 |  |            |  |               |  |               |  | ­> Zoey
 |..|            |..|               |..|               |  |
  ­­              ­­                 ­­                 ­­

             addSorted "Alfred"   addSorted "Bertha"   addSorted "Joey"
Sorting algorithms

Perhaps the easiest sorting algorithm to code and understand is bubble sort, which can be 
depicted in a series of pass diagrams:

  ­­        ­­     ­­     ­­     ­­     ­­     ­­     ­­ 
 |5 |      |5 |   |5 |   |5 |   |2 |   |2 |   |2 |   |2 |
 |22|      |8 |   |8 |   |2 |   |3 |   |3 |   |2 |   |2 |
 |8 |      |22|   |2 |   |3 |   |5 |   |2 |   |3 |   |3 |
 |67|      |2 |   |3 |   |8 |   |2 |   |5 |   |5 |   |5 |
 |2 |      |3 |   |22|   |2 |   |8 |   |8 |   |8 |   |8 |
 |3 |      |45|   |2 |   |22|   |22|   |22|   |22|   |22|
 |45|      |2 |   |45|   |45|   |45|   |45|   |45|   |45|
 |2 |      |67|   |67|   |67|   |67|   |67|   |67|   |67|
  ­­        ­­     ­­     ­­     ­­     ­­     ­­     ­­
      Pass:  1      2      3      4      5      6      7

  
. N = number of elements in the list

. On pass 1, the largest element "67" is bubbled up to the
  last position in the list

. On pass 2, the second largest element, "45" is bubbled up to
  the second last position in the list.

. N­1 passes later, the list is sorted

. adjacent elements are "bubbled" to the end of the list
   depending on whether (array[j] > array[j+1])

Pseudocode

* Sort the elements in <array> in ascending order
* by bubbling up the largest elements to the end of the list

bubbleSort(array)
for (pass = 1 to nElements)
for (i = 0 to nElements­1)
if (array[i] is greater than array[i+1])
swap(array[i],array[i+1])
Algorithm performance

Bubble sort presents a simplistic approach, but is unfortunately slow in execution, especially 
with large list sizes.

Specifically, we can say that this algorithm is of order n squared, or O(n2) for short, which means
to say that as n, the size of the list increases, so does the algorithm execution time by a factor of 
the square of n.  We can readily write down and graph this expression by taking note of the inner
for loop of order n, which multiplies the outer for loop, also of order n.

  |         .          |
  |        .           |                     
t |       .          t |      .    .  
i |      .           i |   .     
m |    .             m | .
e | .                e |.  
   ­­­­­­­­­­­          ­­­­­­­­­­­­­­
       n                    n

  Bubble sort         Quick sort
   O (n2)               O (nlog(n))
Quicksort

A better solution is an O(nlog(n)) algorithm called quicksort, which certainly merits 
construction.

The algorithm involves two steps:

. Partitioning the list into two sublists
. Sorting each sublist recursively

The partitioning is the trickiest part.  We wish to divide the list into two parts, of which all the 
elements in the lowest part of the list are less than or equal to those in the higher part of the list.  
Consider an unsorted list, L:

 
    ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
 L |34|2 |98|44|19|11| 5| 4 |10|42 |
    ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

We wish to arrange L into two distinct parts.  To the left of a certain pivot value in the list, all the
elements are less than or equal to the pivot value.  Let's choose the first element in the list, 34 as 
the pivot value.  Then the following would be an acceptable partitioning of L, conforming to the 
partitioning rule:

    ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
   |2 |5 |4  |10 | 19 | 11 | 34 | 98 | 44 | 42 |
    ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
       "less than side"     /\   "greater than side"
                            ||
                           pivot

If we had an algorithm that could arrange a list like this, then we could reapply it to the "less than
side" and the "greater than side" until the list was sorted.
A partitioning algorithm

Partitioning can be done quickly by swapping elements alternately from the "less than" and 
"greater than" sides of the list.  Consider the following method by example on the original list:

  34  2  98  44  19  11  5  4  10  42

Starting from the greater than side, compare each element with the pivot, 34 until an element less
than 34 is found, then swap 34 with that element.  In the above case it is 10.  The list becomes:

  10  2  98  44  19  11  5  4  34  42

Now, starting from the less than side, beginning from the position of the last swapped element, 
10 in this case, compare each element with the pivot, 34 until an element greater than 34 is 
found, then swap 34 with that element.  In the above case it is 98.  The list becomes:

  10  2  34  44  19  11  5  4  98  42

Back to the greater than side we compare from the last swapped element, 98 to swap 34 with 4:

  10  2  4  44  19  11  5  34  98 42

Back to the less than side, we swap 34 with 44:

  10  2  4  34  19  11  5  44  98 42

Back to the greater than side, we swap 34 with 5 and we are done because no more elements 
need to be swapped, all elements to the left of 34 are less than or equal to 34; all elements on the 
right are greater than 34.

  10  2  4  5  19  11  34  44 98 42
    "less than side"   /\   "greater than side"
                       ||
                      pivot
Quicksort pseudocode

**********************************************************
* Sorts a <list> of elements from index <low> to <high>
* recursively

quicksort(list,low,high)
if (low < high)
pivot = partition(list,low,high)
quicksort(list,low,pivot­1)
quicksort(list,pivot+1,high)

**********************************************************
* Arranges a <list> of elements from index <low> to <high>
*  into two contiguous sets:
*
* Set 1 appears on the lower portion of list
* Set 2 on the upper portion of the list
* Set 1 has the property that all elements in it are less
*  than or equal to the pivot value
* Set 2 has the property that all elements in it are greater
*  than or equal to the pivot value
* The pivot value is chosen as list[low]
*
*  Returns the index of the pivot value

partition(list,low,high)
 ...
Binary search

One of the advantages of having the data arranged in sorted order in the array is that we can 
employ binary search, a quick "subdivide and conquer" search routine, several times faster than 
the "brute force" linear search approach employed in the DArray.

Consider the following sorted namelist and a search for "Martha":

0  Alfred
1  Brenda
2  Carol
3  Dana
4  Edward
5  Frank           
6  Hergold          
7  Ian               
8  Jennifer   *          
9  Karen      <­      <­   
10 Laura        |       |     *    
11 Martha       |       |     <­|     *
12 Nancy        |     <­      <­|
13 Oliver       |     *   
14 Paula        |
15 Quintus      |
16 Rasputin     |
17 Sandy        |
18 Theodore   <­

             Pass 1  Pass 2  Pass 3  Pass 4
                                      success!

On pass 1, we subdivide the list in two and compare the name in the middle, the pivot (*), 
"Jennifer" for a match with "Martha".  If there is no success, we decide which of the two sublists 
to check for "Martha" in.  Obviously, her name could only be in the upper sublist because 
"Martha" appears higher alphabetically than the pivot "Jennifer".  Continuing like this, we 
squeeze out the result in no less than log2(18) = 4 comparisons.

 
Performance :  O (log2(n))
Packaging the dynamic sorted array

We could go ahead and implement the functions sort() and addSorted() and binarySearch() and 
add them to the list of operations in DARRAY.C.

However at this time, it is worthy to mention an alternate packaging, one to preserve the original 
contents of DARRAY.C.  We could in fact create a new name of whole new data structure that 
supports the new philosophy of an ordered list, a DSArray, a dynamic sorted array, and add the 
new functionality to another file, DSARRAY.C with its own header DSARRAY.H.

 
Advantages

. The application designer could choose between a DArray or DSArray type depending on 
his/her needs, and without us, the data structure designers having to add to or take away from the
already simple definition/usage of the DArray.

. Such code is somewhat easier to read.  When we see a DSArray type in the declaration section 
of a function, we know the code deals particularly with a sorted list.

It turns out that this method is preferred in industry, especially in the case of the design of C++ 
class libraries.

Let's package our DSArray using this philosophy.
DSArray pseudocode

************************************************************
* Insert <data> into <array> at <index>
*  All following array elements are bumped up one cell
*  Increments the <nElements> count in <array>

insert(array,data,index)
if (nElements+1 > size)
grow(array)
for (i = nElements­1 to index step ­1)
swap(array[i],array[i+1])
array[index] = data
nElements = nElements + 1

***********************************************************
* Searches for <data> in <array> by successively calling
* <compareFunction>
*
* <closestIndex> is set to the index of the "closest" match
* regardless of whether a match was found or not
*
* Returns <TRUE> or <FALSE> depending on whether a match
* was found

* Assumes <array> is sorted by a key value corresponding
* to <compareFunc>

binarySearch(array,data,compareFunction,closestIndex)
if (nElements == 0)
closestIndex = 0
return FALSE
upper = nElements­1
lower = 0
do
mid = (upper + lower)/2
result = compareFunction(array[mid],data)
if (result is EQUAL)
closestIndex = mid
return TRUE
else if (result is GREATER THAN)
upper = mid ­ 1
else if (result is LESS THAN)
lower = mid + 1
while (upper >= lower)
if (compareFunction(array[mid],data) == LESS THAN)
closest = mid+1
else
closest = mid
return FALSE

***********************************************************
* Add <data> to <array> in the correct sorted order
* Assumes <array> is currently in sorted order

addSorted(array,data,compareFunction)
binarySearch(d,data,compareFunction,closestIndex)
insert(array,data,closestIndex)
The DSArray definition (DSARRAY.H)

#ifndef DSARRAYH
#define DSARRAYH

#include "darray.h"

enum {GREATER_THAN,LESS_THAN,EQUAL};

typedef DArray DSArray;

extern void sort(DSArray *d,CompareFunction cmp);
extern void addSorted(DSArray *d,void *data,CompareFunction cmp);
extern int binarySearch(
DSArray *d,
void *data,
CompareFunction cmp,
int *closestIndex);

#endif
The DSArray operations (DSARRAY.C)

#include "dsarray.h"

extern int grow(DArray *d);

/* utility functions */

static void swap(void **i,void **j)
{
void* temp = *i;
*i = *j;
*j = temp;
}

static int insert(DSArray *d,void *data,int index)
{
int i;

if (d­>nElements+1 > d­>size)
{
if (ERROR == grow(d))
return(ERROR);
}

for (i = d­>nElements­1; i >= index; i­­)
{
swap(&d­>array[i],&d­>array[i+1]);
}

d­>array[index] = data;
d­>nElements++;

return(NO_ERROR);
}
/* Core functions */

void sort(DSArray *d,CompareFunction cmp)
{
int pass,j;
for (pass = 1; pass < d­>nElements; pass++)
{
for (j = 0; j < d­>nElements­1; j++)
{
if ((*cmp)(d­>array[j],d­>array[j+1]) == GREATER_THAN)
swap(&d­>array[j],&d­>array[j+1]);
}
}
}

int binarySearch(
DSArray *d,
void *data,
CompareFunction cmp,
int *closestIndex)
{
int upper,lower,mid,result;
if (!d­>nElements)
{
*closestIndex = 0;
return(FALSE);
}
upper = d­>nElements­1;
lower = 0;

do
{
mid = (upper+lower)/2;
result = (*cmp)(d­>array[mid],data);
if (result == EQUAL)
{
*closestIndex = mid;
return(TRUE);
}
else if (result == GREATER_THAN)
upper = mid­1;
else
lower = mid+1;
}
while (upper >= lower);
if ((*cmp)(d­>array[mid],data) == LESS_THAN)
*closestIndex = mid+1;
else
*closestIndex = mid;
return(FALSE);
}

void addSorted(DSArray *d,void *data,CompareFunction cmp)
{
int closestIndex;
binarySearch(d,data,cmp,&closestIndex);
insert(d,data,closestIndex);
}
Code to test the DSArray (DSTEST.C)

#include <stdio.h>
#include <string.h>
#include "dsarray.h"

/* ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ */
/* APPLICATION TEST CODE on integer data  */
/*  to verify the basic operations of the */
/*  DSARRAY, dynamic array data structure */
/* ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ */

void displayData(DArray *d)
{
int i;
printf("[");
for (i = 0; i < nElements(d); i++)
{
int *intData = (int*)get(d,i);
printf("%d ",*intData);
}
printf("]\n");
}

static int compareFunction(void *d1,void *d2)
{
int *id1 = (int*)d1,*id2 = (int*)d2;
if (*id1 > *id2)
return(GREATER_THAN);
else if (*id1 < *id2)
return(LESS_THAN);
else
return(EQUAL);
}

void main()
{
#define NELEMENTS 8

DSArray da,*d = &da;

int i,j=11,data[NELEMENTS] = {5,22,8,67,2,3,45,2};

init(d,2,2);
for (i = NELEMENTS; i >= 0; i­­)
{
add(d,(void*)&data[i]);
}
displayData(d);
sort(d,compareFunction);
displayData(d);
addSorted(d,(void*)&j);
displayData(d);
wrapup(d,FALSE);
}

Output

[ 5 22 8 67 2 3 45 2 ]
[ 2 2 3 5 8 22 45 67 ]
[ 2 2 3 5 8 11 22 45 67 ]
A fast record search application

Let's take our telephone search application and expand it to suit the needs of a wider application. 
Suppose that a recruiting agency or training institute wishes to perform fast searches on client 
telephone number and/or on the company that clients are currently working for.  Study the 
following sample run:

1 ­ Read client file
2 ­ Search for clients by name
3 ­ Search for clients by company
4 ­ Quit

=> 1
Enter client file name: client.dat
40 records read..

=> 2
Enter client name: Halling, K
2 matches found...
Halling, Kim, Computer world, (613) 432­4521 ext 2133 
Halling, Jake E., Learning Enterprises, (613) 222­1141 

=> 3
Enter company name: Cybex systems
3 matches found...
Arnold, M., Cybex systems, (613) 567­6544 ext 8410
Irina, H., Cybex systems, (613) 567­6544 ext 7655
Desere, N. M., Cybex systems, (613) 567­6544 ext 8410
...

Obervations:

To perform binary searches on the two fields, name and company, we will need two ordered 
DSArrays, one ordered by name and one ordered by company.

We only need one copy of the data.  Each sorted DArray points to the data in the correct order.

For efficiency, we can sort the records after they are read, rather than insert the records in sorted 
order at read time.
Memory map

Let dSNameList    be the list ordered by name
    dSCompanyList be the list ordered by company

(*)'s denote the client records as ordered in the input file 

(1) Telnet, Joe          (2) Orr, Bob         (3) Arnold, M         (4) Irina, H
Radarscape technology    Neton inc.           Cybex systems         Cybex systems
456­5678                 543­5333 ext 001     567­6544 ext 8410     567­6544 ext 7655

(5) Desere, N.M          (6) Zeren, J.        (7) Kaliko, M         (8) Geiger, O.
Cybex systems            Knick nack suppliers  Abraham Inc.          The real way
567­6544 ext 8410        666­6666              432­5675 ext 404      431­6611

dsNameList               dsCompanyList
 ­­­                        ­­­
| . | ­> (3)               | . | ­> (7)
| . | ­> (15)              | . | ­> (9)
| . | ­> (13)              | . | ­> (14)
| . | ­> (14)              | . | ­> (11)
| . | ­> (5)               | . | ­> (3)
| . | ­> (11)              | . | ­> (4)
| . | ­> (8)               | . | ­> (5)
| . | ­> (12)              | . | ­> (15)
| . | ­> (9)               | . | ­> (6)
| . | ­> (4)               | . | ­> (12)
| . | ­> (10)              | . | ­> (10)
| . | ­> (16)              | . | ­> (16)
| . | ­> (7)               | . | ­> (2)
| . | ­> (2)               | . | ­> (13)
| . | ­> (1)               | . | ­> (1)
| . | ­> (6)               | . | ­> (8)
 ­­­                        ­­­

(9) Halling, Kim         (10) Jimlo, B       (11) Ena, B            (12) Halling, Jake E.
Computer World            Magazine Plus      Corrington Warehouses  Learning Enterprises
432­4521 ext 2133         444­4112           543­7890 ext 87        222­1141
(13) Blade, J.           (14) Cathleen, S    (15) Ballerina, T      (16) Johnson, T
Neton, Inc.              Computer World       Granville exports     Magazine Plus
543­5333 ext 001         432­4521 ext 2101    321­1332              801­9876
Application pseudocode

ClientStructure: ClientRec
  pointer to name
  pointer to telephone
  pointer to company

********************************************************
* MAIN: display menu to user and decode the options:
*   1 ­ Read client info
*   2 ­ Search for a client by name
*   3 ­ Search for a client by company 
*   4 ­ Quit

initialSize = 50
growthSize  = 10
       
main()

   DArray dsNameList,dsCompanyList;
   ClientRec searchRecord

   init(dsNameList,initialSize,growthSize)
   init(dsCompanyList,initialSize,growthSize)
   repeat
     userChoice = displayMenu()
     if (userChoice is READ)
        input filename
 flush(dsNameList,TRUE)
 flush(dsCompanyList,FALSE)       * Note we cannot delete the actual data
                                           a second time, so we code FALSE
                                           for the <shouldDelete> parameter
                                           on the second flush().

        readRecords(filename,dsNameList,dsCompanyList)
        sort(dsNameList,nameMatch)
        sort(dsCompanyList,companyMatch)
     else if (userChoice is SEARCH BY NAME)
        input personsName
        searchRecord.name = personsName
        index = search(dsNameList,nameMatch,searchRecord)
        if (index is FOUND)
displayClientDataByName(dsNameList,personsName,index)
     else if (userChoice is SEARCH BY COMPANY)
        input companyName
        searchRecord.company = companyName
        index = search(dsCompanyList,companyMatch,searchRecord)
        if (index is FOUND)
displayClientDataByCompany(dsCompanyList,companyName,index)
    until (userChoice is QUIT)
    deallocateRecords(dsNameList)
    wrapup(dsNameList,TRUE)
    wrapup(dsCompanyList,FALSE)
********************************************************
* Checks if <rec1.name> matches <rec2.name>
* up to the minimum number of characters
* between the two names
* Returns EQUAL upon a match otherwise {GREATER THAN,LESS THAN}

nameMatch(rec1,rec2)
if (rec1.name = rec2.name)
return EQUAL
else if (rec1.name > rec2.name)
return GREATER THAN
       else
return LESS THAN

********************************************************
* Checks if <rec1.company> matches <rec2.company>
* up to the minimum number of characters
* between the two names
* Returns TRUE upon a match otherwise FALSE

companyMatch(rec1,rec2)
if (rec1.company = rec2.company)
return EQUAL
else if (rec1.company > rec2.company)
return GREATER THAN
       else
return LESS THAN

********************************************************
* Read client records from <fileName>
* and store in heap records  
* and add to <dsNameList> and <dsCompanyList>

readRecords(fileName,dsNameList,dsCompanyList)
open fileName
if (successful)
while (not end of file)
read name, company name, telephone
rec = new ClientRec         * create a heap record to be
                                                * deleted by the caller
rec.name      = name
rec.company   = company
rec.telephone = telephone
add(dsNameList,rec)
add(dsCompanyList,rec)
             close fileName

********************************************************
* Display full record information for clients with
* matching names of <name> in sorted name
* list <dsNameList> starting from <index>
displayClientDataByName(dsNameList,name,index)
i = index
while ( (i < nElements(dsNameList))
               AND matchesMinimally(get(dsNameList,i)­>name,name))
displayClientData(dsNameList[index])
index = index + 1
display i­index,"matching records found"
Unit II exercises

(1) The DSArray data structure supports two methods of sorting records.  When would it be 
desirable to use the addSorted() function over the sort() function?  List some examples.

(2) You are asked to create a system that reads <first name>, <last name> author records in 
random order from a file and displays the names sorted by first name within last.  Example:

  Holbrook J.                Foster
  Holbrook Johnny             1..Jenny
  Holbrook Sam                2..K.      
  Holbrook Abe      =>       Hillington
  Lincolne C.                 1..Tad  
  Lincoln A.                 Holbrook
  Foster K.                   1..Abe
  Foster Jenny                2..J.
  Hillington, Tad             3..Johnny
                              4..Sam
                             Lincoln
                              1..A.
                             Lincolne
                              1..C.

(3) (a) Users of the name,company search utility have asked for support to facility update/query 
on clients' previous work experience.  The additional information they wish cached is:

Date, Previous employer, Address, Employer telephone number

In the reporting sequence of the search­reporting scenario, this involves a more detailed view of 
the clients.  For example, consider the sample run:

...
Enter client name: => Farley, M.
1 Record matched...
Farley M, Anarac data systems, 567­7889 ext 809
Work experience:
 1996 Bat cable operations, 119 Holgate Rd. 765­6545
 1994 Jumper cable corp, 4565 Essen way, 789­9888 ext 33
 1992 Able cable, #2 111 Mean St, 555­5555 ext 55

 
You are asked to sketch the design for the enhanced system.  Start with a memory map of your 
"view" of the data, then sketch the pseudocode.

(b) If you are adventurous, develop a prototype in C for your design in part (a).

(2) You are requested to upgrade the DSArray to support quicksort.  Write pseudocode for the 
quicksort algorithm.  Walk through your pseudocode with several unsorted lists.  Try ones with 
only one or two elements, and those with several elements the same value.  Write the C code and
test it with numerous examples to verify its integrity.
The linked list

If we take a close hard look at the DSArray, we will observe three things.

  . Excellent search performance.  O(logn)
  . Poor add/delete performance.   O(n)
  . Mediocre allocation of memory.

When the list is sorted, the DSArray has great search performance, we will not argue with that, 
but look at the slow down when inserting or deleting elements.  Expensive memory shifts are 
necessary,  not to mention that the allocation of memory is not optimal.  More often than not, the 
tail­end portion of the DSArray pointer structure is not in use.

If ordering and search performance is the key issue in our application, we will stick with the 
DSArray and be content.  But if we analyze our application carefully and discover that most of 
the operations are of the type {insertion/deletion}, then we may not be overly pleased with the 
DArray.

The question is, does there exist another data structure that improves upon the latter failings of 
the DSArray?

Yes!  The linked list.
What is a linked list?

A linked list is an unordered set of data, not required to be contiguous in memory, yet 
grouped via links between adjacent elements starting from a head pointer.

Here is an example of what we may visualize as a singly linked list of five elements:

 ­­­­­­­­­­­
| Element 1 | <=== head                       end
 ­­­­­­­­­­­                                   /\
    ||                                         ||
    ||                                     ­­­­­­­­­­­­
    \/                                    | Element 5  |
   ­­­­­­­­­­­            ­­­­­­­­­­­      ­­­­­­­­­­­­
  | Element 2 |   ====>  | Element 3 |           /\
   ­­­­­­­­­­­            ­­­­­­­­­­­            ||
                                      \\         ||
                                       \\        ||
                                         ­­­­­­­­­­­­
                                        | Element 4  |
                                         ­­­­­­­­­­­­

The actual data of elements 1..5 may be scattered arbitrarily through memory, but each element 
knows where exactly one of his brother resides, so we don't lose the whole.

For example, if we wanted to get to element 3's data, we would start at head, follow the pointer 
to element 1, then follow the pointer from 1 to 2 to get to element 2, then follow the pointer from
2 to 3 to get to element 3.

This is what we would call a traversal.  By following successive pointers, we can effectively 
visit, or traverse the entire data set in the linked list.

Note the beauty of this scenario:  We don't need to allocate an array and make assumptions about
the initial size of the list, hence an optimal memory model.
Inserting/deleting elements

Bottom line  :  great performance!, O(1)

Deleting elements

If we wish to delete an element, we can patch a pointer from the previous element to the next, 
and deallocate the piece of data.  Effectively, we would have removed the data from the chain.

Suppose that we wish to delete element 3 from the following list:

         ­­­­­        ­­­­­       ­­­­­        ­­­­­
head => |  1  |  =>  |  2  |  => |  3  |  =>  |  4  | => end
         ­­­­­        ­­­­­       ­­­­­        ­­­­­
                           \                   /\
                            \__________________||
                    previous                    next

Adding elements

Adding an element poses no difficulty either.  We just allocate the new piece of data in memory 
somewhere, anywhere in fact, and patch a pointer at the insertion point from the previous to the 
next element in the chain.

Suppose that we wish to insert the element 3.1 between elements 3 and 4 in the above example:

         ­­­­­        ­­­­­       ­­­­­        ­­­­­
head => |  1  |  =>  |  2  |  => |  3  |      |  4  | ­> end
         ­­­­­        ­­­­­       ­­­­­        ­­­­­
                                   ||            /\ 
                                   ||            ||
                                   ||    ­­­­­   ||
                                    ===>| 3.1 |===
                                         ­­­­­
Searching for elements

Bottom line  :  Thumbs down!

There is no secret that the link list's searching performance is linear, that is, O(n), no different 
than the original DArray's.

If we have a thousand elements in the linked list then the worst case scenario is that we have to 
visit all the nodes, to find out that the element was not in the list.

The average case scenario for a match would be that we have to visit on average, half of the 
elements before we find the one we are looking for.

If searching is important, we had better stick to the DSArray!
Design

In theory, the linked list seems to be a simple entity.  In fact it is, but we will see that its 
implementation is semi­tricky.

First, let's identify this data structure's components.  We have seen data and links in the pictorial 
description.   We can even define a linked list as a series of data joined by links.  Actually the 
two are inseparable and we will call them together a node.  Having said this, we can refine our 
definition of a linked list as a series of nodes.

            ­­­­­­               ­­­­­­
head =>    | DATA |    =>       | DATA |  =>         ...
            ­­­­­­               ­­­­­­
                      link               link
           \_________________/   \_________________/ 
                  NODE               NODE     

Node
  pointer to data
  pointer to next node
Our initial linked list, devoid of data, turns out to be merely a pointer to a node, set to a sentinel 
value, for convenience zero.

head => 0

When the first piece of data is added to the list, head no longer points to 0, the empty set, but 
rather a physical node sitting in memory at some address.

           ­­­­­­­­­­­­­­­
head =>   | FIRST ELEMENT |
          |      .        | => 0
           ­­­­­­­­­­­­­­­

When the next piece of data is added, we must tell the linked list where to insert the data.  
Suppose we told it to insert it after head by passing in the head pointer to add() as the place of 
insertion.  We would get the result: 

           ­­­­­­­­­­­­­­­­       ­­­­­­­­­­­­­­­
head =>   | SECOND ELEMENT |     | FIRST ELEMENT |
          |      .         | =>  |       .       | => 0
           ­­­­­­­­­­­­­­­­       ­­­­­­­­­­­­­­­

If we told add() to insert a third element after the first element, we would get:

           ­­­­­­­­­­­­­­­­       ­­­­­­­­­­­­­­­      ­­­­­­­­­­­­­­­­­
head =>   | SECOND ELEMENT |     | FIRST ELEMENT |    | THIRD ELEMENT   |
          |      .         | =>  |       .       | => |       .         | => 0
           ­­­­­­­­­­­­­­­­       ­­­­­­­­­­­­­­­      ­­­­­­­­­­­­­­­­­
Linked list traversal

Linked list's do not provide random access to data like the DArray does.  Recall that the DArray 
allows any element to be accessed at any time via the get() function.  We merely pass an index to
the function and it returns a pointer to the data in that slot.

When we wish to access an element in the linked list, we must visit each node starting from head
until we get to the one we want.   

Equivalently to the DArray, the linked list must provide a get() interface.  In the linked list's case
it will not be a direct get, but a get of a pointer to the next element in the list.  In order to start 
getting next elements in the list, we must have already accessed a previous element.  This leads 
to the need of a function which returns the very first element in the list.

Traversal functions:

begin()   :  return a pointer to the first element
                   in the list

      next()    :  return a pointer to the next element 
                   in the list

* In order to facilitate a next(), the linked list must maintain a record of the current element 
being accessed.
Formal definition

Putting all the points of discussion together, we can formulate a data definition for the linked list 
with a comprehensive set of operations.

LinkedList
  head: pointer to a LinkedList Node

LinkedListNode
  data: pointer to data
  next: pointer to the next LinkedList Node in the chain

Operations

init()
begin()
current()
next()
add()
remove()
wrapup()
Pseudocode for the operations

****************************************************************
* Initialize a linked list for useful work to be done
* This function must be called first before <llist> can be used
* Initializes head,current to sentinel values

 
init(llist)
head    = NULL
current = NULL

****************************************************************
* Deallocate all structural information associated with  <llist>
* if <shouldDelete> is set, all node data is deallocated as well

wrapup(llist,shouldDelete)
node = head
while (node is not NULL)
if shouldDelete
deallocate node­>data
nextNode = node­>next
deallocate node
node = nextNode

****************************************************************
* Prepare a linked list <llist> to be traversed
* This function must be called before next() can be called

begin(llist)
current = 0

****************************************************************
* Return the pointer to the last accessed or "current"
* element in <llist>

current(llist)
return(current)

****************************************************************
* Returns a pointer to the next element in <llist>
* Updates internal pointer <current>
next(llist)
if (current is NULL)             * CASE 1, get 1st element in list
current = head
else
current = current­>next  * CASE 2, get subsequent
      return(current)                              elements in list
****************************************************************
* Adds <data> element to <llist> after the node <insertAfterNode>
* If <insertAfterNode> is NULL, assumes caller wishes to add
* the node at the head of the list, otherwise adds <data>
* after <insertAfterNode>

add(llist,data,insertAfterNode)

newNode = new LinkedListNode
newNode­>data = data
if (insertAfterNode is NULL)
newNode­>next = head                   * CASE 1: add at head
head          = newNode
else
newNode­>next = insertAfterNode­>next  * CASE 2: add after head
insertAfterNode­>next = newNode

current = newNode

****************************************************************
* Trys to find <data> in <llist> by comparing <data> with
* elements in <llist> using <compareFunction>
* If a match is found <previousNode> is set to the node previous
*  to the matched node 
* Returns the node if a match found otherwise NULL

find(llist,data,compareFunction,previousNode)
LLBegin(llist)
previousNode = NULL
while (currentNode = LLNext(llist))
if (compareFunction(currentNode­>data,data) is EQUAL)
current = currentNode
return(current)
previousNode = currentNode
return(NULL)

****************************************************************
* Trys to remove <data> from <llist> by comparing <data> with
* elements in <llist> using <compareFunction>
* If a match is found the node containing <data> is removed
* If <shouldDelete> is set, the <data> is deallocated
* Returns TRUE if remove is successful, otherwise FALSE

remove(llist,data,compareFunction,shouldDelete)
nodeToDelete = find(llist,data,compareFunction,previousNode)
if (nodeToDelete is NULL)
return(FALSE)
if (previousNode is NULL)
head = nodeToDelete­>next
else
previousNode­>next = nodeToDelete­>next
if (shouldDelete)
deallocate nodeToDelete­>data
deallocate nodeToDelete
current = previousNode  
The linked list definition (LL.H)

#ifndef LLH
#define LLH

enum {FALSE,TRUE};
enum {ERROR,NO_ERROR};
enum {GREATER_THAN,LESS_THAN,EQUAL};

typedef int (*CompareFunction)(void *data1,void *data2);

typedef struct LLNode
{
void *data;
struct LLNode *next;
} LLNode;

typedef struct
{
LLNode *head;
LLNode *current;
} LinkedList;

extern void LLInit(LinkedList *llist);
extern void LLWrapup(LinkedList *llist,int shouldDelete);
extern void LLBegin(LinkedList *llist);
extern void LLCurrent(LinkedList *llist);
extern LLNode* LLNext(LinkedList *llist);
extern void LLAdd(LinkedList *llist,void *data,LLNode *insertAfterNode);
extern LLNode* LLSearch(
LinkedList *llist,
void *data,
CompareFunction cmp);
extern int LLRemove(LinkedList *llist,
void *data,
CompareFunction cmp,
int shouldDelete);

#endif
The linked list operations (LL.C)

#include "ll.h"
#include <stdlib.h>

void LLInit(LinkedList *llist)
{
llist­>head = 0;
llist­>current = llist­>head;
}

void LLWrapup(LinkedList *llist,int shouldDelete)
{                  
LLNode *node,*nextNode;
node = llist­>head;
while (node)
{
if (shouldDelete)
free(node­>data);

 /* important to get next node before freeing <node>! */

nextNode = node­>next;
free(node);
node = nextNode;
}  
}

void LLBegin(LinkedList *llist)
{
llist­>current = 0;
}

LLNode* LLCurrent(LinkedList *llist)
{
return(llist­>current);
}

LLNode* LLNext(LinkedList *llist)
{
/* CASE 1: get first element in list */

if (!llist­>current)
{
llist­>current = llist­>head;
return(llist­>current);
}
/* CASE 2: get subsequent elements in list */

llist­>current = llist­>current­>next;
return(llist­>current);
}
void LLAdd(LinkedList *llist,void *data,LLNode *insertAfterNode)
{
LLNode *newNode;

/* Create the new node */

newNode = (LLNode*)malloc(sizeof(LLNode));
newNode­>data = data;

if (!insertAfterNode)
{
/* CASE 1: insert element at start of list */

newNode­>next = llist­>head;
llist­>head   = newNode;
}
else
{
/* CASE 2: adding after the first element */

newNode­>next         = insertAfterNode­>next;
insertAfterNode­>next = newNode;
}

/* Make the added node the current one */

llist­>current = newNode;
}

static LLNode* LLFind(
LinkedList *llist,
void *data,
CompareFunction cmp,
LLNode **previousNode)
{
LLNode *currentNode;
LLBegin(llist);
*previousNode = 0;
while (currentNode = LLNext(llist))
{
if ((*cmp)(currentNode­>data,data) == EQUAL)
{
llist­>current = currentNode;
return(currentNode);
}
*previousNode = currentNode;
}
return(0);
}
LLNode* LLSearch(
LinkedList *llist,
void *data,
CompareFunction cmp)
{
LLNode *previousNode;
return(LLFind(llist,data,cmp,&previousNode));
}

int LLRemove(LinkedList *llist,
void *data,
CompareFunction cmp,
int shouldDelete)
{

LLNode *nodeToDelete,*previousNode;
nodeToDelete = LLFind(llist,data,cmp,&previousNode);
if (!nodeToDelete)
return(FALSE);

if (!previousNode)
{
/* CASE 1: delete element at start of list, update the head pointer */

llist­>head = nodeToDelete­>next;
}
else
{
/* CASE 2: delete an element after the first element,
patch a previous pointer */

previousNode­>next = nodeToDelete­>next;
}
if (shouldDelete)
free(nodeToDelete­>data);

free(nodeToDelete);

/* make the previous node current */

llist­>current = previousNode;

return(TRUE);
}
Code to test the Linked list (LLTEST.C)

#include "ll.h"                                         Output
#include <stdio.h>                           ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
#include <stdlib.h>                            <9>
                                               <4> <9>
void displayData(LinkedList *llist)            <3> <4> <9>
{                                              <2> <3> <4> <9>
LLNode *node;                           <99> <2> <3> <4> <9>
int *element;                           <88> <99> <2> <3> <4> <9>
                                               Element < 3 > found               
LLBegin(llist);                        Element < 100 > added after < 3 > 
while (node = LLNext(llist))           <88> <99> <2> <3> <100> <4> <9>
{                                      Element ­4 not found
element = (int*)node­>data;     Element < 88 > removed
printf("<%d> ",*element);       <99> <2> <3> <100> <4> <9>
}                                      Element < 3 > removed
printf("\n");                          <99> <2> <100> <4> <9>
}                                            ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

static int compareFunction(void *d1,void *d2)
{
int *id1 = (int*)d1,*id2 = (int*)d2;
if (*id1 > *id2)
return(GREATER_THAN);
else if (*id1 < *id2)
return(LESS_THAN);
else
return(EQUAL);
}

#define NELEMENTS 6

void main()
{
int i,data[NELEMENTS] = {9,4,3,2,99,88};
int three = 3,minusFour = ­4,eightyEight = 88,oneHundred= 100;
LinkedList list,*llist=&list;
LLNode *node;

LLInit(llist);
for (i = 0; i < NELEMENTS; i++)
{
LLAdd(llist,(void*)&data[i],0);
displayData(llist);
}
node = LLSearch(llist,(void*)&three,compareFunction);
if (node)
printf("Element < %d > found\n",*(int*)node­>data);
LLAdd(llist,(void*)&oneHundred,node);
printf("Element < %d > added after < % d >\n",oneHundred,three);
displayData(llist);
node = LLSearch(llist,(void*)&minusFour,compareFunction);
if (!node)
{
printf("Element ­4 not found\n");
}
if (LLRemove(llist,(void*)&eightyEight,compareFunction,FALSE))
printf("Element < %d > removed\n",eightyEight);
displayData(llist);
if (LLRemove(llist,(void*)&three,compareFunction,FALSE))
printf("Element < %d > removed\n",three);
displayData(llist);
LLWrapup(llist,FALSE);
}
A linked list of heterogeneous data

So far we have assumed a single type of data residing in nodes of our data structures.  If we 
choose students to store in our DArray we would not populate it also with telephone records.

But note, with our data structure design centered around the void* data type, we can populate our
data structures with any type of data.  At first this may seem silly, but we will learn that powerful
storage paradigms can be derived from this technique.

Let's study a basic example.

A holiday ticket agency sells various types of tickets to various types of clients, specifically:

(1) Live concerts 
(2) Boat cruises
(3) Sightseeing tours

They wish to record all the tickets sold from various locations throughout the province.  Any 
particular shop will sell tickets during the day, record all the info judiciously, and at the stroke of
five, when the business day ends, immediately get a hardcopy of all the sales, while a clerk fires 
up all the relevant data to home office.  The ticket details are as follows:

Concert info:           Boat cruise info       Tour info
  Performing artist:      Destination:           Details:
  Date:                   Date:                  Date:
  Seat #                  Company:               Start time:
  Cost:                   Cost:                  End time:
  Rain date:                                     Cost:
Design

Searching and sorting is not a requirement in this application.  Why opt for the overhead of a 
DSArray when we can add incoming ticket information to the end of a linked list?

This is a nice idea but how do we handle the varying types in one list?

Of course if we were to mix data types in the same data structure, we would quickly realize that 
the application would have to decode the data as it is snatched from the data structure.  
Remember the data structure has no knowledge of the details of the data, it only returns a void* 
pointer to the caller.

The application could facilitate this by having a type field in every record which defines how to 
decode it:

Concert info        Boat cruise            Tour    
  type = CONCERT     type = BOAT_CRUISE     type = TOUR
  artist             destination            details
  date               date                   date 
  seat               ...                    ...
  ...

We can make a generic type called Ticket, consisting of some core fields and a union of all the 
Concert, Cruise, and Tour structures.
Data definition

Type : [ CONCERT, CRUISE, TOUR ]

Ticket
  type                   <­­ Field to decode the specific ticket 
  clientName                        <­­ Data common to
  cost                              <­­ all tickets
  union of Concert, Cruise and Tour    <­ Specific ticket data

Concert
 artist
 date
 seatNumber
 rainDate

Cruise
 destination
 date
 company

Tour
 details
 date
 startTime
 endTime
Memory map

llist
 ­­­
| . ­­­> current
| . |
 ­|­
  | head
 \ /
  .
 ­­­           ­­­­­­­­­­­­­­­­­­­­­   
| . ­­­­­­­­­>| CONCERT             |
| . |         |  A.K Lansley        |
 ­|­          |  $26.00             |
  |           |  The blues brothers |
  |           |  10/01/96           |
  |           |  82A                |  
  |           |  10/10/96           |   
  |            ­­­­­­­­­­­­­­­­­­­­­
  |
 \ /
  .
 ­­­           ­­­­­­­­­­­­­­­­­­­­­
| . ­­­­­­­­­>| CRUISE              |
| . |         |  Alison R.          |
 ­|­          |  $60.00             |
  |           |  Dow's lake         |
  |           |  11/20/96           |
  |           |  Valley cruise inc. |  
  |            ­­­­­­­­­­­­­­­­­­­­­
  |
 \ /
  .
 ­­­           ­­­­­­­­­­­­­­­­­­­­
| . ­­­­­­­­­>| TOUR               |
| . |         |  Ross E.           |
 ­|­          |  $15.00            |
  |           |  Old city          |
  |           |  11/20/96          |
  |           |  9:00 AM           |  
  |           |  3:00 PM           |  
  |            ­­­­­­­­­­­­­­­­­­­­
  |
  |
  |
 \ /
  .
  .
  .
Pseudocode

****************************************************************
* Input ticket records from the user and store them in a linked
* list until QUIT is selected
* At this time, a report of all tickets sold is written to
* an output file
*
* MENU:
*
* 1 = CONCERT     ticket
* 2 = BOAT CRUISE ticket
* 3 = TOUR        ticket
* 4 = QUIT

main()
init(llist)
lastNode <­ current(llist)
do
choice <­ doMenu()
if (choice = CONCERT)
ticketRecord <­ readConcertRecord()
else if (choice = CRUISE)
ticketRecord <­ readCruiseRecord()
else if (choice = TOUR)
ticketRecord = readTourRecord()
add(llist,lastNode,ticketRecord)
lastNode <­ current(llist)
while (choice is not QUIT)
writeTicketRecords(llist)
deallocateRecords(llist)
wrapup(llist,TRUE)

****************************************************************
* Displays a menu of four choices to the user and asks the
* user for his/her choice
* Returns the choice without validation

doMenu()
display "1 ­­ CONCERT     ticket"
display "2 ­­ BOAT CRUISE ticket"
display "3 ­­ TOUR        ticket"
display "4 ­­ QUIT"
input choice
return (choice)
****************************************************************
* Input the required information for purchasing a concert
* ticket from the user
* Creates the new ticket record to store the information in
* and returns the pointer to this record to the caller
*
* NOTE: it is the responsibility of the caller to deallocate
*       the record and all its fields
*       <artist>, <date>, <seatNumber>, <rainDate>

readConcertRecord()
ticketRecord <­ new Ticket
readGeneralTicketInfo(ticketRecord)
input ticketRecord.artist
input ticketRecord.date
input ticketRecord.seatNumber
input ticketRecord.rainDate
return (ticketRecord)

****************************************************************
* Input the general information common to all tickets
* from the user
* Fields include <purchaser's name> and <ticket cost>

readGeneralTicketInfo(ticketRecord)
input ticketRecord.name
input ticketRecord.cost

****************************************************************
* Write all ticket records in <llist> to a file

writeTicketRecords(llist)
open file
begin(llist)
while (node <­ next(llist))
ticketRecord <­ node.data
writeTicketRecord(file,ticketRecord)
close file

****************************************************************
* Write complete details of <ticketRecord> to <file>
* First the record header information is written
* Then the ticket record <type> is decoded and control transferred
* to the appropriate low­level file output function
*  {concert, cruise, tour}

writeTicketRecord(file,ticketRecord)
write to file, ticketRecord.name
write to file, ticketRecord.cost
if (ticketRecord.type = CONCERT)
writeConcertRecord(file,ticketRecord)
else if (ticketRecord.type = CRUISE)
writeCruiseRecord(file,ticketRecord)
else if (ticketRecord.type = TOUR)
writeTourRecord(file,ticketRecord)

****************************************************************
* Write all details of concert record to <file> assuming
* that <ticketRecord> is encoded in concert record form

writeConcertRecord(file,ticketRecord)
write to file, ticketRecord.artist
write to file, ticketRecord.date
write to file, ticketRecord.seatNumber
write to file, ticketRecord.rainDate
Selected application code

TICKET.H

#ifndef TICKETH
#define TICKETH

enum {CONCERT=1,CRUISE,TOUR};

typedef struct
{
char *artist;
char *date;
char *seat;
char *rainDate;
} Concert;

typedef struct
{
char *dest;
char *date;
char *company;
} Cruise;

typedef struct
{
char *details;
char *date;
char *startTime;
char *endTime;
} Tour;

typedef union
{
Concert    concert;
Cruise     cruise;
Tour       tour;
} TicketData;

typedef struct
{
int type;
char *name;
float cost;
TicketData ticketData;
} Ticket;

#endif
TICKET.C

#include "ll.h"
#include "ticket.h"

#include <stdio.h>
#include <stdlib.h>

#define MAX_CHARS 80
#define FILENAME "C:\\TICKET.DAT"

static int doMenu()
{
int reply;
printf("1 ­ Concert\n");
printf("2 ­ Boat cruise\n");
printf("3 ­ Tour\n");
printf("4 ­ Quit\n");
printf("Enter 1­4: ");
scanf("%d",&reply);
return(reply);
}

static void readGeneralTicketInfo(Ticket *ticket)
{
char name[MAX_CHARS];
printf("Enter client's name:");
scanf("%s",name);
ticket­>name = (char*)malloc(sizeof(name)+1);
strcpy(ticket­>name,name);
printf("Enter ticket price $:");
scanf("%f",&ticket­>cost);
}
static Ticket* readConcertRecord()
{
Ticket *ticket;
TicketData *ticketData;
char buf[MAX_CHARS];
ticket = (Ticket*)malloc(sizeof(Ticket));
readGeneralTicketInfo(ticket);
ticket­>type = CONCERT;
ticketData = &ticket­>ticketData;
printf("Enter artist:");
scanf("%s",buf);
ticketData­>concert.artist = (char*)malloc(sizeof(buf)+1);
strcpy(ticketData­>concert.artist,buf);
printf("Enter concert date:");
scanf("%s",buf);
ticketData­>concert.date = (char*)malloc(sizeof(buf)+1);
strcpy(ticketData­>concert.date,buf);
...
return(ticket);
}

static Ticket* readCruiseRecord()
{
Ticket *ticket;
TicketData *ticketData;
char buf[MAX_CHARS];
ticket = (Ticket*)malloc(sizeof(Ticket));
readGeneralTicketInfo(ticket);
ticket­>type = CRUISE;
ticketData = &ticket­>ticketData;
printf("Enter destination:");
scanf("%s",buf);
ticketData­>cruise.dest = (char*)malloc(sizeof(buf)+1);
strcpy(ticketData­>cruise.dest,buf);
printf("Enter date:");
scanf("%s",buf);
ticketData­>cruise.date = (char*)malloc(sizeof(buf)+1);
strcpy(ticketData­>cruise.date,buf);
...
return(ticket);
}

static Ticket* readTourRecord()
{
...
}
static int writeConcertRecord(FILE *fp,Ticket *ticket)
{
TicketData *ticketData;
Concert *concert;
ticketData = &ticket­>ticketData;
concert = &ticketData­>concert;
fprintf(fp,"%s\n",concert­>artist);
fprintf(fp,"%s\n",concert­>date);
fprintf(fp,"\n");
}

static int writeCruiseRecord(FILE *fp,Ticket *ticket)
{
TicketData *ticketData;
Cruise *cruise;
ticketData = &ticket­>ticketData;
cruise = &ticketData­>concert;
fprintf(fp,"%s\n",cruise­>dest);
fprintf(fp,"%s\n",cruise­>date);
fprintf(fp,"\n");
}

static int writeTourRecord(FILE *fp,Ticket *ticket)
{
...
}

static void writeTicketRecord(Ticket *ticket)
{
/* write common data to all tickets */

fprintf(fp,"%d: %s\n",clientNumber++,ticket­>name);
fprintf(fp,"$%6.2f\n",ticket­>cost);

/* write details of each ticket sold */

if (ticket­>type == CONCERT)
writeConcertRecord(fp,ticket);
else if (ticket­>type == CRUISE)
writeCruiseRecord(fp,ticket);
else if (ticket­>type == TOUR)
writeTourRecord(fp,ticket);
}

static int writeTicketInfoToFile(LinkedList *llist)
{
int clientNumber;
LLNode *node;
Ticket *ticket;
FILE *fp;
if (!(fp = fopen(FILENAME,"w")))
  return(FALSE);
LLBegin(llist);

clientNumber = 1;
while (node = LLNext(llist))
{
ticket = (Ticket*)node­>data;

writeTicketRecord(fp,ticket);
}
fclose(fp);
return(TRUE);
}
static void deallocateConcertRecord(Ticket *ticket)
{
Concert *concert;
TicketData *ticketData;
ticketData = &ticket­>ticketData;
concert = (Concert*)ticketData;
free(concert­>artist);
free(concert­>date);
...
}

static void deallocateCruiseRecord(Ticket *ticket)
{
Cruise *cruise;
TicketData *ticketData;
ticketData = &ticket­>ticketData;
cruise = (Cruise*)ticketData;
free(cruise­>dest);
free(cruise­>date);
...
}

static void deallocateTourRecord(Ticket *ticket)
{
...
}

static void deallocateTickets(LinkedList *llist)
{
LLNode *node;
Ticket *ticket;
LLBegin(llist);
while (node = LLNext(llist))
{
ticket = (Ticket*)node­>data;

/* deallocate data common to all tickets */

free(ticket­>name);

/* deallocate particular ticket details */

if (ticket == CONCERT)
deallocateConcertRecord(ticket);
else if (ticket == CONCERT)
deallocateCruiseRecord(ticket);
else if (ticket == TOUR)
deallocateTourRecord(ticket);
}
}
void main()
{
LinkedList list,*llist=&list;
LLNode *node,*lastNode;
int choice;
Ticket *ticket;
LLInit(llist);
lastNode = LLCurrent(llist);
do
{
choice = doMenu();
ticket = 0;
if (choice == CONCERT)
ticket = readConcertRecord();
else if (choice == CRUISE)
ticket = readCruiseRecord();
else if (choice == TOUR)
ticket = readTourRecord();
if (ticket)
{
LLAdd(llist,(void*)ticket,lastNode);
lastNode = LLCurrent(llist);
}
} while ((choice >= CONCERT) && (choice <= TOUR));
writeTicketInfoToFile(llist);
deallocateTickets(llist);
LLWrapup(llist,TRUE);
}
   
Doubly linked lists

Sometimes we may wish to traverse the elements from the end of the list backwards, or visit the 
element previous to the one current.

If we add backward links to our singly linked list model and a previous() function, we can create 
a doubly linked list.

This data structure is specifically engineered for bi­directional sequential access: browsing help 
pages, documents, records etc. 

Definition

LList
  head: 
  tail:
  current:

LLNode
  pointer to data
  pointer to previous node
  pointer to next     node

           ­­­­­­          ­­­­­­                        ­­­­­­
          |DATA 1|  ====> |DATA 2|  ====>  ...   ====>  |DATA N| 
 head ­>  |      | <====  |      | <====        <====   |      |
           ­­­­­­          ­­­­­­                        ­­­­­­
                                                           /\
                                                           ||
 tail ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

Operations

init()
begin()
current()
next()
previous()
add()
remove()
search()
wrapup()
Sorted linked lists

The other enhancement we can make to a linked list is to insert the elements in sorted order.  The
result is always a sorted list.

This could be effected by the mere addition of a function:

addSorted(llist,data,comparisonFunction)

to the linked list library.

Performance

Each addSorted() call is O(n).

Note that the linked list addSorted() performance is no worse than the DSArray's.  They both are 
O(n), yet for different reasons.

Recall that the DSArray addSorted() bottleneck occurs when the insert memory shift takes place.
The linked list bottleneck occurs when searching linearly for the insert position.  For the linked 
list, we can't use binary search because we don't have random access to the elements!

A one time DSArray quicksort is definitely faster, but if we know that the linked list is fairly 
static, or equivalently if adds are infrequent, addSorted() is acceptable.

Note that this is not a good way of going about sorting elements!  DSArray quicksort is faster, 
but if we know that the linked list is fairly static or equivalently, if adds are infrequent, 
addSorted() has a cause.
  
Unit III exercises

(1) (a) Implement a sorted linked list as outlined in the notes.     
(b) Use the sorted linked list from part (a) to enhance the ticket program.  You are required to 
generate two other reports: one which displays all the tickets sold during the day, sorted from 
highest price to lowest price, a second which reports the tickets upon date of use.  Send the 
output to two separate files, report1.dat and report2.dat.

(2) (a) Design a doubly linked list from memory map to pseudocode to C code.  Test and debug 
it.

(b) Use your doubly linked list to create a record browser that allows both forward and backward
motion through the records via F and N keyboard commands.  The details of the records and how
you populate your linked list with those records is up to you.  Be creative!

(3) Implement a function called forEach(llist,codeToExecute), a useful and generic linked list 
traversal function.

It scans all the elements in <llist> and executes the function codeToExecute() for each element, 
passing a pointer to the current element.

For example, suppose we wanted to display all tickets whose cost was greater than $40, we could
write code like:

void displayExpensiveTickets(void *pointerToTicket)
{
Ticket *ticket;
ticket = (Ticket*)pointerToTicket;
if (ticket­>cost > 40)
displayTicketRecord(ticket)
}

void main()
{
LinkedList linkedList,*llist=&linkedList;
...
LLForEach(llist,displayExpensiveTicket)
}
(4) A ring list, or circular list, is useful for polling data sequentially.  There is neither a head nor 
tail, just an entry point into a looped chain of data:

  ­­­           ­­­
entry ­>   | A |  ===>   | B |
                 ­­­           ­­­
                  /\            ||
                  ||            ||
                  ||            \/
                 ­­­           ­­­
                | D |  <===   | C |
                 ­­­           ­­­

Entry can point to any node in the loop.

When we traverse the data, we actually traverse the data forever: A, B, C, D, A, B, C, D, A, B, ...

(a) Create a circular linked list data structure CLinkedList, based on the singly linked list design. 
The operation interface remains the same as the singly linked list's.

(b) Use this new data structure to create the following application:

Electronic bulletins at airports announce recent arrivals and near­future departures.  Consider a 
single line bulletin board that updates its flight information every t seconds for flights within a w 
minute time window.  Suppose t = 5 and w = 30.  Then we would expect arrival/departure detail 
lines to update every five seconds, showing those arrivals within the last 30 minutes and those 
departures due within the next 30 minutes.  Here is a snapshot at 11:20 AM :
 

 
 ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
| Arrivals                   | Departures                 | Current Time : 11:20  |
|                            |                            |                       |
|  TWA 678 Frankfurt 11:13   |  JA 99 Paris 11:40         |                       |
 ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
 
Suppose flight information is fixed and recorded in the file flight.dat.  Your program can read 
this file, along with a suggested user defined t and w, and simulate the above bulletin display.  
Check the system help to find out how to query the current date and time in code.

Sample flight data (flight.dat):

TWA_66  Moscow     11:01       A
KLM_504 Athens     11:07       D
TWA_678 Frankfurt  11:13       A
MNB_01  New_York   11:15       A
KLM_113 London     11:17       D
DBA_333 Helsinki   11:24       D
JA_99   Paris      11:40       D
JA_102  Boston     14:01       A
...

(c) Enhance your application to simulate bulletin boards of s lines of flight details.  Suppose s = 
3, then up to three arrival/departure lines can be displayed at any one time:

 
 ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
| Arrivals                   | Departures                 | Current Time : 11:15  |
|                            |                            |                       |
|  MNB_01  New York  11:15   |  KLM 113 London    11:17   |                       |
|  TWA 678 Frankfurt 11:13   |  DBA 333 Helsinki  11:24   |                       |
|  TWA 66  Moscow    11:01   |  JA  99  Paris     11:40   |                       |
 ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

Anda mungkin juga menyukai