Addison - Wesley.problem - Solving.and - Program.design - In.c.advanced - topics.supplement.1993.SCAN DARKCROWN
Addison - Wesley.problem - Solving.and - Program.design - In.c.advanced - topics.supplement.1993.SCAN DARKCROWN
tee
INC
i i snes y
i if et : i : te ‘ : ) : thi
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.
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
J.R.H.
E.B.K.
EEE
14. Dynamic Data Structures 730
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.
“srgnp = +) 5
731
732 Dynamic Data Structures
int *nump;
char *letp;
planet_t *planetp;
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
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
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:
i
Ran
(*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.
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
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).
/* 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
free(letp);
14.1 Pointers and the Functions malloc and calloc 737
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);
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:
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
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:
n3_p = n2_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;
n2_p->volts
and
n3_p->volts
nl_p->linkp->volts
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.
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.
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
newp = 7
prevp->linkp = ;
prevp = newp;
= NULL;
744 Dynamic Data Structures
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.
{
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
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
/*
* 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
List-Traversing
Loop Control
cur_nodep = cur_nodep—>restp
Variable
cur_nodep
after
(continued)
14.3 Linked List Operators 747
{
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));
}
(continued)
748 Dynamic Data Structures
return (cur_nodep);
}
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.
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
Figure 14.21
A Queue of
Passengersin
a Ticket Line
q.frontp
q.rearp
q.size
/* 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
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
(continued)
14.4 Representing a Queue with a Linked List 753
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 */
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
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);
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
s.to PP C
+
s.topp /
C
+
/*
#include <stdio.h>
(continued)
14.5 Representing a Stack with a Linked List 759
/*
* 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 */
/* Sets
stack pointer to point to new node */
(*sp).topp = newp;
}
/*
GBecuEn (ans)?
is
(continued)
760 = Dynamic Data Structures
ont
main(void)
{
stack_t s = {NULL}; /* stack of characters - initially empty */
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
{
stack_t stk = {NULL};
push(&stk, 'a');
push(&stk, 'b');
pop(&stk);
push(&stk, 'c');
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.
<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
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
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.
* Program that builds an ordered list through insertions, and then modifies
* it through deletions.
(continued)
764 Dynamic Data Structures
int
main(void)
{
nite next_key;
ordered list _t my list = {NULL, 0};
iaeheiubaigl (((0)))5
(continued)
14.6 Ordered Lists 765
UW
On
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
Recursive Step
old_listp new_key
new_listp is old_listp
with circled component’
changed to
ea
14.6 Ordered Lists 767
/*
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);
}
/*
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.
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.
/*
* 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,
(continued)
770 = Dynamic Data Structures
is deleted = 1;
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.
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;
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.
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;
(continued)
14.7 A Linked-List Application in Mathematics 773
return (ansp);
(*listp).headp =
delete ordered _node((*listp).headp, target,
&is deleted);
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.
<.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
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 */
xO Ar SxS. = 2x40 25
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.
nodep = (node
_t *)malloc(sizeof (node _t));
776 Dynamic Data Structures
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
first term aa
remaining terms
NyAQ"
\
"+5x4S-2x+0.5"
result polynomial
first term node
¢ ee coeff represents
oe x4
xA4
ee Ga5x3 —-2x 0.5
Final polynomial
created by
scan_poly
778 Dynamic Data Structures
ea te 5x Se me 20 +025
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
/*
* 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
(continued)
14.7 A Linked-List Application in Mathematics 779
(continued)
780 Dynamic Data Structures
(continued)
14.7 A Linked-List Application in Mathematics 781
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);
(continued)
782 Dynamic Data Structures
if (status T= BOR). {
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
/*
(continued)
784 Dynamic Data Structures
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.
SOKns task 2h 2K — 93
{
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;
}
}
}
return (tlistp).-
}
/*
786
14.7 A Linked-List Application in Mathematics 787
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.
/*
(continued)
Figure 14.43 (continued)
double ans = 0;
term node t *tliistp;
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.
788
14.8 Queues and Ordered Lists in Simulations 789
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:
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
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 */
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.
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
Figure 14.48 shows our implementation of the data types, typed allocation
macro, function main, and two tracing output functions.
#include <stdio.h>
#include <stdlib.h>
(continued)
798 Dynamic Data Structures
event t event;
struct list _node_s *restp;
} list_nodet;
/*
* 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
(continued)
800 Dynamic Data Structures
}
break;
/* 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 */
return (0);
802 Dynamic Data Structures
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.
“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
ans = to_freep->event;
(*eventsp).headp = to freep->restp; /* deletes first node */
free(to_freep);
return(ans);
}
/*
/*
ansp = (*qp).frontp;
(*qp).frontp = (*qp).frontp->restp;
--((*qp).size);
(continued)
14.8 Queues and Ordered Lists in Simulations 805
Function scan_events
240 aka
300 aLal
390 555
460 ipa
headp
[Ee etn Tira
ye
(continued)
14.8 Queues and Ordered Lists in Simulations 807
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 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
{
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.
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
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.
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
np = hp->nextp;
strcpy(np->pronoun, "she");
mp = hp->nextp;
np = mp->nextp;
mp->nextp = np->nextp;
free(np);
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.
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?
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.
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
/*
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
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
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.
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)
819
820 Plotting Functions
F(x)
Figure 15.1
Mapping a
Graph onto
a Computer
Screen
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
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)
Figure 15.5
Scaling Problem
in the x
Dimension
xX_init X_final
15.1 Plotting Functions of One Variable 823
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
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:
Figure 15.6
Scaling Problem
in the y
Dimension
18
19
824 Plotting Functions
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.
Figure 15.7
Structure Chart
for Plotting
Program
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];
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
/*
/*
(continued)
828 Plotting Functions
/* 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');
(continued)
15.1 Plotting Functions of One Variable 829
int
main(void)
{
double xvinit, liner, yamax,nyliner;
char graph[NROW][NCOL];
return (0);
830 Plotting Functions
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
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.
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
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
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
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
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)
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
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.
z>0.8 #
z20.6 @
z20.4 g
z20.2 +
z<0.2
ee
ANALYSIS
The function that yields the hemispherical surface is
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
/*
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);
}
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
(continued)
842 Plotting Functions
be
printf("\n %s", x_graph);
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.
Condition Symbol
i #
lean @
ea g
We} +
ean
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).
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
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.
REVIEW QUESTIONS
PROGRAMMING PROJECTS
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
Figure 15.18
Plot of Two
Functions with
Area between
Curves Shaded
X_init x_final
Programming Projects 847
x =(Vcos6)t
1
Mai gt? + (V sin6)t
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
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
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
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...
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)
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
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-
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.
#include <stdio.h>
#include <math.h>
#include "methods.h" /* Header file for personal numerical methods
library containing bisection function ; */
/* 3 2
ERO Xe ese tS
*/
double
£(double x)
{
(continued)
16.1 Finding Roots of Equations 857
/* 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;
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))).
#include <stdio.h>
#include <math.h>
/*
(continued)
16.1 Finding Roots of Equations 859
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
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
the statement
feleenep (x)
is equivalent to
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.
Co fx i)
ti d f')
fx) =x3-x
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.
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
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.
~ 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
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)
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
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;
Figure 16.12 Function mat_vec_prod That Computes the Product of a Matrix and
a Vector
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.
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
/*
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
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
[0][3] [3]
[2][2] [10]
(2 x 4+ 2)
[2][3] [11](2
x 4+ 3)
double (*c)[]
Figure 16.17 Matrix Library Function That Multiplies Matrices of Any Size
(continued)
872 Introduction to Numerical Methods
1 1 1
Alawar 3 iL
1 -1 -1
by a vector
16.3 Solving Systems of Linear Equations 873
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
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,
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.
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
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
Local Variables
int p /* the current row */
int ok /* a flag indicating whether the system has a
unique solution */
#define FALSE 0
#define TRUE 1
#define N 3
/*
/* 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;
(continued)
878 Introduction to Numerical Methods
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.
* 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;
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 */
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).
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
Siile= 2ug(i
N= tee a aug iiliii * xia
j=i
/*
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);
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.
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.
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
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!
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
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
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 */
(continued)
888 Introduction to Numerical Methods
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
Figure 16.30
Scatter Plots
with
Correlations
(a) (b)
High correlation
(c) (d)
Low correlation Low correlation
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:
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
ps
= 0;
forma) =305)) aa i<pnee ts.) df
zxi = (x[i] - x_mean) / sx;
Zvi =" (Viaje yomean) / sy;
DSot= 2x1 zyi:
}
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.
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.
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
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
|
aj
Jo%j-2 f@) de
as
cay
) Pi@) dz
Zi-2
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
x f@i-)
i= 2
Step 2
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
/* 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) )>
}
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);
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
return (0);
}
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.
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
/*
* 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:
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 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
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.
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
PROGRAMMING PROJECTS
1. The polynomial
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
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
Figure 16.38
Geometric
Interpretation
of Regula-Falsi
904 Introduction to Numerical Methods
a 2 1
A= 2 1 -1
-1 1 il
iL 2 il ak 0 0
A= 2 al 0 a 0
-1 1 1 0 0 il
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
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
= 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
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
xl.
als, Sl
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:
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
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
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
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
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;
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
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*****
CHAPTER 16
Section 16.1
Section 16.5
1. The definite integral from 1 to 2 of f£(x)dx is approximately 0.6944.