0% found this document useful (0 votes)
332 views196 pages

Addison - Wesley.problem - Solving.and - Program.design - In.c.advanced - topics.supplement.1993.SCAN DARKCROWN

This chapter discusses dynamic data structures in C using pointers, linked lists, queues and stacks. It introduces the malloc and calloc functions for allocating memory dynamically. Linked lists are represented using pointers, with operators for insertion, deletion and traversal. Queues and stacks can be implemented using linked lists. Ordered lists are also discussed, with a case study on maintaining a sorted list of integers. Applications include polynomial manipulation and simulations.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
332 views196 pages

Addison - Wesley.problem - Solving.and - Program.design - In.c.advanced - topics.supplement.1993.SCAN DARKCROWN

This chapter discusses dynamic data structures in C using pointers, linked lists, queues and stacks. It introduces the malloc and calloc functions for allocating memory dynamically. Linked lists are represented using pointers, with operators for insertion, deletion and traversal. Queues and stacks can be implemented using linked lists. Ordered lists are also discussed, with a case study on maintaining a sorted list of integers. Applications include polynomial manipulation and simulations.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 196

: i Ni D ose: ie. ae.

tee

INC

i i snes y

i if et : i : te ‘ : ) : thi

< a * ‘© of Ms i : 2 x BES Rf RA eb ree . eae


Advanced Topics
Supplement
A
VV
Publishing Company
UNIVERSITY OF WYOMING
Reading, Massachusetts
Menlo Park, California
New York
(RR loy mm - eeelt
7 V-N Don Mills, Ontario
Wokingham, England
Amsterdam
FRANK Bonn * Sydney
L. FRIEDMAN
Singapore * Tokyo
Madrid » San Juan
TEMPLE UNIVERSITY Milan « Paris
The programs and applications presented in this book have been included for their
instructional value. They have been tested with care but are not guaranteed for any par-
ticular purpose. The publisher does not offer any warranties or representations, nor
does it accept any liabilities with respect to the programs or applications.

Many of the designations used by manufacturers and sellers to distinguish their prod-
ucts are claimed as trademarks. Where those designations appear in this book, and
Addison-Wesley was aware of a trademark claim, the designations have been printed in
initial caps or all caps.

Copyright © 1993 by Addison-Wesley Publishing Company, Inc.

All rights reserved. No part of this publication may be reproduced, stored in a retrieval
system, or transmitted, in any form or by any means, electronic, mechanical, photo-
copying, recording, or otherwise, without the prior written permission of the publisher.
Printed in the United States of America.

ISBN 0-201-55071-7

123456789 10—MU—96959493
le supplement makes the textbook Problem Solving and Program Design in
C by Jeri R. Hanly, Elliot B. Koffman, and Frank L. Friedman useful in a variety
of courses. Together, the text and supplement provide ample material for a two-
quarter introductory computer science sequence. A beginning programming
course for engineering students can be based on Chapters 1 through 9 of the text
and Chapters 15 and 16 of the supplement, with other chapters included in
keeping with the interests of the teacher and the students.
For a course in C as a second language, students can quickly meet the
basic control structures by reading Chapter 2 and the Chapter Review and
Common Programming Errors sections of Chapter 3 through Chapter 5 of the
basic text. Then they can focus on the challenging material found in Chapters 6
through 13 of the text and Chapter 14 of the supplement that cover more
advanced computer science topics including abstract data types, arrays, strings,
structures, unions, recursion, text and binary files, linked lists, stacks, and
queues. To achieve more complete coverage of C language features, instructors
can draw additional examples from the supplement’s Chapters 15 and 16 and
from the text’s appendixes. Case studies in Chapter 13 of the text and Chapter
14 of the supplement are of sufficient complexity to introduce more advanced
students to the difficulty of solving real-world problems.

Acknowledgments
Many people participated in the development of this supplement. We thank
especially Thayne Routh, University of Wyoming graduate student, who served
as first reviewer of the manuscript, verified the programming examples, and pro-
vided answer keys for the exercises. His contribution to the quality of this sup-
plement is immeasurable.
The reviewers of Chapters 14 to 16 were enormously helpful in suggesting
improvements and in finding errors. They include Sharon Salveter, Boston
University; Robert Signorile, Boston College; Naikuan Tsao, Wayne State
University; Stephen Allan, Utah State University; Thomas A. Bailey, University
of Wyoming.
It has been a pleasure to work with the Addison-Wesley team on this pro-
ject. The sponsoring editor, Lynne Doran Cote, was closely involved in all
phases of the manuscript preparation and provided much guidance and encour-
agement. Assistant Editor Andrea Danese coordinated the review process and
iv Preface

handled a great variety of other details. Loren Hilgenhurst Stevens supervised


the design and editing of the supplement, and Patsy DuMoulin coordinated the
conversion of the manuscript to a finished book.

J.R.H.
E.B.K.
EEE
14. Dynamic Data Structures 730

1 Pointers and the Functions malloc and calloc 731


2 Linked Lists 738
3 Linked List Operators 744
-4 Representing a Queue with a Linked List 750
5 Representing a Stack with a Linked List 756
6 Ordered Lists 761
Case Study: Maintaining an Ordered List
of Integers 76]
14.7 A Linked-List Application in Mathematics 773
Case Study: Manipulation of Polynomials 774
14.8 Queues and Ordered Lists in Simulations 788
Case Study: Simulation of a Factory 789
14.9 Common Programming Errors 809
Chapter Review 810

15. Plotting Functions 818


15.1 Plotting Functions of One Variable 819
15.2 Plotting Parametric Equations 830
15.3 Plotting Functions of Two Variables 831
Case Study: Contour Plot for a Hemispherical
Surface 837
15.4 Introduction to Computer-Aided Design 843
15.5 Common Programming Errors 844
Chapter Review 845

16. Introduction to Numerical Methods 851

16.1 Finding Roots of Equations 852


16.2 Vectors and Matrices 862
16.3 Solving Systems of Linear Equations 872
16.4 Linear Regression and Correlation 883
vi Contents

16.5 Numerical Integration 890


16.6 Using Numerical Methods Libraries 897
16.7 Common Programming Errors 900
Chapter Review 900

Answers Al
‘ PROBLEM
SOLVING Jog
bene eeeeeeereseeee sees

| PROGRAM

Topics
Supplement
14.1
Pointers and the Functions malloc and calloc
14.2
Linked Lists
14.3
Linked List Operators
14.4
Representing a Queue with a Linked List
14.5
Representing a Stack with a Linked List
14.6
Ordered Lists
Case Study: Maintaining an Ordered
List of Integers
14.7
A Linked-List Application in Mathematics
Case Study: Manipulation of Polynomials
14.8
Queues and Ordered Lists in Simulations
Case Study: Simulation of a Factory
14.9
Common Programming Errors
Chapter Review
6Fa chapter discusses C facilities for creating dynamic data structures,
structures composed of blocks of memory allocated as a result of explicit
requests made by an executing program. These facilities give a computer system
the chance to defer until a later time its decision regarding how much space to
use in processing a data set. A program that can procrastinate in this way is far
more flexible than a comparable program that must make this decision on space
earlier. In Chapter 8, we studied how to store a list of data in an array that we
declared as a variable. Although we could handle lists of different lengths by
only partially filling the array, the maximum list size had to be determined
before the program was compiled.
In this chapter, we will study how the use of one dynamic memory alloca-
tion function allows us to delay the setting of the maximum list size until the
program is already running. Using another function permits us to allocate space
separately for each list member, so the program itself never actually sets an
upper bound on the list size. Since the program can call these functions to
request memory at any time, it can use information from the input data as the
basis for determining how much space to request and what data types are to be
stored in the blocks.
When allocating memory dynamically, a program can use the space to
store any of the simple and structured data types and any of the data structures
presented in earlier chapters. In addition, it can combine separately allocated
structured blocks called nodes to form composite structures that expand and con-
tract as the program executes. Such composite dynamic structures are extreme-
ly flexible. For example, it is relatively easy to add new information by creating
a new node and inserting it between two existing nodes. It is also relatively easy
to delete a node.
In this chapter, we will examine how to create and manipulate a composite
data structure called a linked list and how to use this structure in forming lists
and queues of varying lengths.

14,1 POINTERS AND THE FUNCTIONS malloc AND calloc

In earlier chapters, we saw two important uses of pointer variables, variables


whose values are memory addresses. We first met pointer variables in Chapter 6
where we used them as function output parameters. We used the indirection
(“pointer-following”) operator * to store one of several function results in a
variable whose address had been passed as an actual argument:

“srgnp = +) 5

731
732 Dynamic Data Structures

In Chapter 11, we saw that structured output arguments were handled in a


similar fashion. In Chapter 8 and Chapter 9, we saw that the value of an array
name in C is actually the address of (that is, a pointer to) the array's initial ele-
ment.
In this section, we meet a third context in which C uses pointers—as a
means of accessing a memory block allocated in response to an explicit program
request. We have seen that declarations such as

int *nump;
char *letp;
planet_t *planetp;

allocate variables of types “pointer to int”, “pointer to char”, and “pointer to


planet_t” where planet _t is a user-defined structure type like the one we
defined in Chapter 11. If nump, letp, and planetp are local variables of a
function, then they are allocated at the time the function block is entered, as
shown in Fig. 14.1.
In order to allocate an integer variable, a character variable, and a struc-
tured planet_t variable dynamically, we call the C memory allocation func-
tion malloc, which resides in the stdlib library. This function requires a single
argument, that is, a number indicating the amount of memory space needed.
Applying the sizeof operator to the data type we plan to store in the dynamic
block gives us precisely the needed number. Thus,

malloc(sizeof (int))

allocates exactly enough space to hold one type int value and returns a point-
er to (the address of) the block allocated.
Of course, when we work with pointers in C, we always deal with a “pointer
to some specific type,” rather than simply a “pointer.” Therefore the data type of

Figure 14.1 Function data area

Data Area of a
Function with
Three Pointer-
Type Local
Variables

planetp
14.1 Pointers and the Functions malloc and calloc 733

the value returned by malloc (void *) should always be cast to the specific
type we need, such as

nump = (int *)malloc(sizeof (int));


letp = (char *)malloc(sizeof (char) );
planetp = (planet_t *)malloc(sizeof (planet_t));

The result of these three assignment statements is shown in Fig. 14.2. Notice
that the area in which the new memory blocks are allocated is called the heap.
This storage area is separate from the stack, the region of memory in which
function data areas are allocated and reclaimed as functions are entered and
exited.
Values may be stored in the newly allocated memory using the indirection
operator (*), the same operator we used to follow pointers representing function
output parameters. The statements

*nump = 307;
Set pa aO ay
*planetp = blank planet;

would lead to the memory snapshot in Fig. 14.3 if we assume the following dec-
laration of blank_planet:

planet, t blankiplanety=) {57° 70), 0037 0/10};

Function data area


Figure 14.2
Dynamic nump
Allocation of
Variables for
an int, a char,
and a Five-
Component
planet_t
Structure planetp
734 Dynamic Data Structures

Function data area


Figure 14.3
Assignment of
Values to
Dynamically
Allocated
Variables

i
Ran

Accessing a Component of a Dynamically


Allocated Structure
In Chapter 11, we saw that a component of a structure accessed through a point-
er could be referenced using a combination of the indirection (*) and direct
component selection (.) operators such as

(*planetp) .name

We also met C’s single operator that combines the function of these two opera-
tors. This indirect component selection operator is represented by the character
sequence —> (a “minus” sign followed by a “greater than”). Thus, these two
expressions are equivalent.

(*structp) .component structp->component

We have chosen to use both notations; however, we will use them according to
a
pattern that helps us convey additional information to the readers of our programs.
Prior to our study of the function malloc, the only context in which the
indirect component selection operation had arisen was in dealing with structure
type variables to which the & operator had been applied, typically to create a
pointer to an output argument. We have chosen to reserve the notation
(*structp).component for this context, so that all nonarray output para-
meters will be referenced using the * operator. On the other hand, we will con-
sistently use the -> operator for referencing structures that we have dynamically
allocated by calling malloc.
14.1 Pointers and the Functions malloc and calloc 735

Figure 14.4 poterencing, Components of a Dynamically Allocated Structure

printf("%s\n", planetp->name);
printf(" Equatorial diameter: %.0f km\n", planetp->diameter);
printf(" Average distance from the sun: %.4e km\n", planetp->dist_ sun);
printf(" Time to complete one orbit of the sun: %.2f years\n",
planetp->orbital prd);
printf(" Time to complete one rotation on axis: $%.4f hours\n",
planetp->axial_rot_prd);

This choice may seem very arbitrary. However, it does allow us to distin-
guish between pointers that other high-level languages, such as Pascal and Ada,
hide from the programmer (pointers for output parameters and other uses of &)
and pointers to dynamic storage that are almost universally placed under the
programmer's control. We encourage you to read our examples critically so
you can decide whether this distinction is a help or a hindrance for you.
In Fig. 14.4 are statements that print our dynamically allocated planet
(assuming the planet_t definition in Section 11.1).

Dynamic Array Allocation with calloc


We can use function malloc to allocate a single memory block of any built-in or
user-defined type. To dynamically create an array of elements of any built-in or
user-defined type, we use the contiguous allocation function from stdlib, calloc.
Function calloc takes two arguments: the number of array elements needed and
the size of one element. Figure 14.5 allocates and fills three arrays—an array of
characters accessed through stringl1, an array of integers accessed through
array of _nums, and an array of planets accessed through array_of plan-
ets. Figure 14.6 shows memory as we reach the end of the fragment in Fig. 14.5.

Figure 14.5 Allocation of Arrays with calloc

/* necessary #includes and scan planet from Fig. 11.4 go here eal
int
main(void)
{
char *stringl;
ane *array_of nums;
planet_t *array of planets;
(continued)
736 Dynamic Data Structures

Figure 14.5 (continued)

int str_siz, num_nums, num planets, i;

printf("Enter string length and string> ");


scanf("%d", &str_siz);
stringl = (char *)calloc(str_siz, sizeof (char) );
scant("%s", String);

printf£("\nHow many numbers?> ");


scanf("%d", &num_nums);
arra
of y
nums = (int *)calloc(num_nums, sizeof (int) );
of ay
arr nums[0] = 5;
for (i = 1; i < num_nums; ++i)
arra
of nums[i]
y = arra
of nums[i
y - 1] * i;

printf("\nEnter number of planets and planet data> ");


scanf("%d", &num planets);
arr _of ay
planets = (planett *)calloc(num_planets,
sizeof (planet _t));
for (i = 0; i < num planets; ++i)
scan_planet(&array_of planets[i]);

Enter string length and string> 9 enormous

How many numbers?> 4

Enter number of planets and planet data> 2


Earth UZ7ES.5) LSOOOOCO0LO 1.072470
Jupiter 142800.0 778300000.0 11.9 9.925

Returning Cells to the Heap


Execution of a call to the function free returns memory cells to the heap so
they can be reused later in response to calls to calloc and malloc. For
example,

free(letp);
14.1 Pointers and the Functions malloc and calloc 737

Function data area


Figure 14.6
Stack and Heap
after Program
Fragment in
Fig. 14.5 array_of_nums

array_of_planets

returns to the heap the cell whose address is in letp, that is, the cell in which
we stored a 'Q' (see Fig. 14.3);

free(planetp);

returns the entire structure pointed to by planetp.


Often, more than one pointer points to the same memory block. For exam-
ple, the following statements result in the situation pictured in Fig. 14.7.

double *xp, *xcopyp;

xp = (double *)malloc(sizeof (double)


);
*xp = 49.5;
XCOPYP = XP;
free(xp);
738 Dynamic Data Structures

Figure 14.7
Multiple xp
Pointers to Ws
a Cell in
the Heap

After the call to free, the cell containing 49.5 may be allocated as part of
another structure. Pointer xcopyp should not be used to reference the cell
after it is freed, or errors can result. Make sure you have no further need for a
particular memory block before you free it.

Self-Check
EXERCISES FOR
SECTION 14.1 Consider Fig. 14.3. Write statements to accomplish the following:

. Print the character accessed through letp.


. Scan a new value into the location whose value is currently 307.
. Store the value "Uranus" in the name component of the structure.
Re. Store in nump the address of a dynamically allocated array of 12 integers,
BRWN

and initialize all the array elements to zero.


5. Store in letp the address of a 30-character dynamically allocated string
variable.

14.2 LINKED LISTS

A linked list is a sequence of nodes in which each node is linked, or


connected,
to the node following it. The following is a linked list of three nodes.
In all
nodes but the last, the Linkp component contains the address of the
next node
in the list.

current volts linkp current volts linkp current volts linkp

Structures with Pointer Components


To construct a dynamic linked list, we will need to use nodes that have
pointer
components. Because we may not know in advance how many elements will
be
14.2 Linked Lists 739

in our lists, we can allocate storage for each node as needed and use its pointer
component to connect it to the next node. A definition of a type appropriate for
a node of the linked list pictured earlier is

typedef struct nodes {


char cuprent[
3];
alge volts;
struct node_s *linkp;
} node_t;

In Chapter 11, we noted that a typedef such as this establishes the type
name node_t as equivalent to the type struct node_s. However, up to
now, we've seen little utility in the type struct node_s. Here we use the type
struct node _s * in the declaration of one component to indicate that the
linkp component of our node points to another node of the same type. We use
struct node_s * rather than node_t * because the compiler has not yet
seen the name node _t.
We can allocate and initialize the data components of two nodes as follows:

node se *nlip,.*n2 ppt hoeDs

nl_p = (node_t *)malloc(sizeof (node _t));


strcepy(nl_p->current, "AC");
nl p—>volts) = 115;
n2_p = (node_t *)malloc(sizeof (node_t));
strcpy(n2_p->current, "DC");
n2_p->volts = 12;

If we then copy the pointer value of n2_p into n3_p,

n3_p = n2_p;

we will have the memory values shown in Fig. 14.8.

current volts linkp


Figure 14.8
Multiple
Pointers to current volts linkp
the Same
Structure
740 _= Dynamic Data Structures

We can compare two pointer expressions using the equality operators


==
and !=. The following conditions are all true for our node_t *
variables
nl_p,n2_p,andn3_p.

np a= 22p nisp =n sp n2_p == n3_p

Connecting Nodes
One purpose of using dynamically allocated nodes is to enable us to
grow data
structures of varying size. We accomplish this by connecting individua
l nodes.
If you look at the nodes allocated in the last section, you will see that their
linkp components are undefined. Because the linkp components
are of type
node_t *, they can be used to store a memory cell address. The
pointer assign-
ment statement

nl_p->linkp = n2_p;

copies the address stored in n2_p into the Linkp component


of the node
accessed through n1_p, thereby connecting the white and light-colored
nodes as
pictured in Fig. 14.9.
We now have three ways to access the 12 in the volts compone
nt of the
second node: the two references that were also valid in
Fig. 14.8,

n2_p->volts

and

n3_p->volts

as well as one through the Linkp pointer just assigned

nl_p->linkp->volts

current volts linkp


Figure 14.9
Linking Two
Nodes
current volts link
ICMR One EeheOT
14.2 Linked Lists 741

Table 14.1 Analyzing the Reference n1_p->linkp->volts

nl_p->linkp Follow the pointer in n1_p to a structure and select the


linkp component.
linkp->volts Follow the pointer in the Linkp component to another
structure and select the volts component.
aaa)

In Table 14.1, we analyze this third reference a section at a time.


The linkp component of our structure with three access paths is still
undefined, so we will allocate a third node, storing its pointer in this link. Then
we will initialize the new node's data components.

n2_p->linkp = (node_t *)malloc(sizeof (node _t));


strcepy(n2_p->linkp->current, "AC");
n2_p->linkp->volts = 220;

Now we have the three-node linked list shown in Fig. 14.10.


However, we still have an undefined linkp component at the end.
Clearly, we cannot continue allocating nodes indefinitely. At some point our list
must end, and we need a special value to mark the end showing that the linked
list of nodes following the current node is empty. In C, the empty list is repre-
sented by the pointer NULL, which we will show in our memory diagrams as a
diagonal line through a pointer variable or component. Executing the assignment

n2_p->linkp->linkp = NULL;

marks the end of the data structure pictured in Fig. 14.11, a complete linked list
whose length is three. The pointer variable n1_p points to the first list element,
or list head. Any function that knows this address in n1l_p would have the
ability to access every element of the list.

malep current volts linkp


Figure 14.10
Three-Node
Linked List current volts linkp
with Undefined
Final Pointer
current volts linkp
742 Dynamic Data Structures

current volts linkp


Figure 14.11
Three-Element
Linked List
Accessed current volts linkp
through n1_p

current volts linkp

Advantages of Linked Lists


A linked list is an important data structure because it can be modified
easily. For
example, a new node containing DC 9 can be inserted between the
nodes DC 12
and AC 220 by changing only one pointer value (the one from DC 12)
and setting
the pointer from the new node to point to AC 220. This means of
modifying a
linked list works regardless of how many elements are in the list. The
list shown in
Fig. 14.12 is after the insertion; the new pointer values are shown in
color.

nl_p current volts linkp


Figure 14.12
Linked List
After an
Insertion current volts linkp current volts linkp

current volts linkp

Similarly, it is quite easy to delete a list element. Only


one pointer value
within the list must be changed, specifically, the pointer that
currently points to
the element being deleted. The linked list is redrawn as is
shown in Fig. 14.13
after the node containing DC 12 is deleted by changing
the pointer from the

Figure 14.13 Linked List After a Deletion


14.2 Linked Lists 743

node AC 115. The deleted node is effectively disconnected from the list and
could be returned to the heap (if we had another pointer through which to access
the node). The new list consists of AC 115, DC 9, andAC 220.

EXERCISES FOR ~ Self-Check


SECTION 14.2 1. Here is the final linked list created
code fragment that follows it?
in this section. What is printed by the

iil56)

n2_p = nl_p->link->link;
printf(" %s %s %s\n", n2_p->current,
nl_p->linkp->current, nl_p->current);
printf("%3d%4d%4d\n", nl_p->linkp->volts,
nl_p->volts, n2_p->volts);

2. Complete the given code fragment so it will create a linked list containing the
musical scale, if the input is
dom irepmilrisiag tsolmplaqnt edo

typedef struct scale nodes {


char note[4];
struct, scale) node s *linkp;
} scale node t;

scale node t *scalep, *prevp, *newp;


aioe aL.

scalep = (scale node t *)malloc(sizeof (scale_node_ t));


scanf("%s", scalep->note);
prevp = scalep;
fOr (ie = 0p a ie yf

newp = 7

SCant(; 6S: «, note);

prevp->linkp = ;
prevp = newp;

= NULL;
744 Dynamic Data Structures

14.3 LINKED LIST OPERATORS

This section and the ones that follow consider some common list-processing
operations and show how to implement them using pointer variables. We assume
that the structure of each list node corresponds to type list_nodet, declared
as shown. Pointer variable pi_ fracp points to the list head.

typedef struct list_nodes {


int digit;
struct list_node_s *restp;
} lvstonode t;

{
list_node t “Disiecacpre

Traversing a List
In many list-processing operations, we must process each node in the list in
sequence; this is called traversing a list. To traverse a list, we must start at the
list head and follow the list pointers.
One operation that we must perform on any data structure is displaying its
contents. To display the contents of a list, we traverse the list and display only
the values of the information components, not the pointer fields. Function
print_list in Fig. 14.14 displays the digit component of each node in the
existing list whose list head is passed as an input parameter (of type
list_node_t *). If pi_fracp points to the list

the function call statement

print list(pi, fracp);

displays the output line

14159

We have chosen a linked list to store the decimal representation of the fraction-
al part of m because this would permit us to save many more digits of this
frac-
tion than we could represent in an int or a double.
We observed in Chapter 10 that problems involving varying-length
lists
were well suited to recursive solutions, so we have written a recursive
14.3 Linked List Operators 745

Figure 14.14 Function print_list

/*
* Displays the list pointed to by headp
*/
void
print_list(list_node_t *headp)
{
if (headp == NULL) { /* simple case - an empty list */
Prinvi(e\n.)
} else { /* recursive step - handles first element */
printf("%d", headp->digit); /* leaves rest to * /
print_list(headp->restp); /* recursion */
}
}

print_1list. This function takes a typical recursive approach: “If there's any-
thing in this list, I'll be happy to take care of the first element; but somebody else
(i.e., another call to the function) will have to deal with the rest of the list.”
Figure 14.15 compares recursive and iterative versions of print_list.
The type of recursion we see in print list is termed tail recursion because
the recursive call is executed as the function's last step, if it is executed at all. Tail

Figure 14.15 Comparison of Recursive and Iterative List Printing

/* Displays the list pointed to by headp */


void
print_list(list_node_t *headp)
{ list node _t *cur_nodep;
if (headp == NULL) {/* simple case */
PEINtCLEe \n |)? for (cur_nodep = headp; /* start at
} else { /* recursive step */ beginning*/
printf("%d", headp->digit); cur_nodep != NULL; /* not at
print _list(headp->restp) ; end yet */
cur _nodep = cur_nodep->restp)
printf("%d", cur_nodep->digit);
prantt
(= \n")'
746 = Dynamic Data Structures

cur_nodep digit restp digit restp


Figure 14.16 before
Update of [es] +>

List-Traversing
Loop Control
cur_nodep = cur_nodep—>restp
Variable
cur_nodep

after

recursion is relatively easy to convert to iteration. Compilers for languages specif-


ically developed for list processing even do such conversions automatically.
Let's examine the header of the iterative version's for loop. This header
makes traversing every element of a linked list as easy as a counting for loop
makes processing every element of an array.
We want to begin by examining the linked list's first node, so we initialize
our loop control pointer variable cur_nodep to the value of headp. We want
to stay in the loop as long as there remain nodes to process—that is, as long as
cur_nodep does not contain the NULL “end-of-list” pointer. Our loop control
update effectively gets us to the next node of the list. Figure 14.16 shows how
cur_nodep might appear before and after one such update.

Getting an Input List


Function get_list in Fig. 14.17 creates a linked list from a sequence of
integers entered as input. Entry of the sentinel —1 marks the end of the data. The
function's recursive algorithm recognizes the sentinel value as an empty data list

Figure 14.17 Recursive Function get_list

#include <stdlib.h> /* gives access to malloc */


#define SENT -1
/*

* Forms a linked list of an input list of integers


* terminated by SENT
x7
list_nodet *
get_llist( void)

(continued)
14.3 Linked List Operators 747

Figure 14.17 (continued

{
int data;
list_node_t *ansp;

scanf("%d", &data);
if (data == SENT) {
ansp = NULL;
} else {
ansp = (list_node_t *)malloc(sizeof (list_node_t));
ansp->digit = data;
ansp->restp = get_list();
}

Eeturn (ansp));
}

and returns NULL, which is automatically converted to type list_node_t *


upon assignment to ansp. Function get_list views a nonsentinel data item
as the first value in the list it is creating, so it allocates a node and places the
integer in the digit component. The problem is that the other component,
restp, should point to the linked list constructed from the rest of the input.
Like all good recursive algorithms, this one knows when it's time to call in an
expert: It simply trusts that a function whose purpose is to form a linked list
from some input data will do its job as advertised, and it calls get_list (i.e.,
itself) to find out the pointer value to store in restp.
Figure 14.18 shows a looping version of get_list.

Figure 14.18 Iterative Function get_list

* Forms a linked list of an input list of integers terminated by SENT


*/
bust ‘node: t*
get_list(void)
{
int data;
list_node_t *ansp,

(continued)
748 Dynamic Data Structures

Figure 14.18 (continued)

*to fillp, /* pointer to last node in list whose


restp component is unfilled */
*newp; /* pointer to newly allocated node * /

/* Builds first node, if there is one */


scanf("%d", &data);
if (data == SENT) {
ansp = NULL;
} else {
ansp = (list_node_t *)malloc(sizeof (list_node t));
ansp->digit = data;
to fillp = ansp;

/* Continues building list by creating a node on each


iteration and storing its pointer in the restp component of
the node accessed through to fillp */
for (scanf("%d", &data);
data != SENT;
scanf("%d", &data)) {
newp = (list _node _t *)malloc(sizeof (list_node t));
newp->digit = data;
to_fillp->restp = newp;
to_fillp = newp;
}

/* Stores NULL in final node's restp component */


to_fillp->restp = NULL;
;
return (ansp);

Searching a List for a Target


Another common operation is searching for a target value in a list. A list search
is similar to an array search in that we must examine the list elements in
sequence until we find the value we are seeking or until we examine all list ele-
ments without success. The latter is indicated by advancing past the list node
whose pointer field is NULL.
Function search in Fig. 14.19 returns a pointer to the first list node that
contains the target value. If the target value is missing, search returns a value
of NULL.
14.3 Linked List Operators 749

Figure 14.19 Function search

* Searches a list for a specified target value. Returns a pointer to


* the first node containing target if found. Otherwise returns NULL.
*/
list_node_t *
search(list_ node t *headp, /* input - pointer to head of list */
aug target) /* input - value to search for * /
{
list_node_t *cur_nodep; /* pointer to node currently being checked */

for (cur_nodep = headp;


cur_nodep != NULL && cur_nodep->digit != target;
cur_nodep = cur_nodep->restp) {}

return (cur_nodep);
}

Avoid Following a NULL Pointer


Observe carefully that the order of the tests in the loop repetition condition of
search is critical. If the order of the tests were reversed and if cur_nodep
were NULL,

cur _nodep->digit != target && cur_nodep != NULL

our program would attempt to follow the NULL pointer, an action that usually
causes a run-time error. Because C always does short-circuit evaluation of log-
ical expressions, we can be certain that in the original expression, there will be
no attempt to follow cur_nodep if it is found to be NULL.

EXERCISES FOR °°!"Check


SECTION 14.3 1. Trace the execution of function search for a list that contains the three
numbers 4, 1, and 5. Show the value of pointer cur_nodep after the update
of each iteration of the for loop. Do this for the target values 5, 2, and 4.

Programming
1. Write a function that finds the length of a list of list _node_t nodes.
2. Write a recursive version of function search.
750 Dynamic Data Structures

14.4 REPRESENTING A QUEUE WITH A LINKED LIST

A queue is a data abstraction that can be used, for example, to model a


line of
customers waiting at a check-out counter or a stream of jobs waiting to be
printed by a printer in a computer center. In a queue, new elements are inserted
at one end (the rear of the queue), and existing elements are removed from the
other end (the front of the queue). In this way, the element that has been waiting
longest is removed first. A queue is called a first-in, first-out list (FIFO).
We can implement a queue using a linked list that grows and shrinks as
elements are inserted and deleted. We will need to keep track of both the
first
node of the linked list, which is the front of the queue, and the last
node, which
is the rear, since removing a node from the queue requires access to the front
and adding a node requires access to the rear. In addition, we need to be able
to
find out the size of the queue, preferably without having to traverse the entire
list counting nodes. The typedefs in Fig. 14.20 define a queue type with
the
desired features.
One queue we might wish to model is a line of passengers waiting to be
served by a ticket agent. Figure 14.21 shows such a queue. The two primary
operations required to maintain a queue are addition and removal of elements.
The ability to display the queue is also helpful. Figure 14.22 shows a
main
function that creates and maintains a queue of passengers based on
the user's
input. The function's main control structure is a do-while loop to
get user
choices and an embedded switch statement to process each choice.
Figure 14.23 shows functions add_to_q and remove from
_q.
Because queue elements are always added at the end of the queue,
add_toq

Figure 14.20 Structure Types for a Linked-List Implementation of


a Queue

/* Insert typedef for queue_element_t */

typedef struct queue_nodes {


queue_element t element;
struct queue_node_s *restp;
} queue_node
t;

typedef struct queue_s {


queue_node_t *frontp,
‘aelsyeuig\oyy
SLigRe size;
} queue t;
14.4 Representing a Queue with a Linked List 751

Figure 14.21
A Queue of
Passengersin
a Ticket Line

q.frontp

q.rearp

q.size

* Creates and manipulates a queue of passengers.

/* Insert functions scan_passenger, print_passenger, add_to


q,
remove _from_q, and displayq */
int
main(void)
{
queue_t pass_q = {NULL, NULL, 0}; /* passenger queue - initialized to
empty state */
queue element t next pass, fst pass;
char choice; /* user's request */

/* Processes requests */
do {
printf("Enter A(dd), R(emove), D(isplay), or Q(uit)> ");
while (!isalpha(choice = getchar())) {}
switch (toupper(choice)) {
case 'A':
printf("Enter passenger data> ");
scan_passenger(&next_pass);
add_to_q(&pass_
gq, next _pass);
break;

case 'R':
LEa(passiq.size >.0) 4
fst_pass = remove _from_q(&pass q);
(continued)
752 Dynamic Data Structures

Figure 14.22 (continued)

printf("Passenger removed from queue: \n");


print_passenger(fst_pass);
} else {
printf("Queue empty - no-one to delete\n");
}
break;

case 'D':
if (pass q.size > 0)
display q(pass_q);
else
printf("Queue is empty\n");
break;

case 'Q':
printf£("Leaving passenger queue program with $d \n",
pass q.size);
printf("passengers in the queue");
break;

default:
printf("Invalid choice -- try again\n");
}
} while (toupper(choice) != 'Q');

Ge eumman (Oi

Figure 14.23 Functions add_to_q and remove_from_q

Adds ele at the end of queue accessed through qp


Pre: queue is not empty

(continued)
14.4 Representing a Queue with a Linked List 753

Figure 14.23 (continued)

void
add_to_q(queue t *qp, /* input/output - queue */
queue_element_t ele) /* input - element to add */
{
if ((*qp).size == 0) { /* adds to empty queue */
(*qp).rearp = (queue_nodet *)malloc(sizeof (queue_node t));
(*qp).frontp = (*qp).rearp;
} else { /* adds to non-empty queue */
(*qp).rearp->restp =
(queue_nodet *)malloc(sizeof (queue_node t));
(*qp).rearp = (*qp).rearp->restp;
}
(*qp).rearp->element = ele; /* defines newly added node */
(*qp).rearp->restp = NULL;
++((*qp).size);
}
/*
* Removes and frees first node of queue, returning value stored there.
* Pre: queue is not empty
*/
queue element _t
remove from_q(queue_t *qp) /* input/output - queue */
{
queue node t *to freep; /* pointer to node removed */
queue_element_t ans; /* initial queue value which is to be
returned */

to freep = (*qp).frontp; /* saves pointer to node being


deleted */
ans = to freep->element; /* retrieves value to return * /
(*qp).frontp = to freep->restp; /* deletes first node */
free(to freep); /* deallocates space * /
--((*qp).size);
if ((*qp).size == 0) /* queue's ONLY node was deleted */
(*qp).rearp = NULL;

return (ans);
754 Dynamic Data Structures

works primarily with the pointer rearp. The pointer frontp would be affect-
ed by an addition to the queue only if the queue were previously empty. On the
other hand, elements are always removed from the front of a queue, so
remove _from_q deals exclusively with the pointer frontp unless the ele-
ment being removed is the only one remaining. Since queue nodes are dynami-
cally allocated, we must explicitly free their memory when it is no longer
needed. Function remove_from_q saves a copy of the frontp pointer in the
variable to_freep before placing a new value in frontp. Then it uses
to_freep to free the space allocated for the node being removed.
Figure 14.24 shows the addition of passenger Carson to a queue that
already contains passengers Brown and Watson. The “After” diagram shows
the changes in color.
Figure 14.25 shows the removal of passenger Brown from the queue.

Before
Figure 14.24
Addition of One
Passenger to a
Queue

q.frontp

q.rearp

q.size

add_to_q(éeq, next_pass);

After

q.frontp

q.rearp

q.size
14.4 Representing a Queue with a Linked List 755

Before
Figure 14.25
Removal of One
Passenger from
a Queue

q.frontp

q.rearp

q.size

remove_from_q(é&q);

During
function
call to_freep

q.frontp

q.rearp

q.size

Value returned
by remove_from_q

cy heee. EET! Abe tae sae ealecherk


EXERCISES FOR
SECTION 14.4 1. What does the following segment do to the final queue g, as shown in Fig.
14.25? Draw the result.

queue_element_t one_pass = {"Johnson", 'E', 5};

q-.rearp->restp =
(queue_nodet *)malloc(sizeof (queue_node t));
756 Dynamic Data Structures

q-rearp = q.rearp—>reap;
q-rearp->element = one_pass;
q-rearp->restp = NULL;
+t+((q.Size);

2. Draw the queue resulting from executing

one = remove from_q(&pass_q);

ifpass_qis

pass_q.frontp

pass_q.rearp

pass_q.size

We saw in Chapter 10 how the stack data abstraction could be used to track mul-
tiple parameter and local variable values created by recursive function calls. The
stack data structure is used extensively in computer system software such as
compilers and operating systems.
In a stack, elements are inserted in and removed from the same end of the
list. This end is called the top of the stack. Since the element that is removed
first is the one that has been waiting the shortest length of time, a stack is
called a last-in, first-out list (LIFO).
We frequently use stacks. in everyday life. A stack of trays in a cafeteria is
one classic last-in, first-out example. We also stack tasks automatically to deal
with interruptions. If my phone were to ring right now, I would put aside this
chapter (i.e., place it on the stack) and answer the phone. If, during the phone
conversation, a person with a desperate look burst into my office and asked
for the phone number of the campus police, I would put aside the phone con-
versation and look up the number. Then I would resume the phone conversation
(i.e., remove it from the top of the stack for processing). Only when the phone
call was complete would I get back to writing.
The primary operations used in manipulating a stack are the addition and
removal of elements, operations called push and pop, respectively. In addition,
one must be able to examine the element at the top of the stack and to determine
if the stack is empty.
14.5 Representing a Stack with a Linked List 757

EXAMPLE > A stack s of character elements is shown in Fig. 14.26. The stack has four ele-
ments: the first element placed on the stack was '2', and the last element
added was '*'.
Figure 14.27 shows the stack s after its top element ('* ') has been
popped from the stack. Fig. 14.28 shows the stack after a '/' has been pushed
onto s.
A stack can be implemented as a linked list in which all insertions and
deletions are performed at the list head. List representations of our stacks from
Fig. 14.27 and Fig. 14.28 are shown on the left side of Fig. 14.29. The nodes
that hold elements of the stack are typical linked-list node structures with an
information field plus a pointer field that points to the next node.
The stack s can be represented by a structure with a single pointer com-
ponent, topp, that points to the top of the stack. The typedefs of Fig. 14.30
define such a stack type.
Figure 14.31 shows implementations of the functions push and pop anda
driver program that first builds the stack illustrated in Fig. 14.26 and then car-
ries out the operations shown in Fig. 14.27 and Fig. 14.28. Finally, it repeated-
ly pops and prints stack elements until the stack is empty.
Function push allocates a new stack node, storing the pointer to the cur-
rent stack in the new node's restp component and setting the stack top to
point to the new node. Function pop matches our remove_from_q function
except that there is no “rear” pointer to deal with in function pop. c

Figure 14.26
Stack s
x
Q
w+

Figure 14.27
Stack s after
pop Operation

Figure 14.28
Stack s after
push Operation {Dh
(AS)

n
758 Dynamic Data Structures

Figure 14.29 Linked-List Representation of Stacks

Stack of three characters

s.to PP C
+

Stack after insertion of '/'

s.topp /
C
+

typedef char stack element “t;

typedef struct stack_nodes {


stack_element_t element;
Struct stack node s *restp;
} stack_node_t;

typedef struct stack_s {


stack_node_t *topp;
Pestack t;

Figure 14.31 Stack Manipulation with Functions push and pop

/*

* Creates and manipulates a stack of characters


*/ =

#include <stdio.h>
(continued)
14.5 Representing a Stack with a Linked List 759

Figure 14.31 (continued)

/* Include typedefs from Fig. 14.30 */

/*
* The value inc is placed on top of the stack accessed through sp
* Pre: the stack is defined
*/
void
push(stack_t *sp, /* input/output - stack */
char GC) /* input - element to add */
{
stack_node_t *newp; /* pointer to new stack node */

/* Creates and defines new node */


newp = (stack_node t *)malloc(sizeof (stack_node_t));
newp->element = c;
newp->restp = (*sp).topp;

/* Sets
stack pointer to point to new node */
(*sp).topp = newp;
}

/*

* Removes and frees top node of stack, returning character value


* stored there.
* Pre: the stack is not empty
*/
char
pop(stack_t *sp) /* input/output - stack */
{
stack_node t *to_freep; /* pointer to node removed */
stack_element t ans; /* value at top of stack */

to_freep = (*sp).topp; /* saves pointer to node being deleted


*/
ans = to freep->element; /* retrieves value to return */
(*sp).topp = to_freep->restp; /* deletes top node */
free(to_ freep); /* deallocates space */

GBecuEn (ans)?
is
(continued)
760 = Dynamic Data Structures

Figure 14.31 (continued)

ont
main(void)
{
stack_t s = {NULL}; /* stack of characters - initially empty */

/* Builds stack of Fig. 14.26 */


push(&s, '2');
push(és, “+ );
push(&s, 'C');
push(&S, ‘*');

/* Pops stack's top element and prints it */


printf£("te\n", pop(&s)):

/* Completes stack of Fig. 14.28 */


push(&s, '/');

/* Empties stack element by element */


printf("\nEmptying stack: \n");
while (s.topp != NULL) {
printt("tc\n", pop (és) );
}

Beeman, (0):
}

Emptying stack:

EXERCISEFOR = °2"<nec
If-Check
SECTION 14.5 1. Draw the stack resulting from execution of the following fragment. Assume
you are working with a linked-list implementation of a stack of individual
14.6 Ordered Lists 761

characters, as illustrated in Fig. 14.29.

{
stack_t stk = {NULL};

push(&stk, 'a');
push(&stk, 'b');
pop(&stk);
push(&stk, 'c');

14.6 ORDERED LISTS

In queues and stacks, the time when a node was inserted in the list determines
the position of the node in the list. The data in a node of an ordered list include
a key component that identifies the structure (for example, an ID number). An
ordered list is a list in which each node's position is determined by the value of
its key component, so that the key values form an increasing or decreasing
sequence as we move down the list.
Maintaining an ordered list is a problem in which linked lists are particu-
larly helpful because of the ease with which one can insert and delete nodes
without disturbing the overall list. As you might expect, we can use an ordered
list to maintain a list of integers, real numbers, or airline passengers. We could
modify the menu-driven program in Fig. 14.22 to maintain an ordered list of
passengers instead of placing the passengers in a queue. By using the passen-
ger's name as the key component, we would keep the list in alphabetical order.
An advantage of using an ordered list is that we can delete any passenger from
the list, whereas in a queue only the passenger at the front can be removed.
Also, we can easily display the passengers in an ordered list in sequence by key
field. Programming Project | at the end of this chapter asks you to modify the
menu-driven program to use an ordered list of passenger data. We solve a sim-
pler problem next.

Case Study: Maintaining an Ordered List


of Integers
PROBLEM

To illustrate some common operations on ordered lists, we will write a program


that builds an ordered list of integer values by repeated insertions and then dis-
plays the size of and values in the list. The next section of the program deletes
values as requested, redisplaying the list after each deletion.
762 Dynamic Data Structures

<a O
The representation of an ordered list should have a component to represent the
list size so that we will not need to traverse all the nodes to count them when-
ever we need the size. Let's sketch a couple of ordered lists, and then we will
specify our data requirements.
A nonempty ordered list would be

my_list

An empty ordered list would be

Data Requirements
Structure Types
ordered list _t
components:
headp /* pointer to first of a linked
list of nodes Ba
size /* number of nodes currently in list */

list_nodet
components:
key /* integer used to determine node order of)
restp /* pointer to rest of linked list shi

Problem Constant
SENT -999 /* sentinel value a,

Problem Input
int next_key /* each record key uals

Problem Output
ordered list _t my list /* the ordered list ay)
14.6 Ordered Lists 763

ee
DESIGN
Algorithm

1. Create an empty ordered list.


2. for each nonsentinel input key
3. Insert the key in the ordered list.
4. Display the ordered list and its size.
5. for each nonsentinel input key
6. Delete node marked by key.
7. if deletion is successful
8. Display the ordered list and its size.
else
9. Display error message.

IMPLEMENTATION

The type definitions and main function are shown in Fig. 14.32. Algorithm
Step 1 is accomplished through initialization of an ordered list variable at dec-
laration. Step 2 and Step 5 use typical sentinel-controlled for loops. Step 6 and
Step 7 are combined since we can design function delete to return as the
function value a flag indicating the success or failure of its attempt to delete a
node.

Figure 14.32 Building an Ordered List through Insertions and Deletions

* Program that builds an ordered list through insertions, and then modifies
* it through deletions.

typedef struct list node s {


Int key;
struct list_node_s *restp;
P PESt node t ;

typedef struct ordered list _s {


list_nodet *headp;
int size;
} ordered_list_t;

(continued)
764 Dynamic Data Structures

Figure 14.32 (continued)

/* include here functions insert, delete, and print _list */

#define SENT -999

int
main(void)
{
nite next_key;
ordered list _t my list = {NULL, 0};

/* Creates list through in-order insertions */


printf("Enter integer keys--end list with %d\n", SENT);
for (scanf("%d", &next_key);
next_key != SENT;
scanf("%d", &next_key)) {
insert(&my_ list, next_key);
;

/* Displays complete list */


printf("\nOrdered list before deletions:");
prant: list(my list);

/* Deletes nodes as requested */


printf("\nEnter a value to delete or %d to quit> ", SENT);
for (scanf("%d", &next_key);
next_key != SENT;
scanf("%d", &next_key)) {
if (delete(&my_ list, next_key)) {
printf("%sd deleted. New list:", next_key);
print liscimy 11st);
} else {
printf("No deletion. %d not found\n", next key);
}

iaeheiubaigl (((0)))5

(continued)
14.6 Ordered Lists 765

Figure 14.32 (continued)


Fa ee ee eee a 2 OR ie aera CL OCU Oc GnUra CacCSh anno CAA Ree ae IBC DOCS ar PAA EE cntthb Clin SOO TOREe ace ce Come IE Bars CRORENSOS Hrn ERIE TRIAS MaRS hGbn son aaah

Enter integer keys--end list with -999


5 8 4 6 -999
Ordered list before deletions:
size = 4
list =

UW
On

Enter a value to delete or -999 to quit> 6


6 deleted. New list:

Enter a value to delete or -999 to quit> 4


4 deleted. New list:
size = 2
list = 5
8

Enter a value to delete or -999 to quit> -999

Functions insert, delete, and print_list


Function insert is similar to our add_to_queue function in that both the
size and pointer components of our ordered list structure will require modifica-
tion. However, function insert differs from our queue functions in that we
must first search our linked list of nodes for the proper place to insert. Finding
the right place to insert is quite simple to conceptualize if we use a recursive
approach. We use recursion in our design of helper function insert-
in_order, the function that handles the linked-list aspect of element insertion.
Then function insert can simply increment the list size and update the value
of the list head pointer with the value resulting from a call to insert_-
in_order.

Algorithm for insert_in_order

1. if the list is empty /* simple case | oi


2. The new list is just a new node containing the new key and an empty
restp component.
766 Dynamic Data Structures

else if the key to insert should precede /* simple case 2 a


the list's first node
3. The new list is a new node containing the new key and with the old list
as the restp component.
else /recursiveisteps £4
4. The new list starts with the first value of the old list. The restp com-
ponent is the rest of the old list with the new node correctly inserted.
Figure 14.33 illustrates the three possibilities.
For function delete, we need to traverse the list until we find the node to
delete. Always on the lookout for an opportunity to reuse code previously devel-

Simple Case 1
Figure 14.33
Function insert old_listp new_key new_listp
and Recursive La
Function Va
insert_in_order

Simple Case 2

old_listp new_key new_listp

Recursive Step

old_listp new_key
new_listp is old_listp
with circled component’
changed to

which is the result of inserting


6 in order in

ea
14.6 Ordered Lists 767

Figure 14.33 (continued)

/*

* Inserts a new node containing new_key in order in old list, returning as


* the function value a pointer to the first node of the new list
*/
list_node_t *
insert_in order(list_node t *old_listp, /* input/output */
int new_key) /* input */
{
list _node t *new_listp;

if (old_listp == NULL) {
new_listp = (list _node_t *)malloc(sizeof (list_node t));
new_listp->key = new_key;
new_listp->restp = NULL;
} else if (old_listp->key >= new_key) {
new_listp = (list _node_ t *)malloc(sizeof (list node t));
new_listp->key = new_key;
new_listp->restp = old _listp;
} else {
new_listp = old _listp;
new_listp->restp = insert_in order(old listp->restp, new key);
}
return (new _listp);
}
/*

*Inserts a node in an ordered list.


*/
void
insert(ordered_list_t *listp, /* input/output - ordered list */
int key ) /* input */
{
++((*listp) Jsize);
(*listp).headp = insert _in order((*listp).headp, key);

oped and tested, we might expect that our function search from Fig. 14.19
would provide a good starting point. Let's consider the result of applying the
algorithm of search's for loop to our longest ordered list in search of the
768 Dynamic Data Structures

value 6 to delete.

for (cur_nodep = headp;


cur_nodep != NULL && cur_nodep->digit != target;
cur _nodep = cur_nodep->restp) {}

At loop exit, our memory status will be


headp

cur_nodep

This memory status is a good news/bad news situation. The good news is that we
did find the node to delete. The bad news is that we have no way of accessing
the node whose restp component will need to be changed to carry out the dele-
tion! Clearly, what we really need to search for is not the node we wish to
delete, but rather the node that precedes the node we wish to delete. This imme-
diately suggests that deleting the list's first node is a special case, leading us to
the initial algorithm that follows.

Initial Algorithm for delete

1. if target is found in the list's first node


2. Copy headp into to_freep.
3. Change headp to point to rest of list.
4. Free to_freep's memory block.
5. Decrement list size.
6. Setis_deletedtol.

7. Initialize cur_nodep to frontp and traverse list as long as


the node cur_nodep accesses is not the last in the list, and the
node after it does not contain the target.
8. if target is found
9. Copy the address of the node to delete into to_freep.
10. Move the restp pointer of the node being deleted into the
restp component of the node accessed by cur_nodep.
11. Free to_freep's memory block.
14.6 Ordered Lists 769

12. Decrement list size.


13. Setis_deletedtol.
else
14. Setis_ deleted to 0.
15. Return is deleted.
If we trace this algorithm on a few lists, we find that this algorithm is actu-
ally a generic “delete from linked list” algorithm that does not take advantage of
the fact that our list is ordered. Since the list is ordered, we actually do not
always need to search all the way to the end of the list to know that our target is
not present. As soon as we encounter a key greater than the target, we can give
up. To do this, we need only to modify the last phrase of Step 7 to read “and the
node after it does not contain the target or a key greater than the target.” In addi-
tion, we must add a test that allows us to handle a deletion request for an empty
list. Figure 14.34 shows an implementation of this algorithm.

Figure 14.34 Iterative Function delete

/*
* Deletes first node containing the target key from an ordered list.
* Returns 1 if target found and deleted, 0 otherwise.
*/
int
delete(ordered list _t *listp, /* input/output - ordered list */
int target) /* input - key of node to delete */
{
list_node_t *to freep, /* pointer to node to delete */
*cur nodep; /* pointer used to traverse list until it
points to node preceding node to delete */
riake isedeveted,

/* If list is empty, deletion is impossible * /


if ((*listp).size == 0) {
is deleted = 0;

/* If target is in first node, delete it * /


} else if ((*listp).headp->key == target) {
to freep = (*listp).headp;
(*listp) .headp = to_freep->restp;
free(to_freep);
--((*listp).size);

(continued)
770 = Dynamic Data Structures

Figure 14.34 (continued)

is deleted = 1;

/* Otherwise, look for node before target node; delete target */


} else {
for (cur_nodep = (*listp) .headp;
cur_nodep->restp != NULL && cur_nodep->restp->key < target;
cur_nodep = cur_nodep->restp) {}
if (cur_nodep->restp != NULL && cur_nodep->restp->key == target) {
to_freep = cur_nodep->restp;
cur_nodep->restp = to_freep->restp;
free(to freep);
--((*listp).size);
is_deleted = 1;
} else {
is deleted = 0;
}
;

return (isdeleted);

By the time we handle all the special cases, our simple little search loop
has become a fairly complex algorithm. Let's investigate to what extent writing
a recursive delete _ordered_node helper function would simplify the
process. Our insert_in_order helper function was quite straightforward,
but then we did not have to deal with the possibility that the insertion might fail.
We will pattern delete_ordered_node after insert in order with
respect to its returning as its value a pointer to the first node of the revised list.
We will also need an output parameter that is a flag indicating whether the
deletion occurred. Assuming the existence of our helper function makes
delete's algorithm very simple, as shown in Fig. 14.35.

Figure 14.35 Function delete Using Recursive Helper Function

Deletes first node containing the target key from an ordered list.
Returns 1 if target found and deleted, 0 otherwise.

(continued)
14.6 Ordered Lists 771

int
delete(ordered list_t *listp, /* input/output - ordered list */
int target) /* input - key of node to delete */
{
int is deleted;

(*listp).headp = delete ordered _node((*listp)-.headp, target,


&is deleted),
if (is_deleted)
--((*listp).size);

return (is deleted);


}

We can use the algorithm we developed for our iterative delete as a guide
in identifying the cases to handle in function delete ordered_node.

Algorithm for delete_ordered_node

ik. di. Listp is NULL /* simple case 1 “7


2. Set is_ deleted output parameter to 0.
3. Set ansp to NULL.
else if first node contains target /* simple case 2 ea
4. Set is_ deleted output parameter to 1.
5. Copy listp into to _ftreep:
6. Set ansp to restp pointer of first node.
7. Free memory block accessed by to_freep.
else if key in first node > target key /* simple case 3 t/
8. Set is_deleted output parameter to 0.
9. Copy listp into ansp.
else PP Tecursivesico: =7/
10. Copy listp into ansp.
11. Use a recursive call to delete target from rest of list
and store result in restp pointer of first node.
12. Return ansp.

Figure 14.36 shows an implementation of this algorithm. We will leave our


print list function as an exercise for you to do.
772 Dynamic Data Structures

Figure 14.36 Recursive Helper Function delete_ordered_node

If possible, deletes node containing target key from list whose first
node is pointed to by listp, returning pointer to modified list and
freeing deleted node. Sets output parameter flag to indicate whether or
not deletion occurred.
*/
list_nodet *
delete ordered _node(list
node t *listp, /* input/output - list to
modify */
mnt target, /* input - key of node to
delete */
Int *is deletedp)/* output - flag indicating
whether or not target node
found and deleted */
{
list_node t *to freep, *ansp;

/* if list is empty - can't find target node - simple case 1 */


fee (listp ==NULL) 4
*is deletedp = 0;
ansp = NULL;

/* if first node is the target, delete it - simple case 2 */


} else if (listp->key == target) {
*is deletedp = 1;
to_freep = listp;
ansp = listp->restp;
free(to freep);

/* if past the target value, give up simple case 3 */


} else if (listp->key > target) {
*is deletedp = 0;
ansp = listp;

/* in case target node is farther down the list, - recursive step


have recursive call modify rest of list and then return list * /
} else {
ansp = listp;

(continued)
14.7 A Linked-List Application in Mathematics 773

Figure 14.36 (continued)

ansp->restp = delete ordered node(listp->restp, target,


is deletedp) ;
}

return (ansp);

EXERCISES FOR Self-Check

Compar e the original call to delete _ordered_node (from function


SECTION 14.6 1. del ete)

(*listp).headp =
delete ordered _node((*listp).headp, target,
&is deleted);

and the recursive call to the same function

ansp->restp =
delete ordered node(listp->restp, target,
is _deletedp);

Why does one call apply the address-of operator to the third argument and the
other does not?
2. Modify helper function insert_in_ order so that it will not insert a
duplicate key. Use an output parameter to signal the success or failure of the
insertion.

Programming
1. Write the print _1list function called by the program in Fig. 14.32.
2. Write a function retrieve_node that returns a pointer to the node con-
taining a specific key. The function should take a single ordered_list_t
input parameter. If the desired node is not found, retrieve_node should
return the NULL pointer.

14.7 A LINKED-LIST APPLICATION IN MATHEMATICS

Manipulation of polynomials is a very common activity in mathematics and in


many mathematics-based disciplines. In this section, we study an approach to rep-
resenting polynomials that allows us to automate some frequent manipulations.
774 Dynamic Data Structures

Case Study: Manipulation of Polynomials


PROBLEM

We have been asked to develop a representation of a polynomial in a single vari-


able that will ultimately allow us to create, scan, print, add, multiply, evaluate,
differentiate, integrate, and find zeros of polynomials automatically. Our initial
system is to implement basic I/O, addition, and evaluation.

<.Se
A polynomial can be viewed as a list of terms in which each term is represented
by a coefficient that is a nonzero signed real number and an exponent that is a
nonnegative integer. Because there may be any number of terms, an imposition
of an upper bound on this number is to be avoided if possible. Although we may
arrive at other representations when manipulating polynomials, we usually pre-
fer to see polynomials displayed in a form in which exponents are in decreasing
order and each exponent appears only once, such as

ee 4 5x?
= 2x + 0.5

Since this is an ordered list with exponents as keys and the list uses a descend-
ing ordering of keys, we can model some operations for polynomial manipula-
tion on operations we developed for ordered lists.
First, we need to specify the structure of each term and of the list as a
whole. Then, we can outline the interface information for our functions. Since C
has no exponentiation operator and our basic character set has no superscript
indicator, we need to adopt a convention for showing exponents in our external
representation of polynomials. We will use the caret ~ to mean “the integer
that follows is an exponent.” This means that the polynomial

x + 5x? — 2x 410.5

would be written as

iA — ey ie DE eine

Data Structures and Operations


Structure Types
poly t /* polynomial */
components:
variable /* variable name such as xX) Vi; Vp ets *y
term_listp /* pointer to an ordered list of term
nodes */
14.7 A Linked-List Application in Mathematics 775

term _t /* one term of a polynomial */


components:
coeff /* coefficient */
expon /* exponent */

term node. t /* one node of a list of terms */


components:
term /* one term structure */
restp /* pointer to rest of term nodes */

Operations
scan_poly /* scans a line of text producing a
polynomial */
print poly /* prints a polynomial */
add_poly /* adds two polynomials in the same
variable * /
eval_ poly /* evaluates a polynomial ata
given value for its variable */

Using the structures just described leads to the internal representation of

xO Ar SxS. = 2x40 25

that is illustrated in Fig. 14.37.

Figure 14.37
Internal
Representation
of x*4 + 5x3 - x4 5x3 -2x 0.5
2x + 0.5

<a
We design and implement each operation separately. Figure 14.38 shows our
implementation of the polynomial data type.

Implementation of Polynomial Data Type


We have noticed a pattern in our allocation of linked list nodes that we will
formalize in a macro for this case study. If we are allocating a node of type
node_t, we always cast the pointer before assigning it.

nodep = (node
_t *)malloc(sizeof (node _t));
776 Dynamic Data Structures

Figure 14.38 typedefs for Polynomial Case Study

typedef struct terms { /* one term of a polynomial */


double coeff;
int expon;
} terme t?

typedef struct term_node_s { /* one node of a linked list


of polynomial terms */
eerie etn
struct term _node_s *restp;
} term_node t;

typedef struct polys { /* a polynomial */


char variable;
term_node_t *term listp;
pupolyst;

The following macro definition will simplify our dynamic allocation of


nodes:

#define TYPED ALLOC(type) (type *)malloc(sizeof (type) )

With this macro, we can give nodep the desired value simply by writing

nodep = TYPED_ALLOC(node_t);

Function scan_poly
Algorithm for scan_poly

1. Get a deblanked line of text.


2. if not EOF
3. Break off first term.
4. Create a term node unless term is 0, and initialize result
polynomial.
5. for each remaining term
6. Break off term.
7. Create a term node and insert it in order in term list of
TeeSiUntes
8. Assign result indirectly through output parameter and return
1.
else
9. Return EOF.
14.7 A Linked-List Application in Mathematics 777

In our development of linked-list functions, we have seen how helpful it is


to sketch the various structures and nodes being processed. In this example, we
omit detailed designs of the steps of scan_poly and of the helper function
make_term so as not to overemphasize the string processing aspects of the
case study. However, in Fig. 14.39, we illustrate the mental picture associated

INPUT: x44 + 5x43 -2x+0.5


Figure 14.39
“Mental Picture”
of Algorithm for 1. Get a deblanked line of text.
scan_poly
"%4445x43-2x+0.5"

3. Break off first term.

first term aa
remaining terms
NyAQ"

\
"+5x4S-2x+0.5"

4. Create a term node and


initialize result polynomial.

result polynomial
first term node

¢ ee coeff represents
oe x4

5. For each remaining term

6. Break off term.


7. Create a term node and insert it
in order in term list of result.

xA4
ee Ga5x3 —-2x 0.5
Final polynomial
created by
scan_poly
778 Dynamic Data Structures

with each of scan_poly's major steps, as applied to our sample polynomial

ea te 5x Se me 20 +025

Figure 14.40 shows an implementation of this algorithm. Helper function


insert_descending is based on insert_in_ order from Fig. 14.33.
Frequently, developing I/O functions is one of the most complex parts of imple-
menting a new data type. This is certainly true for polynomials. In fact, function
make_term gives you a glimpse of the type of parsing that is a compiler's ini-
tial activity. Notice that make_term’s algorithm is fairly complex even though
it does no error checking.

Figure 14.40 Functions scan_poly, scan_deblanked_line, make_term, and


insert_descending

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

#define POLYSIZ 100 /* maximum length of input string representing


polynomial */
#define TERMSIZ 25 /* maximum length of input string representing a term */

/* macro for typed dynamic memory allocation */


#define TYPED ALLOC(type) (type *)malloc(sizeof (type) )

/*

* Gets and deblanks one line of data from standard input. Returns EOF on
* end of file, 1 otherwise. If data line will not fit in allotted space,
* stores portion that does fit and discards rest of input line.
7
int
scan_deblanked_line(char *dest, /* output - destination string */
int dest_len) /* input - space available in dest */
{
ante chie

/* Get next line one character at a time, storing only non-blank


characters */
i, = Oe

(continued)
14.7 A Linked-List Application in Mathematics 779

Figure 14.40 (continued)

for (ch = getchar();


t=O Ne S6& ch l= EOF &&4 i*< dest: lenu=r1;
ch = getchar())
if (!isspace(ch) )
dest[it++] = ch;
dest[i] = '\0';

/* Discard any characters that remain on input line */


while (ch != '\n' && ch != EOF)
ch = getchar();
if (dest[0] == '\0' && ch == EOF)
Bet Uienen (EOE)
else
igsKewbein ((1L))\F
}
/*

* Forms a term structure to represent the term of a polynomial stored in


* termstr;
* Internal comments assume var is 'x'-- algorithm works for any letter
* Pre: termstr is a valid term
*/
Eermnt
make _term(const char termstr[], /* input - string representing one term */
char var) /* input - name of variable * /
{
WSN Ne ey
char *rest_term; /* starting address of unprocessed piece of
termstr */
sl ghe maybe expon = 1, /* flag indicating whether it is still
possible that termstr contains an
explicit exponent */
i;
if (termstr[0] == var) { /* term begins x * /
t.coeff = 1;
rest _term = &termstr[1];
} else if (termstr[0] == '+' && /* term begins +x */
termstr[1] == var) {
t.coeff = 1;

(continued)
780 Dynamic Data Structures

Figure 14.40 (continued)

rest _term = &termstr[2];


} else if (termstr[0] == '-' && /* term begins -x */
termstr[1] == var) {
t.coeff = -1;
rest term = s&termstr[2];
} else { /* term begins with coefficient */
sscanf(termstr, “%1f", &t.coeff);
for’ \(a.aeele /* looks for x */
i < strlen(termstr) && termstr[i] != var;
ay
if (termstr[i] == var) { /* found x */
rest_term = &termstr[i + 1];
} else { /* no x in string */
maybe expon = 0;
t.expon = 0;
}
}
if (maybe_expon) /* scans exponent if present */
if (rest_term[0] == '*')
sscanf(&rest_term[1], "%d", &t.expon);
else
t.expon = 1;
jeenevein (2) 9
}
/*

* Inserts new_term in ordered list of term nodes so exponents remain


* in descending order
*/
term node _t *
insert_descending(term_node_t *old listp, /* input/output - list of term
nodes */
term t new_term) /* input */
{
term_node_t *new_listp;

if (old_listp == NULL) { /* empty list - simple case 1 */


new_listp = TYPED _ALLOC(term_node
t);

(continued)
14.7 A Linked-List Application in Mathematics 781

Figure 14.40 (continued)

new_listp->term = new_term;
new_listp->restp = NULL;
} else if (old_listp->term.expon <= /* new_term's exponent should
new_term.expon) {
/* precede first node
- simple case 2 7,
new_listp = TYPED _ALLOC(term_node
t);
new_listp->term = new_term;
new_listp->restp = old _listp;
} else { /* have insert descending +7
new_listp = old_listp; /* insert new term in rest
of list: recursive step =,
new_listp->restp = insert _descending(old listp->restp, new_term);
}

return (new_listp);

Scans a line of text producing a polynomial. Sample valid input:


Me A oes 2 OS
Returns value 1 on successful conversion, EOF on end of file
Pre: input format is correct
*/
int
scan_poly(poly t *polynom) /* output - the polynomial scanned */
{
polyp© =p /* local variable for polynomial */
term_t term; /* one term of the polynomial af
char polystr[POLYSIZ], /* space for string representation of
polynomial a7
termstr[TERMSIZ]; /* space for string representation of term */
Sige stacus, iL, JF
char *rest.poly; /* address of rest of polynomial string after a
term is broken off /.

/* Get deblanked line of text */


status = scan _deblanked_line(polystr, POLYSIZ);

(continued)
782 Dynamic Data Structures

Figure 14.40 (continued)

if (status T= BOR). {

/* Break off first term, extracting variable name */


p.variable = '\0'; /* initialize variable name and */
p.term_listp = NULL; /* term list to empty */
j= 0;
for (i = 0;
i < TERMSIZ - 1 && polystrfiy’ !='"\0" Ses
polystr[i] '= "-' && polystr[i] != "+":
seal). af
termstr[j++] = polystr[i];
if (isalpha(polystr[i]) )
p-variable = polystr[i];
}
termstr[j] = '\0';
rest_poly = &polystr[i];
12 (cermstr [0] Y= "\0")
p.term_listp = insert_descending(p.term listp,
make_term(termstr, p.variable));

/* Break off and insert remaining terms */


while (rest_poly[0] != '\0') {
joy
termstr[j++] = rest_poly[0];
OEP (ar =i
i < TERMSIZ - 1 && rest poly[i] != '\0' &&
rest_poly[i] != '-' && rest poly[i] != '+';
tur): ¢{
cvermstE[ j++) = rest poly[i);
}
termstr [|] =-"\0":
rest_poly.= &rest_poly[i];
p.term_listp = insert_descending(p.term listp,
make_term(termstr, p.variable));
}
*polynom = p;
Jo 7 end if °*/

return (status);
14.7 A Linked-List Application in Mathematics 783

Function print_poly
The following is a sample polynomial to have in mind as we develop an
algorithm for printing:

ae G43 Om. 3 =e 2 eS

Algorithm for print_poly

1. if term list is empty


2. Print0:
else
3. Print leading term.
4. for each term in the rest of the list
5. Print sign of coefficient surrounded by blanks.
6. Print absolute value of term.

The implementation in Fig. 14.41 uses helper function print


abs termto
carry out Step 6.

Figure 14.41 Functions print_poly and print_abs_term

Prints absolute value of one term of


a polynomial; if var is x,
* iL 0 n
= x is
printed as x; x is omitted; x (n> 1) is printed as x*n
*/
void
print_abs_term(term_t tm, /* input - term structure with coefficient,
exponent */
char var) /* input - variable name */
a
if ((fabs(tm.coeff) != 1.0) || (tm.expon == 0))
Printi(’s.3f ,ofabs(tm.coett)):
if (tm.expon == 1)
PRIinth(ster, var):
else if (tm.expon > 1)
printf("%c*%d", var, tm.expon);
}

/*

* Prints a polynomial on the current output line.

(continued)
784 Dynamic Data Structures

Figure 14.41 (continued)

> & (n> L)cds printed as x*n


*/
void
print_poly(poly t pl) /* input - polynomial to print */

term_node_t *tmsp; /* pointer to traverse linked list of term nodes */


termot current;

if (pl.term_listp == NULL) { /* prints zero polynomial */


pEInte(] 04).
} else {
tmsp = pl.term listp; /* erines Lirst term */
current = tmsp->term;
if (current.coeff < 0)
printf("-");
print_abs_term(current, pl.variable);

for (tmsp = tmsp->restp; /* traverses rest of terms */


tmsp != NULL; /* printing each and using */
tmsp = tmsp->restp) { /* signs as operators */
current = tmsp->term;
if (current.coeff < 0)
DELNEE(~ — ")>
else
Prints ("+")
print_abs_term(current, pl.variable);

Function add_poly
We design add_poly with an eye to the future, using a helper function
combine _1like_terms that will also be useful in multiplying polynomials.
Since we have already written insert descending, which inserts a term in
the proper place in an existing polynomial, we can add two polynomials by
building a new polynomial through insertion of all the terms of both addend
polynomials.

(=5x°3 + 4x = 3) 9+ (3x02 8k) SS) =5x 230+ 3x A oss


14.7 A Linked-List Application in Mathematics 785

We can apply function combine


like terms to simplify, giving the result

SOKns task 2h 2K — 93

Algorithm for add_poly(p1, p2)

1. if variables of pl and p2 do not match


2. Print message.
3. Use pl as sum_poly.
else
4. Initialize sum_poly to zero (the zero polynomial), with variable
from pl.
5. for each term of p1's term list
6. Insert term in descending order in sum_poly's term list.
7. for each term of p2's term list
8. Insert term in descending order in sum_poly's term list.
9. Apply combine_like terms to sum_poly's term list.
10. Return sum_poly.

Algorithm for combine_like_ terms

1. if term list is empty there are no changes.


else
2. Initialize cur_nodep to point to the head of the term list.
3. Repeat while current node is not the last
4. if current term has the same exponent as the term after it
5. Store the sum of the two coefficients in current term.
6. Delete node containing second term.
else
7. Advance cur_nodep to next node.
8. Traverse list again deleting nodes with zero coefficients.
9. Return modified term list.
Figure 14.42 shows an implementation of these functions.

Figure 14.42 Functions add_poly and combine_like_terms

* Combines like terms of term list, modifying list


*/
£erm inode t *
combine like terms(term_node_ t *tlistp) /* input/output - ordered list of
terms */
(continued)
Figure 14.42 (continued)

{
term_node_t *cur_nodep, *nextp;

if (tlistp != NULL) {
cur_nodep = tlistp;
while (cur_nodep->restp != NULL) {
nextp = cur_nodep->restp;
if (cur_nodep->term.expon ==
nextp->term.expon) { /* terms to combine */
cur_nodep->term.coeff += nextp->term.coeff;
cur_nodep->restp = nextp->restp;
free(nextp);
} else { /* move to next pair of terms */
cur_nodep = nextp;
}
}
}

/* Removes terms whose coefficients round to zero */


while (tlistp != NULL && fabs(tlistp->term.coeff) < .0005)
tlistp = tlistp->restp;
if (tlistp != NULL) {
cur_nodep = tlistp;
while (cur_nodep->restp != NULL) {
nextp = cur_nodep->restp;
if (fabs(nextp->term.coeff) < .0005) {
cur_nodep->restp = nextp->restp;
free(nextp);
} else {
cur_nodep = nextp;
t
}
}

return (tlistp).-
}
/*

* Adds two polynomials in the same variable


*/
polyt
add_poly(poly t pl, poly t p2) /* input */
{
poly t sum poly;
term node“) “tlistp;
(continued)

786
14.7 A Linked-List Application in Mathematics 787

Figure 14.42 (continued)

if (pl.variable != p2.variable) {
printf("ad poly
d does not add polynomials in two variables.\n");
printf("Returning first argument as function value\n");
sum_poly = pl;
} else {
sum_poly.variable = pl.variable;
sum_poly.term_listp = NULL;
for (tlistp = pl.term listp; /* traverse pl, inserting nodes */
tlistp != NULL; /* in sum */
tlistp = tlistp->restp) {
sum_poly.term listp =
insert_descending(sum_poly.term listp, tlistp->term) ;
b
for (tlistp = p2.term listp; /* traverse p2, inserting nodes */
tlistp != NULL; /* in sum */
tlistp = tlistp->restp) {
sum_poly.term_listp =
insert_descending(sum_poly.term listp, tlistp->term) ;
}
sum_poly.term_listp = combine like terms(sum_poly.term listp);
}

return (sum_poly);

Function eval_poly
Evaluating a polynomial at a particular value of its variable is a very straight-
forward process. The list of terms is traversed, raising the value to the correct
exponent, multiplying the result by the coefficient for each term, and accumu-
lating the sum of these products. Figure 14.43 shows the function.

Figure 14.43 Polynomial Evaluation Function eval_poly

/*

* Computes value of p when variable = val


*/
double
eval _poly(poly t p, /* input - polynomial to evaluate */
double val) /* input - value of variable */

(continued)
Figure 14.43 (continued)

double ans = 0;
term node t *tliistp;

for (tlistp p.term_listp;


tlistp != NULL;
tlistp tlistp->restp)
ans += tlistp->term.coeff * pow(val, tlistp->term.expon);

return (ans);
}

Self-Check
EXERCISES FOR
SECTION 14.7 1. Revise function eval_poly so pow is not called with exponent values of 0
and 1. Discuss the pros and cons of the two versions.
2. Revise function add_pol1y so that the else branch consists of a single call
to a recursive function. Also write function add_term_lists.
sum_poly.term_listp = add_term_lists(pl.term_listp,
p2.term_listp);

Programming
\
1. Write function mult_poly, which multiplies two polynomials.

14.8 QUEUES AND ORDERED LISTS IN SIMULATIONS

Many of the problems we attempt to solve in this age of expanding technology


cannot be dealt with on a trial-and-error basis. We do not routinely risk the lives
of astronauts to debug our moon-launch systems, nor do we stage occasional
nuclear accidents to train cleanup crews and perfect power-plant monitoring
software. A new fighter pilot needs exposure to far more dangerous scenarios
than it is reasonable to include in actual training missions. An auto maker cannot
build five manufacturing facilities in order to find out which of several assembly-
line configurations is the most efficient. When actual experimentation is imprac-
tical, computer scientists build software systems that model real-life processes in
a way that allows extensive experimentation, study, or training. Such a program
that imitates a real-life process is called a simulation. If you were to plan a field
trip to meet some very advanced simulation programs, you would need to venture
no farther than to your local video-game arcade.
In our next case study, we use queues and an ordered list to simulate a

788
14.8 Queues and Ordered Lists in Simulations 789

bottleneck in a manufacturing process.

Case Study: Simulation of a Factory


PROBLEM

The manager of the manufacturing division of Westcraft's Well-Turned Widgets


has received a report that of the five steps in the widget manufacturing process,
it is the fourth, process D, that is holding back the rate of production (see Fig.
14.44). Process D is accomplished by a single, very expensive machine, and it is
important to be able to predict the improvement in manufacturing throughput
that would result from the purchase of a second machine to perform process D.
Ultimately, it would be necessary to simulate the behavior of the present
factory, as well as the behavior of the system with one additional machine for
process D, and then to compare the throughput of the two. However, as a first
step, we have been asked to model processes D and E of the current plant only
(see portion of Fig. 14.44 inside the colored rectangle).
We are to take as input a list of the times that widgets emerge from process

Raw materials
Figure 14.44
Diagram of
Widget
Manufacturing Process Process
System A B

Process
(G
790 Dynamic Data Structures

C along with a number indicating widget type. We will assume that there is zero
delay between the completion of one process and the beginning of the next,
unless the next processor is busy. In addition to a trace of the simulation events,
the manufacturing manager would like our simulation to produce a summary
showing the total time of the simulation, the number of widgets processed, and
the average time spent waiting for process D and process E.

be
ANALYSISog
When we investigate the characteristics of processes D and E, we find that
process D always takes 3 minutes (180 seconds). Process E takes 2 minutes
(120 seconds) for type 111 widgets and 3.2 minutes (192 seconds) for type 555
widgets. In our simulation, we will model events (occurrences at specific
moments in time) and their effects on the system. Let's look at the sample data
set in Fig. 14.45 to get a clearer idea of what is involved.

Sample Input
Figure 14.45
Sample Data Time of Entry Widget
Set into Simulation Type

240 ipabl
300 Sie
390 5515
460 Idk

Since this problem is noticeably more complex than those solved in earl-
ier case studies, we will lead you through a simulation of our sample data set
using simple tokens before proceeding with a more formal analysis. We encour-
age you to assemble the necessary materials to participate in this hands-on sim-
ulation. You will need tokens for three type-111 widgets and one type-555
widget (one-cent and five-cent coins work well). In addition, you will need a
copy of the diagram in Fig. 14.46, which follows, showing processes D and E
and spaces for waiting and completed widgets. Finally, you will need paper on
which to take notes about various details of the simulation. These notes will pro-
vide a basis for deciding our program's data requirements as well as for deter-
mining many aspects of the necessary algorithms.
Before you begin, line up your widgets on the “Widgets Entering
Simulation” line like this
14.8 Queues and Ordered Lists in Simulations 791

Figure 14.46
Game Sheet for ae rie
Simulation of Widgets Entering :
Simulation
Widget Problem
with Tokens
D
—. 180 seconds
Widgets Waiting for D (WWD)

E
111: 120 sec
555: 192 sec
Widgets Waiting for E (WWE)

Completed Widgets

in accordance with the data shown in Fig. 14.45. Then, follow the step-by-step
instructions.
Please note: Unless you actually assemble the props and play this game, it
is quite useless to bother to read the instructions below. If you have no prior
experience with computer simulations but are unwilling to actively follow the
directions listed, you would probably be well advised to skip this case study. If
you do play along and find yourself unable to carry out one of the instructions,
review and modify your note-taking procedure so that the necessary information
will be recorded. Then try the simulation again.

Step-by-Step Instructions
1. Copy into your notes the list of widget entry times from Fig. 14.45.
2. Look at your notes to see what should happen first.
Move widget | into the “Widgets Waiting for D” line; note that the time is
240. Since there are no other widgets in the WWD line, put the widget on
process D to show that processing begins immediately. Make a note (literal-
ly) of when the D processing of widget 1 will finish.
3. Look at your notes to see what should happen next.
792 Dynamic Data Structures

(The current time is 240; the next event time noted is 300, so... .)
Move widget 2 into the WWD line and record the time. Since widget 1 is still
being processed, widget 2 will need to wait. Make a note of the time when
widget 2 begins waiting for D.
4. Look at your notes to see what should happen next.
Move widget 3 into the WWD line and record the time. Widget | is still being
processed, so make a note of the time when widget 3 begins waiting for D.
5. Look at your notes to see what should happen next.
Process D is complete for widget 1. Move widget 1 from process D to the
WWE line and record the time. Since there are no other widgets in the WWE
line, processing begins immediately. Make a note of when process E will be
complete for widget 1. Since D has finished one widget, it can start on the
next. Make a note of how long widget 2 waited for process D. Make a note of
when the D processing of widget 2 will be complete.
6. Look at your notes to see what should happen next.
Widget 4 enters the simulation. Make a note of when it begins waiting for D.
7. Look at your notes to see what should happen next.
Process E is complete for widget 1, so record the time and move widget 1 to
the “Completed Widgets” line. No widgets are waiting for process E.
8. Look at your notes to see what should happen next.

Continue this process until you feel you have a good grasp of the problem.
Keep in mind that at the end of the simulation you will need to report the aver-
age waiting time for process D and for process E, as well as the total number of
widgets handled and the total simulation time.
In Fig. 14.47, we see a diagram of the progression of the first part of the
simulation. Notice that there are two sources of events in our system. One is our
input data that describes each widget's entrance into our model. Our initial
event list consists entirely of these widget-entering-model events. The second
source of an event is the initiation of a process. At the time when the process
begins, we can predict when it will end in this particular system. This prediction
of a widget's exit time from a process is another event to be monitored by inclu-
sion in our event list. We have only three types of events that trigger changes in
the system:

1. A widget completes process C and arrives at process D (the definition of


“entering the simulation”).
2. A widget completes process D and arrives at process E.
3. A widget exits the model upon completion of process E.

Let's review a few of the events you traced in your coin-widget simulation.
When the first widget enters the model, it triggers the start of process D imme-
diately, and the event representing the widget's exit can be added to our list.
14.8 Queues and Ordered Lists in Simulations 793

Figure 14.47 Diagram of Part of Widget Manufacturing Simulation

Time Description Situation

240 Event: Widget 1 enters the simulation.

Consequence: The queue for process D ae


was empty so process D begins
immediately on W1. Process D
will be complete on W1 at time
240 + 180 = 420.
Completed widgets

300 Event: Widget 2 enters the simulation.


W2 f..
Consequence: W2 must wait for D. (111)

390 Event: Widget 3 enters the simulation.


W3 J. | W2 J..
Consequence: W3 must wait for D. (855)f {C11

Event: D completes processing of W1.

Consequence: W1 enters the queue for (855)


E. Since this queue was previously
empty, processing begins
immediately and will finish at
time 420 + 120 = 540 (W1 is type
111). Processing of W2 by D Completed widgets
begins, ending W2's 120-second
wait. This processing will finish
at time 420 + 180 = 600.

460 Event: Widget 4 enters the simulation.

Consequence: W4 must wait for D. any) "(ese “ai |


Ql11L} |(655)9 JalbDy po

W1

Completed widgets
794 Dynamic Data Structures

However, at time 300 when widget 2 enters the model, process D is busy (a fact
we must be sure to represent), so widget 2 must wait. Ninety seconds later,
widget 3 arrives and must also wait. To model this list of waiting widgets we
will need a data structure that will allow us to take widgets for processing from
the front of the list and add widgets that are waiting to the end of the list. This is
precisely the behavior of a queue, so we can use our queue functions from
Section 14.4 as templates for the queue functions here. Another queue is needed
for widgets awaiting process E. Although we could have a separate variable to
represent the widget currently undergoing a particular process, it is simpler to
adopt the convention that the first queue element is, in fact, being processed.
Then we can determine if a given processor is available by checking to see if its
queue is empty.
In Fig. 14.47, as in your own system trace, the time variable does not
advance by even intervals but increases only when a new event that causes
changes in the system occurs. A simulation of this type is called an event-driven
simulation. As you study the simulation data requirements and initial design that
follow, look back at your simulation game sheet and notes to relate the proposed
computer model to your own informal model.

Data Requirements
Problem Constants
PRCS_D LEN 180 /* seconds to complete process D * /
PRCS ELLEN 11) 120 /* seconds to complete process E
for type 111 widgets */
PRCSTE
LEN 555 192 /* seconds to complete process E
for type 555 widgets */

Structure Types
widget_t /* type of one element of a widget queue */
components:
id /* widget id number, generated as
widget enters simulation */
type /* 111 or 555 */
tearrival D /* time when widget enters queue
for D *f
Clrarrival se /* time when widget enters queue
for E */

queue _node t. /* type of one node of a queue's linked


list */
components:
widget /* information about one widget */
restp /* pointer to rest of list */
14.8 Queues and Ordered Lists in Simulations 795

queue t /* type of widget queue */


components:
frontp /* pointers to front and rear of */
rearp /* queue's linked list */
size /* number of elements in queue */

event_t /* type of one simulation event */


components:
time /* time (in seconds since simulation
start) when event occurs */
type /* character D (ready for entry
to D), E (ready for entry to E),
or X (ready for eXit) */

list_node t /* type of one node of linked list of


events */
components:
event /* information about one event */
restp /* pointer to rest of event nodes */

asthe /* type of event list */


component:
headp /* pointer to first node of linked
list of events */

Problem Inputs
list_t event_list /* list of simulation events x
queue_t wdgt_input_q /* queue of widgets entering
simulation x/

Problem Outputs
trace of events
int num_widgets /* total number of widgets
: processed * /
int tot_time /* total elapsed seconds in
simulation * /
double avg_wait_prces_D/* average time each widget
waited for process D, */
double avg _wait_prcsE /* for process E */

Program Variables
queue_t wdgt_prcs
D q /* queue of widgets ready for
process D, */
796 Dynamic Data Structures

queue_t wdgt_pres
E q /* for process E * /
int tot_wait_prcsD /* total time spent by all */
int tot_wait_prcesE /* widgets waiting for pro- */
/* cess D, for process E */
event_t current_event /* event most recently
removed from list */
queue_node_t *wdgt_nodep /* pointer to widget node
being moved from one
queue to another */
list_node_t *to freep /* pointer to event list
node to be freed */

<a
Initial Algorithm
1. Initialize D and E total wait time variables to 0.
2. Form lists of input events (event_list) and widgets (wdgt_input_q).
3. while event list is not empty
4. Remove and process first event.
N. Record total time.
6. Compute and display average waiting times for processes D and E.

We will define a function scan_events to handle Step 2, so now we


will refine Step 4.

Refinement of Step 4
4.1 Remove first event from list, saving itin current event.
4.2 Select relevant event process.
'D' 4.3 Remove widget from wdgt_input_q, printing trace, and add
ittowdgt_pres_ D gq.
4.4 ifwdgt_prcs_D_q has just one element
4.5 Process D entry.
Remove widget from wdgt_prces_D_q, printing trace, and
add ittowdgt_prcs_E _q.
4.7 if wdgt_prcs_D_q is not empty
4.8 Process D entry.
4.9 ifwdgt_prcs_E_q has just one element
4.10 Process E entry.
"X' 4.11 Remove widget from wdgt_prcs_E_q, printing trace.
4.12 if wdgt_prcs_E_q is not empty
4.13 Process E entry.
all others
4.14 Print error message.
14.8 Queues and Ordered Lists in Simulations 797

IMPLEMENTATION of Main Function

Figure 14.48 shows our implementation of the data types, typed allocation
macro, function main, and two tracing output functions.

Figure 14.48 Partial Implementation of Widget Simulation

* Simulation of widget manufacturing processes D and E. Simulation traces


events and monitors time spent waiting for each of the two processes
modeled.
*/

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

#define TYPED ALLOC(type) (type *)malloc(sizeof (type) )

typedef struct widget_s { /* one widget */


Ages siels
int type; phe WO b ee Sai ay/
int t_arrival_D, /* times when added to */
t_arrivalE; /* queues for D & E */
} widget _t;

typedef struct queue_node_s { /* one node of a widget queue */


widget _t widget;
struct queue _node_s *restp;
} queue_node
t;

typedef struct queues { /* widget queue */


queue_node t *frontp,
*rearp;
int size;
} queue _t;

typedef struct events { /* one event */


int time;
char type;
} event_t;

typedef struct list_node_s { /* one node of event list */

(continued)
798 Dynamic Data Structures

Figure 14.48 (continued)

event t event;
struct list _node_s *restp;
} list_nodet;

typedef struct listis /* event list */


list_node_t *headp;
} TIst ot:

/* Insert other helper functions here */

/*
* Prints trace message noting start-up of a process on a widget
¥/
void
print_widget_entry(widget_t w, char process name) /* input */
{
PEINCE (” Widget %d of type %d entering process %c\n",
w.id, w.type, process name);
}
/*
* Prints trace message noting exit of a widget from a process at an event
* time
*/
void
print_widget_exit(int time, widget_t w, char process name) /* input */
4
printf("%4d Widget %d of type %d exiting process %c\n", time, w.id,
w.type, process name);
}

int
main(void)
{
LIStLe event_list; /* input(initially) - simulation events
ordered by time */
queue _t wdgt_input_q, _/* input - queue of widgets to process */
wdgt_prces_
D q = {NULL, NULL, 0}, /* queues of widgets */
wdgt_prces_Eq = {NULL, NULL, 0}; /* for processes D, E
(first element in each is being

(continued)
14.8 Queues and Ordered Lists in Simulations 799

processed, rest are waiting;


initially queues are empty) */
CVentirt current_event; /* event most recently removed from
event list */
queue_nodet *wdgt_nodep; /* pointer to widget node being moved
from one queue to another */
list_node_t *to_freep; /* pointer to event list node which is
to be freed */
int num widgets; /* total number of widgets in
simulation */
int tot_wait_pres D = 0; /* total time widgets spent at
double avg_wait pres D; /* waiting for processes D x,
int tot_wait_pres_ E = 0; /* and E, and average time */
double avg_wait pres E; [* each widget waited for x7
/* each process xf
int tot_time; /* total time of simulation a)

/* Forms lists of input events and widgets, and saves number of


widgets 4/
scan_events(&event_list, &wdgt_input_q);
num widgets = wdgt_input_q.size;

/* Prints headings and processes events sas


printf("Simulation of Westcraft Well-Turned Widgets ");
printf("Processes D and E\n\n");
printf ("Time Event\n");

while (event_list.headp != NULL) {

/* Removes first event */


current _event = remove _event(&event_list);

/* Selects relevant event process */


switch (current _event.type) {
case 'D': /* Remove widget node from widget input queue,
printing trace =,
wdgt_nodep = remove_qnode(&wdgt_input_q);
printf("%4d ", current event.time);

(continued)
800 Dynamic Data Structures

Figure 14.48 (continued)

printf£("Widget %d of type %d entering simulation\n",


wdgt_nodep->widget.id, wdgt_nodep->widget.type);

/* Adds widget to wdgt_pres Dq * /


wdgt_nodep->widget.t_arrival_D = current event.time;
add_qnode(é&wdgt_pres_D_q, wdgt_nodep);

/* If wdgt_prcs_D_q has just one element, begins processing */


if (wdgt_prc D q.size
s == 1) {
print_widget_entry(wdgt_nodep->widget, 'D');
process D entry(current_event.time,
wdgt_nodep->widget.t_arrival_D,
&event list, &tot_ wait prcs D);
i
break;

case 'E': /* Removes widget node from wdgt_pre Ds


q,
printing trace */
wdgt_nodep = remove_qnode(&wdgt_prces D q);
print_widget_exit(current_event.time, wdgt_nodep->widget,
Be

/* Adds widget to wdgt_pres


Eq */
wdgt_nodep->widget.t_arrival_E = current event.time;
add_qnode(é&wdgt_prces_Eq, wdgt_nodep);

/* If wdgt_prcs_D q is not empty, begins processing next


widget */
if (wdgt_prcs D q.size > 0) {
process _Dentry(current_event.time,
wdgt_pres D q. fronkb=>wiagetitiewenden DF
&event_list, &tot_wait_prcs D);
print_widget_entry(wdgt_prces_D q.frontp->widget, Spee
i

/* If wdgt_pres_E q has just one element, begins processing */


if (wdgt_prcsE q.size == 1) {
process E entry(current_event.time,
wdgt_nodep->widget.t_arrival E,
wdgt_nodep->widget.type,
&event_list, &tot_wait pres E);
(continued)
14.8 Queues and Ordered Lists in Simulations 801

Figure 14.48 (continued)

}
break;

case 'X': /* Removes widget node from wdgt_prcs


E q,
printing trace */
wdgt_nodep = remove_qnode(&wdgt_prcs
E q);
print_widget_exit(current_event.time, wdgt_nodep->widget,
'E');
free(wdgt_nodep) ;

/* If wdgt_prcs
E q is not empty, begins processing next
widget */
if (wdgt_prcs
E q.size > 0) {
process E entry(current_event.time,
wdgt_pres_E q.frontp->widget.t_arrival E,
wdgt_prcs E q.frontp->widget.type,
&event_list, &tot_wait_prcs E);
print_widget_entry(wdgt_prcs
E q.frontp->widget, 'E');
}
break;

default:
printf("\n%4d ***INVALID EVENT CODE: %c***\n",
current _event.time, current _event.type);
} /* end switch */
} /* end for */

/* Computes and prints summary statistics */


avg_wait_prcs_D = (double)tot_wait_prces_D / (double)num widgets;
avg_wait_prcs_E = (double)tot_wait_prces_E / (double)num widgets;
tot_time = current_event.time;
printf("\n*****Simulation Summary*****\n\n");
printf("Average wait time for access to process D:%.2f seconds\n",
avg_wait pres D);
printf("Average wait time for access to process E:%.2f seconds\n",
avg_wait pres E);
printf("In %d seconds, a total of %d widgets were processed.\n",
tot_time, num_widgets);

return (0);
802 Dynamic Data Structures

Table 14.2 Modifications of Queue and Ordered List Functions


for Simulation

Simulation Template Differences Other than


Function Function Data Type Stored

add_event insert_in_order none


from Fig. 14.34
remove event remove_from_q no dealings with “rear” pointer, since
from Fig. 14.23 additions to event list are not
necessarily at the rear; also, event list
variable does not track its own size
add_qnode add_to_q from item to add is an actual queue node rather
Fig. 14.23 than the information to store in a queue
node; therefore, no space is allocated
remove _qnode remove from _q returns an actual queue node rather than
Fig. 14.23 just the information stored in it; there-
fore no space is freed
a

HELPER FUNCTIONS

We will not show detailed designs of most of the helper functions called by
main, for these functions are adaptations of functions we developed earlier in
this chapter. Since our widget lists are classic queues, our add_qnode and
remove_qnode facilities are based on similar functions developed in Section
14.4. One significant difference is that add_qnode does not allocate a widget
node and remove_qnode does not free one. Whereas our earlier functions
dealt with the information to be stored in the queue, our simulation queue func-
tions deal with widget queue nodes. This way, we need only allocate a widget
node once as it enters the simulation, although it is added to and removed from
three queues.
Processing of our event list incorporates features of two previously studied
list types. Elements are added in order by time as for an ordered list, but they are
always removed from the front of the list as for a queue.
Table 14.2 summarizes the relationships between our simulation's helper
functions and template functions discussed earlier in the chapter. Figure 14.49
shows our implementations of these functions.

Figure 14.49 List Maintenance Functions for Simulation

Inserts a new event node in order in a linked list of event nodes


* ordered by time.
(continued)
14.8 Queues and Ordered Lists in Simulations 803

Figure 14.49 (continued)

“as
list_nodet *
add_event(list_node_t *old_listp, /* input/output - linked list of event
nodes * /
sae! time, /* input - key for insertion x/
char event_type)/* input - D, E, or X (ready for D, E,
or eXit from simulation */
{
list_node_t *new_listp;

if (old_listp == NULL) {
new_listp = TYPED ALLOC(list_ node t);
new_listp->event.time = time;
new_listp->event.type = event_type;
new _listp->restp = NULL;
} else if (old_listp->event.time >= time) {
new_listp = TYPED ALLOC(list_node t);
new_listp->event.time = time;
new_listp->event.type = event type;
new_listp->restp = old _listp;
} else {
new_listp = old _listp;
new _listp->restp = add_event(old listp->restp, time,
event
_type);
}

return (new_listp);
}
/*
* Removes and frees first node of event list, returning event stored
* there
* Pre: list is not empty
* /
event_t
remove _event(list_t *eventsp) /* input/output - event list */
{
list node t *to freep; /* pointer to node removed */
event_t ans; /* value to return * /
to freep = (*eventsp).headp; /* saves pointer to node being
deleted */

(continued)
804 Dynamic Data Structures

Figure 14.49 (continued)

ans = to_freep->event;
(*eventsp).headp = to freep->restp; /* deletes first node */
free(to_freep);

return(ans);
}

/*

* Adds a widget node to the rear of queue


* /
void
add_qnode(queue_t *qp, /* input/output - queue to modify */
queue_node_t *to_addp) /* input - dynamically allocated node
to add */
4
Jtat(*dp).saze =— 0)'4 /* add to empty queue */
(*qp).rearp = to _addp;
(*qp).frontp = (*qp).rearp;
} else { /* add to non-empty queue */
(*qp).rearp->restp = to_addp;
(*qp).rearp = (*qp).rearp->restp;
R
++((*qp) <size);:
}

/*

* Removes and returns a node from the front of a queue


* Pre: queue is not empty
*/
queue node t *
remove_qnode(queue_t *qp) /* input/output - queue to modify */
{
queue_node_t *ansp; /* pointer to node to return */

ansp = (*qp).frontp;
(*qp).frontp = (*qp).frontp->restp;
--((*qp).size);

(continued)
14.8 Queues and Ordered Lists in Simulations 805

Figure 14.49 (continued)

if ((*qp).size == 0) /* queue's ONLY node was deleted */


(*qp).rearp = NULL;

BECUEN (ans p));

Function scan_events

The purpose of function scan_events is to transform our input list

240 aka
300 aLal
390 555
460 ipa

into an initial event list

headp
[Ee etn Tira

and a widget input queue

ye

Algorithm for scan_events

1. Initialize event list and widget queue to empty.


2. As long as valid input is available
3. Add to event list a type 'D' event at time scanned.
4. Build and add to widget queue a widget of type just scanned and
with widget count as its id.

Functions process_D_ entry and process_E_ entry


These functions handle new event generation and bookkeeping associated with a
new widget's arrival at the front of the process D and process E queues. Each
806 Dynamic Data Structures

function generates an event to mark completion of the process on the widget, an


event whose time is determined by adding the process length to the current
time. In addition, the function looks at the time the widget arrived in this
process queue and computes the amount of time it has waited, using this number
to update the running total of time spent by widgets waiting for the process.
Figure 14.50 contains implementations of these remaining helper functions.

* Takes input consisting of lines representing times


of widget entry into
* simulation and types of widgets. Builds an initial events list
* of type 'D' events and a list of entering widgets.
*/
void
scan_events(list_t *eventsp, /* output - initial list of events */
queue_t *widgetsp) /* output - queue of widget nodes */
{
int status, /* input status */
evt_time, /* time when event should occur */
wdgt_type, /* type 111 or 555 widget * /
wet = 0; /* widget counter */
queue_node_t *new_wdgtp; /* pointer to a new widget node */

/* Initializes list and queue to empty */


(*eventsp).headp = NULL;
(*widgetsp).frontp = NULL;
(*widgetsp).rearp = NULL;
(*widgetsp).size = 0;

/* Gets and processes data as long as no error or EOF * /


for (status = scanf("%$d%d", &evt_time, &wdgt_type);
status == 2;
status = scanf("%d%d", &evt_time, S&wdgt_type)) {
/* Adds to event list a type 'D' event at time
just scanned */
(*eventsp).headp = add_event((*eventsp) .headp,
evt_time, ‘D')>;

/* Builds and adds to widget queue a widget of the type just


scanned */
new_wdgtp = TYPED_ALLOC(queue_node t);
new_wdgtp->widget.id = ++wct;

(continued)
14.8 Queues and Ordered Lists in Simulations 807

Figure 14.50 (continued)

new_wdgtp->widget.type = wdgt_type;
add_qnode(widgetsp, new _wdgtp);

#define PRCS
D LEN 180 /* seconds to complete process D */

/*

* As a widget enters process D, this function generates the event that


* represents completion of process D on this widget, and the function
* also updates the total time widgets have waited for D
*/
void
process D entry(int time, /* input - current time */
int arr time, /* input - time widget queued up for D */
list_t *eventsp, /* input/output - list of events */
int *tot_waitp) /* input/output - total time widgets
have spent waiting for D */
4
(*eventsp).headp = add_event((*eventsp).headp, time + PRCS_D LEN, 'E');
*tot_waitp += time - arr_time;
:
#define PRCS
E LEN 111 120 /* seconds to complete process E for type 111
widget */
#define PRCS
E LEN 555 192 /* seconds to complete process E for type 555
widget */

/*
As a widget enters process E, this function generates the event that
represents completion of process E on this widget, and the function
* also updates the total time widgets have waited for E
*/
void
process E entry(int time, /* input - current time */
int arr time, /* input - time widget queued up for E */
int widg type, /* input - widget type (111 or
555) */
list_t *eventsp, /* input/output - list of events */
int *tot waitp) /* input/output - total time widgets
have spent waiting for E */
(continued)
808 Dynamic Data Structures

Figure 14.50 (continued)

{
int process len;

Switch (widg_type) {
case 111:
process_len = PRCS
E LEN 111;
break;

case 555:
process _len = PRCS_E
LEN 555;
break;

default:
printf("\nInvalid widget type: %d\n", widg type);
process len = 0;
}
(*eventsp) .headp = add_event((*eventsp)
.headp, time + process len, 'X');
*tot_waitp += time - arr time;
}

To check the simulation program for correctness, one should try it on situations
such as an empty input list or on some cases that simplify hand computation
of
results. For example, this list

200 111
370 111
550 111
730 111

always has widgets arriving at process D ten seconds before the previous
widget
completes its processing. Of course, to use the program for its intended
purpose,
one would need data from the manufacturer on the typical input stream.

Simulation Languages
Writing a simulation program provides an excellent opportunity for us
to put to
use our C implementations of queues and ordered lists. However,
in environ-
ments where new simulations are needed daily to provide statistica
l data for
decision making, developers typically use special-purpose simulation
languages
that handle much more easily almost all of the detail involved.
14.9 Common Programming Errors 809

Self-Check
EXERCISES FOR
SECTION 14.8 1. Predict the output of the simulation program for the data set given in the dis-
cussion of testing.
2. Design a three-widget data set that maximizes wait times for process E while
having zero wait times for process D. Have the first widget enter the simula-
tion at time 100.

Programming
1. Write print_widget_gq and print_event_list functions that could
be used in debugging the simulation program. Use iteration in print_wid-
get_q, and use a recursive helper function for print_event_list.

14.9 COMMON PROGRAMMING ERRORS


Remember that the indirect selection operator -> is correctly used to reference
a component of a structure that is accessed through a pointer, so

var->component

is valid only if var is of a pointer-to-structure type. If you elect to use both the
-> operator and the equivalent combination of indirection and direct selection
operators,

(*var) .component

you should establish a usage pattern that assists the reader of your code. Our
pattern has been to reserve the two-operator combination for structured output
arguments, using the single, indirect selection operator for accessing compo-
nents of dynamically allocated structures.
It is easy to create an invalid reference when using the *, the ., and the
-> operators in combination with other operations. You should keep an operator
precedence chart handy (see Appendix C) or use parentheses to fully specify the
order of evaluation that you desire.
There are a number of run-time errors that can occur when you are tra-
versing linked data structures with pointers. The most common error is an
attempt to follow a NULL pointer. This error can happen easily when traversing
a list in a loop whose repetition condition does not explicitly check for the
NULL. Any attempt to follow a pointer in an undefined variable will usually
cause a run-time error as well.
Problems with heap management can also cause run-time errors. If your
program gets stuck in an infinite loop while you are creating a dynamic data
810 Dynamic Data Structures

structure, it is possible for your program to consume all memory cells on the
storage heap. This situation will lead to a heap overflow or stack over-
f low run-time error message.
Make sure your program does not attempt to reference a list node after the
node is returned to the heap. Also be careful to return a storage block to the heap
before losing all pointers to it.
Because printing the value of a pointer variable is not very informative, it
can be difficult to debug programs that manipulate pointers. To trace the exe-
cution of such a program, you must print an information component that unique-
ly identifies a list element instead of printing the pointer value itself.
When you are writing driver programs to test and debug list operators, it is
often helpful to create a sample linked structure using the technique shown in
Section 14.2. Use a sequence of calls to malloc and temporary pointer vari-
ables to allocate the individual nodes. Next, use assignment statements to define
the data and pointer components of the structures.

CHAPTER REVIEW

This chapter introduced several dynamic data structures. We discussed the use of
pointers to reference and connect elements of a dynamic data structure. The func-
tion malloc was used to allocate single elements, or nodes, of a dynamic data
structure. We demonstrated the use of calloc for dynamically creating an array,
and we studied how function free returns memory cells to the storage heap.
We also covered many different aspects of manipulating linked lists. We
showed how to build a linked list, how to traverse a linked list, and how to
insert
and delete elements of a linked list. We explored how to use linked lists in
representing queues, stacks, and ordered lists.

New C Constructs
The C constructs introduced in this chapter are described in Table 14.3. The
table also reviews pointer type definitions and declaration of pointer variables.

QUICK-CHECK EXERCISES
1. Function _______ allocates storage space for a single data object that is
referenced’ throughia tos Ly ePunetion 2s): Leallocates stor-
age space for an array of objects. Function______ returns the storage
space to the
Quick-Check Exercises 811

Table 14.3. New C Constructs

Pointer declaration
typedef struct nodes { The type name node_t is defined as a synonym of
int info; the type struct node _ss, which is a structure con-
struct node_s *restp; taining an integer component and a component that
PeNnOdemtr is a pointer to another structure of the same type.
node _t *nodep; nodep is a pointer variable of type pointer to
node t.
int *nums; nums is a pointer variable of type pointer to int.

Dynamic memory allocation


nodep = (node_t *) A new structure of type node_t is allocated on the
malloc(sizeof (node _t)); heap, and its address is stored in nodep.
nodep->info = 5; Values are stored in the new structure like this:
nodep->restp = NULL;
nodep info restp
\

nums = (int *) A new 10-element array of integers is allocated on


calloc(10, sizeof (int)); the heap, and its starting address is stored in nums.
cope (aL S| OR ah es OR ararak)’ The elements of the new array are all set to zero.
nums[i] = 0;

Memory deallocation
free(nodep); The memory blocks accessed through the pointers
free(nums); nodep and nums are returned to the heap.

Pointer assignment
nodep = nodep->restp; The pointer nodep is advanced to the next node in
the dynamic data structure pointed to by nodep.
SSS a TS LEST A DEE LSE GS ILIA AE OO LT IGE

2. When an element is deleted from a linked list, it is automatically returned to


the heap. True or false?
3. All pointers to a node returned to the heap are automatically reset to NULL so
they cannot reference the node returned to the heap. True or false?
4. According to the conventions used in this text, what is the implied difference
812 Dynamic Data Structures

in meaning between the following two references?

(*enginep) .piston enginep->piston

What is the syntactic difference?


. Assume the following data type definition and declaration:

typedef struct nodes {


int num;
struct node_s *restp;
} node -t;

node_t *headp, *cur_nodep;

Write a for loop header that causes cur_nodep to point in succession to


each node of the linked list whose initial pointer is stored in headp. The
loop should exit when cur_nodep reaches the end of the list.
. The process just implemented in Exercise 5iscalled______——_aq ist.
. If a linked list contains three nodes with values "hinsso "her? and
"its", and hp is a pointer to the list head, what is the effect of the fol-
lowing sevementa? Assume the data component of node type pro_node t
is pronoun, the link component is nextp, and np and mp are pointer
variables.

np = hp->nextp;
strcpy(np->pronoun, "she");

8. Answer Exercise 7 for the following code fragment:

mp = hp->nextp;
np = mp->nextp;
mp->nextp = np->nextp;
free(np);

9. Answer Exercise 7 for the following code fragment:

np = hp;
hp = (pro_node_t *)malloc(sizeof (pro_node t));
strcpy(hp->pronoun, "his");
hp->nextp = np;

10. Write a single statement that will place the value NULL in the last node
of
the three-element list in Exercise 7.
Review Questions 813

11. Write a for loop that would place zeros in the even-numbered elements of
the following dynamically allocated array.

nums_arr = (int *)calloc(20, sizeof (int) );

ANSWERS TO QUICK-CHECK EXERCISES

1. malloc, pointer; calloc; free, heap


2. false, free must be called.
3. false
4. The first, (*enginep).piston, implies that we are referencing the
piston component of a structured output parameter. The second,
enginep->piston, implies that we are referencing the piston com-
ponent of a dynamically allocated structure.
There is no syntactic difference.

5. for (cur_nodep = headp;


cur_nodep != NULL;
cur _nodep = cur_nodep->restp)

6. traversing
7. replaces "her" with "she"
8. The third list element is deleted.
9. A new node with value "his" is inserted at the front of the list.
10. hp->nextp->nextp->nextp = NULL;
WOLEOuE aE 22402 OFT ASC2 Oi ee = AE)
nums_arr[i] = 0;

REVIEW QUESTIONS
1. Differentiate between dynamic and nondynamic data structures.
2. Describe a simple linked list. Indicate how the pointers are utilized to estab-
lish a link between nodes. Also indicate any other variables that would be
needed to reference the linked list.
3. Give the missing type definitions and variable declarations and show the
effect of each of the following statements. What does each do?

wp = (word node *)malloc(sizeof (word_node) );


strcpy(wp->word, "ABC");
wp->next = (word_node *)malloc(sizeof (word_node) );
814 Dynamic Data Structures

qp = wp->next;
strcpy(qp->word, "abc");
qp->next = NULL;

Assume the following type definitions and variable declarations for Questions
4— 9. Each function you write should return a value of type name_list_t.

typedef struct name node s {


char name[11];
struct name _node_s *restp;
} name_node
t;

typedef struct name _list_s {


name_node_t *headp;
int size;
je mamenlList.tt;

a
name list; t,, list;
name node _t *np, *qp;

4. Write a program segment that places the names Washington, Roosevelt, and
Kennedy in successive elements of the linked list referenced by structure
list. Define list.size accordingly.
5. Write a program segment to insert the name Eisenhower between Roosevelt
and Kennedy.
6. Write a function called delete last that removes the last element from
any list referenced by structure list.
7. Write a function place_first that places its second parameter value as
the first node of the linked list referenced by structure List, which is passed
as the function's first argument.
8. Write a function called copy list that creates a linked list with new
nodes that contain the same data as the linked list referenced by the single
argument of copy_list.
9. Write a function that you could call to delete all nodes with name component
"Smith" from a linked list referenced by structure list. The linked list
and the name to delete are the function's two parameters.

PROGRAMMING PROJECTS
1. Rewrite the passenger list program whose main function is shown in Fig.
14.22 so that it uses an ordered list (alphabetized by passenger name) rather
than a queue. Menu selection 'D' (delete) should prompt for the name of the
Programming Projects 815

passenger to delete. In addition to the functions you write to manipulate the


ordered list, you will need to write functions scan_passenger and
print_passenger, which are mentioned in Fig. 14.22.
2. Create header and implementation files ("stack.h" and "stack.c") for
a data type stack_t and operators for maintaining a stack of single char-
acters. Use functions push and pop. Also implement function retrieve
whose prototype is given here:

/*

* The value at the top of the stack is returned as the


* function value. The stack is not changed.
* Pre: s is not empty
* /
int
retrieve(stack_t s) /* input */

3. Use an adaptation of your stack library from Project 2 to implement the fol-
lowing algorithm for displaying a string in reverse order.
1. Create an empty stack of characters.
2. Push each data character onto a stack.
3. Pop each character and display it.
4. A postfix expression is an expression in which each operator follows its
operands. Table 14.4 shows several examples of postfix expressions.
The grouping marks under each expression should help you visualize
the operands for each operator. The more familiar infix expression corre-
sponding to each postfix expression 1s also shown.
The advantage of postfix form is that there is no need to group subex-
pressions in parentheses or to consider operator precedence. The grouping

Table 14.4 Examples of Postfix Expressions

Example = Infix Expression = Value


eae By) ks (5) 30

By Goll apes 5s (Omi) 35

56* 9 - (Sea 6.) 9 IAL

4 B 604 sey + 4 + ((5 * 6) / 3) 14


816 Dynamic Data Structures

marks in Table 14.4 are only for our convenience and are not required. You may
have used pocket calculators that require entry of expressions in postfix form.
Use an adaptation of your stack library from Project 2 to write a pro-
gram that simulates the operation of a calculator by scanning an integer
expression in postfix form and displaying its result. Your program should
push each integer operand onto the stack. When an operator is encountered,
the top two operands are popped, the operation is performed on its operands,
and the result is pushed back onto the stack. The final result should be the
only value remaining on the stack when the end of the expression is reached.
. Extend the polynomial case study by writing functions differentiate
and integrate, each of which takes a polynomial input parameter and
returns a polynomial result representing the derivative or an antiderivative of
the polynomial.
. Extend the widget factory simulation so the user can decide whether one or
two machines are available to perform process D.
. Write a program to monitor the flow of an item into and out of a warehouse.
The warehouse will have numerous deliveries and shipments for the item (a
widget) during the time period covered. A shipment out is billed at a profit of
50 percent over the cost of a widget. Unfortunately, each shipment received
may have a different cost associated with it. The accountants for the firm
have instituted a first-in, first-out system for filling orders. This means that
the oldest widgets are the first ones sent out to fill an order. This method of
inventory can be represented using a queue. Each data record will consist of

S or O: shipment received or an order to be sent


#: quantity received or shipped out
Cost: cost per widget (only for a shipment received)
Vendor: character string that names company sent to or received from (up to
20 characters)

Write the necessary functions to store the shipments received and to


process orders. The output for an order will consist of the quantity and the
total cost for all the widgets in the order.
Hint: Each widget price is 50 percent higher than its cost. The widgets
used to fill an order may come from multiple shipments with different costs.
. Each student in the university may take a different number of courses, so the
registrar has decided to use a linked list to store each student's class schedule,
and to use an array of structures to represent the whole student body. A por-
tion of this data structure follows.
id restp
lll /
Programming Projects 817

The records show that the first student (array element 0, id 1111) is
taking Section 1 of CIS120 for 3 credits and Section 2 of HIS001 for 4
credits; the second student (array element 1, id 1234) is not enrolled, and so
on. Define the necessary data types for creating this structure. Provide oper-
ators for creating the original array of student ID numbers, inserting a stu-
dent's initial class schedule, adding a course, and dropping a course. Write a
menu-driven main function to use this structure.
. The radix sorting algorithm uses an array of eleven queues to simulate the
operation of the old card-sorting machines. The algorithm requires that one
pass be made for every digit of the numbers being sorted. For example, a list
of three-digit numbers would require three passes through the list. During the
first pass, the least significant digit (the ones digit) of each number is exam-
ined, and the number is added to the rear of the queue whose array subscript
matches the digit. After all numbers have been processed, the elements of
each queue beginning with queue[ 0] are copied one at a time to the end of
an 11th queue prior to beginning the next pass. Then, the process is repeated
for the next-most significant digit (the tens digit) using the order of the num-
bers in the 11th queue. Finally, the process is repeated using the hundreds
digit. After the final pass, the 11th queue will contain the numbers in ascend-
ing order. Write a program that implements the radix sort.
15.1
Plotting Functions of One Variable
15.2
Plotting Parametric Equations
15.3
Plotting Functions of Two Variables
Case Study: Contour Plot for a
Hemispherical Surface
15.4
Introduction to Computer-Aided Design
15.5
Common Programming Errors
Chapter Review
I,this chapter, we illustrate several different techniques for plotting functions in
C. You will study a few of the fundamental concepts of computer graphics.
Graphics packages are now widely available both as separate program products and
as components of other tools. Software that produces graphical displays is found in
applications ranging from spreadsheets and decision support tools to specialized
packages for fluid modeling, finite element analysis, and computer-aided design.
Sections of this chapter demonstrate how to plot functions with one vari-
able and how to plot parametric equations. We also show how to represent the
graph of a function of two variables as a contour plot. Finally, we will provide a
brief overview of computer facilities for engineering drawing and design.

15.1 PLOTTING FUNCTIONS OF ONE VARIABLE

In this section and in the next two, we will discuss several relatively simple
means of having the computer plot graphs of functions. In this section, we will
discuss plotting functions of one variable. In the equation

y=f(x)

the functi f describes


on a relationship between y, the dependent variable, and x,
the independent variable.

Mapping a Function onto a Screen


The method we are about to describe is general and will work for any function
that has no singularities in the range ofx values that we are plotting. A singu-
larity exists at xp if | (xq) is too large to be represented in the computer. (Be
careful, though, because this definition of singularity differs from the mathe-
matical definition, according to which a singularity exists at Xo if | f(xo)| is
undefined at xp.
In order to plot a function of one variable, we compute f(x) on a sequence
of equally spaced x values. Our goal is to develop a program that will allow the
user to specify the first and the last of the x values:
1. x_init: the initial x value
2.x) tinal: the final.« value
The program will then plot f(x) on the interval [x_init, x final]. The num-
ber of points plotted will depend on the characteristics of the output device. We
will assume the computer screen has 24 rows and 80 columns.

819
820 Plotting Functions

F(x)
Figure 15.1
Mapping a
Graph onto
a Computer
Screen

Plotting a function of one variable involves mapping a rectangular region


of the x-y plane onto our computer screen. Since the x-y plane is a two-dimen-
sional continuum and our computer screen is a two-dimensional arrangement of
discrete cells, a considerable amount of information will be lost. Figure 15.1
depicts the function plotting problem. The shaded region in the x-y plane is to be
mapped onto the computer screen causing the curve y = f(x) to be represented as
a finite sequence of characters.
The region of the x-y plane that will be mapped onto the computer screen
is bounded by four lines:

I thelinex =x init on-the left


2. the line x = x_final on the right
3. the line y = y_min, the minimum computed value for f(x) on the interval
[xn Cees eaena |
4. the line y = y_max, the maximum computed value for f(x) on the interval
[x in tex tana1

The maximum and minimum values off(x) will be relative to a finite number of
tabulation points. We will discuss how these tabulation points are determined
later in this section.
Plotting the continuous curve y = f(x) requires imposing a grid structure
upon the rectangular region of the x-y plane in Fig. 15.1. Suppose we plan to
organize our computer screen so that 20 rows and 70 columns will be devoted to
displaying the graph (see Fig. 15.2). The rest of the screen will be used to pre-
sent labeling information. We can use graph, a two-dimensional array of 20
rows and 70 columns, to represent our graph; each element in array graph
corresponds to a cell of our grid structure.
Figure 15.3 shows a portion of the screen in more detail. A 10-by-11 cell
portion of our screen is superimposed on the corresponding portion of the x-y
plane. The plotting program requires that the graph of the function, which is a
15.1 Plotting Functions of One Variable 821

Figure 15.2
Detail of
Computer
Screen Layout 20 rows

4 rows

continuous curve, be represented by a finite number of characters, one for each


column. We will use the character '*' in plotting the function. Now how do we
determine where each asterisk should be placed?
Figure 15.4 shows a part of Fig. 15.3 but with additional detail. Our plot-
ting method progresses column by column. In each column, a row is marked
with an asterisk. Figure 15.4 shows us at column 27; columns 1—26 have already
been marked. An important observation is that each column corresponds to a
particular interval of x values, for example, [xl, xr]. More significantly for
our algorithm, these intervals have a midpoint xm. It is at this midpoint that we
will evaluate the functionf, yielding a number, yv = f(xm). This y value, yv,
will then be converted to a row number, and the corresponding cell in array
graph will be marked with an asterisk.

Column
Figure 15.3
Segment of 23-24 25 26 27 28 29 30 31 32 33
Graph and
Its Plot
822 Plotting Functions

Column
Figure 15.4
Plotting the
Point (xm, yv)

The Scaling Problem


The scaling problem is central to plotting functions with a computer. The scal-
ing problem consists of deriving the mathematical relationships between
columns and xm values on the one hand and rows and yv values on the other. In
terms of Fig. 15.4, the scaling problem is to compute xm given a particular
column number k, and to compute a row number r for a particular function
value yv.
Let us first consider how the midpoint xm is determined by the column
number k. Figure 15.5 illustrates the decision that x_ init will correspond to
the midpoint of the first column and that x_ final will correspond to the mid-
point of the last column of the graph. (We are referring to columns relative to
the graph, not relative to the computer screen.) This decision completely deter-
mines the relationship between column number k and midpoint xm. There are 69

Figure 15.5
Scaling Problem
in the x
Dimension

xX_init X_final
15.1 Plotting Functions of One Variable 823

increments of size x_incr separating x_init and x_final. Therefore, the


increment size is

x_incr = (x final - x init) / 69.0

and the midpoint for column k is

2S Xeni +k * xeincr

This equation is the solution to the scaling problem along the x dimension of the
graph. The divisor, in this case, 69.0, will change with the dimensions of the
plot, but it will always be one less than the number of columns to be plotted.
We want to be able to compute a row number r along the y dimension,
given the functional value yv = f(xm). This problem is a little tricky. Figure 15.6
shows a decision to make the midpoint of row 0 correspond to the maximum
value for f(x), y_max, and to make the midpoint of row 19 correspond to the
minimum value for f(x), y_min. This decision determines that the distance
between successive midpoints is

y_incr = (y_ max - y min) / 19.0

The row number r for a given y value yv depends on the distance between
yv and y_max measured in terms of y increments y_incr. This distance,
y_dist, is computed as follows:

y_dist = (y_max - yv) / y_incr

Figure 15.6
Scaling Problem
in the y
Dimension

18

19
824 Plotting Functions

y_dist Ne isk sham: ee fos


y_ dist < 0.0 impossible
0.0 S y dist < 0.5 0
OF Sie Sanyaecaista <a le 5 1
020s yadiist << 255 2
OsO S VWichoae = seh 3
OR ONS tyaidwsity< 235 4

DeQ S47 Chighe — asses 18


0.0 < y dist < 19.5 I)
WV_Chigis = iWOgs impossible
SSS a

For example, y_dist is 1.0 means that yv is one y increment away from
y_max, so yv belongs in row 1. If y_dist is 19.0, then yv is y_ min and
belongs in row 19.
Adding 0.5 to round and using a cast to truncate the fractional part of the
value, we can express the relationship between row r and y_dist as

Paint)
Cy Gist + 10.5.)

Table 15.1 shows the relationship between y_dist and r imposed by this for-
mula. This table covers all possibilities, since y_dist cannot be less than
0.0 or greater than 19.0.
Now that we have solved the scaling problem, we can write a program that
plots mathematical functions.

A Function Plotting Program


Figure 15.7 is a structure chart of a program system that plots a given function
f(x) on a specified interval [x_init, x_final]. The user provides the values
for x_init and x_final as input. The output consists of a discrete repre-
sentation of the graph y = f(x).
The main program variables include the two-dimensional array graph.
The array graph is declared to be of element type char and is initialized to all
blanks by nested for loops. The function get_data gets the initial and final
x values: x_init and x_final. In addition, this function computes and
returns the value for x_incr, which is needed by two of the other functions.
Variable x_final is local to get_data since none of the other functions
needs this value.
15.1 Plotting Functions of One Variable 825

Figure 15.7
Structure Chart
for Plotting
Program

get_data plot_points print_graph

The function plot_points does the actual plotting of the function by


initializing the graph array to all blanks and then marking the appropriate
row of each column with an asterisk. This function is the heart of the plotting
program. The macro F defines the function we are plotting.

#define F(x) (4.0 * pow((x) - 0.5, 2.0))

The function that is defined by this macro must have no singularities on the
interval (xe initox final:
There are two phases in the logic of plot_points. First, we must deter-
mine the maximum and minimum values for the function f(x) at the 70 evalua-
tion points xm given by

n= Xenia
ky * x incr for
ki =70, ol ,.1 0 86, ENCODsSet

This phase involves 70 evaluations of the function f(x). Because we will need
these same 70 values during the second phase of our function, we store them in
an array yv declared:

double yv[NCOL];

This storage of the 70 values is a matter of saving time by the expenditure of


space (computer storage). Once the maximum and minimum values, y_max
and y_min, are known, we can compute the increment value y_incr.
During the second phase of the function plot_points, we use the scal-
ing formula derived earlier to convert each of the 70 functional values yv[k] to
a unique row value r. The cell graph[r][k] is then marked with an asterisk.
826 Plotting Functions

The function print _graph prints the graph row by row along with
appropriate labels. In order to get the x scale printed at the bottom of the graph,
the function uses array x_scale to store the x values that will be printed. The
complete program is shown in Fig. 15.8.
The output generated by the program shown in Fig. 15.8 is given in Fig.
eee

Figure 15.8 Plotting a Function f(x)

/*

* Plots the function


* 2
* £(x) = 4.0 * (x - 0.5)
* on an NROW row by NCOL column grid
*/
#include <stdio.h>
#include <math.h>

#define NROW 20 /* number of rows in grid */


#define NCOL 70 /* number of columns in grid */
#define F(x) (4.0 * pow((x) - 0.5, 2.0)) /* function to plot */
/*

* Asks the user for the initial and final x values


* and computes the x increment
*/
void
get_data(double *x_initp, /* output - initial x value */
double *x_incrp) /* output - x increment */
{
double x_final;

/* Gets initial and final x values from user */


printf("The function will be plotted between the values you enter...)
printf£("\n\nEnter initial X value> ");
scant (s1t 7 ex. initp)s
printf("Enter final X value> ");
scanf("%1£/,.&x final).

/* Computes and returns x increment */


*x INnCrp l= (x final — +x initp) /) (NCOL oul).
}
(continued)
J

15.1 Plotting Functions of One Variable 827

Figure 15.8 (continued)

/*

* Plots the function by marking appropriate cells of the array


* graph with asterisks.
*/
void
plot_points(char graph[NROW][NCOL], /* output - grid representing plot */
double x_init, /* input - initial x value */
double x_incr, /* input - x increment */
double *y maxp, /* output - largest y value */
double *y _incrp) /* output - y increment */
{
double yv[NCOL], y_ dist, ymin, ymax, xm;
int ees Ieee ars,

/* Initializes graph grid to all blanks */


LO (EY =O 0eh |1 NROW SIE)
fom Gy = 0s BP < NCOL. ~++7))
graph[i}[j 1° =";

/* Phase ONE: Determines maximum and minimum functional values * /


ymax = F(x init);
ymin = ymax;
yv[0] = ymax;

£OED (k= 1 7 ki NCOL:) ttk) {


x eh kee OKner;
yv[k] = F(xm);
if (yv[k] > ymax)
ymax = yv[k];
if (yv[k] < ymin)
ymin = yv[k];
}

/* Returns y maximum and computes and returns y increment * /


*y maxp = ymax;
*y incrp = (ymax - ymin) / (NROW - 1);

/* Phase TWO: Marks graph column by column using computed


function values */

(continued)
828 Plotting Functions

Figure 15.8 (continued)

for (k = 07 ‘ke<eNCOLs 4+k) 4


y_dist = (ymax - yv[{k]) / *y_incrp;
m= (Ant) (yadisess 0.5);
graph[r][k] = ‘*';

#define SCALE_INTERVAL 10 /* number of columns between x scale labels */


/*

* Prints out graph with labels


*/
void
print_graph(const char graph[NROW][NCOL], /* input - grid of characters in
plot */
double xin &, /* input - initial x value */
double Nee, /* input - x increment 3
double y_max, /* input - largest y value * /
double y inex) /* input - y increment */
{
IO bie Ky ie
double x, y,
x_scale[NCOL / SCALE INTERVAL];/* labels for xscale at bottom
of graph */

/* Prints out graph row by row with labels to the left of the first
row and every fifth row
*/
prinei(\n\n “Y\n");
print£("%6.1f >, yomax)> /* label and print initial row */
for (k = 0; k < NCOL; ++k)
putchar(graph[0][k]);
putchar('\n');

for (xr = 1; xr < NROW; ++r) { /* print rest of plot */


LE (er 1) eee e= 0) { /* label every fifth row */
Yes y Max = 2 ey incr:
prant£( 26. Lt ae)
} else {
PeintLt(” pales
»

(continued)
15.1 Plotting Functions of One Variable 829

Figure 15.8 (continued)

for (k = 0; k < NCOL; ++k)


putchar(graph[r][k]);
putchar( “\n')
}

/* Computes the x scale values */


k = SCALE INTERVAL;
for (i= 0; i < NCOL / SCALE INTERVAL; ++i) {
x = x_init + (k - 1) * x_iner; /* x value for column k */
x scale[i)|) = x-¢
k += SCALE INTERVAL;
}

/* Prints the x scale at the bottom of the graph */


print£(" bees I");
for (i = 0;' i < NCOL / SCALE INTERVAL - 1; ++i)
printf ("--------- eye
pErmtt ("Nn SG
Lk eX Dt)
for (1 = 0; i < NCOL / SCALE INTERVAL; ++i)
PEInNtL(” 60.18 5 Xsscale[ iy).
peintt( “\n X-->\n");
}

int
main(void)
{
double xvinit, liner, yamax,nyliner;
char graph[NROW][NCOL];

/* Gets interval starting point and increment,


plots function, and prints results */
get_data(&x_init, &x_incr);
plot points(graph, x_init, x incr, &y_max, &y_ incr);
print graph(graph, “x*init, x“iner vy sax, sysincr);

return (0);
830 Plotting Functions

Figure 15.9 Sample Function Plot

The function will be plotted between the values you enter.

Enter initial X value> -10.0


Enter final X value> 10.0

Ye
A410)
*

348.2 * *
*
*
*
*
**
*
*
*
23251 * +
*
*
K*
**
K*
*
*
**

116.1 x +
** **

KKK K*

KKK KKK

KEK KEEK
0.0 KEKKKKKEKEKE
|--------|---------
|--------- |--------- |---------|--------- [aaa ——— |
= 10140 Sipe =4.5 =e 6 ies 4.2 (Ps 10.0
Xoo

Parametric equations express the values of a collection of variables as functions


of a common parameter. For example, we might express the following values of
x and y in terms of some parameter f:

x = f(t)
y = g(t)
15.3 Plotting Functions of Two Variables 831

The parameter ¢ is often viewed as a time parameter. The equations then express
the evolution of x and y values in terms of time.
Parametric equations for two variables, such as the pair of equations just
shown, determine a curve in the x-y plane, if we assume that the functions f and
g are continuous. Parametric equations for three variables would determine a
curve in three-dimensional space.
The techniques developed in Section 15.1 can be modified easily to plot
parametric equations. A two-dimensional array, graph, should be used to store
the points to be plotted. You must determine
e the range of values fort [t_init, t_ final]
e the time interval, t_incr, between points
The algorithm for the function that marks cells in graph follows.

Algorithm for Marking Cells in graph


1. for t values starting at t_init and increasing by t_incr
2. Compute x(t)
.Compute y(t)
.Find the row number, r, corresponding to x(t)
.Find the column number, k, corresponding to y(t)
. Place an asterisk in graph[r][k]
BW
Nn

Step 4 and Step 5 correspond to the scaling problem discussed in Section


15.1 and should be solved in the same way. If the ranges of x and y values are
known beforehand, it is an easy matter to compute x_incr and y_incr and to
use these values in Steps 4 and 5 (1.e., compute x_dist, y_dist, andthenr
and k). Otherwise, we must first tabulate and store all values of x(t} and y(t) to
find [x_min, x_max] and [y_min, y_max]. This determination of maximum
and minimum values should be done in a separate step before attempting to
mark the cells in graph.

15.3 PLOTTING FUNCTIONS OF TWO VARIABLES


As we continue our discussion of plotting functions using C, we might consider
plotting the graph of a function of two variables

z= f(x, y)
As in Section 15.1, the scaling problem is a central concern. You will be happy
to learn that the scaling problem for functions of two variables is essentially
solved in the same manner as scaling for functions of one variable.
Many techniques to plot the graph of a function of two variables exist, but
most of these techniques are beyond the scope of this text. (They involve issues
832 Plotting Functions

of perspective and other sophisticated topics in computer graphics.) However,


we will discuss two simple techniques here: displaying the graph plane by plane
and using contour plots.

The Information Loss Problem


Graphing a function of two variables presents us with new problems insofar as
information loss is concerned. We mentioned earlier that when we map a func-
tion of one variable onto the computer screen a loss of information occurs
because the computer representation is discrete, whereas the actual graph is a
continuum. The same sort of consideration holds when we graph a function of
two variables.
The graph of a function of one variable defines a curve in two-dimen-
sional space (see Fig. 15.10). The graph of a function of two variables defines a
surface in three-dimensional space (see Fig. 15.11). Because our computer

Figure 15.10
Graph of a
Function of One
Variable

Figure 15.11
Graph of a
Function of Two
Variables
15.3 Plotting Functions of Two Variables 833

screens and printouts are two-dimensional and the graph is three-dimensional,


we have a serious problem with respect to information loss. How can we map
the three-dimensional graph onto the computer screen and yet retain enough
information to make our computer representation useful?

Plotting the Function Plane by Plane


One way of plotting the graph of a function of two variables is to project the
graph plane by plane onto the computer screen. To do this, a sequence of N y
values (y,, >, -- - » Yy) is chosen. Each y value determines a plane, y = Yj Each
of these y values, when substituted into our original function of two variables,
f(x, y), will yield a function of one variable 8 (Xx):

8)(x) = Fj Y)

The graph of z = 8 (x) is the intersection of the plane y = ve with the graph
of the original function, f(x, y). Each of these N functions, 8 (0) J Sa eaN
can be plotted using the technique described in Section 15.1. Figure 15.12 sug-
gests what the output of such a program might look like. We can reconstruct the
general contours of the original function z = f(x, y) in our mind's eye by exam-
ining these projections.

Figure 15.12
Representing a
Function Graph
as a Set of
Planes

Contour Plots
Contour plots can be used to represent the graph of a function

z= f(x, y)
834 Plotting Functions

as a whole, not plane by plane. The idea of a contour plot is straightforward. If


you have ever worked with a geographical map where heights above and below
sea level are color coded, then you already have some familiarity with the basic
concept. In a computer-generated contour plot for a function of two variables,
the distance of the surface of the graph from the x-y plane is encoded using char-
acters. All points on the surface of the graph within the same range of dis-
tances from the x-y plane are assigned the same character.
Figure 15.13 shows a small contour plot where the symbols +, —, and
blank are used to code the value of z. The key to the contour plot is shown on
the left. A grid structure consisting of 14 rows and 20 columns has been imposed
on a portion of the x-y plane.
The function being plotted is evaluated at each of 14 x 20 = 280 grid
points, and a symbol is chosen to represent the computed value. The plot in Fig.
15.13 shows a diamond-shaped hill (values of z > 0.5) rising above a valley (val-
ues of z < —0.5).
In general, a contour plot such as the one shown in Fig. 15.13 is generated
by imposing a grid structure on a region of the x-y plane and evaluating the
function at a point within each cell of the grid. This region is delimited by the
lines x = x_min, x = x max, y = y_min, and y = y_max. This region
is
mapped onto our computer screen as shown in Fig. 15.14. We have decided to
devote 20 rows and 60 columns to the plot so that a key can be printed on the
left side of the screen.
Each of the 20 x 60 = 1200 cells on the screen corresponds to a definite
region of the x-y plane. That region is a two-dimensional continuum of points.
The constraints of our simple technique dictate that the function be represented
by only one character at each of these 1200 cells. This discrete sampling of the
function is responsible for some of the information loss that occurs in contour
plotting. The rest of the information loss is due to the fact that a continuum of

KEY plot
Figure 15.13 Z value symbol) === ===
A Contour Plot z= 0.5 Se Se, a
and Its Key SOI z <0'5% Sblank: | «252! Seen ee
z<-0.5 -— -=—-—-=- GP aS
--— +++ --
- +4++++
- +4+t+4+4+4+4+
—_—— +4444 _—=
—--—-—- +++ ---
15.3 Plotting Functions of Two Variables 835

Figure 15.14
Mapping a
Function of Two
20
Variables onto rows
a Screen

60 columns

possible functional values is reduced to one of a small collection of characters.


The number of characters in the contour plot key is typically in the range of five
to ten. If we incorporate too many characters in our key, hoping to counteract
the information loss, the result is a plot that is visually confusing. The use of
pixels of different colors can reduce this confusion. Figure 15.15(a) shows a
small section of the x-y plane. The grid is shown along with the grid points at
which the function will be evaluated. Functional values at these grid points are
shown as colored lines perpendicular to the plane. Figure 15.15(b) shows the
corresponding portion of our contour plot.

Figure 15.15
z=f(x, y)
Evaluation of
f(x, y) at the
Grid Points and
Contour Plot for
f(x, y)

Column
(a) (b)

The Scaling Problem in the x and y Dimensions


Contour plotting involves three scaling problems, one in each of the three
dimensions: x, y, and z. The scaling in the x dimension is solved once the deci-
sion is made that the midpoint of the first column of our plot will correspond to
836 Plotting Functions

x_min and the midpoint of the last column of our plot will correspond
to
x_max. If there are NCOL columns (NCOL is 60 in Fig. 15.14), the midpoints
of
successive columns are separated by the distance

x_incr = (x_max — x min) / (NCOL — a)

Similarly, the scaling problem in the y dimension is solved once we decide


to make the midpoint of the first row correspond to y_max and the midpoint
of
the last row correspond to y_min. Then the midpoints of successive rows
will
be a distance y_incr apart where

y_incr = (y_ max -y min) / (NROW - 1)

and NROW is the number of rows in our contour plot (NROW is 20


in Fig. 15.14).
Our computational strategy is to compute z = f(x, y) at the center point
of
each cell. The center point of the cell at the top left corner of
our plot is
(x_min, y_max). The nested for loop that follows causes x and
y to take on
the appropriate values so that all grid points are “visited.” The grid
points will
be visited row by row. Note that as we progress from row to row,
y decreases
because the top of the contour plot corresponds to the maximum
y value.

/* Compute z value for each point to be plotted */


Y = y_max;
for (r= 0; r < NROW; ++r) {
xX = x min;

y -= y_incr; /* Get next y value */

The Scaling Problem in the z Dimension


The scaling problem in the z dimension involves associa
ting an appro-
priate symbol based on the key of the contour map with every
functional value
z = f(x, y). There are two possibilities:
1. The key is known when we write the program.
2. The key must be computed by the program itself.
15.3 Plotting Functions of Two Variables 837

Table 15.2 Sample Key for a Contour Plot

ye55 1155) #
ND @
ZEW g
#2 OM +
a0)

We will now discuss the first of these possibilities in detail. We will discuss the
second case briefly at the end of this section.
In some instances, the key can be determined by mathematical analysis and,
therefore, is known before we write the program. For example, in a contour
plot of the function

z= sin(a X x) + cos(b X y)

a simple analysis reveals that z must always lie within the range —2 to 2 since we
are adding two sinusoidal functions (sin and cos) with amplitude 1. The scaling
problem in the z dimension for the function just shown would require creating a
table of correspondences between z values and characters (see Table 15.2). The
symbols were chosen so that larger z values are represented by darker sym-
bols. Furthermore, we want to highlight the details for positive z values, so we
have decided to treat all negative values the same.
It is a simple matter to save this table in a C program and to use it to
convert a given z value to a plot symbol. This process is illustrated in Section
15.4.
In the next problem, we will apply the theory that has been developed so
far. The problem deals with writing a computer program to generate a contour
plot.

Case Study: Contour Plot for a


Hemispherical Surface
PROBLEM

We want to write a program that generates a contour plot of a function whose


graph is a hemispherical surface.
838 Plotting Functions

Table 15.3 Key for Hemispherical Contour Plot

z>0.8 #
z20.6 @
z20.4 g
z20.2 +
z<0.2

ee
ANALYSIS
The function that yields the hemispherical surface is

Ax, y) = 1.0 — minimum(x? + y2, 1.0)

where minimum(a, b) means a or b, whichever is smaller. We will plot


this
function for all x values between —1.0 and 1.0 and all y values between —1.0 and
1.0. Consequently, the values of z = f(x, y) must lie between 0.0 and 1.0. We
will
use the key in Table 15.3 for the plot.
The boundary values for z and the plot symbols will be saved in arrays
z_bound and symbol, respectively. The values for X_MIN, X_MAX, Y_MIN,
and Y_MAX are program constants. We will draw the plot one row at a
time,
starting with the line y = Y_MAX. The symbols to be printed in the current
row
will be stored in the character string r_ graph.

Data Requirements
Problem Constants
X MIN -1.0 /* the minimum x value */
X_ MAX 1.0 /* the maximum x value */
Y_MIN -1.0 /* the minimum y value yh
Y_MAX 1.0 /* the maximum y value */
KEYESIZ 05 /* the number of symbols in the key */
NROW 20 /* the number of rows in the contour jotlieye, 37/
NCOL 60 /* the number of columns in the contour plot*/

Problem Outputs
char r_graph[NCOL +1] /* each row of the contour plot
represented as a string * /
15.3 Plotting Functions of Two Variables 839

Program Variables
ene, 32 row number of the row being
plotted +/
double x /* the x value for row r CHE
double Sener /* the increment on the x-axis a7,
ewe /* the column being defined */
double y /* the y value for column k aa,
double y_incr /* the increment on the y-axis xy
double z_bound[KEY SIZ] /* the boundary values for the
plot key */
char symbol [KEY SIZ] the plot symbols a7,

Initial Algorithm
1, Compute x incr andy incr.
2. Define and display each row of the contour plot, starting with the row for
which y is Y_MAX.

Algorithm Refinements
We discussed the general approach to Step 2 earlier. For a given y value (a
row of the plot), we must determine the appropriate plot symbol for each x
value along that row. To do this, we must first compute the value off(x, y) for
each x and then convert this value to the corresponding plot symbol (a charac-
ter). We will store the plot symbols as individual characters in the string vari-
able r_graph.

Step 2 Refinement
2.1 Initialize y to y_max.
2.2 for each row of the plot
2.3 Set x (OX SMIN.
2.4 for each column, k, of the plot
2 SaComputetz— ony)
2.6 Convert z to a plot symbol.
2.7 Store the plot symbol as the kth character in string r_graph.
2, oqnerement x DY xa5lner:
2.9 Display the string r_graph.
2.10 Decrement y by y_incr.
840 Plotting Functions

IMPLEMENTATION

Figure 15.16 shows the contour plot program.

/*

* Generates a contour plot of a hemispherical surface


*/
#include <stdio.h>
#include <math.h>
#define X MIN -1.0 /* the minimum x value
#define oil
X MAX 10 /* the maximum x value
#define “7
Y MIN -1.0 /* the minimum y value
#define 2)
Y MAX INE) /* the maximum y value
#define self
KEY SIZ 5 /* the number of symbols in the key
#define
*7
NROW 20 /* the number of rows in the contour plot * /
#define NCOL 60 /* the number of columns in the contour plot */

Se
* Returns the minimum of its two arguments
*/
double
minimum(double a, double b)
{
double ans;

Ee (ai <5)
ans = a;
else
ans = b;
return (ans);
}

#define F(x,y) sqrt(1.0 - minimum((x) * (x) + CY oe sVaipa ebroy)

int
main(void)
{
char r_graph[NCOL + 1]; /* each row of the contour plot */
tit sis /* row number of the row being plotted */
double x; /* the x value for row r */

(continued)
15.3 Plotting Functions of Two Variables 841

Figure 15.16 (continued)

double x_incr; /* the increment on the x-axis */


int k; /* the column being defined */
double y; /* the y value for column k */
double y incr; /* the increment on the y-axis */
double z; /* the z value at point (x,y) */
double z_ bound[KEY SIZ] /* the boundary values for the plot key */
= A0c3, WsG, Oot» Os2— Oswhe
char symbol[KEY_ SIZ] /* the plot symbols */
ee ap Hap? 1g SG
= {'#',

/* Computes x and y increments */


einer. =" (XiMAX)= X MIN) y/) (NCOLA— 1)
y_incr = (Y_MAX - Y_MIN) / (NROW - 1);

/* Generates contour plot a row at a time */


r_graph[NCOL] = '\0'; /* Inserts "end of string" character */
y — < MAX;
FOGe (iG —) Osa Ge SENROW 3 ate)
x = X MIN;
fOG (k= Oe ke<) NCO +k)

/* Computes z value at point (x, y) x/


z= F(X, y);

/* Places appropriate symbol in r_graph */


Lt u(zZe>52. bound! 0)},)
r_graph[k] = symbol[0];
else if (z >= z_bound[1])
r_graph[k] = symbol[1];
else if (z >= z_bound[2])
r_graph[k] = symbol[2];
else if (z >= z_bound[3])
r_graph[k] = symbol[3];
else if (z < z_bound[4])
r_graph[k] = symbol[4];

/* Gets next x value */


x tex sincr;

(continued)
842 Plotting Functions

Figure 15.16 (continued)

be
printf("\n %s", x_graph);

/* Gets next y value */


y == y incr;

Figure 15.17 shows a sample contour plot generated for the test function
f(x, y)
described earlier. To draw another contour plot, insert a different
definition
for macro F(x,y). Make sure that you also modify the initiali
zation for
z_bound if the range of function values changes. Alternatively,
you could
create a separate function to initialize the array z_bound, as discusse
d next.

Figure 15.17 Sample Contour Plot

OFA Oade Cis. nian wranager eee Pe Se eB Be leMee ce oe ae Me eerie wees


ero NG. tec? ee aay) oa tt tbh hhh hh db HOCCOCPOCO
OLELHUUOOHHE HH...”
feat idaour mea es tHe HHHHSUOOOOOOOOOHHHOOHOHOHOOOOO
RR%R% H%% 4 a Biatoheo a gia icy SONG
Fe Naty eke pains tthe bbe@@@@COCCOCOOOHHH FHF HEF FE@OOCOOOHOOOUH%H%%44......-”
Ru Rents ++ EHHhOCOCOCOOHER HHH HEHEHE EH EF HEHEHE FC OCOCOOORUM%% 4...”
+H EHUHCCOOCCOOHE EEF HHHE HEE HEHE HEHEHE HEHE FOOOCOOOO%XR% Es.
FE ROUHOOCCCCCOHEE EHH F FFE EEE EE EEE HHEEEE EEE EEE E@@OOOO%%%%
4s.
Fe ROUNOOCOCOOHHEHHEE SE FFE HEPES EEF HHE THEE EE EEEEE@O@O@OOO%%%s»
TH RENOCCCOOCHEF HEF HHHHHHE EERE AETHER EEE EERE HEE EE SOO OOOO%%%s 4”
Ft BEhOCOOCOCHHF HF EFE HA HHH HHT TREE HEHEHE EEE HEHEHE EE @OOOOO%%% ss”
Ft BEEN OOCOCOCOCOHEE HEHEHE HEE HARARE HEHEHE EEE EHEEE SE OOOOOO%%%% 44”
FH HhELCOCOOCCOOHH HFEF HEFHHHHE EHH EFHHHEE EEE HEHEHE E@@OOOORS%% es.
(+t RHHHCCOCOCOOHHEEHEH HF EFER TFET EHH EEEEE EE HO OO@OOOO%%%% ss”
te bes t+ HhEHCOCOCOOCOOHEFFHH HEHEHE EHH EEHE EEF @OCOOOOOO%XX%%Gs...
PS PRS oi Pe ++ HHDHLOCCOOCOCOOOHHEH HH HEHE FF @OOOOOOOOOOR%Y%%G4.....
Set ee ae +++ Hh HH HHOCOCCOCOCOOOOCOCOOOOOOO
O%*%*%%%S44........-
Be ira Ue; (Aes on Some eT ee FH HDHHEOHHEHHOSO OOO
OH HGDSE HH...”
A ty Ao ee eer ee od RH Fah oR oy His ee ae I Oe SO te
15.4 Introduction to Computer-Aided Design 843

Table 15.4 Key for Unknown Range of z Values

Condition Symbol

i #
lean @
ea g
We} +
ean

Computing the Key at Execution Time


Suppose we have decided on a given collection of characters to use in our con-
tour plot, but we do not know the range of z values that will correspond to a
given character (see Table 15.4). The program can determine the desired z val-
ues once the maximum and minimum z values (z_max and z_min, respec-
tively) are known. To determine these two values, the program must compute
z = f(x, y) at each grid point, requiring nested indexed for loops that take us
row by row through the two-dimensional pattern of grid points. Once z_max
and z_min have been found, we can assign z values that divide the z range into
five regions for Table 15.4.

Programmin
EXERCISE FOR Si ker erin
SECTION 15.3 1. Write a function that defines the values stored in the array z_ bound for a
function f(x, y).

Hint: The input parameters to the function should be x_min, x_incr,


y_max, y_incr, numcol, numrow, and keysiz. The output parameter is
the array z_ bound.

15.4 INTRODUCTION TO COMPUTER-AIDED DESIGN

Besides using the computer to plot functions, engineers frequently use the com-
puter as a design tool. Computer graphics systems are widely available to help
engineers make engineering drawings, the specifications of which are saved on
disk. Later the engineer can modify the existing drawing or reuse parts of it in
new drawings.
844 Plotting Functions

Early graphics systems provided a library of functions that the designer


could call to draw standard objects such as circles, rectangles, and even poly-
gons. The designer could specify the size and position of each individual object
in an engineering drawing. The drawings were printed on line printers with
graphics capability or were drawn on special output devices called plotters.
Today, interactive menu-driven systems are available on personal com-
puters and graphics workstations. The designer uses a mouse to position the cur-
sor at the point where each new line or object is to be drawn. Then, the designer
selects the desired object or line shape from a menu of available objects.
The engineer can also create three-dimensional drawings using a comput-
er-aided design (CAD) system. To create a three-dimensional object, the engi-
neer can rotate a two-dimensional object around a specified axis. Once an object
has been designed, a CAD system can compute and display certain of the
object’s properties such as areas, volumes, and cross sections.
When displaying a three-dimensional object, the engineer can rotate the
object in space or request different views of the object. The engineer can use
different colors to distinguish between parts of the drawing. For example, ina
heating/air-conditioning diagram, components of the heating system can be
drawn in one color, components of the cooling system in another color, and
components used in both systems in still another color.
CAD systems are widely used in the following engineering fields:
aerospace: for design of aircraft and space vehicles
automotive: for design of automobile bodies, engines, and other components
architecture: for building design and room layout
electronics: for circuit board layout, integrated circuit fabrication, and
wiring diagrams

CAD systems can also be integrated with computer-aided manufacturing


(CAD/CAM). In CAD/CAM, the specifications and dimensions created in the
design phase are used as inputs in the manufacturing phase to control production
tools, thereby automating the manufacturing process.
Computer-aided engineering (CAE) is another extension of CAD. A CAE
system can complete an entire design by extrapolating from a few Key sections
provided by the engineer. The CAE system can also decompose an object into its
component elements and perform mathematical computations to determine prop-
erties of individual elements such as stress and temperature.

15.5 COMMON PROGRAMMING ERRORS

Because we did not introduce any new C statements in this chapter, we have no
new pitfalls to warn you about. However, this chapter does make extensive use
Answers to Quick-Check Exercises 845

of arrays and functions, so you should review the errors that are common with
their use. To avoid argument list errors, you should continue to be careful about
argument list length and the order of arguments. Also, check subscript boundary
values in loops that process arrays to ensure that each array subscript remains in
range for all values of the loop parameters.

CHAPTER REVIEW

This chapter discussed the use of the computer for plotting functions. Several
kinds of function plots were described, including graphs of parametric equations
and contour plots. We also introduced the topic of computer-aided design.

QUICK-CHECK EXERCISES
. What is a singularity?
winea plot of jy = (@), = =
Oe ts: the, dependentsvartanlcsand
is the independent variable.
. What are parametric equations?
. How many dimensions can be plotted in a contour plot?
. Differentiate between CAD, CAD/CAM, and CAE.
. Name
BW
Nn two techniques for plotting a three-dimensional curve.

ANSWERS TO QUICK-CHECK EXERCISES


. A singularity is a point on a curve whose value is too large to be plotted.
ae
. Parametric equations are equations that express the values of a collection of
variables as functions of a common parameter.
. three dimensions
. CAD is used for drawing or drafting. In CAD/CAM, the computer also con-
trols the manufacturing process. In CAE, the computer analyzes properties of
a drawing and assists in the engineering process.
. Contour plots and plotting the function plane by plane are two techniques for
plotting a three-dimensional curve.
846 Plotting Functions

REVIEW QUESTIONS

1. Explain the scaling problem.


2. What is the scaling problem in a three-dimensional plot?
3. How can you use two-dimensional plots to represent a three-dimensiona
l
surface?
4. List three applications of computer-aided design in engineering.

PROGRAMMING PROJECTS

1. Plot each of the following functions over a suitable interval:


a. x* — 6x3 — 5x2 70x -9
b. 3 cos(x) —x
c. ¥| sin(x) |+ |cos(x)|

die G)
2. Write a program that plots two functions on an interval [Xsintt,<afanady:
for example

y = f(x)
y = g(x)

Use different symbols for the plots off and g. Wherever the two
functions
intersect, use a third plotting symbol. Try your program on

fix) = V4.0
—x?
g(x) = 1.0+22

3. Modify the program you wrote for Programming Project 2 so


that the area
between the two functions that are being plotted will appear
shaded (i.e.,
filled with the symbol / as shown in Fig. 15.18). Try your program
on the

Figure 15.18
Plot of Two
Functions with
Area between
Curves Shaded

X_init x_final
Programming Projects 847

functions given in Programming Project 2 and on any other functions you


might want to try.
. Write a parametric equation plotting program and plot the following para-
metric equations. Consider each curve separately. Choose an appropriate x-y
scale and time increment in each case.
Aska yor ues f= 2
bixse4 yee OFs
% <in3
Cox aay =e! Murer
< In?
decease osnG) = = te
2) 2)
y = 4 sin(#)
Clee =COS() ORSei6 =e it
y = sin?(r)
. Write a program to plot the trajectory of a golf ball for various initial veloc-
ities V and angles of elevation, 0. The user will be asked to enter the values
for v (in feet/second), theta (in radians), and the distance h from the tee (at
the origin) to the hole.
The equations describing the ball's trajectory are

x =(Vcos6)t
1
Mai gt? + (V sin6)t

where g is the gravitational acceleration of 32 feet/second2.


In your plot, show the location of the tee and the hole and the trajectory of
the ball from the tee to where it first makes landfall, or waterfall, as the
case may be (see Fig. 15.19).
. You are hidden from view of the golfer in Programming Project 5 at some
point on the line between the tee and the hole. The distance between you and

Figure 15.19
Golf Ball
Trajectory
Golf ball
trajectory
848 Plotting Functions

Figure 15.20
Trajectory to Hit
Opponent's Ball

the golfer is D. You plan to deflect the golfer's ball by a shot of your own.
You will hit your ball with an initial velocity Vp at an angle of elevation, Op.
Write a program that will accept your opponent's initial velocity v and
angle of elevation theta. The program will also be given the values of h
(distance from tee to hole) and d (distance between you and your opponent).
Once these values have been entered, the program will execute a loop in
which it will accept a sequence of vp and thetap values. For each vp and
thetap value, the program will plot your ball's trajectory along with the tra-
jectory of your opponent's ball. If both balls end up in the same cell of the
array graph at the same time, both trajectories will be terminated in a hit at
that point (see Fig. 15.20).
Continue to enter vp and thetap values until you hit your opponent's
ball or until you get tired.
. The problem of graphing equations given in polar coordinates duplicates
several features of our program to plot parametric equations. An equation in
polar coordinates has the form

r= f(@)
where r is interpreted as the distance of a point from the origin and
0 (theta) is the angle (see Fig. 15.21).
To plot equations given in polar coordinates, we let theta take on a
sequence of values, usually from 0 through 2m. A small enough theta
increment must be chosen to get sufficient detail. For each theta, we com-
pute r = f(theta). Then we transform the (r, theta) pair to rectangular
coordinates using the equations

X = r cos(theta)
y =r sin(theta)
Programming Projects 849

Figure 15.21
Conversion of
Polar
Coordinates to
Rectangular
Coordinates

These equations can be derived from a consideration of Fig. 15.21. Once


we have the (x, y) pair for the current values of r and theta, we can mark
the appropriate row and column of the array graph.
Implement the technique described above to plot the following equations
given in polar coordinates:
anf = sin(@)
b. r= cos(30@)
¢. r= sin(7@)
d. r= 3-3 sin(@)
Cmm—itan(o))
. Apply our contour-plotting technique to each of the following functions in an
appropriate region near the origin.
aie eee ny
Dee = (sin5 ) (sin5 )
Cz = 逓sin(y)
. Write a program that generates a contour plot of the interference pattern
produced by two sinusoidal wave generators operating in phase. The ampli-
tude z of the disturbance at each point (x, y) depends on the distance between
(x, y) and the individual generators (or sources). The formula for z in terms of
these distances r, and r, is

Z = cos(r,) + cos(ry)

where r, denotes the distance between the first generator and (x, y) and r,
denote the distance between the second generator and (x, y) (see Fig. 15.22).
If the generators are at the points (1, 1) and (—1, —1), as shown in Fig.
15.22, then r, and r, are given by the formulas

ry = V(x— 1.0)? + (y- 1.0)

ry = V(x + 1.0)2 + (y + 1.0)?


850 = Plotting Functions

Figure 15.22
Distances
between Point
(x, y) and Two
Sinusoidal
Wave
Generators

Generate the contour plot for z delimited by the lines x = 10, x = -10, y=
10, and y=-—15.
16.1
Finding Roots of Equations
16.2
Vectors and Matrices
16.3
Solving Systems of Linear Equations

Linear Regression and Correlation


16.5
Numerical Integration
16.6
Using Numerical Methods Libraries
16.7
Common Programming Errors
Chapter Review
I, this chapter, we will introduce the topic of numerical methods. We will dis-
cuss several computational techniques that are widely used in solving prob-
lems in engineering, economics, statistics, business, natural science, and social
science. These techniques include finding roots of equations, doing matrix arith-
metic, solving simultaneous equations, fitting a line to a data set, and perform-
ing numerical integration. We will also discuss ANSI C's facility for passing a
function as an argument, a feature that is quite useful in numerical computation.
The sections in this chapter are fairly independent and may be studied in
any order. Due to space limitations, we have generally presented only one
method for performing a particular numerical operation. Several different meth-
ods are available for each of the operations discussed in this chapter. A list of
references is provided for those wishing to delve deeper into the topic of numer-
ical methods. An extensive set of programming projects, many of which present
alternative methods, is also provided.

16.1 FINDING ROOTS OF EQUATIONS


A useful mathematical idea is to compute the roots of an equation

f(x) = 0

The roots of the equation just given are the values for x that make this equation
true. If we graph the function f(x), as shown in Fig. 16.1, the roots of the equa-
tion are those points where the x-axis and the graph of the function intersect.
The roots of the equation f(x) = 0 are also called the zeros of the function f(x).
In this section, we will discuss two numerical methods for finding approx-
imate real roots of an equation. When a numerical method is successful in find-

Figure 16.1
Six Roots for
the Equation
f(x) = 0 y= tx)

852
16.1 Finding Roots of Equations 853

ing a root, it is said to have converged to the root. When a numerical method
fails, it is said to have diverged. Different root-finding methods have different
properties in terms of convergence and divergence. Among the methods we
will discuss, in general, the bisection method converges relatively slowly as
compared to Newton's method, which we discussed in Chapter 7. The speed
with which a method converges becomes an important issue when the function
f(x), whose roots we are computing, requires costly computations; finding roots
of equations involves repeated evaluations of this function.

Convergence Criteria
In general, root-finding methods generate a sequence of approximations to a
root:

X15 %Q,%3,--+5%
jy...

One problem is deciding when to terminate this generation process. In other


words, when is a given x; value “good enough” to be accepted as our answer?
There are three criteria for judging whether a given x; is “good enough.”
Since the satisfaction of one of these criteria causes the root-finding method to
terminate, or converge, these criteria, which follow, are called convergence cri-
teria. The true root is denoted by rt; epsilon is some predetermined small num-
ber, x; is the latest approximation, and Xj 4 is the approximation before that.

Criteria for Convergence to the Root


ile |(| < epsilon
2, |x; = rt| < epsilon
33. |; ~ x;-4| < epsilon

Criterion | states that x, is considered a good enough approximation to the root


if the value of |.f,)| is very small. Criterion 2 states that x, is considered a good
enough approximation to the root if it is very close to the true root. Criterion 3
states that x, is considered a good enough approximation to the root if it is very
close to the previous approximation, Xi: In other words, we do not expect to
gain required accuracy by generating additional approximations.
One might wonder how the second criterion can be applied in practice
unless rt is already known. Actually, we do not need to know rt to claim that x.
is within epsilon of rt, as the bisection method will demonstrate. It is sufficient
to know that x, and rt have been isolated to the same sufficiently small interval.
The convergence criteria are not equivalent. For example, Fig. 16.2(a)
shows a situation in which x; is within a distance epsilon of rt, but |f(x,)| is
greater than epsilon. Figure 16.2(b) also shows a situation where |f)| < epsilon,
but here the distance of x from rt is greater than epsilon.
854 Introduction to Numerical Methods

f(x) f(x)
Figure 16.2 X = rt- epsilon X = rt - epsilon
Showing
Nonequivalence
of Convergence
Criteria 1 and 2

y = epsilon y = epsilon

(a) (b)

The Bisection Method


The bisection method applies the second convergence criterion. We repeatedly
generate approximate roots x until we have an approximation that is certain to be
within a distance of epsilon from the true root rt. This approximation can be found
if we can isolate the true root and the approximate root within the same interval
whose length is less than epsilon. This is exactly what the bisection method does.
When we use the bisection method, we first tabulate function values to
identify intervals on which changes of sign occur. If a change of sign occurs on
an interval, that interval must contain an odd number of roots. Figure. 16:3
shows two such intervals.
Let us assume that [x,, x,] (x_left to x_right) is an interval on which
a change of sign does occur and in which there is exactly one root. Furthermore,
assume that the function f(x) is continuous on this interval. If we bisect this

Figure 16.3
Change of Sign
Implies an Odd
Number of
Roots

(a)
(b)
One root Three roots
16.1 Finding Roots of Equations 855

interval by computing its midpoint Xmig Using the formula


Xy +x
Xmid = =Oa ‘

there are three possible outcomes: the root is in the lower half of the interval,
[x), X mil; the root is in the upper half of the interval, [Xmiad X]; or f%,,;q) 18 Zero.
Figure 16.4 shows these three possibilities graphically.

Figure 16.4
Three
Possibilities
That Arise
When the
Interval [x,, x,]
Is Bisected
y = f(x)

(a)
The root rt is in the half interval [x,, Xmial-

y = f(x)
(b)
The root rt is in the half interval [x mia’ X,1-

f(x m ig) = 0.0


856 Introduction to Numerical Methods

If f(x,,jq) 18 Zero, x,,,, is a root and the process terminates. If the root is in
one of the half intervals and the length of that interval is less than epsilon,
then the midpoint of that half interval must be within epsilon of the true root.
Thus, the midpoint of that half interval satisfies convergence criterion 2, and we
are done. Otherwise, we make the half interval containing the root the new
interval, and we continue by bisecting it. It should be obvious that under the
assumptions we have given, if f(x,,,,) never hits zero, then eventually the inter-
val size will become less than epsilon and the method will converge.

A Bisection Program
The C program in Fig. 16.5 looks for approximate roots for equations f(x) = 0
and g(x) = 0 on the interval [x,, x,] using the bisection method. The left and right
endpoints, x, and x,, and the tolerance, epsilon, are inputs from the user. The
main function gets these three inputs, calls function bisect (shown in Fig.
16.6) to perform the bisection procedure, and prints out the final results.

Figure 16.5 Finding a Function Root Using the Bisection Method

* Finds roots of the equations


f(x) = 0 and g(x) = 0
on a specified interval [x_l, x_r] using the bisection method from
* a personal numerical methods library.
*/

#include <stdio.h>
#include <math.h>
#include "methods.h" /* Header file for personal numerical methods
library containing bisection function ; */

/* Functions for which roots are sought * /

/* 3 2
ERO Xe ese tS
*/
double
£(double x)
{
(continued)
16.1 Finding Roots of Equations 857

Figure 16.5 (continued)

return (5 * pow(x, 3.0) - 2 * pow(x, 2.0) + 3) ¢


}

/* 4 2
Ske — 3X 8
*/
double
g(double x)
{
return (pow(x, 4.0) - 3 * pow(x, 2.0) - 8);
}

ine
main (void)
{
double x 1, x _r, /* left and right endpoints of interval */
epsilon, /* error tolerance e/.
GLOote
alMate error;

/* Get endpoints and error tolerance from user */


printf("\nEnter interval endpoints> ");
Scant (“tltsliy,c&x1, &xir)s
printf("\nEnter tolerance> ");
scanf("%lf", &epsilon);

/* Useibisect tunetion;toslooksfor roots of) frandag * /


(; \nFunction £\n")-:
printf’
root = bisect(x_l, x_r, epsilon, f, &error);
if (!error)
pEinti( “A\nsief(te7£)= se\n4, root;sf(root)):

printf("\nFunction g\n");
root = bisect(x_l, x_r, epsilon, g, &error);
if (!error)
print£(s\n9 (hg(%.7f)= ¢e\n" / Loot,, gi(root))).

BEC UEININ (0)Ts


858 Introduction to Numerical Methods

Figure 16.6 Personal Library Function bisect

#include <stdio.h>
#include <math.h>

#define TRACE /* turns on tracing output from bisect */


#define FALSE 0
#define TRUE 1

/*

Implements the bisection method for finding a root of a function fp.


Finds a root (and sets output parameter error flag to FALSE) if signs of
fp(x_l) and fp(x_r) are different. Otherwise sets output parameter error
flag to TRUE.
*/
double
bisect(double x_l, /* input - endpoints of interval in which xf
double x_r, /* to look for a root iT)
double epsilon, /* input - error tolerance “7
double (*fp)(), /* input - the function «7,
int *errp) /* output - error flag */
{
double x_mid, /* midpoint of interval */
ieglee /* £pi(x 1) */
iimid, /* fp(x_mid)
*/
£ rs /* fp(x_r)
*/
int root_found = FALSE;

/* Computes function values at initial endpoints of interval


|
Fh = ="fp(x 1);
i}
Fh 5 Ep(scer));

/* If no change of sign occurs on the interval there is not a unique


root. Searches for a root if there is one. oy
Die (bol te fore Oy sf /* same sign */
*errp = TRUE;
preinti(
No wunique reotuin [%.7£, $.78)]\n",exol, x ig
} else {
*errp = FALSE;

(continued)
16.1 Finding Roots of Equations 859

Figure 16.6 (continued)

Searches so long as interval size is large enough and no


root has been found if
while (fabs(x_r - x_l) > epsilon && !root found) {

/* Computes midpoint and function value at midpoint */


xmid = "(xe xr yeiao0.
f mid = fp(x_mid);

LEV CEPT ==F0509), 64 /* Here's the root */


root _found = TRUE;
pel semi tele sivmid’<1 020) (7 /*sRootiinapxal moomid 7
x Yr = X%mid;
} else { /#*ROOt, Ine |xemid, oe /
4 aL X_mid;
na £vmid?
}

/* Prints root and interval or new interval if trace is on */


#if defined (TRACE)
if (rootetound) 7f
DEant£( Root. tound vat ‘x )=3%./f, " 7, x mide
PEINER( MLdpOLNe OL | tat, Sew,
|Nino eee
} else {
Printe (New interval is (taf; )%./
fi \ 7a
}
#endif
}
}

siti cthere tis val root, itis) the imidpointtofs (xe, vx] */
recurntne( xs betes) o/e2.0)i¢

Computing the product f(x;) x f(x,) is the easiest way to detect a change of
sign in the interval [x,, x,]. This productis negative when a change ofsign
occurs.
A sample run of the program is shown in Fig. 16.7. Note that because
TRACE is defined, the intermediate values x, and x, are printed by bisect,
showing the convergence to the final answer.
860 Introduction to Numerical Methods

Figure 16.7 Sample Run of Bisection Program with Trace


Code Included

Enter interval endpoints> -1.0 1.0


Enter tolerance> 0.001

Function f
New interval is [ -1. 0000000, 0.0000000]
New interval is [ -1.0000000, -0.5000000]
New interval is [ -0.7500000, -0.5000000]
New interval is [ -0.7500000, -0.6250000]
New interval is [ -0.7500000, -0.6875000]
New interval is [ -0.7500000, -0.7187500]
New interval is [ [-0. 7343750, -0.7187500]
New interval is [ [-0. 7343750, -0.7265625]
New interval is [ [-0. 7304688, -0.7265625]
New interval is [ -0. 7304688, -0.7285156]
New interval is [ [-0.7294922, -0.7285156]
£(-0.7290039) = -2.697494e-05

Function g
No unique root in [-1.0000000, 1.0000000]

Functions as Arguments
Notice that our call to bisect passes a function name as the fourth argument.
This is the first time we have seen a function name used as an actual argument.
Because the bisection method as implemented in Fig. 16.6 can be applied to any
function that both returns a type double value and takes a single type double
argument, using a function as an input parameter gives bisect great flexibility.
The data type of bisect's formal parameter fp is “pointer to function
returning double.” Variable fp is a pointer because it represents the address of
the code that carries out the function's purpose. ANSI C allows a variable of
type “pointer to function” to be used in a function call statement in the same
way that a function name would be used. Thus, when bisect is executing as a
result of function main's statement

root = bisect(x /dyixarmepsidliongut: sSenror)>


16.1 Finding Roots of Equations 861

the statement

feleenep (x)

is equivalent to

fad°=) fis 1):

However, when bisect executes as a result of the call statement

root = bisect(x_l, x_r, epsilon, g, &error);

the same statement means

Eels = Gi(xcs)):*

Of course, if bisect were to use functions f and g directly, rather than using
them through its function pointer parameter, the file in which bisect is found
would need to contain prototypes of £ and g. No such advance knowledge is
required when a function pointer parameter is used.

Review of Newton's Method


In Chapter 7, we worked with Newton's method for finding the roots of an equa-
tion. This method starts with a guess x, and then generates successive approxi-
mate roots x,,X5,..., Xjy XyCe . , using the iterative (or open) formula

Co fx i)
ti d f')

where L(%) is the derivative of fevaluated at x = Xj.


Newton's method uses convergence criterion 3. We consider X;,, the
approximate root when two successive approximations are sufficiently close—
that is, when

[>j01 — x,| < epsilon

A famous pathological case where Newton's method cycles indefinitely occurs


in trying to find the root of

fx) =x3-x

with an initial guess of x = 1 or x = —-1. In general, Newton's method is more


reliable and faster than the bisection method.
862 Introduction to Numerical Methods

Self-Check
EXERCISES
FOR SECTION —. Find endpoints of an interval one unit long in which a root of g(x) = 0 is
16.1 found for function g from Fig. 16.5.
2. If functions f and g referred to in function main of Fig. 16.5 were defined in
a separate file and compiled separately, what declarations would need to be in
the source file with main?

Programming
1. Rewrite the program from Fig. 7.17 as a function that uses Newton's method
to find an approximate root of a function. The function should have as input
parameters the initial guess, the maximum number of guesses, and functions
f and fprime. As an output parameter, the function should have an error
flag.

16.2 VECTORS AND MATRICES

One- and two-dimensional arrays were introduced in Chapter 8. In many tech-


nical applications in science, engineering, economics, and business, arrays are
used to represent mathematical objects called vectors and matrices. In this sec-
tion, we will learn how vectors and matrices are represented in C and how to
write C functions that perform basic operations on these objects.

Representing Vectors
A vector is a mathematical object consisting of a sequence of numbers (the
components of the vector). A vector is said to be of dimension n if it consists of
n components. Unfortunately, the use of the word “dimension” in C is not con-
sistent with the standard mathematical terminology for vectors. The C array x in
Fig. 16.8(a) is one-dimensional (in C terms), whereas the vector it represents,
shown in Fig. 16.8(b), is three-dimensional (in mathematical terms). An n-
dimensional vector is represented in C as a one-dimensional array of size n. You

C array x[0] x{[1] x{[2]


Figure 16.8
C Representa-
tion of a Vector
Vector X, =X,
components
(a) (b)
16.2 Vectors and Matrices 863

will also notice some disparity between the standard notation for a vector com-
ponent and the C notation. Vector X's third component (the 4) is represented in
mathematics texts as x3. In C, however, it will be called x[2], since C array
subscripts always start with 0.

The Scalar Product


Two vectors with the same number of components can be multiplied together,
forming the scalar (dot or inner) product, by computing the sum of the products
of corresponding components. Hence, if X= <1, 2, 4>and W= <2, 3, 1>,
their scalar product is

AeW=<l,2,4>¢<2,3. i> =1*242*344*71=12

In general, if X = <X1,%,.-..,xX,> and W=<w,, W>,...,W,> are two vectors


of dimension n, their scalar product is
n

~ XW)
where x, and w, denote the kth components of the vectors X and W, respectively.
Coding the computation of the scalar product of two n-dimensional vectors
X and Wis straightforward. A counting for loop is required to traverse the vec-
tors component by component. A variable sum_prod is required to accumulate
the sum. The C code for this calculation is shown next.

sum_prod = 0;
nus 3 (Ce ee WO RS ele pais)
sum_prod += x[{k] * w[k];

Many of the functions we will develop in the rest of this chapter will
require us to calculate a sum, as we did in computing the scalar product.
Translating from summation notation to C is a fairly mechanical process. Figure
16.9 shows in some detail the correspondence between the mathematical expres-
sion of the scalar product and its implementation in C. When the summation
expression involves vector components, some adjustment of the counter variable

nN

Figure 16.9
Summation
XeW= LX xwy)
Se k = 1 ee

Notation and
for Loop vhs t Ne
Parameters variable for statement quantity added to
sum_prod and its the sum being
for the sum parameters accumulated in sum_prod
864 Introduction to Numerical Methods

is required to accommodate C's use of arrays with an initial subscript of 0.


Although a literal translation of the summation symbol in Fig. 16.9 would be

for (k=1; k<=n; ++k)

since vector components | through n are represented by array elements 0


through n—1, we must modify our for loop header slightly, giving us the new
header

for 2.(ki= 107. skit ++k)

Representing Matrices
A matrix is a mathematical object that consists of a rectangular arrangement of
numbers called the elements of the matrix. An m-by-n matrix consists of m
rows and n columns. Each row is an n-dimensional vector, and each column is
an m-dimensional vector. The element in the ith row and jth column is denoted
by Aijs which, as we saw in Chapter 8, is a[i] [3] in C (assuming use of zero
as the starting point for i and j in C). Thus, an m-by-n matrix can be imple-
mented in C as a two-dimensional array with m rows and n columns.
Figure 16.10(a) shows an m-by-n (in this case four-by-three) matrix, and
Fig. 16.10(b) shows its implementation as a two-dimensional array.

Figure 16.10
Four-by-Three ‘eae led o|
Matrix and Its a(t }e3 44 |
Implementation A 1 -1 -1 1
as a Two- Ol wil. 42
Dimensional
Array

(a) (b)

Multiplying a Matrix by a Vector


If A is an m-by-n matrix and X is an n-dimensional vector, then we can form the
product ofA and X, denoted by A * X, yielding an m-dimensional vector V. A
matrix with m rows and n columns can be multiplied on the right only by a
vector of dimension n. If A is an m-by-n matrix and W is an m-dimensional
16.2 Vectors and Matrices 865

Figure 16.11 Multiplying a Matrix by a Vector

A ie ace V W me A = Z
Tete gal ; 5 [201-1] + erates! = [3 0 -1]
eRe: a ND = |10 ETE a
ipasiree 2 =e il esh Sal
O i w 6 Oe lel

(a) Multiplication on the right (b) Multiplication on the left

vector, then we can form the product W and A, denoted by W * A, yielding an n-


dimensional vector Z. A matrix with m rows and n columns can be multiplied on
the left only by a vector of dimension m. Figure 16.11(a) shows the multiplica-
tion of A, a four-by-three matrix, on the right, and Fig. 16.11(b) shows the
multiplication on the left. We will restrict our detailed discussion to Fig.
16.1 1(a).
How were the components of the result vector in Fig. 16.11(a) computed?
The ith component of V, v[i], is the scalar product of the ith row of the matrix
A and the vector X. Keep in mind that with regard to C, we begin counting
with zero. For example, v[1] (the second component of vector V) is the scalar
product of row 1 (the second row) ofA (considered as a vector) and the vector
X. These vectors are in Fig. 16.11(a). The relevant computation is

WAlbdkl| PSA Sik IES Oo Sis PSS Po GL so SP es Deb a MA De SO)

The mathematical formula for computing v, in the general case of the mul-
tiplication of an m-by-n matrix A and an n-dimensional vector X is
n

We 2 Ai)
Translating mathematical summation notation into C with the appropriate
adjustments for zero-based array subscripts, we get the code for computing v;:

v[i] = 0
fe)Teme (GS Il
xe OF Ske nie Ebi)
Vif) Staal aK el;

Once we have a description of how to compute a typical element of a vec-


tor, it is a simple matter to code the computation of the entire vector. All we
need to do is embed the computation of the typical element v[i] in a counting
for loop where i is the loop index. The C function mat_vec_prod in Fig.
866 Introduction to Numerical Methods

Figure 16.12 Function mat_vec_prod That Computes the Product of a Matrix and
a Vector

* Computes the product of M-by-N matrix a and the N-dimensional vector x.


* The result is stored in the output parameter v, an M-dimensional vector.
mf
void
mat_vec_prod(double a /* output - M-dimensional product vector */
const double a[M][N], /* input - M-by-N matrix */
const double x[]) /* input - N-dimensional vector */
{
Aue aby Lee

LOE (2 = Ofna Me) oar) 4


0;

16.12 uses this idea to compute the product of an m-by-n matrix A and an n-
dimensional vector X. The result is an m-dimensional vector V. As is typical in C
functions that compute an array result, mat_vec_prod expects the function
that calls it to provide space in which to store this vector. As always, we are
using capital letters for the row and column dimensions to imply that they are
defined as constant macros. Although our function could allow dimension m (the
number of rows) to vary, since the number of columns must be a constant,
we
have elected to use constants for both dimensions.

Matrix Multiplication
Multiplying a matrix by a vector is just a special case of multiplying a matrix by
a matrix, since an n-dimensional vector can be viewed as either a matrix
with n
rows and 1 column or a matrix with 1 row and n columns. We will now develop
a C function that will multiply two matrices, returning the product matrix.
Two matrices A and B can be multiplied together, yielding a new matrix,
C =A * B, if the number of columns in A is equal to the number of rows in
B. In
this case, the matrices A and B are said to be “conformable” for multiplica-
16.2 Vectors and Matrices 867

tion. IfA is an m-by-n matrix and B is n-by-p, then the product matrix C will be
m by p. This result is consistent with our earlier discussion of multiplying a
matrix (m by n) by a vector (n by 1) yielding a new vector (m by 1).
In analyzing the computation of the product of two matrices, we first con-
sider the computation of a typical element. For example, a typical element in the
product matrix C is the element in the ith row and jth column. This element is
denoted by c[i][j]. Once we know how to compute c[i] [Jj], it is a trivial
matter to compute the rest of the matrix C. All we have to do is embed the
computation of c[ i] [Jj] in an appropriate looping mechanism, which allows i
and j to take on all relevant values, as shown.

£O Ge OG itr tee <> eet) ad,


CORMMNGH=) 07.8 eh << pie) AEE
- Compute c[i][j]

Let us now describe the computation of that typical element, c[i][j], of


the product matrix, C = A * B. Element c[i][j] is just the scalar product of
the ith row ofA and the jth column of B. Figure 16.13 shows the computation of
C when the square matrices A and B are multiplied together. (A matrix is square
if it has the same number of rows and columns.) For example, in computing the
blue element of C in Fig. 16.14, the blue row ofA (considered as a vector) is
multiplied by the blue column of B (considered as a vector).
The C code that follows accomplishes this vector multiplication of the
ith row ofA and the jth column of B to compute c[i][j].

cfi]{j] 0;
fOr. WheHn0 re Kus on? ik)
Ci eva | Kk) SSD ies

Embedding the computation just shown into the appropriate nested for
loop structure yields the computation of the entire product matrix. This compu-
tation is done in the function mat_prod of Fig. 16.14.

A i B = C
Figure 16.13
iL i L 2 0 1 6 0 0
Multiplying
Matrix A by Z 1 1 -l 0 10 -2 di
Matrix B i ole eat 3 thai -2 0 2
868 Introduction to Numerical Methods

/*

* Multiplies matrices A and B yielding product matrix C


*/
void
mat_prod(double C{[M][P], /* output - M by P product matrix */
const double a[M][N], /* input - M by N matrix */
const double b[N][P]) /* input - N by P matrix */
if
int i, 3, k;
for (i= 0; i<M; ++i){

The += operator is used in Fig. 16.14 not only because it is concise


but
also because it is efficient. An ANSI C implementation must
ensure that in
evaluating the statement

efi) += aftitk) **brki fa);


the computation of c[i][j]'s memory address occurs only once.
In contrast,
the computation could occur twice for the statement

efi}(j] = clil(3] + alil(k] * blk] [3];

Library Functions with Matrices of Varying Sizes


Although function mat_ prod is very useful in a specific program for which
matrix dimensions are known, having fixed sizes for all
three two-dimensional
array parameters would be a serious deficiency if mat_prod
were a library
function. Although there are several ways to circumvent C's
requirement that
16.2 Vectors and Matrices 869

sizes of all dimensions of an array parameter (except for the first size) must be
constants, no single way is so totally straightforward that it has emerged as the
“obvious” choice to a consensus of scientific programmers. We will present an
approach that is compatible with an option available in one well-known mathe-
matical library. First, however, we must have a more detailed understanding of
how a two-dimensional array is stored in memory. C uses a row-major storage
scheme in which the elements of row 0 are stored first, then the elements of row
1, and so on, as shown in Fig. 16.15.
In our matrix library function example, we exploit the fact that a memory
block storing a 3-by-4 matrix is identical to a memory block for a 12-element
one-dimensional array of values of the same data type. Our function will need to
compute explicitly the one-dimensional array subscript corresponding to each of
the matrix elements. In Fig. 16.16, we show how this single subscript corre-
sponding to [i] [Jj] can be computed as a function of i, j, and the number of
columns in the matrix.
In our library version of function mat_prod, we use a macro named
MAT_SUB to represent the subscript calculation just shown. With the definition
of this macro, producing a library version of mat_ prod based on the original is

Matrix nums Memory


Figure 16.15 column O 1 2 3
Row-Maijor
Storage of a nums([0O][0]
TrOWS
Two-Dimen-
nums[O][1]
sional Array
nums[0][2]
cols
nums[0][3]

nums[1][0]

nums[1][1]

nums[1][2]

nums[1][3]

nums[e][0]

4 nums[2][1]
nums[2][2]

nums[2][3]
870 Introduction to Numerical Methods

Two-dimensional nums One-dimensional


Figure 16.16 subscript subscript
One-Dimen-
sional Array [0][0] [0]
Subscripts
Corresponding [0][1] [1]
to Matrix
Elements [0][2] [2]

[0][3] [3]

[1][0] [4] (1x 4+0)

[1][1] [5] (1x 4+ 1)

[1][8] [6] (1x


4+ 2)

[1][3] [7] (1x 4+3)

[3] [0] [8] (2x 4+0)

[2][1] [9] (2x 4+ 1)

[2][2] [10]
(2 x 4+ 2)

[2][3] [11](2
x 4+ 3)

quite simple. This macro's behavior mimics the behavior of a function,


for
using it causes each of its arguments to be evaluated exactly once.
The header file prototype that we show in Fig. 16.17 gives the calling
function's picture of library function mat_prod's array parameters. Since
a
two-dimensional array of type double is actually an array of one-dimensional
arrays and since every array is represented as a pointer, the declaration

double (*c)[]

seems like an acceptable parameter type to match up with a two-dime


nsional
actual argument array of type double. The only problem is that
the header's
prototype does not match the implementation's prototype. As a general
rule, it is
a bad idea to tell anything less than “the truth, the whole truth, and nothing
but
the truth” to your compiler and your linker! However, most C compiler
s and
linkers permit the disparity between the two-dimensional-array view
of the user
and the one-dimensional-array view of the implementation that we show
here. If
your compiler does not, it may allow you to use the implementation's
true pro-
totype in the header file, if you are willing to put up with a warning
pertaining to
the dangers of converting pointer types.
16.2 Vectors and Matrices 871

Figure 16.17 Matrix Library Function That Multiplies Matrices of Any Size

/***** Header file "matlib.h" *****/


/*

* Multiplies m-by-n matrix a and n-by-p matrix b producing


* m-by-p matrix c.
* Pre: n and p must be DECLARED numbers of columns of respective
* matrices; multiplication of matrices with some "empty"
* columns will not give correct results
*/
extern void
mat_prod(double (*e) [], /* output - m-by-p result matrix */
const double (*a)[], /* input - m-by-n matrix */
const double (*b)[], /* input - n-by-p matrix */
int m, int n, int p); /* input - relevant sizes */

/*****k main source file *****/


#include <stdio.h>
#include "matlib.h"

/* Driver to test separately compiled library function mat prod */


int
main(void)
{
double m1[4][3] {iS cavEA AO e201 ad Sh AO 520.
{902820 7.05) (30 Oofor oe Oe
double m2[3][2] CeO? 40), (120, 220 et Ono nO
double ans[4][2];
2h6 gcEn a
te

mMat_prod(ans, ml, m2, 4, 3, 2);

/***** Implementation file matlib.c *****/

/* Subscript computation for (i,j)th element of a ?-by-cols


* matrix when matrix that is stored in row-major order
* is processed as a one-dimensional array */
#define MAT SUB(i, j, cols) ((1) * (cols) + (j))

(continued)
872 Introduction to Numerical Methods

Figure 16.17 (continued)

* Multiplies m-by-n matrix a and n-by-p matrix b producing


* m-by-p matrix c. Uses one-dimensional array parameters plus
* explicit address computation so as to be able to handle any
Values Onhem, np
* Pre: n and p must be DECLARED column dimensions of respective
* matrices; multiplication of matrices with some "empty"
* columns will not give correct results
*/
void
mat_prod(double GlAli, /* output - m-by-p result matrix */
const double a[], /* input - m-by-n matrix */
const double b[], /* input - n-by-p matrix * /
Pie eM, einen, sant op), /* input - relevant sizes */
{
PNET epics

for (1 = 0; i <m; ++i) {


LOD M(Ga= 406 wai epee $44 )iaf
C[MAT_SUB(i, j, p)] = 0;
for (k= 0;' k < n3> +tk)
C[MAT SUB(i, j, p)] += a[MAT_SUB(i, k, n)j *
b[MAT_SUB(k, Ty P)1?
}
}
}

1 1 1
Alawar 3 iL
1 -1 -1

by a vector
16.3 Solving Systems of Linear Equations 873

on the right, then the result is a vector

Now, let us consider another sort of problem. Suppose we know the matrix A
and the vector Y, but we don't know the vector X. We want to know which vec-
tor X can be multiplied on the left by matrix A to produce the vector Y. The
problem is to find the three unknowns, X1,X, and x3, in the equation:

A s = _i
il 1 alt xX) 4

Pg tino) Bealga ees al bee |bs sed ax)


1 -1 -1 x3, -2

To find the values of X1,%, and x3, we must solve a system of three linear
equations in three unknowns. We illustrate this problem by showing how each
component of the vector Y is computed in terms of the matrix A and the vector
of unknowns X. For example, y, is the scalar product of the first row of the
matrix A and the vector X, as in

= * * Ee x
Vy = 4% + Ayn" XQ + A,3 7%, = 4

Replacing the matrix elements aj; with their numerical values, we get the entire
system of three linear equations in three unknowns, x,, x, and x3.

My iH NZ hex 8 Sst
2x4 ap 2a) x, = 9
Lp ekg WSl Weak oe,

We will use Gaussian elimination to solve these equations.

Gaussian Elimination
In Gaussian elimination, we attempt to reduce the original system of n linear
equations to triangular form (also called upper triangular form). In triangular
form, the coefficients below the diagonal in the matrix of coefficients are all 0.
The most useful triangular form for Gaussian elimination is one in which the
rows are scaled so that the diagonal elements are all 1. Figure 16.18 shows the
original system of equations in scaled triangular form. The coefficients above
the diagonal (in color) and the components of the constant vector Y no longer
have their original values.
874 Introduction to Numerical Methods

i 1 i xX] 4
Figure 16.18
0 1 -1 * xo = il
Original System
0 0 Hl 5 1
of Equations in
Scaled
Triangular Form

The three equations that correspond to Fig. 16.18 are shown here:

Xj + X% + xX, = 4

x, = |

This system can easily be solved for X1, Xz, and x3 by solving the last equation
TOE We(1.€2, x3 18 1), substituting this value in the next to last equation and solvy-
ing for xX» (i.e., x, is 2), and substituting these values in the first equation and
solving for x, (i.e., x, is 1). This process is called back substitution. The algo-
rithm for Gaussian elimination follows.

Algorithm for Gaussian Elimination


1. Transform the original system into scaled triangular form.
2. Solve for the x; by back substitution.

The Augmented Matrix


Before we can proceed, we must decide on appropriate data structures for rep-
resenting a system of n linear equations in n unknowns. One widely used method
of representation for such systems is the augmented matrix. This particular rep-
resentation allows for concise coding of both triangularization and back substi-
tution.
Figure 16.19 shows the form of an augmented matrix for a system of n lin-
ear equations in n unknowns. The augmented matrix will be represented by a
two-dimensional array, Aug. Note that the augmented matrix has n rows and
n +
1 columns. The last column of the augmented matrix (shown in color) contains
the constant vector Y (e.g., aug, 41S y,). The matrix of coefficients A is stored
in
the rest of the columns of the augmented matrix (Cig aug,; 1s a, ;). Note
that

Gy G2" "A137 yy 1 1 1 4
Figure 16.19
Oy Vay! Gan ahy, Das 1 9
Original
Augmented
G3 1 "KARO aay Try, Ine Waal AYPEIS S20
Matrix Aug (a) General form (b) Our example
16.3 Solving Systems of Linear Equations 875

Figure 16.20
lve Bue) opuiay Lad Fang le 4
Triangularized Odi dscls Vue@asie 2! Oia} kay’ gl
0 0 1 y3, 0 0 1 1
and Scaled
Augmented
(a) General form (b) Our example
Matrix

the vector of unknowns is nowhere to be seen. When a system of linear equa-


tions is represented as an augmented matrix, the unknowns are implicit.
Our first goal is to scale and triangularize the coefficients in the augment-
ed matrix—that is, to reduce the matrix to a form in which aug, p> WUE 9, and
aug, are 1 and AUS> 1, AUS 1, and aug, 9 are Zero, as shown in Fig. 16.20. All
other ae shown in color in Fig. 16. 20(a), are written as symbols such as ai;
and y,'. The primes are used to emphasize that these values are not the same as
the ones in the original system of equations.

Triangularizing and Scaling the Augmented Matrix


Let us now turn our attention to the process of triangularization and scaling,
which transforms the original system of equations into a new system in scaled
upper triangular form. The rules of linear algebra guarantee that this new system
will have the same solution as the original system if we confine ourselves to the
following operations on the augmented matrix Aug:
1. Multiply any row of Aug by a nonzero number.
2. Add to any row of Aug a multiple of any other row.
3. Swap any two rows.

If the system has a unique solution, we can get the system into the desired form
by using these three operations. (If our system does not have a unique solution,
our algorithm will detect this.)
We triangularize the augmented matrix by systematically moving down the
diagonal, starting at aug, ,. When we are working with a particular diagonal
element, we call that element the pivot. When aug, pis the pivot, we have two
goals:

e scale the pivot row, so that the pivot will take the desired value 1
° set all coefficients in the column below the pivot to zero—that is, give the
elements AUS 541 ,p» UB n42,p> >>>» WUBy »the value 0.

The first goal is achieved by multiplying the pivot row (e.g., row p) by an
appropriate constant (i.e., I/aug, ,); this is an application of operation 1. The
second goal is achieved by applying operation 2 to the rows beneath the pivot
row.
876 Introduction to Numerical Methods

Experts recommend that to minimize computational round-off errors dur-


ing the triangularization process, you should always place the largest possible
coefficient (in absolute value) in the pivoting position. When you work with the
pth pivot, you should examine all the coefficients in the column beneath the
pivot to find the coefficient that has the largest absolute value. The pivot row
and the row containing that largest coefficient should be swapped. In no way is
the solution of the system of equations changed, because swapping rows is one
of the three permissible operations (operation 3). The process of switching the
current row with the one containing the maximum pivot is called pivoting.
What if we encounter a maximum pivot element whose value is zero?
This can happen only when all values beneath the pivot element are also zero. If
a nonzero value cannot be found in the column beneath the pivot, the given sys-
tem of equations does not have a unique solution.
The operations just discussed are summarized in the algorithm that fol-
lows. The local variable ok indicates whether the system of equations has a
solution.

Local Variables
int p /* the current row */
int ok /* a flag indicating whether the system has a
unique solution */

Algorithm for Function gauss


1. Initially assume the system has a unique solution.
2. Initialize p to the subscript of the initial row.
3. Repeat as long as there is a solution possible and p < subscript of final
row
4. Pivot using the maximum pivot strategy.
5.. if a solution is still possible
6. Scale the pivot row.
7. Eliminate the coefficients beneath the pivot.
8. Go on to the next row (++p)
9. if last coefficient is zero
10. No unique solution.
else if there is a solution
11. Scale last row.
Function gauss is shown in Fig. 16.21. In the statements that perform the
scaling of the pivot row (Step 6), first the reciprocal of the pivot value is saved.
Then, the pivot element is set to 1, and the elements to the right of the pivot (in
columns p + 1 through N) are multiplied by the pivot's reciprocal. The row
elements to the left of the pivot element require no change because they are all
16.3 Solving Systems of Linear Equations 877

Figure 16.21 Function gauss

#define FALSE 0
#define TRUE 1
#define N 3

/*

* Triangularizes the augmented matrix aug. If no unique solution exists,


* sends back FALSE through sol_existsp
*/
void
gauss(double aug[N][Nt+1], /* input/output -augmented matrix representing
system of N equations */
int *sol_existsp) /* output - flag indicating whether system has a
unique solution */
sf
int j, k, p;
double piv_recip, /* reciprocal of pivot */
xmult;

/* System is assumed non-singular; moves down the diagonal */


*sol_existsp = TRUE;

for (p= 0; *sol_existsp && p< (N- 1); ++p) {

/* Pivots with respect to the pth row and the pth column */
pivot(aug, p, sol _existsp);
if (*sol_existsp) {
/* Scales pivot row */
piv_recip = 1.0 / aug[p][p];
aug[p][p] = 1.0;
LOR KS pier le ke Sy No ee Stk)
aug[p][k] *= piv_recip;

/* Eliminates coefficients beneath pivot */


for, ((js=opet is. Jes NN? ae
xmult = -aug[j][P];
aug[j][(p] = 0;
former =sprt ls kes N liew ee+k)
aug[Jj][k] += xmult * aug[p][k];

(continued)
878 Introduction to Numerical Methods

Figure 16.21 (continued)

/* If last coefficient is zero, there is no unique solution */


if (aug[N-1][N-1] == 0) {
*sol_existsp = FALSE;
} else if (*sol_existsp) { /* Scales last row */
piv_recip = 1.0 / aug[N-1][N-1];
aug[N-1][N-1] = 1.0;
aug[N-1][(N] *= piv_recip;

zeros. The test in Step 9 detects the situation where scaling the last row will
cause division by zero. In Step 11, only the elements in the last two columns
need to be changed.
In Step 7, each row j beneath the pivot (j = p+1, p+2,..., N-1) is mod-
ified so that the element in aug[j][p] becomes zero. This modification is
done by multiplying the pivot row (row p) by -aug[j][p] (saved in xmult)
and then adding the pivot row to row j.
Step 4 of gauss (the pivot step) is performed by function pivot. The
data requirements and algorithm for pivot follow; the function shown in Fig.
16.22 assumes access to function fabs from the math library.

Figure 16.22 Function pivot

* Performs pivoting with respect to the pth row and the pth column
* If no non-zero pivot can be found, FALSE is sent back through piv_foundp
*/
void
pivot(double aug[N][N+1], /* input/output - augmented matrix */
int By /* input - current row */
Lie *piv_foundp) /* output - whether or not non-zero pivot
found */

(continued)
16.3 Solving Systems of Linear Equations 879

{
double xmax, xtemp;
int j, k, max_row;

/* Finds maximum pivot */


xmax = fabs(aug[p][p]);
max _row = p;
Lora j= pris Seca teak
if (fabs(aug[j][p]) > xmax) {
xmax = fabs(aug[j][p]);
max row = Jj;
}
a

/* Swaps rows if non-zero pivot was found */


if (xmax == 0) {
*piv_foundp = FALSE;
} else {
*piv_foundp = TRUE;
if (max_row != p) { /* swap rows */
for yee gars eck e— Nels Ark) ef
xtemp = aug[p][k];
aug[p][k] = aug[max_row][k];
aug[max_row][k] = xtemp;
}
}
i‘ \

Data Requirements
Input Parameters
intrp /* the current row */

Input/Output Parameter
double aug[N][{N+1] /* augmented matrix */

Output Parameter
int piv_foundp /* flag indicating whether a
non-zero pivot was found */
880 Introduction to Numerical Methods

Local Variables
double xmax /* largest absolute value in column p*/
int max_row /* row containing the maximum value */

Algorithm for pivot


1. Starting at row p, find the row, max_row, whose element in column p has
the largest absolute value.
2. if the largest absolute value is zero
3. Set piv_found to false.
else if max_row is not row p
4. Swap rows p and max_ row.

Back Substitution
We can now derive the C code for back substitution in terms of the augmented
matrix aug. We will also need an array x to represent the vector of unknowns.
The scaled and triangularized augmented matrix for our example system, shown
in Fig. 16.20(b), is rewritten here on the left with the corresponding storage
locations in array aug shown on the right (e.g., the contents of aug[0][3]
is 4).

1a Ee SE aug[0][(0] aug[0][1] aug[0][2] aug[0][3]


Oy base el aug[1](0] aug{1][1] aug[1][{2] aug[1][3]
Oe sOnne Lek aug[2][0] aug[2][1] aug[2][2] aug[2][3]

The part of the matrix that is used in back substitution is shown in color.
Applying the principle of back substitution to the diagram just shown, we can
observe from the last row that

x[2] = aug[2][3] =1

Next, we see from row number 1 that

x[1] = aug[1][3] - (aug[1](2] * x[2])) =1- (-1*1) =2


Finally, we see from row 0 that

x[0] = aug[0][3] - (aug[0][1] * x[1] + aug[0][2] * x[2])


241 * 1p
=4-( 1*Stee (sjea

These observations lead to the following algorithm for back substitution.


16.3 Solving Systems of Linear Equations 881

Algorithm for Back Substitution Using the Revised Augmented


Matrix (with Zero-Based Subscripts)
1. Set the last element of the vector of unknowns to be the last element of the
augmented matrix.
2. For each i =N-2,...,1, 0 in turn, compute
N-1

Siile= 2ug(i
N= tee a aug iiliii * xia
j=i

This algorithm is implemented as function back_sub shown in Fig. 16.23.


Figure 16.24 shows a main function that tests functions gauss and
back_sub on one system of linear equations. The declaration of the augment-
ed matrix aug initializes it to the matrix of Fig. 16.19; the sample run dis-
plays the solution vector x.

Figure 16.23 Function back_sub

* Performs back substitution to compute a solution vector to a system of


* linear equations represented by the augmented matrix aug.
* Pre: the coefficient portion of the augmented matrix has been
* triangularized, and its diagonal values are all 1.
*/
void
back _sub(const double aug[{[N][N+1], /* input - scaled, triangularized
augmented matrix */
double x[N]) /* output - solution vector */
{
double sum;
int BIg 8

x(N - 1] =‘aug{N - DL) (N];


for. (1 = N’=02+io=" 034 =-1) -{
sum 0;
fOGee (sss lee eecaites 2ty.)
sum += aug[i][j] * x[j];
x[i] = aug[i][N] - sum;
882 Introduction to Numerical Methods

/*

* Solves a system of linear equations using Gaussian elimination and back


* substitution.
*/
#define N 3 /* size of coefficient matrix; augmented matrix is N by Nt1l */

int
main(void)
{
double aug[N][N+1] = {{ 1.0, 1.0, 1.0, 4.0}, /* augmented matrix */
Er2E0 U3 NO Pa OPS One
{Y.0;" =1.. 0, <= lad 2s0Ny
x(N]; /* solution vector */
int sol_exists; /* flag indicating whether or not a solution can be
found *if
/* Calls gauss to scale and triangularize coefficient matrix within
aug
*/
gauss(aug, &SOl_ exists);

/* Finds and displays solution if one exists */


if (sol_exists) {
back_sub(aug, x);
printf("The values of x are: $10. 2£210 22210228
\n) bx [Oy rea,
x[2]);
} else {
printf£("No unique solution\n");
}
return (0);
}

The values of x are: 1.00 2.00 1.00

lll-Conditioned Systems
Unfortunately, not every system of n equations in n unknowns has a unique
solution. The system may have no solution, in which case it is said to be singu-
lar; or it may have an infinite number of solutions, in which case it
is said to be
16.4 Linear Regression and Correlation 883

degenerate. The triangularization process will fail if the given system does not
have a unique solution. A more serious danger is that our system of equations
may be ill conditioned. In the two-dimensional case (n is 2), this occurs when
the two lines whose intersection we are trying to find are nearly parallel.
Another problem that arises in practice is that computational round-off
errors can be significant when n is large. The larger n is, the more multiplying
gets done in triangularizing the matrix. In large systems, these accumulating
round-off errors may make the results obtained from Gaussian elimination use-
less. In these situations, iterative methods, such as Gauss—Seidel, are recom-
mended. The Gauss-Seidel method is described at the end of this chapter in
Programming Project 11.

16.4 LINEAR REGRESSION AND CORRELATION

In this section, we will discuss how two important ideas from statistics—linear
regression and correlation—are translated into C code. Our discussion will be
rather sketchy because we do not have the space to develop the full rationale
behind some of the formulas or to elaborate on certain issues. The interested
reader is referred to the references at the end of the chapter.

The Concept of Regression


Regression is normally used for prediction. Suppose X and Y are two measurable
quantities (or observables). For example, X might be the annual rainfall and Y
might be annual tree ring growth, or X might be a professor's intelligence quo-
tient and Y might be his or her salary. The concept of regression is to determine
a relationship

Y= f(x)

on the basis of a finite sample ofX and Y scores. This relationship is then used
to predict Y values for given X values.
Regression might be linear or nonlinear, depending on whether the rela-
tionship, f(X), is linear. Our discussion will be confined to linear regression,
where the relationship f(X) is linear. The linear relationship is then called the
best fit line through the sample data. The line is of the form

Ye=VAT
Bi tox

To determine the best fit line through sample data, we must make assump-
tions about X and Y. Different assumptions will yield different solutions for
884 Introduction to Numerical Methods

Figure 16.25
Distribution of
Y Values for
Three X Values

the coefficients A and B. Because we assume that Y and not X is the dependent
variable, we can measure X exactly; the Y values for a given X are assumed to be
normally distributed about some mean.
Figure 16.25 shows three X values and the distributions of Y values for the
populations they determine. The mean of the Y values for a given X value is
denoted by My\x- We will denote the standard deviation of the Y values for a
given X by Syix:
Figure 16.26 shows the distribution of Y values for the population associ-
ated with a given X value. The mean, My, is a measure of central tendency for
the Y values for this population, and the standard deviation, Sy,y, is a measure of
the dispersion (or spread) of the Y values for this population. For example, Fig.
16.26 might represent the distribution of intelligence quotients for professors
with a given income. The shaded region indicates all professors whose intelli-
gence quotient is within one standard deviation of the mean for this population.
In linear regression, we assume that the means My\y lie on some line

My\x =Qa+B*X

The problem is that we cannot determine this line exactly because we have only a
finite amount of data relating to just some of the X values. On the basis of this
finite sample, we must compute an estimate for the above linear relationship.
That estimate is called the best fit line through the sample data. Its equation is

Va= AG Biee eX

As you can see, A is a Statistical estimate for a, and B is a statistical estimate for
B. Depending on the statistical properties of our finite sample, the estimates A
16.4 Linear Regression and Correlation 885

Normalized
Figure 16.26 frequency
Distribution of of
Y Values for a Y
Given X with
Mean My, and
Standard
Deviation Sy,

and B will be more or less reliable. (We will not discuss further the issue
of how
confident we can be in the predictions we make with the computed coefficients
A and B. There are, however, formulas for determining this confidence.)
Figure 16.27 shows a scatter plot for some sample data. A scatter plot rep-
resents each observed pair of X—Y values ina sample as a point on a coordinat
e
grid. Superimposed over the scatter plot are lines representing the actual linear
relationship

Myy=0+B8*X
and the best fit line,

Y=A+B*X

based on the data available (eleven data points). As shown in Fig. 16.27, the best
fit line (in color) is a statistical estimate for the relationship based on a finite
sample of data.

Figure 16.27
Scatter Plot
Showing Best
Fit and Actual
Lines
886 Introduction to Numerical Methods

Computing the Linear Regression Coefficients, A and B


The values A and B are called linear regression coefficients. How are these
coefficients computed on the basis of a finite sample? Suppose we have a col-
lection of n data points. Let us denote the ith data point by the pair (X,, Y,). That
is, Y; was observed in conjunction with X,. The best fit line is defined as the
unique line that minimizes the sum of the squares of the distances d, shown in
Fig. 16.28. Each d; is the distance, measured parallel to the Y axis, between the
point (X;,, Y;) and the best fit line. Thus,

d,=Y,-(B* X,+A)

One of the marvels of calculus is that we can discuss the distance d; to the
best fit line when it is the best fit line that we are trying to determine. The sum
that we are trying to minimize,

Ms G7 = LL BGA) = FG B)
i i 1!

is a function of the linear regression coefficients. We have called this function


F(A, B). By computing the partial derivatives of F(A, B) with respect to A and B
and setting these partial derivatives equal to 0, we get two linear equations in
two unknowns A and B: t

8F(A, B) _ 9
7
bF(A,B) _ 9
SB

Figure 16.28
Best Fit Line
Minimizes the
Sum of the
Squares of the
Distances d;
16.4 Linear Regression and Correlation 887

When wesolve the resulting system of equations, we get

De, 1)
peel
B= =

Da AOL
i=1

Ave VRB ox
where X and Y
denote the means of the observed X and Y values, respectively.
The C function in Fig. 16.29 returns the linear regression coefficients A
and B for a given collection of data points, (X;, Y,;). Variables x_mean and
y_mean denote the means X and Y, respectively. Variable sum_xy is

esi
PUTO eC
Weng
and sum_xds is

Day (Xdk
esl

Variable x_diff is introduced so that (x[i] - X_mean) need not be com-


puted twice for every execution of the for loop.

Figure 16.29 Computing the Linear Regression Coefficients A and B

Computes the linear regression coefficients a and b for the n data points
* (x[{i], yl[il), given x_mean and y mean.
*/
void
linear _regress(const double x[], /* input - arrays of (x,y) coordinates
const double y[], /* of n data points */
ant hi, /* input - number of data points */
double x mean, /* input - mean of x values */
double y_mean, /* input - mean of y values * /
double *ap, /* output - the linear regression * /
double *bp) [* coefficients */

double ‘sum xy, sum xds;,, x dift*


ajene sie

(continued)
888 Introduction to Numerical Methods

Figure 16.29 (continued)

sum_xy = 0
sum_xds = 7
for (i = SW So VESarm ahEh
x diff = x[i] - x_mean;
sum_xy += x diff * (y[i] - y_mean);
sum xds += x_ diff * x diff;

*bp
sum_xy / sum_xds;
* ap
y_mean - *bp * x_mean;

Correlation
Correlation differs from regression in that it measures the strength of a rela-
tionship rather than the actual parameters of that relationship. In computing
the correlation between X and Y, we assume that both variables are random. In
other words, we do not assume that the X values or Y values can be determined
exactly.
Correlation is said to be high if there is a strong linear relationship
between X and Y values. Otherwise, correlation is said to be low. Figure 16.30
shows four scatter plots along with a qualitative assessment of the correlation
between X and Y. Although there is obviously a quadratic relationship between X
and Y values in Fig. 16.30(d), the correlation is low because correlation is a
measure of the strength of linear relationships.
A formula for a statistic r, which is a measure of correlation, follows.
This statistic is called the correlation coefficient. This statistic will be present-
ed in terms of another statistic called z-scores. Z-scores are measured for the X
and Y values independently. Again we assume a sample of n data points, (X;, Y;).
The z-score for a given X; is its distance from the sample mean X, measured in
terms of the standard deviation Sy:

ae
Why =
I Sy

An unbiased estimate of the standard deviation for our sample is


16.4 Linear Regression and Correlation 889

Figure 16.30
Scatter Plots
with
Correlations

(a) (b)
High correlation

(c) (d)
Low correlation Low correlation

The z-score for a given Y, value is

Zy. =
Y¥,-¥
:
i Sy

where Yis the mean and Sy is the standard deviation for the observed Y values.
The correlation coefficient r is computed in terms of the z-scores as fol-
lows:

(PS Ss, 2x, * Ly,


i=l n—1

The values for r range between —1 and 1. If [7 = 1, then the given data points all
lie on a line. If r = 0, then the X and Y values are said to be uncorrelated, mean-
ing that absolutely no linear relationship is observed between X and Y values.
A C function that computes the correlation coefficient r is given in Fig.
16.31. The arguments provided to the function are type double arrays x and y,
which store the data points, and the int variable n, which denotes the number
of data points in the sample. The variables x_mean and y_mean represent
890 Introduction to Numerical Methods

Figure 16.31 Function for Computing the Correlation Coefficient

Computes the correlation coefficient, r, using z-scores for the n data


* points (x[i], y[i]})
*/
double
r(const double x[], /* input - (x, y) coordinates of n */
const double y[], /* data points */
int n, /* input - number of data points */
double x_mean, /* input - means of x */
double y_mean, /* and y values */
double Sx, /* input - standard deviations of */
double sy) /* x and y values */
{
double ps, zxi, zyi;
ooh ee ale

ps
= 0;
forma) =305)) aa i<pnee ts.) df
zxi = (x[i] - x_mean) / sx;
Zvi =" (Viaje yomean) / sy;
DSot= 2x1 zyi:
}

return (ps / (n - 1));


}

the means, and the variables sx and sy represent the standard deviations. The z-
score for X, is denoted by zxi; zyi denotes the z-score for Y;. The sum of the
products of zxi and zyi is accumulated in ps.

16.5 NUMERICAL INTEGRATION

The definite integral


b
J)fe) de
denotes the area in the Euclidean plane bounded by the z-axis, the line z = a, the
line z = b, and the curve y = f(z). In Fig. 16.32, this area is shown for two dif-
16.5 Numerical Integration 891

Figure 16.32
Geometric
Meaning of the
Definite
Integral

(a) (b)

ferent functions. Note that if the curve y = f(z) dips below the z-axis, the part of
the curve below the z-axis makes a negative contribution to the integral.
There are two general classes of solutions to the problem of evaluating
definite integrals by computer. Both classes involve evaluating the function
f(z), which we are integrating, at a collection of sample points. The quadrature
methods determine the sampling points by analysis of the function. Simpson's
rule and members of its class choose the sampling points independently of the
function being integrated. In practice, Simpson's rule is an effective and efficient
method for doing numerical integration despite its simplicity.

Derivation of Simpson's Rule


Simpson's rule is derived from the following considerations. Suppose we want to
integrate f(z) over the interval [a, b], as shown in Fig. 16.33. Clearly, if we
divide the interval [a, b] into an even number, n, of intervals as shown in Fig.
16.33, then

fkg Made = byerieAHO (16.1)


Step2

Our notation
n

Dy
i
Step2

means that i takes on the values 2, 4, 6,..., n—2, n. (Don't forget that n is
assumed to be even.)
892 Introduction to Numerical Methods

Figure 16.33
Dividing the
Interval of
Integration into
Typical
Subintervals subinterval
Cera

Equation (16.1) shows that the original integral equals the sum of the S
integrals

IL capaleier
Gf 2

These yy integrals are computed over the subintervals [Z;_>, Z,]. Note that Zj-1 18
the midpoint of the subinterval [z;_5, 2;]. Furthermore, the subscript i-1 of a
midpoint is always odd. The subscripts i-2 and i at the endpoints are always
even.
A typical subinterval [z, 5, z,] and the integral we are computing on that
subinterval are shown in Fig. 16.34. The three points

point 1 = (z;_5, f(z;_»))


point i (ane iZ4))
point 3 = (z,, f(z,))
determine a unique quadratic polynomial. That is, there is a unique polynomial,

PQer zest

that passes through the three given points. This polynomial is called an inter-
polating polynomial. In Fig. 16.34, the polynomial P(z) is drawn as a black
curve.
The fact that the polynomial P,(z) passes through three known points gives
us enough information to solve for the coefficients r, s, and t. We know that
P(z) = f(z) when z is Zj-2> Z;_1, OF Z;. This information yields three equations
in
the three unknowns, r, s, and f. This system can be solved for r, s, and t (the
solution is not shown).
16.5 Numerical Integration 893

; A(z)
Figure 16.34
Interpolating Point 2
Polynomial P,(z) Point 1
|

Simpson's rule approximates

aj
Jo%j-2 f@) de
as

cay
) Pi@) dz
Zi-2

When we substitute the coefficients 7, 5, and t in P,(z) and integrate, we get

Lt
Wy Pi(2) az= 2 (fZj_2) a 4 f(Zi-1) + f(z)) (16.2)

where h = bau is the size of one of the n intervals. Equation 16.2 approximates
the integral of the original function f(z) over the interval [Z;_>, Z;] in terms of the
values off(z) at the endpoints and the midpoint of the interval.
Substituting Equation 16.2 into Equation (16.1), we get Simpson's rule:

b n j
J f@d =X J" pod
Step2

Las ie =peng
= <i-2
Step 2

= 3 & (Mea) + 4M) + fe)


Step 2
894 Introduction to Numerical Methods

Recalling that z) = a and Z, = b, we can rewrite the approximate integral as


b
J),fe) de= XR + (fa) +4 flay) + flee) + flea) + 4fles) + flea) + flea)
Pa fas) toe + f(z) +A FZ,1)eb) (16.3)
n n—2
3 Fla) +fb)+4 2X f@i+2 ZX fe)
Step 2, Step 2

C Implementation of Simpson's Rule


Next we will write a C function to implement Simpson's rule as expressed in
Equation 16.3. Let us accumulate the sum
n

x f@i-)
i= 2
Step 2

in the variable sum_odd and the sum


D2
2, fai)
Step 2

in the variable sum_even. The approximate integral is then

(h/s.0') * (€(a)a+ £(b) + 4 e%sum odd 4+ 2 * sum_even)

In our function, f is represented by the function pointer parameter fp.


Function simpson in Fig. 16.35 computes an approximate value for
b
J fle) de
using Simpson's rule for a given value of a, b, and n. For computational
pur-
poses, we rewrite the expressions for sum_odd and Sum_even just given
as
follows:

n
sum_odd = x f(z-1) = fla +h) + fla + 3h) +--+ +f(at+ (n—Wh)
Step2

n-2
sum_even = 2, F(zi) = fla + 2h) + flat 4h) +--+ + f(a + (n—2)h)
Step 2
16.5 Numerical Integration 895

Figure 16.35 Computing a Definite Integral Using Simpson’s Rule

Computes an approximation of the definite integral of function fp


* from a to b using Simpson's rule with n intervals
*/
double
simpson(double (*fp)(), /* input - function to integrate */
double a, /* input - end points of the */
double b, /* integration region [a, b] * /
int n) /* input - number of intervals */
{
double h, sum_odd, sum even;
shige, Sue

/* Compute interval size */


h = (b- a) /n;

/* Compute sum_odd */
sum_odd = 0;
POrSH (1 5= 2s Chic =( ne Cire: 2.)
sum_odd- += fp(a.t+ (i - 1) * h);
}

/* Compute sum_even * /
sum_even = 0;
for: (1 °=-2;" 1-<]in — 23 4 S22) 4
sum_even += fp(a + i * h);
}

/* Return approximation */
Feturn. (h-/ 3-0 * (fp(a). + fp(b) +.4.0° * ‘sumseddia260 * sum even) )>
}

Applying Simpson's Rule for a Sequence of Values of n


As n increases, the approximate integral becomes closer to the actual integral.
However, when we use function simpson, there is a point of diminishing
returns because of computational round-off errors. If you try to compute
the integral for a sequence of increasing values of n, for example, n = 2, 4, 8, 16,
32,..., that point of diminishing returns will become readily apparent. Before
896 Introduction to Numerical Methods

that point, successive approximations agree to more and more decimal places;
beyond that point, the agreement becomes less and less.
In practice, we apply Simpson's rule for a sequence of n values to identify
either the point of diminishing returns or the point at which our result is correct
to the desired number of decimal places. Usually we choose a sequence of n val-
ues in which n doubles for each iteration of Simpson's rule. A general rule of
thumb is that if the integral we get when n = 2k and the integral we get when
n = k agree to d decimal places, then the integral we got for n = 2k is correct to
d+ 1 decimal places.

Self-Check
EXERCISES FOR
SECTION 16.5 1. Predict the output of the following program, that calls simpson.

#include <stdio.h>
#include <math.h>

extern double
simpson(double (*fp)(), double a, double b, int n);

/* A function with a simple indefinite integral */


double
£f(double x)
{
return, (1.0 /% x)e
}

/* A function with no simple indefinite integral */


double
g(double x)
{
Beturns, (Sin(x)/ 3)s
}

int
main(void)
<
printf£("\nThe definite integral from 1 to 2 ");
printf("of f(x) dx is approximately %.4f\n",
Simpson (f490hs0e 12% One?)4
16.6 Using Numerical Methods Libraries 897

printf("\nThe definite integral from 1 to 3 ");


printf("of g(x)dx is approximately %.4f\n",
simpson(g, W007 320; 241)
je

return (0);
}

2. Function f in Exercise | is a function for which one would not normally


approximate a definite integral. It is quite straightforward to compute the def-
inite integral using the fundamental theorem of the calculus

J) fo) dx = F(b)- Fla)


b

where F is an indefinite integral of f (in this case, F(x) = In(x)). Calculate the
value of the definite integral
2
J, fe) ax
for function f in Exercise 1, and compare this value to the approximation that
you predicted.

Programming
1. Write a program that uses function simpson with a variety of values of n to
approximate
3
J, fx) dx
for function £ defined in Self-Check Exercise 1. Try 4, 8, 16, 32, 64, 128,
and 256 as values of n, and print a table of these approximations.

16.6 USING NUMERICAL METHODS LIBRARIES


So far, we have introduced a number of functions for performing highly special-
ized numerical operations. Some functions came with a warning that although
they would not work in all cases, other techniques might work. We also saw that
it would be fairly time-consuming for each individual engineer or scientist to
implement his or her own library of functions. For all these reasons, several
libraries of numerical functions have been developed for use by engineers, sci-
entists, and others who frequently perform numerical operations on data.
898 Introduction to Numerical Methods

The International Mathematics and Statistics Library (IMSL) is a collec-


tion of numerical routines that has been widely used by FORTRAN program-
mers for many years. A C version of the IMSL is now available as well. The
IMSL comprises coordinated libraries of functions for general applied mathe-
matics, statistics, and other special algorithms. Before you use any commercial
library, first consult the manual for that library. The manual provides docu-
mentation for each function along with instructions for using that function and
an example of its use. You will need to write a C program that stores the data
to
be processed and correctly calls the function you select.

EXAMPLE 16> One IMSL function is ims 1_d_lin_sol_gen, which solves a system of
lin-
ear algebraic equations. Rather than using an augmented matrix as we
did in
Section 16.3, imsl_d_lin_sol_gen uses separate input parameters for
the
linear system's n x n coefficient matrix and the length n vector that represent
s
the system's right-hand side (the final column of our augmented matrix).
Figure
16.36 shows a simplified version of part of the documentation
of
ims1_d_lin_sol_gen. You will notice that in the documentation and
our
example, the n x n coefficient matrix argument is actually provided as
a one-

imsl_d_lin_sol_
gen
PURPOSE: Solve a real general system of linear equations Ax = b, returning the solution
ina
dynamically allocated array.
REQUIRED DIRECTIVE: #include <imsl.h>
USAGESx y=) imsl
‘ling soljigen
id(n, a, ib; -0:)%.
ARGUMENTS:
inten (input) number of equations
double a[] (input) size nx n array containing the coefficient matrix of the linear
system
double b[] (input) size n array containing the right-hand side of the linear system
RETURN VALUE: a double * pointer to a dynamically allocated array containing
the solu-
tion to the linear system; a NULL return value indicates that no solution was computed
Function ims1_d_lin_sol_gen accepts a number of optional arguments. The zero
shown
as the final argument signals the end of the argument list. If we prefer to have the
result vector
stored in a size n array that we provide as an output argument, the appropriate function
call is
imsl_d_lin_sol_gen(n, a, b, IMSL RETURN USER, x, 0);
16.6 Using Numerical Methods Libraries 899

dimensional array. However, use of an n X n two-dimensional array would also


produce correct results, although one would probably receive compiler warnings
regarding suspicious pointer conversions. ess

In Fig. 16.37, we use ims1_d_lin_sol_gen to solve the linear system


that was our primary example in Section 16.3.

Figure 16.37 Using Function imsl_d_lin_sol_gen

/*
* Uses IMSL function imsl_d_lin_sol_gen to solve a system of n linear
* equations in n unknowns.
*/

#include <imsl.h>
#define N 3 /* size of coefficient matrix */

nt
main(void)
{
double coeff[N * N] = {1.0, 1.0, 1.0, /* coefficient matrix */
Dai es OveY BO
POMEL ICG? 251 VON,
rt Jsidetny =—{4-07," 920; =250} /* right-hand side of system
*/
double *x; /* address of solution vector */
int i:

x = imsl_d_lin_sol_gen(N, coeff, rt_side, 0);

T£ (x {= NULL)
printt("The values of x are: $10.2£%10.2£%10.2f\n",-x[0), x({11,
x[2]);
} else {
printf("No unique solution\n");
}
return (0);
}

The values of x are: 1.00 2.00 0,0


900 Introduction to Numerical Methods

16.7 COMMON PROGRAMMING ERRORS

The most common errors in programs that perform the numerical techniques dis-
cussed in this chapter are caused by inaccuracy in mathematical computations. A
small round-off error is often magnified by the repeated computations required
in many of these algorithms. This loss of accuracy can Cause programs to exe-
cute forever instead of converging to a result. If the programs do terminate,
the
results may be so inaccurate that they would be useless.
Since these programs rely heavily on the use of functions, be very careful
when writing argument lists. Make sure each argument list has the correct
num-
ber of arguments and that the arguments are not misspelled or placed in
the
wrong position. Remember that when you use a matrix as a function parameter
,
all dimensions provided in the prototype's declaration of the parameter must
be
constants. Only the number of rows can be omitted. In order for a matrix
argu-
ment m to have its elements correctly accessed using a reference such
as
m[i][j], its declared dimensions must match exactly those specified
for the
corresponding function parameter.

CHAPTER REVIEW

A number of different numerical techniques were discussed in this


chapter,
including manipulating vectors and matrices, finding roots (bisectio
n and
Newton's method), solving simultaneous equations (Gaussian
elimination), lin-
ear regression and correlation, and numerical integration (Simpson
's rule). We
have just scratched the surface in our discussion of numerical methods.
A small
sample of the dozens of good references on the subject are listed.

REFERENCES ON NUMERICAL METHODS

John, Peter W.M. Statistical Methods in Engineering and Quality


Assurance.
New York: Wiley, 1990.
Nonweiler, T.R. F. Computational Mathematics: An Introduction
to Numerical
Approximation. New York: Halsted Press, 1984.
Press, William H., et al. Numerical Recipes in C: The
Art of Scientific
Computing. New York: Cambridge University Press, 1988.
User's Manual: IMSL C/Math/Library™, Version 1.0. Houston: IMSL, Inc..,
1991.
Review Questions 901

QUICK-CHECK EXERCISES
. Name two methods for finding roots of a function.
. List the three criteria for converging to a root.
. What condition must hold to allow multiplication of matrix A by matrix B?
. C's storage scheme for multidimensional arrays is called
_________— because, for a matrix, it stores all the elements of row 0 first,
then all the elements of row 1, and so on.
. What property must a coefficient matrix have to be in triangular form?
. A matrix composed of the coefficients of a system of linear equations and of
the vector of constants representing the right-hand side of the system is
called a(n)
. A regression line is the line through sample data.
Itis usedto __________ Y values for given X values.

ANSWERS TO QUICK-CHECK EXERCISES


. Newton's method and the bisection method
. Assuming that epsilon is a very small value, rt is the actual root, x; is the cur-
rent guess for a root, and Xj_1 Was the previous guess,
(1) |rt— i| < epsilon
(2) |.f(~) |< epsilon
(3) |x; - x;-1| < epsilon
. The two matrices must be conformable (i.e., matrix A must have the same
number of columns as matrix B has rows).
. row major
. All elements below the diagonal must be zeros.
. augmented matrix
A » best fit; predict
NYDN

REVIEW QUESTIONS
. If f(x,) < 0 and f(x,) < 0, what can you conclude about the interval [x,, x]
with regard to roots of functionf?
. Why is it an especially good idea to use C's compound assignment operators
whenever possible if the target of the operation is an array element?
902 Introduction to Numerical Methods

3. Write the augmented matrix corresponding to the following system of equa-


tions:

2X4 + 2X5 — 4x3 =e 0)


Ki, — 44, + %, = 4
iy + 3X5 - 2x3 = -5

4. Scale and triangularize the augmented matrix for Question 1.


5. What three operations can be performed ona system of linear equations?

PROGRAMMING PROJECTS

1. The polynomial

Ax? = 12.3x2 —x + 16.2

has two zeros between 1 and 2. Use function bisect to find them. You
might want to tabulate the function values first to isolate the zeros in two
intervals. Use a tolerance of 0.0005.
. The polynomial

8x3 + 2x2 — 5x41

has three real zeros.


a. First, plot the function to determine appropriate intervals for the root-
finding program.
b. Then, use the function bisect to find each zero to within a tolerance of
0.0005.
. Repeat Project 2 for the following functions:
a. f(x) = x4 + 3x3 — 3x2 -—6x41
b. f(x) = x4 -— 26x3 + 131x2 — 226x + 120
. If x" = c, then x" — c = 0 and the nth root of c is a zero of the second
equation.
Use this observation in conjunction with the bisection routine to compute
ania)
and
b. V7
to six decimal places.
Programming Projects 903

Ds Repeat Project 3, using function newton proposed in the programming


exercise at the end of Section 16.1.
6. In the Regula—Falsi method, the root is isolated to an interval [x), x,] Just as
in the bisection method. However, instead of breaking the interval into two
subintervals at the midpoint Xeaig of the interval, where

poe xy X41

we break the interval into two subintervals at the point Xe defined by the for-
mula

ice are) =F
xX,
— X41

We then isolate the root to the subinterval (either [x,, Xgl or [x,, x,]) that has
the change of signs. Figure 16.38 shows a geometric interpretation of the
Regula—Falsi method. Write a program that will find roots of equations using
the Regula-Falsi method.
. Write a program that will take a 4-by-4 square matrix A (same number of
rows and columns) and a nonnegative integerp and will compute A?, that is,
A raised to the power p. A? is defined as

A° = the identity matrix, I (all Os except for 1s on the diagonal)


AP=A* AP“! p21

Figure 16.38
Geometric
Interpretation
of Regula-Falsi
904 Introduction to Numerical Methods

8. Use Gaussian elimination to solve each of the following systems of equa-


tions:
a x+2y+z=4
2x+y-z=-1
—xX+y+z=2
b. x-y+2z=3
2x + 3y-—6z=1
4x+y-2z2=7
CG, Shia =7
2x+y=6
ye =p
. The inverse of a square matrix A is defined to be the matrix A_iny that satis-
fies

A*A_inv=I (See the identity matrix in Project 7.)

If A_inv exists, then A is said to be invertible. (IfA is invertible, then


A_inv is
also invertible, and A is the inverse of A_inv.) The inverse of a matrix
A can
be computed using Gaussian elimination. This technique will also tell us if
A
is not invertible.
First let us describe with an example how to set up the data to find
the
inverse of a matrix A. Suppose we want to find the inverse of the followin
g
matrix:

a 2 1
A= 2 1 -1
-1 1 il

The problem solution is derived by setting up a work matrix W that


has the
original matrix A as its first three columns and the identity matrix
as its last
three columns:

iL 2 il ak 0 0
A= 2 al 0 a 0
-1 1 1 0 0 il

We now apply Gaussian elimination to the three leftmost columns


.
However, we modify Gaussian elimination as presented in the
text, so that the
three leftmost columns become the identity matrix. In other
words, it is not
sufficient to get the three leftmost columns in scaled triangular form.
Getting
these columns in the correct form requires only a slight modifica
tion of the
function gauss. When we eliminate coefficients in the pivot
column, we do
so both above and beneath the pivot.
Programming Projects 905

The key to this method is that as we apply Gaussian elimination to the left
half of W, we apply all row operations throughout the matrix W. When the
left side of W is the identity matrix, the right side will contain the inverse of
A. If Gaussian elimination fails because no nonzero pivot is found for a cer-
tain pivot element, then the original matrix A must be noninvertible.
Write a program that will take a square matrix and compute its inverse by
this modification of Gaussian elimination.
10. A second class of solutions to systems of linear equations is by successive
approximations (or iterative methods). In Jacobi's iteration, we solve the
system

Aa = x=) Bi

by first calculating a new matrix D, where

ree —a,y ! Ajj, when bSA i)


UJ 0, otherwise

and a new vector C, where

c; = b;/a,, for alli

Clearly, a necessary condition for doing Jacobi's iteration is that all of the
diagonal elements of the matrix A must be nonzero.
The next step is to choose an initial approximate solution vector

xl] =0 for alli

We then compute subsequent solution vectors using the iterative formula

= 1 DY dy*
xl lly +c,
j#l

In other words, the ith component of the mth solution vector is computed in
terms of the components of the [m-1]st solution vector. We continue com-
putation until successive values of the solution vector become close enough
together. The stopping criterion is

xm], xlm-1) | < epsilon for alli

Write a program that will solve systems of linear equations using Jacobi's
iteration. Try your program on some of the systems given in Project 8.
906 Introduction to Numerical Methods

Lil In Gauss-Seidel, we attempt to speed up convergence to the solution by


feeding into the computation of the new solution vector elements

xl.

the latest available values, which include

als, Sl

Thus, the iterative formula for Gauss—Seidel is

i= Il n
al = 2 dij* xl, fe > dij* sl
aa Sy
df = j=itl

Write a program that will use this technique to solve systems of linear
equations.
UP, Find the best fit line and the correlation coefficient for the following data:

Hours of TV Watched Student's Grade Point


per Day, X Average, Y

OFZ 3.98
ORS Sg io)
yO 4.00
dhe Sic 15)
3.0 205
4.0 1.56
4.2 55
Bye 0) LOO
6.1 0.98
= 95 ORO

132 Compute approximations of the following definite integrals using Simpson'


s
rule:
Di

a. J V2 —(sin
x)? dx

2
b. I x cos +7 dx

2
C: J (3 — 2x? +x +5) dx

1
d. iP(1 +x®) dx
Programming Projects 907

14. Write a program that does integration using Simpson's rule with n taking on
the values 2, 4, 8, 16, and 32. For each n > 2, print out an error estimate
using the formula

E Pp
if 1S
where E,, is the estimated error when the integral is computed with n inter-
vals, S,, is the integral for n intervals, and S_/, is the integral for n/2 intervals.
Try your program on some of the integrals in Project 13.
7 ey: 4

eg. sale’ ‘nm ‘ sabe # a0eratniols


ak * : 7 yd ob,

Wap G. contig nid IUD ig>!Mayol tu a4 ons


>« lnsy til oe) Pit e ae
Bt

a ; a
- ben at ss
- 7 > eo : :
hens
ii nr qiitce <4poeSipalg
shee tig? vi ae Sue? Gis
~ @
#4 degPER yp! Lartigaial
Shas.
Odd-Numbered Self-Check Exercises

CHAPTER 14

Section 14.1

imeprintt( te", *lLetpye


3. strcpy(planetp->name, "Uranus" );
5. letp = (char *)calloc(30, sizeof (chaz);

Section 14.2

EAC D Ca AC
Q dis 220

Al
Section 14.3

Me 1. Find 5
headp

4 is
checked

1 is
checked

5 is checked;
cur_nodep is returned

4 is
checked

cur_nodep

lis
checked

cur_nodep pae
checked

cur_nodep

Y] cur_nodep CNULL) is returned

Find 4

headp

ae
4 is checked;
cur_nodep is returned

A2
Answers A3

Section 14.4

it

q.frontp

q.rearp

q.size

Section 14.5

stk.topp
LZ

Section 14.6

1. The delete ordered_node does not apply the address-of operator because is_deletedp is already a
pointer to the integer is_deleted flag.

Section 14.7

1. double
eval _poly(polyt p, /* input - polynomial to evaluate */
double val) /* input - value of variable */
{
double ans = 0;
term node t *tlistp;

£Or (tlastp) = p.temmelistp?,


tlistp != NULL;
tlistp = tlistp->restp)
if (tlistp->term.expon == 0)
ans += tlistp->term.coeff
else if(tlistp->term.expon == 1)
ans += tlistp->term.coeff * val;
else
ans += tlistp->term.coeff * pow(val, tlistp->term.expon);

return (ans);
}
The advantage of doing this is that the computationally expensive pow is only called if it is needed.
The disadvantage is that the checks for 0 and 1 must be made each time through the loop.
A4 Answers

Section 14.8

1. Simulation of Westcraft Well-Turned Widgets Processes D and E

Time Event
200 Widget 1 of type 111 entering simulation
Widget 1 of type 111 entering process D
370 Widget 2 of type 111 entering simulation
380 Widget 1 of type 111 exiting process D
Widget 2 of type 111 entering process D
Widget 1 of type 111 entering process E
500 Widget 1 of type 111 exiting process E
550 Widget 3 of type 111 entering simulation
560 Widget 2 of type 111 exiting process D
Widget 3 of type 111 entering process D
Widget 2 of type 111 entering process E
680 Widget 2 of type 111 exiting process E
730 Widget 4 of type 111 entering simulation
740 Widget 3 of type 111 exiting process D
Widget 4 of type 111 entering process D
Widget 3 of type 111 entering process E
860 Widget 3 of type 111 exiting process E
920 Widget 4 of type 111 exiting process D
Widget 4 of type 111 entering process E
1040 Widget 4 of type 111 exiting process E

*****Simulation Summary*****

Average wait time for access to process D: 7.500000 seconds


Average wait time for access to process E: 0.000000 seconds
In 1040 seconds, a total of 4 widgets were processed.

CHAPTER 16

Section 16.1

1. One root of g(x) is on the interval from —3 to —2.

Section 16.5
1. The definite integral from 1 to 2 of f£(x)dx is approximately 0.6944.

The definite integral from 1 to 3 of g(x)dx is approximately 0.8357.


A E Libraries of numerical
Array allocation, 734, 735 Empty list, 741 methods routines, 897
Augmented matrix, 874 Event of a simulation, 790 Linear equations, see Systems of
Event-driven simulation, 794 linear equations
B Linear regression coefficients,
Back substitution, 874, 880 F 886
Best fit line, 884 First-in, first-out list (FIFO), Linked list, 731, 738
Bisection method, 854 750 advantages, 742
free, 736 connecting nodes, 740
Front of queue, 750 display, 744
C empty list, 741
Function
CAD/CAM, 844 errors, 809
as argument, 860
calloc, 735 list head, 741
pointer, 860
Cast a pointer, 733, 776 node, 731
Compound assignment node type, 740
operator, 868
G
Gaussian elimination, 873 ordered list, 761
Computer-aided polynomials, 773
design (CAD), 843 queue, 750
engineering (CAE), 844 H
search, 748
manufacturing (CAM), Head of list, 741
traversing, 744
844 Heap, 733
List head, 741
Conformable matrices, 866 management errors, 809
Loss of information, 820,
Contour plot, 833 832
key, 835, 843 I
Convergence Ill-conditioned systems, 882
M
criteria, 858 IMSL, 898
Macro for dynamic
of root-finding method, 853 Independent variable, 819
allocation, 776
Coordinates, polar and Indirect component selection
malloc, 732
rectangular, 848 operator (->), 734
Matrix, 864
Correlation coefficient, 888 Indirection operator (*), 733,
augmented, 874
734 conformable, 866
D Infix expression, 815
inverse, 904
Information loss, 820, 832
Dependent variable, 819 library functions, 868
Direct component selection Inner product, 863
multiplication, 866
Interpolating polynomial, 892
operator (.), 734 multiplication by a vector,
Display a list, 744 Inverse of a matrix, 904 864
Divergence of root-finding raised to a power, 903
method, 853 K row-major storage, 869
Dot product, 863 Key square, 867
Dynamic allocation macro, 776 component, 761 triangular form, 873
Dynamic data structure, 731 contour plot, 835, 843 varying sizes, 868
access, 734, 735 Mean, 884
array creation, 735 L Memory
node creation, 732 Last-in, first-out list (LIFO), 756 allocation function, 732
12 Index

heap, 733 Polynomial manipulation, 773 factory, 789


returning cells, 736 add, 784 languages, 808
stack, 733 differentiate, 816 Singularity, 819
Multiplication evaluate, 787 Sort, radix, 817
matrix, 864 integrate, 816 Square matrix, 867
vector, 863, 864 print, 783 Stack, 756
scan, 776 data type library, 815
N Pop, 756, 758 function data areas, 733
Node, 731 Postfix expression, 815 pop, 756, 758
type, 739 Push, 756, 758 push, 756, 758
NULL pointer, 741 Standard deviation, 884, 888
avoid following, 749 Q Statistics
Numerical integration, 890 Queue, 750 best fit line, 884
quadrature methods, 891 front, 750 correlation coefficient, 888
round-off error, 895 rear, 750 linear regression coefficients,
Simpson’s rule, 891, 907 886
Numerical methods libraries, R mean, 884
897 Radix sort, 817 regression, 883
Rear of queue, 750 standard deviation, 884, 888
° Rectangular coordinates, 848 z-scores, 888
Operators Recursive algorithm Systems of linear equations, 872
direct component selection delete from ordered list, 770 Gaussian elimination, 873
(2); 734 insert in ordered list, 765 Gauss-Seidel method, 883,
indirect component selection list input, 746 906
(->), 734 print list, 744, 745 ill-conditioned, 882
indirection (*), 733, 734 tail recursion, 745-746 Jacobi’s iteration, 905
Ordered list, 761 Regression, 883
building, 763 linear regression coefficients, T
delete, 769 886
insert, 765 Tail recursion, 745-746
Returning memory to heap, 736
key, 761 Trajectory of golf ball, 847
Roots
Traverse a list, 744
bisection method, 854
P Triangular form, 873
of an equation, 852
typedef for list node, 739
Parametric equations, 830 Newton’s method, 861
Pivot, 875 Regula-Falsi method, 903
Pivoting, 876 Row-major storage, 869 Vv
Plane by plane plotting, 833 Vector, 862
Plotting S multiplication by a matrix,
contour plot, 833 Scalar product of vectors, 863 864
functions of one variable, 819 Scaling problem, 822, 835 scalar (dot, inner) product,
functions of two variables, Scatter plot, 885 863
831 Screen, displaying a function on
parametric equations, the, 819
830 WwW
Search, 748
plane by plane, 833 Wave generators, 849
Simpson’s rule, 891
scatter plot, 885 Simulation, 788
Pointer, 731 event, 790 Z
to function, 860 event scan, 805 Zeros of a function, 852
Polar coordinates, 848 event-driven, 794 Z-scores, 888
AdVvontedTopicspupplement
PROBLEM
SOLVING
PROGRAM
DESIGN
ihe
Jeri R. Hanly, University of Wyoming
Elliot B. Koffman, Temple University
Frank L. Friedman, Temple University

About the Authors:

Jeri R. Hanly is a member of the computer science faculty at the


University of Wyoming. Since 1984 she has developed software for
target recognition in collaboration with naval researchers in China
Lake, CA. Hanlyis also active as a teacher of software engineering
seminars for professional developers of mk systems in the
United States and Canada.
Dr. Elliot B. Koffman is Professor of Computer and Information
Science at Temple University. He is one of the country’s foremost
computer science educators, a former chairman of the ACM Task Force
for introductory programming methods courses, and author of a num-
ber of successful language texts in Pascal, Turbo Pascal, Fortran,
Modula-2, and Ada.

Dr. Frank L. Friedman is Professor and Chairman of Computer


and Information Science at Temple University. He received M.S. 90000
degrees from Johns Hopkins University and Purdue University and his
Ph.D. in Computer Science from Purdue University. Dr. Friedman is
also the coauthor of textbooks on programming in Fortran and Basic.
His current research and instructional interests are in software engi-
neering, specifically object-oriented paradigms for software design.
Addison-Wesley Publishing Company — ISBN O-201-55071-?

You might also like