02-Chap 02-ARRAYS AND STRUCTURES
02-Chap 02-ARRAYS AND STRUCTURES
02-Chap 02-ARRAYS AND STRUCTURES
We begin our discussion by considering an array as an ADT. This is not the usual per
spective since many programmers view an array only as "a consecutive set of memory
locations." This is unfortunate because it clearly shows an emphasis on implementation
issues. Thus, although an array is usually implemented as a consecutive set of memory
locations, this is not always the case. Intuitively an array is a set of pairs, <index,
value>, such that each index that is defined has a value associated with it. In mathemati
cal terms, we call this a correspondence or a mapping. However, when considering an
ADT we are more concerned with the operations that can be performed on an array.
Aside from creating a new array, most languages provide only two standard operations
for arrays, one that retrieves a value, and a second that stores a value. Structure 2.1
shows a definition of the array ADT.
The Creaie(j, list) function produces a new, empty array of the appropriate size.
All of the items are initially undefined. Retrieve accepts an array and an index. It
returns the value associated with the index if the index is valid, or an error if the index is
invalid. Store accepts an array, an index, and an item, and returns the original array aug
mented with the new <index, value> pair. The advantage of this ADT definition is that it
clearly points out the fact that the array is a more general structure than "a consecutive
set of memory locations."
49
50 Arrays And Structures
structure Array is
objects: A set of pairs <mdex, value> where for each value of index there is a value
from the set item. Index is a finite ordered set of one or more dimensions, for example,
{0, • • ■ , n-1} for one dimension, {(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2,
1), (2, 2)) for two dimensions, etc.
functions:
for all A G Array, i g index, x g item, j, size g integer
Array Create(7, list) return an array of j dimensions where list
is a y-tuple whose zth element is the the size of
the zth dimension. Items are undefined.
Item Retrieve(A, /) if (z G index) return the item associated
with index value z in array A
else return error
Array Store(A,z,x) if (z in index)
return an array that is identical to array
A except the new pair <z, x> has been
inserted else return error.
end Array
declares two arrays each containing five elements. The first array defines five integers,
while the second defines five pointers to integers. In C all arrays start at index 0, so
/z5?[0], ZZ^rfl], /z5r[2], /z5Z[3], and /z\f[4] are the names of the five array elements, each of
which contains an integer value. Similarly, plist[Q], plist[}.], plistl?.], plist[3], and
plist[4] are the names of five array elements, each of which contains a pointer to an
integer.
We now consider the implementation of one-dimensional arrays. When the com
piler encounters an array declaration such as the one used above to create list, it allocates
five consecutive memory locations. Each memory location is large enough to hold a sin
gle integer. The address of the first element /z5Z[0], is called the base address. If the size
of an integer on your machine is denoted by sizeofiint), then we get the following
memory addresses for the five elements of list[]-.
The Array As An Abstract Data Type 51
★
int listl;
and
int list2 [5];
The variables list\ and list'2 are both pointers to an int, but in the second case five
memory locations for holding integers have been reserved, list'2 is a pointer to /z5r2[0]
and list2+i is a pointer to Ust2[i]. Notice that in C, we do not multiply the offset i with
the size of the type to get to the appropriate element of the array. Thus, regardless of the
type of the array list2, it is always the case that (to2 + /) equals &list2[i}. So,
*({ist2 + z) equals list2[i ].
It is useful to consider the way C treats an array when it is a parameter to a func
tion. All parameters of a C function must be declared within the function. However, the
range of a one-dimensional array is defined only in the main program since new storage
for an array is not allocated within a function. If the size of a one-dimensional array is
needed, it must be either passed into the function as an argument or accessed as a global
variable.
Consider Program 2.1. When sum is invoked, input = &input [0] is copied into a
temporary location and associated with the formal parameter list. When /z5r[zj occurs on
the right-hand side of the equals sign, a dereference takes place and the value pointed at
by (list + z) is returned. If list[i} appears on the left-hand side of the equals sign, then the
value produced on the right-hand side is stored in the location (list 4- z). Thus in C, array
parameters have their values altered, despite the fact that the parameter passing is done
using call-by-value.
Example 2.1 [One-dimensional array addressing}’. Assume that we have the following
declaration:
We would like to write a function that prints out both the address of the zth ele
ment of this array and the value found at this address. To do this, print\ {Program 2.2)
uses pointer arithmetic. The function is invoked as print}(&one |()],5). As you can see
52 Arrays And Structures
from the printf statement, the address of the ith element is simply ptr + i. To obtain the
value of the /th element, we use the dereferencing operator, *. Thus, "^{ptr + /) indicates
that we want the contents of the ptr 4- i position rather than the address.
Figure 2.1 shows the results we obtained when we ran print1. Notice that the
Structures And Unions 53
Address Contents
1228 0
1230 1
1232 2
1234 3
4
addresses increase by two. This is what we would expect on an Intel 386 machine. □
2.2.1 Structures
Arrays are collections of data of the same type. In C there is an alternate way of group
ing data that permits the data to vary in type. This mechanism is called the struct, short
for structure. A structure (called a record in many other programming languages) is a
collection of data items, where each item is identified as to its type and name. For exam
ple,
struct {
char name[10];
int age;
float salary;
} person;
creates a variable whose name is person and that has three fields:
We may assign values to these fields as below. Notice the use of the . as the structure
member operator. We use this operator to select a particular member of the structure.
54 Arrays And Structures
strcpy(person.name,"james") ;
person.age 10;
person.salary = 35000;
We can create our own structure data types by using the typedef statement as
below:
This says that human-being is the name of the type defined by the structure definition,
and we may follow this definition with declarations of variables such as:
if (strcmp(personl.name, person2.name))
printf{"The two people do not have the same nameXn");
else
printf("The two people have the same name\n");
It would be nice if we could write if (personl == person2 ) and have the entire
structure checked for equality, or if we could write personl = person2 and have
that mean that the value of every field of the structure of person! is assigned as the
value of the corresponding field of person 1. ANSI C permits structure assignment, but
most earlier versions of C do not. For older versions of C, we are forced to write the
more detailed form:
strcpy(personl.name, person2.name);
personl.age = person2.age;
personl.salary = person2.salary;
ttdefine FALSE 0
ttdefine TRUE 1
A typical function call might be:
Structures And Unions 55
if (humans—equal(personl,person2))
printf("The two human beings are the same\n");
else
printf{"The two human beings are not the same\n");
We can also embed a structure within a structure. For example, associated with
our human-being structure we may wish to include the date of his or her birth. We can
do this by writing:
typedef struct {
int month;
int day;
int year;
} date;
A person born on February 11, 1944, would have the values for the date struct set as:
56 Arrays And Structures
personl.dob.month = 2;
personl.dob.day = 11;
personl.dob.year = 1944;
2.2.2 Unions
personl.sex—info.sex = male;
personl.sex—info.u.beard = FALSE;
and
person2.sex—info.sex = female;
person2.sex—info.u.children - 4;
Notice that we first place a value in the tag field. This allows us to determine which field
in the union is active. We then place a value in the appropriate field of the union. For
example, if the value of sex-info.sex was male, we would enter a TRUE or a FALSE in
Structures And Unions 57
the sex-info.u.beard field. Similarly, if the person was a female, we would enter an
integer value in the sex-info.u.children field. C does not check to make sure that we use
the appropriate field. For instance, we could place a value of female in the sex-info.sex
field, and then proceed to place a value of TRUE in the sex-info.u.beard field. Although
we know that this is not appropriate, C does not check to make sure that we have used
the correct fields of a union.
In most cases you need not be concerned with exactly how the C compiler will store the
fields of a structure in memory. Generally, if you have a structure definition such as:
these values will be stored in the same way using increasing address locations in the
order specified in the structure definition. However, it is important to realize that holes
or padding may actually occur within a structure to permit two consecutive components
to be properly aligned within memory.
The size of an object of a struct or union type is the amount of storage necessary
to represent the largest component, including any padding that may be required. Struc
tures must begin and end on the same type of memory boundary, for example, an even
byte boundary or an address that is a multiple of 4, 8, or 16.
Each instance of the structure list will have two components, data and link, data is a sin
gle character, while link is a pointer to a list structure. The value of link is either the
address in memory of an instance of list or the null pointer. Consider these statements,
which create three structures and assign values to their respective fields:
58 Arrays And Structures
item2.data
item3.data 'c' ;
iteml.link item2.link item3.link NULL ;
Structures item 1, item 2, and z7em3 each contain the data item a, b, and c, respectively,
and the null pointer. We can attach these structures together by replacing the null link
field in item 2 with one that points to item 3 and by replacing the null link field in item 1
with one that points to item 2.
EXERCISES
1. Develop a structure to represent the planets in the solar system. Each planet has
fields for the planet’s name, its distance from the sun (in miles), and the number of
moons it has. Place items in each the fields for the planets: Earth and Venus.
2. Modify the human-being structure so that we can include different information
based on marital status. Marital status should be an enumerated type with fields:
single, married, widowed, divorced. Use a union to include different information
based on marital status as follows:
• Single. No information needed.
• Married. Include a marriage date field.
• Widowed. Include marriage date and death of spouse date fields.
• Divorced. Include divorce date and number of divorces fields.
Arrays are not only data structures in their own right, we can also use them to implement
other abstract data types. For instance, let us consider one of the simplest and most com
monly found data structures: the ordered, or linear, list. We can find many examples of
this data structure, including:
Notice that the years Switzerland fought in World War II is different because it
contains no items. It is an example of an empty list, which we denote as ( ). The other
lists all contain items that are written in the form (itemQ, item j, • • • , itemn-i).
We can perform many operations on lists, including:
♦ Finding the length, n, of a list.
♦ Reading the items in a list from left to right (or right to left).
• Retrieving the /th item from a list, 0 < i < n.
• Replacing the item in the /th position of a list, 0 < / n.
• Inserting a new item in the /th position of a list, 0 < i < n. The items previously num
bered /, i 4-1, • • • , n-1 become items numbered / 4-1,/4-2, ••• , n.
• Deleting an item from the /th position of a list, 0 < / <n. The items numbered / 4-1,
• • • , «-l become items numbered /, /4-1, • • • , n~2.
Rather than state the formal specification of the ADT list, we want to explore
briefly its implementation. Perhaps, the most common implementation is to represent an
ordered list as an array where we associate the list element, itemj, with the array index i.
We call this a sequential mapping because, assuming the standard implementation of an
array, we are storing itenti, itemj + \ into consecutive slots i and i + 1 of the array. Sequen
tial mapping works well for most of the operations listed above. Thus, we can retrieve
an item, replace an item, or find the length of a list, in constant time. We also can read
the items in the list, from either direction, by simply changing subscripts in a controlled
way. Only insertion and deletion pose problems since the sequential allocation forces us
to move items so that the sequential mapping is preserved. It is precisely this overhead
that leads us to consider nonsequential mappings of ordered lists in Chapter 4.
60 Arrays And Structures
Let us jump right into a problem requiring ordered lists, which we will solve by
using one-dimensional arrays. This problem has become the classical example for
motivating the use of list processing techniques, which we will see in later chapters.
Therefore, it makes sense to look at the problem and see why arrays offer only a partially
adequate solution. The problem calls for building a set of functions that allow for the
manipulation of symbolic polynomials. Viewed from a mathematical perspective, a poly
nomial is a sum of terms, where each term has a form ax^, where x is the variable, a is
the coefficient, and e is the exponent. Two example polynomials are:
The largest (or leading) exponent of a polynomial is called its degree. Coefficients that
are zero are not displayed. The term with exponent equal to zero does not show the vari
able since x raised to a power of zero is 1. There are standard mathematical definitions
for the sum and product of polynomials. Assume that we have two polynomials, A (x) =
and B(x) = J^h/x' then:
A (x) • B(x') =
Similarly, we can define subtraction and division on polynomials, as well as many other
operations.
We begin with an ADT definition of a polynomial. The particular operations in
part are a reflection of what will be needed in our subsequent programs to manipulate
polynomials. The definition is contained in Structure 2.2.
We are now ready to make some representation decisions. A very reasonable first
decision requires unique exponents arranged in decreasing order. This requirement con
siderably simplifies many of the operations. Using our specification and this stipulation,
we can write a version of Add that is closer to a C function (Program 2.4), but is still
representation-independent.
This algorithm works by comparing terms from the two polynomials until one or
both of the polynomials becomes empty. The switch statement performs the comparis
ons and adds the proper term to the new polynomial, d. If one of the polynomials
becomes empty, we copy the remaining terms from the nonempty polynomial into d.
With these insights, suppose we now consider the representation question more care
fully.
One way to represent polynomials in C is to use typedef to create the type polyno
mial as below:
structure Polynomial is
a„x en a set of ordered pairs of <ej, ai> where fly in
fl
objects: /?(x) = a^x^' + •- •- •' + a„x^'';
Coefficients and c, in Exponents, e^ are integers >= 0
functions:
for all poly,poly\, poly2 e Polynomial, coefe Coefficients, expon e Exponents
float coef[MAX-DEGREE];
} polynomial;
n
Now if fl is of type polynomial and n < MAX-DEGREE, the polynomial A (x) = ^aix'
i=0
would be represented as:
a.degree = n
62 Arrays And Structures
*
/ d a + b, where a, b, and d are polynomials */
d Zero()
while (! IsZero(a) && ! IsZero(b)) do {
switch COMPARE(Lead —Exp(a), Lead—Exp(b)) {
case -1: d -
Attach(d,Coef(b,Lead—Exp(b) ) ,Lead—Exp(b)) ;
b = Remove(b,Lead—Exp(b));
break;
case 0:
0: sum Coef ( a, Lead—Exp(a)) + Coef(b.
Lead—Exp(b));
if (sum) {
Attach(d,sum,Lead—Exp(a));
a = Remove(a,Lead—Exp(a));
b = Remove(b,Lead—Exp(b));
}
break;
case 1: d =
Attach(d,Coef(a,Lead—Exp(a)), Lead—Exp(a));
a - Remove(a,Lead—Exp(a));
}
}
insert any remaining terms of a or b into d
} polynomial;
polynomial terms[MAX—TERMS];
int avail 0;
Consider the two polynomials A (x) = 2x'^^ + 1 and B(x) = + 10%^ + 3x^ + 1.
Figure 2.2 shows how these polynomials are stored in the array terms. The index of the
first term of A and B is given by starta and startb, respectively, while finisha and finishb
give the index of the last term of A and B. The index of the next free location in the
array is given by avail. For our example, starta = 0, finisha = 1, startb = 2, finishb = 5,
and avail = 6.
This representation does not impose any limit on the number of polynomials that
we can place in terms. The only stipulation is that the total number of nonzero terms
must be no more than MAX-TERMS. It is worth pointing out the difference between our
specification and our representation. Our specification used poly to refer to a polyno
mial, and our representation translated poly into a <start, finish > pair. Therefore, to use
A (x) we must pass in starta and finisha. Any polynomial A that has n nonzero terms has
starta and finisha such that finisha = starta + n - 1.
Before proceeding, we should evaluate our current representation. Is it any better
than the representation that uses an array of coefficients for each polynomial? It cer
tainly solves the problem of many zero terms since A (x) = 2x 4- 1 uses only six units
of storage: one for starta, one for finisha, two for the coefficients, and two for the
exponents. However, when all the terms are nonzero, the current representation requires
about twice as much space as the first one. Unless we know before hand that each of our
polynomials has few zero terms, our current representation is probably better.
We would now like to write a C function that adds two polynomials, A and B,
represented as above to obtain D = A 4- B. To produce D(x), padd (Program 2.5) adds
A (x) and B(x) term by term. Starting at position avail, attach (Program 2.6) places the
term.s of D into the array, terms. If there is not enough space in terms to accommodate
D, an error message is printed to the standard error device and we exit the program with
64 Arrays And Structures
an error condition.
Analysis of padd-. Since the number of nonzero terms in A and in B are the most impor
tant factors in the time complexity, we will carry out the analysis using them. Therefore,
let tn and n be the number of nonzero terms in A and B, respectively. If tn > 0 and n > 0,
the while loop is entered. Each iteration of the loop requires 0(1) time. At each
The Polynomial Abstract Data Type 65
iteration, we increment the value of starta or startb or both. Since the iteration ter
minates when either starta or startb exceeds finisha or finishb, respectively, the number
of iterations is bounded by m + n - 1. This worst case occurs when:
n n
2Z + 1
A(x) = 2 X 2i and S(x) = x
z=o i=Q
The time for the remaining two loops is bounded by O(n + m) because we cannot
iterate the first loop more than m times and the second more than n times. So, the asymp
totic computing time of this algorithm is O(a2 +m). □
Before proceeding let us briefly consider a few of the problems with the current
representation. We have seen that, as we create polynomials, we increment avail until it
equals MAX-TERMS. When this occurs, must we quit? Given the current representa
tion, we must unless there are some polynomials that we no longer need. We could write
a compaction function that would remove the unnecessary polynomials and create a
large, continuous available space at one end of the array. However, this requires data
movement which takes time. In addition, we also must change the start and end indices
for each polynomial moved, that is, we change the values of starti and finishi for all
polynomials moved. In Chapter 3, we let you experiment with some "simple" compact
ing routines.
EXERCISES
1. Write functions readpoly and printpoly that allow the user to create and print poly
nomials.
66 Arrays And Structures
2. Write a function, pmult, that multiplies two polynomials. Figure out the comput
ing time of your function.
3. Write a function, peval, that evaluates a polynomial at some value, xq. Try to
minimize the number of operations.
4. Let A(x) = -I- 2«-2
”^ -i- • • • + x^ -i- x® and B(x) = *x^" -i- + x^
+ x^"
X ^ X -h
+ x^" + •■ •* •' -i- X -t- x.
For these polynomials, determine the exact number of times each statement of
padd is executed.
5. The declarations that follow give us a third representation of the polynomial ADT.
terms [/ ][0].expon gives the number of nonzero terms in the /th polynomial. These
terms are stored, in descending order of exponents, in positions /emw[Z][l],
terms . Create the functions readpoly. printpoly, padd, and pmult for
this representation. Is this representation better or worse than the representation
used in the text? (You may add declarations as necessary.)
2.4.1 Introduction
We now turn our attention to a mathematical object that is used to solve many problems
in the natural sciences, the matrix. As computer scientists, our interest centers not only
on the specification of an appropriate ADT, but also on finding representations that let us
efficiently perform the operations described in the specification.
In mathematics, a matrix contains m rows and n columns of elements as illustrated
in Figure 2.3. In this figure, the elements are numbers. The first matrix has five rows and
three columns; the second has six rows and six columns. In general, we write m x n
(read "m by n") to designate a matrix with m rows and n columns. The total number of
elements in such a matrix is mn. If m equals n, the matrix is square.
The standard representation of a matrix in computer science is a two-dimensional
array defined as a[MAX~ROWS][MAX-COLS]. With this representation, we can locate
quickly any element by writing a[Z][y], where i is the row index and j is the column
index. However, there are some problems with the standard representation. For
The Sparse Matrix Abstract Data Type 67
(a) (b)
instance, if you look at Figure 2.3(b), you notice that it contains many zero entries. We
call this a sparse matrix. Although it is difficult to determine exactly whether a matrix is
sparse or not, intuitively we can recognize a sparse matrix when we see one. In Figure
2.3(b), only 8 of 36 elements are nonzero and that certainly is sparse.
Since a sparse matrix wastes space, we must consider alternate forms of represen
tation. The standard two-dimensional array implementation simply does not work when
the matrices are large since most compilers impose limits on array sizes. For example,
consider the space requirements necessary to store a 1000 x 1000 matrix. If this matrix
contains mostly zero entries we have wasted a tremendous amount of space. Therefore,
our representation of sparse matrices should store only nonzero elements.
Before developing a particular representation, we first must consider the opera
tions that we want to perform on these matrices. A minimal set of operations includes
matrix creation, addition, multiplication, and transpose. Structure 2.3 contains our
specification of the matrix ADT.
Before implementing any of these operations, we must establish the representation
of the sparse matrix. By examining Figure 2.3, we know that we can characterize
uniquely any element within a matrix by using the triple <row, col, value >. This means
that we can use an array of triples to represent a sparse matrix. Since we want our tran
spose operation to work efficiently, we should organize the triples so that the row indices
are in ascending order. We can go one step further by also requiring that all the triples
for any row be stored so that the column indices are in ascending order. In addition, to
ensure that the operations terminate, we must know the number of rows and columns,
and the number of nonzero elements in the matrix. Putting all this information together
suggests that we implement the Create operation as below:
68 Arrays And Structures
structure Sparse-Matrix is
objects: a set of triples, <row, column, value>, where row and column are integers and
form a unique combination, and value comes from the set item.
functions:
for all a,be Sparse-Matrix, x e item, i, j, max-col, max-row g index
fl[0] 6 6 8 h[0] 6 6 8
[1] 0 0 15 [1] 0 0 15
[2] 0 3 22 [2] 0 4 91
[3] 0 5 -15 [3] 1 1 11
[4] 1 1 11 [4] 2 1 3
[5] 1 2 3 [5] 2 5 28
[6] 2 3 -6 [6] 3 0 22
[7] 4 0 91 [7] 3 2 -6
[8] 5 2 28 [8] 5 0 -15
(a) (b)
Figure 2.4(b) shows the transpose of the sample matrix. To transpose a matrix we must
interchange the rows and columns. This means that each element <2[Z][y] in the original
matrix becomes element A>L/][/] in the transpose matrix. Since we have organized the
original matrix by rows, we might think that the following is a good algorithm for tran
sposing a matrix:
Unfortunately, if we process the original matrix by the row indices we will not
know exactly where to place element <7, /, value> in the transpose matrix until we have
processed all the elements that precede it. For instance, in Figure 2.4, we have:
70 Arrays And Structures
The algorithm indicates that we should "find all the elements in column 0 and store
them in row 0 of the transpose matrix, find all the elements in column 1 and store them in
row 1, etc." Since the original matrix ordered the rows, the columns within each row of
the transpose matrix will be arranged in ascending order as well. This algorithm is
incorporated in transpose (Program 2.7). The first array, a, is the original array, while
the second array, b, holds the transpose.
It is not too difficult to see that the function works correctly. The variable,
currentb, holds the position in b that will contain the next transposed term. We generate
the terms in b by rows, but since the rows in b correspond to the columns in a, we collect
the nonzero terms for row i of b by collecting the nonzero terms from column i of a.
Analysis of transpose: Determining the computing time of this algorithm is easy since
the nested for loops are the decisive factor. The remaining statements (two if statements
and several assignment statements) require only constant time. We can see that the outer
for loop is iterated a [0].cc»/ times, where a [0].co/ holds the number of columns in the
original matrix. In addition, one iteration of the inner for loop requires a [Oj.vaZwe time,
where a [Q].value holds the number of elements in the original matrix. Therefore, the
total bme for the nested for loops is columns • elements. Hence, the asymptotic time
complexity is Q(columns • elements). □
for (j 0;
=0; j < columns; j++)
for (i = 0; i < rows; i++)
b[j ] [i] - a [i] [j ] ;
ttdefine MAX—COL 50 *
/
maximum number of columns + 1
/
*
this representation we save both time and space. This was not true of transpose since the
number of elements is usually greater than m£Lx[columns, rows} and columns • elements
is always at least columns ■ rows. In addition, the constant factor for transpose is bigger
than that found in the two-dimensional array representation. However, transpose
requires less space than fast-transpose since the latter function must allocate space for
the row-terms and starting-pos arrays. We can reduce this space to one array if we put
the starting positions into the space used by the row terms as we calculate each starting
position. □
If we try the algorithm on the sparse matrix of Figure 2.4(a), then after the execu
tion of the third for loop, the values of row-terms and starting-pos are:
The number of entries in row i of the transpose is contained in row-terms[i\. The start
ing position for row i of the transpose is held by starting-pos[i].
~ S ^ik ^kj
k=()
The product of two sparse matrices may no longer be sparse, as Figure 2.5 shows.
n 0 01 fl 1 11 n 1 11
1 0 0 0 0 0 1 1 1
1 0 0 0 0 0 1 1 1
We would like to multiply two sparse matrices represented as an ordered list (Fig
ure 2.4). We need to compute the elements of D by rows so that we can store them in
their proper place without moving previously computed elements. To do this we pick a
row of A and find all elements in column j of B for j = 0, 1, • • • , cols-b - 1. Normally,
we would have to scan all of B to find all the elements in column j. However, we can
avoid this by first computing the transpose of B. This puts all column elements in con
secutive order. Once we have located the elements of row i of A and column j of B we
just do a merge operation similar to that used in the polynomial addition of Section 2.2.
(We explore an alternate approach in the exercises at the end of this section.)
To obtain the product matrix D, mmult (Program 2.9) multiplies matrices A and B
using the strategy outlined above. We store the matrices A, B, and D in the arrays a, b,
and d, respectively. To place a triple in d and to reset sum to 0, mmult uses storesum
(Program 2.10). In addition, mmult uses several local variables that we will describe
briefly. The variable row is the row of A that we are currently multiplying with the
columns in B. The variable row-begin is the position in a of the first element of the
current row, and the variable column is the column of B that we are currently multiplying
with a row in A. The variable totald is the current number of elements in the product
matrix D. The variables i and j are used to examine successively elements from a row of
A and a column of B. Finally, the variable new-b is the sparse matrix that is the tran
spose of b. Notice that we have introduced an additional term into both a
(a[foZaZfl-i-l].row = rows-a;) and new-b (new-b[totalb+i].TO'w = cols-b;). These
dummy terms serve as sentinels that enable us to obtain an elegant algorithm.
column = new—b[j].row;
}
else if (new—b[j].row != column) {
storesum (d, Sctotald, row, column, &sum) ;
1 = row—begin;
column = new—b[j].row;
}
else switch (COMPARE(a[i] .col, new—b[j] .col)) {
case -1: /'^ go to next term in a *
/
i + +; break;
case 0: *
/ add terms, go to next term in a and b
/
*
sum + = ( a[i++].value ■k new—b[j++].value);
break;
case 1 : /
* advance to next term in b
D++;
}
} / •jk end of for j
/
* totalb+1 */
for {; a[i].row == row; i + +)
row—begin = i; row = a [ i ] .
} end of for i<=totala
d[0].row = rows
d[0].col cols—b; d[0].value totald;
}
Analysis of mmult: We leave the correctness proof of mmult as an exercise and consider
only its complexity. Besides the space needed for a, /?, d, and a few simple variables, we
also need space to store the transpose matrix new-b. We also must include the addi
tional space required by fast-transpose. The exercises explore a strategy for mmult that
does not explicitly compute new-h.
We can see that the lines before the first for loop require only O{cols-b + totalb)
time, which is the time needed to transpose b. The outer for loop is executed totala
times. At each iteration either i or j or both increase by 1, or i and column are reset. The
maximum total increment in j over the entire loop is totalb + 1. If termsrow is the total
number of terms in the current row of A, then i can increase at most termsrow times
before i moves to the next row of A. When this happens, we reset / to row-begin, and. at
76 Arrays And Structures
the same time, advance column to the next column. Thus, this resetting takes place at
most cols-b time, and the total maximum increment in i is cols-b^termsrow. There
fore, the maximum number of iterations of the outer for loop is cols-b +
cols -b^termsrow + totalb. The time for the inner loop during the multiplication of the
current row is O{cols-b^termsrow -I- totalb}, and the time to advance to the next row is
Q){termsrow}. Thus, the time for one iteration of the outer for loop is
Q{cols-b'^termsrow -I- totalb}. The overall time for this loop is:
O('^(cols-b ■ termsrow + totalb}} = O{cols^b • total-a + rows-a • totalb) □
row
Once again we can compare this time with the computing time required to multi
ply matrices using the standard array representation. The classic multiplication algo
rithm is:
The Sparse Matrix Abstract Data Type 77
This algorithm takes O(r67W5-<3 • cols-a - cols~b) time. Since totala < cols~a ■ rows-a
and totalb < cols~a ■ cols-b, the time for mmult is at most:
However, its constant factor is greater than that of the classic algorithm. In the worst
case, when totala = cols-a ■ rows-a or totalb = cols-a • cols-b, mmult is slower by a
constant factor. However, when totala and totalb are sufficiently smaller than their max
imum value, that is, A and 3 are sparse, mmult outperforms the classic algorithm. The
analysis of mmult is not trivial. It introduces some new concepts in algorithm analysis
and you should make sure that you understand the analysis.
This representation of sparse matrices permits us to perform operations such as
addition, transpose, and multiplication efficiently. However, there are other considera
tions that make this representation undesirable in certain applications. Since the number
of terms in a sparse matrix is variable, we would like to represent all our sparse matrices
in one array as we did for polynomials in Section 2.2. This would enable us to make
efficient utilization of space. However, when this is done we run into difficulties in allo
cating space from the array to any individual matrix. These difficulties also occur with
the polynomial representation and will become even more obvious when we study a
similar representation for multiple stacks and queues in Section 3.4.
EXERCISES
1. Write C functions read-matrix, print-matrix, and search that read triples into a
new sparse matrix, print out the terms in a sparse matrix, and search for a value in
a sparse matrix. Analyze the computing time of each of these functions.
2. Rewrite fast-transpose so that it uses only one array rather than the two arrays
required to hold row-terms and starting-pos.
3. Develop a correctness proof for the mmult function.
4. Analyze the time and space requirements of fast-transpose. What can you say
about the existence of a faster algorithm?
78 Arrays And Structures
151
r1 0 0 1 0 11 22
0 110 0 0 -15
0 0 0 10 0 11
0 0 0 0 0 0 3
1 0 0 0 0 0 -6
0 0 1 0 0 0 91
28
(a) On a computer with w bits per word, how much storage is needed to
represent a sparse matrix. A, with t nonzero terms?
(b) Write a C function to add two sparse matrices A and B represented as in Fig
ure 2.6 to obtain D - A + B. How much time does your algorithm take?
(c) Discuss the merits of this representation versus the one used in the text.
Consider the space and time requirements for such operations as random
access, add, multiply, and transpose. Note that we can improve the random
access time by keeping another array, ra, such that ra [/ ] = number of
nonzero terms in rows 0 through z - 1.
rt-i
/=0
where H is the product of the upperi's. For instance, if we declare a as a [10][ 10][ 10],
then we require 10 • 10 ■ 10 = 1000 units of storage to hold the array. There are two
Representation Of Multidimensional Arrays 79
common ways to represent multidimensional arrays: row major order and column major
order. We consider only row major order here, leaving column major order for the exer
cises.
As its name implies, row major order stores multidimensional arrays by rows. For
instance, we interpret the two-dimensional array A [upperQ][upperi ] as upper^ rows,
row-’o, raw J, • • • , raWypp^^Q_i, each row containing upper y elements.
If we assume that a is the address of A [0][0], then the address of A [Z ][0] is oc +
i • upper X because there are Z rows, each of size upper j, preceding the first element in
the Zth row. Notice that we haven’t multiplied by the element size. This follows C con
vention in which the size of the elements is automatically accounted for. The address of
an arbitrary element, a [Zfij], is a + Z • upper j + j.
To represent a three-dimensional array, A[upperQ\[uppery}[upper2}. we interpret
the array as upper0 two-dimensional arrays of dimension upper 1 y. upper2- To locate
u [Z ][7 J[^ ], we first obtain a -1- i • upper 1 • upper! as the address of a [Z ][0][0] because
there are i two-dimensional arrays of size upper 1 • upper2 preceding this element. Com
bining this formula with the formula for addressing a two-dimensional array, we obtain:
as the address of a [z ] [7 ] .
Generalizing on the preceding discussion, we can obtain the addressing formula
for any element A [ZolU i ] • • • [Z„_i ] in an n-dimensional array declared as:
A [upperQ][upperx} . . . [upper^.^]
If a is the address forX [0J[0]. . . [0] then the address of a [/o](O][O]... [0] is:
+ i„_2upper^-i
+
n-l
Notice that aj may be computed from 0 < j < «-!, using only one multiplication as
■ ^7 + 1 • Thus, a compiler will initially take the declared bounds
upper0 , .. . , upperfi-\ and use them to compute the constants using n-2
multiplications. The address of a [zq] ■ • ■ cnn be computed using the formula,
requiring n-1 more multiplications and n additions and n subtractions.
EXERCISES
2.6.1 Introduction
Thus far, we have considered only ADTs whose component elements were numeric. For
example, we created a sparse matrix ADT and represented it as an array of triples
<row, col, value >. In this section, we turn our attention to a data type, the string, whose
component elements are characters. As an ADT, we define a string to have the form, 5 =
5o, .. . , where Si are characters taken from the character set of the programming
language. If n = 0, then S is an empty or null string.
There are several useful operations we could specify for strings. Some of these
operations are similar to those required for other ADTs: creating a new empty string,
reading a string or printing it out, appending two strings together (called concatenation),
or copying a string. However, there are other operations that are unique to our new
ADT, including comparing strings, inserting a substring into a string, removing a sub
string from a string, or finding a pattern in a string. We have listed the essential opera
tions in Structure 2.4, which contains our specification of the string ADT. Actually there
are many more operations on strings, as we shall see when we look at part of C’s string
library in Figure 2.7.
The String Abstract Data Type 81
structure String is
objects: a finite set of zero or more characters.
functions:
for all 5, t G String, i, j, m E non-negative integers
String Null(m) return a string whose maximum length is
m characters, but is initially set to NULL
1111
We write NULL as
Integer Compare(5, z) if 5 equals t
return 0
else if .y precedes t
return -1
else return +1
Boolean IsNull(5) if (Compare(5, NULL))
return FALSE
else return TRUE
Integer Length(5) if (Compare(5, NULL))
return the number of characters in 5
else return 0.
String Concat(5, t) if (Compare(z, NULL))
return a string whose elements are those
of 5 followed by those of t
else return 5.
String Substr(s, z, 7) if ({7 > 0) && (z +7-I) < Length(5))
return the string containing the characters
of 5 at positions z, i + 1, • • • , z +7-I.
else return NULL.
In C, we represent strings as character arrays terminated with the null character \0.
For instance, suppose we had the strings:
Figure 2.8 shows how these strings would be represented internally in memory.
Notice that we have included array bounds for the two strings. Technically, we could
have declared the arrays with the statements:
Function Description
char ^strcat(char ^dest, char *src) concatenate dest and strings;
return result in dest
stmcat(char ^'dest, char *src, intn)
char * concatenate dest and « characters
from 5rc; return result in dest
strl, char *str2)
char ^strcmp(char * compare two strings;
return < 0 if strl < str2\
if 5Zr7 = str2-,
> 0 if 5rr7 > str2
strncmp(char *
char * strl, char ^str2, int n) compare first n characters
return < 0 if 5Zr7 < str2;
0 if 5Zr7 = str2‘,
> 1 if strl > str2
char ^strcpy(char "^dest, char "^src) copy src into dest; return
char '^strncpy(char "^dest, char ^src, int n) copy n characters from src
string into dest; return Jc^z;
size~-t strlen(char 5*) return the length of a 5
char '^strchr(char *5, intc) return pointer to the first
occurrence of c in s;
return NULL if not present
char '^strrchr(char *5, int c) return pointer to last occurrence of
c in 5; return NULL if not present
char '^strtok(char *5, char ^delimiters) return a token from s; token is
surrounded by delimiters
char ^strstr(char *5, char *pat) return pointer to start of
pat in 5
size—tstrspn(char *s, char ^spanset) scan 5 for characters in spanset;
return length of span
spanset)
size-t strcspn(char *s, char * scan 5 for characters not in spanset;
return length of span
char "^strpbrkichar *5, char ^spanset) scan 5 for characters in spanset;
return pointer to first occurrence
of a character from spanset
sEO] sEl] sEZ] sE31 tEOl tElJ tEZl trai t[4] tE51
a o 9 \0 h o u s e \0
Using these declarations, the C compiler would have allocated just enough space to hold
each word including the null character. Now suppose we want to concatenate these
strings together to produce the new string, "doghouse." To do this we use the C function
strcat (See Figure 2.7). Two strings are joined together by strcat {s, t}, which stores the
result in 5. Although 5 has increased in length by five, we have no additional space in 5
to store the extra five characters. Our compiler handled this problem inelegantly: it sim
ply overwrote the memory to fit in the extra five characters. Since we declared t immedi
ately after 5, this meant that part of the word "house" disappeared.
We have already seen that C provides a built-in function to perform concatenation.
In addition to this function, C provides several other string functions which we access
through the statement ttinclude <string.h>. Figure 2.7 contains a brief summary of these
functions (we have excluded string conversion functions such as atoi). For each func
tion, we have provided a generic function declaration and a brief description. Rather
than discussing each function separately, we next look at an example that uses several of
them. For further information on string functions, including examples, look at the books
by Keminghan and Ritchie or Harbison and Steele cited in the References and Selected
Readings section.
Example 2.2 [String insertion]: Assume that we have two strings, say string i and
string 2, and that we want to insert string 2 into string 1 starting at the /th position of
string 1. We begin with the declarations:
ttinclude string.h>
#define MAX-SIZE 100 size
*
/ of largest string
/
*
*
char stringl[MAX-SIZE], s = stringl;
★
char Strings[MAX-SIZE], t = string2;
In addition to creating the two strings, we also have created a pointer for each string.
Now suppose that the first string contains "amobile" and the second contains "uto"
(Figure 2.9). We want to insert "uto" starting at position 1 of the first string, thereby pro
ducing the word "automobile." We can accomplish this using only three function calls,
as Figure 2.9 illustrates. Thus, in Figure 2.9(a), we assume that we have an empty string
that is pointed to by temp. We use strncpy to copy the first / characters from 5 into temp.
84 Arrays And Structures
Since i = 1, this produces the string "a." In Figure 2.9(b), we concatenate temp and t to
produce the string "auto." Finally, we append the remainder of 5 to temp. Since strncat
copied the first i characters, the remainder of the string is at address (5 + /). The final
result is shown in Figure 2.9(c).
5 a m o b i 1 e \0
t u t \0
temp \0
initially
temp a \0
temp u t o \Q
temp a u t o m 0 b i 1 e \0
Program 2.11 inserts one string into another. This particular function is not nor
mally found in <string.h.>. Since either of the strings could be empty, we also include
statements that check for these conditions. It is worth pointing out that the call
strnins{s, t, 0) is equivalent to strcat {t, 5). Program 2.11 is presented as an example of
manipulating strings. It should never be used in practice as it is wasteful in its use of
time and space. Try to revise it so the string temp is not required. □
Now let us develop an algorithm for a more sophisticated application of strings. Assume
that we have two strings, string and pat, where pat is a pattern to be searched for in
string. The easiest way to determine if pat is in string is to use the built-in function strstr.
If we have the following declarations:
The String Abstract Data Type 85
if (t = strstr(string,pat))
printf("The string from strstr is: %s\n",t);
else
printfC’The pattern was not found with strstr\n");
The call (t = strstr (string,pat)) returns a null pointer if pat is not in string. If pat is in
string, t holds a pointer to the start of pat in string. The entire string beginning at posi
tion t is printed out.
Although strstr seems ideally suited to pattern matching, there are two reasons
why we may want to develop our own pattern matching function:
(1) The function strstr is new to ANSI C. Therefore, it may not be available with the
compiler we are using.
(2) There are several different methods for implementing a pattern matching function.
The easiest but least efficient method sequentially examines each character of the
string until it finds the pattern or it reaches the end of the string. (We explore this
approach in the Exercises.) If pat is not in string, this method has a computing
86 Arrays And Structures
time of O(n ■ m) where n is the length of pat and w is the length of string. We can
do much better than this, if we create our own pattern matching function.
if (j == lastp)
return start; / *
successful */
}
return —1;
}
Example 2.3 [Simulation of nfind} *. Suppose pat = "aab" and string = "ababbaabaa."
Figure 2.10 shows how nfind compares the characters from pat with those of string. The
end of the string and pat arrays are held by lasts and lastp, respectively. First nfind com
pares string [endmatch} and pat [lastp}. If they match, nfind uses i and 7 to move
through the two strings until a mismatch occurs or until all of pat has been matched. The
variable start is used to reset i if a mismatch occurs. □
Analysis of nfind-. If we apply nfind to string = "aa • • • a" and pat = "a • • • ab", then the
computing time for these strings is linear in the length of the string O(m), which is cer
tainly far better than the sequential method. Although the improvements we made over
the sequential method speed up processing on the average, the worst case computing
time is still O(« • m). n
The String Abstract Data Type 87
a a b
T
y lastp
(a) pattern
a b a b b a a b a a
T" T T’
endmatch lasts
(b) no match
a b a b b a a b a a
'T' T nr
endmatch lasts
(c) no match
a b a b b a a b a a
T" T T”
Start endmatch lasts
(d) no match
a b a b b a a b a a
T T"
start endmatch lasts
(e) no match
a b a b b a a b a a
“T T rr
Start endmatch lasts
(f) no match
a b a b b a a b a a
t T rr
Start endmatch lasts
match
pat = 'abcabcacab'
Let 5 = 5o 52 • • • 5^-1 be the string and assume that we are currently determining
whether or not there is a match beginning at 5/. If s^^a then, clearly, we may proceed by
comparing and a. Similarly if 5/ = a and 5j + | b then we may proceed by compar
ing and a. If 5,-5,+ i = ab and Si+2 then we have the situation:
5= a b ? 9 9 ?’
pat = 'a b c a b c a c a Z?’
The ? implies that we do not know what the character in 5 is. The first ? in 5 represents
5,-+2 and 5j+2 c. At this point we know that we may continue the search for a match by
comparing the first character in pat with 5,-+2- There is no need to compare this character
of pat with 5,+i as we already know that 5, + i is the same as the second character of pat,
b, and so 5,+i a. Let us try this again assuming a match of the first four characters in
pat followed by a nonmatch, i.e., 5^+4 b. We now have the situation:
5 = a b c a 9 7 ?’
pat - "a b c a b c a c a h’
We observe that the search for a match can proceed by comparing 5,-+4 and the second
character in pat, b. This is the first place a partial match can occur by sliding the pattern
pat towards the right. Thus, by knowing the characters in the patterm and the position in
the pattern where a mismatch occurs with a character in 5 we can determine where in the
pattern to continue the search for a match without moving backwards in 5. To formalize
this, we define a failure function for a pattern.
Definition: If p = pop j ■ • ■ p„_| is a pattern, then its failure function, f is defined as:
largest i < j such that PoPi " ' Pi- Pj-iPj-i+2 '' ' Pj if such an Z > 0 exists
/(/■) = □
-1 otherwise
The String Abstract Data Type 89
J 0 1 1 3 4 5 6 1 8 9
pat tz b c a b c tz c a b
f -1 -1 -1 0 1 1 3 -1 0 1
From the definition of the failure function, we arrive at the following rule for pat
tern matching; If a partial match is found such that Si^j • • • ’ Pj-\
Si Pj then matching may be resumed by comparing s^ and if j 0. If j =
then we may continue by comparing azzt/ po. This pattern matching rule translates
into function pmatch (Program 2.13). The following declarations are assumed:
linclude <stdio.h>
#include <string.h>
#define max_string_size 100
#define max_pattern_si2e 100
int pmatch();
void fail{);
int failure[max_pattern_size];
char string[max_string_size]t
char pat[max_pattern_size] ;
Note that we do not keep a pointer to the start of the pattern in the string. Instead
we use the statement:
This statement checks to see whether or not we found the pattern. If we didn’t find the
pattern, the pattern index index j is not equal to the length of the pattern and we return
-1. If we found the pattern, then the starting position is / - the length of the pattern.
Analysis of pmatch: The while loop is iterated until the end of either the string or the
pattern is reached. Since i is never decreased, the lines that increase i cannot be exe
cuted more than m = strlen (string) times. The resetting of j to failure[j~l] + l decreases
the value of j. So, this cannot be done more times than j is incremented by the statement
j ++ as otherwise, j falls off the pattern. Each time the statement j ++ is executed, i is
also incremented. So, j cannot be incremented more than m times. Consequently, no
statement of Program 2.13 is executed more than m times. Hence the complexity of
function pmatch is O(m) = O(strlen (string)). □
From the analysis of pmatch, it follows that if we can compute the failure function
in O(strlen (pat)) time, then the entire pattern matching process will have a computing
time proportional to the sum of the lengths of the string and pattern. Fortunately, there is
a fast way to compute the failure function. This is based upon the following restatement
of the failure function:
-1 if 7 = 0
f<J) = f” (j - 1) + 1 where m is the least integer k for which = pj
-1 if there is no k satisfying the above
This definition yields the function in Program 2.14 for computing the failure function of
a pattern.
Analysis of fail: In each iteration of the while loop the value of i decreases (by the
definition of/). The variable i is reset at the beginning of each iteration of the for loop.
However, it is either reset to -1 (initially or when the previous iteration of the for loop
goes through the last else clause) or it is reset to a value 1 greater than its terminal value
on the previous iteration (i.e., when the statement failure[j] = Z + 1 is executed). Since
the for loop is iterated only n-[ (n is the length of the pattern) times, the value of i has a
total increment of at most n-1. Hence it cannot be decremented more than n~l times.
Consequently the while loop is iterated at most n-\ times over the whole algorithm and
the computing time onfall is O(n) = O(strlen (pat)}. □
The String Abstract Data Type 91
void fail(char *
pat)
{
/* ■ compute the pattern's failure function */
int n = strlen(pat);
failure[0] -1;
for (j=l; j n; j++) {
i = failure[j-1] ;
while ((patlj] != pat[i+1]) && (i 0) )
i failure[i];
if (pat[j] == pat[i+1])
failure[j] i + 1;
else failure[j] = -1;
}
}
Note that when the failure function is not known in advance, the time to first compute
this function and then perform a pettern match is 0{strlen (pat) + strlen (string)).
EXERCISES
1. Write a function that accepts as input a string and determines the frequency of
occurrence of each of the distinct characters in string. Test your function using
suitable data.
2. Write a function, strndel, that accepts a string and two integers, start and length.
Return a new string that is equivalent to the original string, except that length
characters beginning at start have been removed.
3. Write a function, strdeh that accepts a string and a character. The function returns
string with the first occurrence of character removed.
4. Write a function, strpos}, that accepts a string and a character. The function
returns an integer that represents the position of the first occurrence of character
in string. If character is not in string, it returns -1. You may not use the function
strpos which is part of the traditional <string.h> library, but not the ANSI C one.
5. Write a function, strchr 1, that does the same thing as strpos 1 except that it returns
a pointer to character. If character is not in the list it returns NULL. You may not
use the built-in function strchr.
92 Arrays And Structures
6. Modify Program 2.11 so that it does not use a temporary string temp. Compare the
complexity of your new function with that of the old one.
7. Write a function, strsearch, that uses the sequential method for pattern matching.
That is, assuming we have a string and a pattern, strsearch examines each charac
ter in string until it either finds the pattern or it reaches the end of the string.
8. Show that the computing time for nfind is O(/i • w) where n and m are, respec
tively, the lengths of the string and the pattern. Find a string and a pattern for
which this is true.
9. Compute the failure function for each of the following patterns:
(a) aaaab
(b) ababa a
(c) ab aab aab
10. Show the equivalence of the two definitions for the failure function.
Several texts discuss array representation in C, including T. Plum, Reliable Data Struc
tures in C, Plum Hall, Cardiff, N.J., 1985 (Chapter 3), and R. Jaesche, Solutiofis in C,
Addison-Wesley, Reading, Mass., 1986 (Chapter 2). The Jaesche text includes a detailed
discussion of array bounds, including altering array bounds, and pointer addressing.
Strings are discussed in B. Kemighan and K. Ritchie, The C Programming
Language, ANSI C, Second Edition, Prentice-Hall, Englewood Cliffs, N.J., 1988, and S.
Harbison and G. Steele, C: A Reference Manual, Third Edition, Prentice-Hall, Engle
wood Cliffs, N.J., 1991 (Chapter 13). A discussion of string pointers can be found in R.
Traister, Mastering C Pointers, Academic Press, San Diego, Calif., 1990. The Knuth,
Morris, Pratt pattern matching algorithm is found in "Fast pattern matching in strings,"
SIAM Journal on Computing, vol. 6, no. 2, 1977.
I. Given an array a[n] produce the array z[n] such that ^[0] = a [n—l],z[l] =
a[n~2], ••• ,z[n-2] =a[l],z[«-13 = <7[0]. Use a minimal amount of storage.
2. An m X n matrix is said to have a saddle point if some entry a [Z Jlj ] is the smallest
value in row i and the largest value in column j. Write a C function that deter
mines the location of a saddle point if one exists. What is the computing time of
your method?
3. A triangular matrix is one in which either all the elements above the main diago
nal or all the elements below the main diagonal of a square matrix are zero. Figure
2.11 shows a lower and an upper triangular matrix. In a lower triangular matrix,
a, with n rows, the maximum number of nonzero terms in row i is i +1. Thus, the
total number of nonzero terms is
n-l
X XXX X X XXX X X
X X X X
X X X X
X X X non X
X X zero X zero X
X non X X X
X zero X zero X X
X X X X
X X X X
X XXX X X X XXX X
4. Let a and b be two lower triangular matrices, each with n rows. The total number
of elements in the lower triangles is «(zi -h 1). Devise a scheme to represent both
triangles in an array ). [Hint: Represent the triangle of a in the lower tri
angle of d and the transpose b in the upper triangle of d.] Write algorithms to
determine the values of a [i ][/ ], b [i ][/ ], 0 < i, J < n.
5. A tridiagonal matrix is a square matrix in which all elements that are not on the
major diagonal and the two diagonals adjacent to it are zero (Figure 2.12). The
elements in the band formed by these three diagonals are represented by rows in
an array, b, with a [0]f0] being stored in Z?[0]. Obtain an algorithm to determine
the value of a [Z ][7 ], 0 < i. j < n from the array b.
94 Arrays And Structures
X X
XXX
XXX
. X
zero
zero X X
XXX
XXX
XXX
6. A square band matrix is an n x n matrix in which all the nonzero terms lie in
a band centered around the main diagonal. The band includes the main diagonal
and <3-1 diagonals below and above the main diagonal (Figure 2.13).
a diagonals
upper band
lower
band n
rows
0
^4,3
n columns
main diagonal
Obtain an addressing formula for the location of an element, d/j, in the lower band of
(location(d lo) = 2 in the example above).
7. A generalized band matrix D^ a^b is an az x n matrix in which all the nonzero terms
lie in a band made up of <3-1 diagonals below the main diagonal, the main diago
nal, and b-i bands above the main diagonal (Figure 2.14).
n
rows
n columns
main diagonal
^n.a,h
(c) Obtain a sequential representation of the band D^^a^b in the one dimensional
array e. For this representation, write a C function value (n, a, b, i, j, e)
that determines the value of element da in the matrix D,'n,a.b. The band of
^n.a,.7, is represented in the array e.
8. A complex-valued matrix X is represented by a pair of matrices <a, b>, where a
and b contain real values. Write a function that computes the product of two
complex-valued matrices <a, b > and <d, e >, where <a, /? > * <J, e > = (a + ib)
* (d + ie) = (ad - be) + i (ae + bd). Determine the number of additions and mul
tiplications if the matrices are all nxn.
9. § [Programming project] There are a number of problems, known collectively as
"random walk" problems, that have been of longstanding interest to the mathemati
cal community. All but the most simple of these are extremely difficult to solve,
and, for the most part, they remain largely unsolved. One such problem may be
stated as:
An n X m array count is used to represent the number of times our cockroach has
reached each tile on the floor. All the cells of this array are initialized to zero.
The position of the bug on the floor is represented by the coordinates (ibug, jbug).
The eight possible moves of the bug are represented by the tiles located at
(ibug + imove [k ], jbug + jmove [k j), where 0 < k < 7, and
Additional Exercises 97
zwove[0) = -1 7m<?ve[0] = 1
= 0 /move[l] = 1
zm6)v^[2] = 1 jmove[2] = 1
imove[3] = 1 jmovel3] = 0
zm(9v^[4] = 1 7move’[4] = -1
zm(9v^[5] = 0 ym<7ve[5] = -1
imove[6] = -1 jmovel6] = -1
zm<?v^[7] = -1 7m€>v^[7] = 0
A random walk to any one of the eight neighbor squares is simulated by generat
ing a random value for k, lying between 0 and 7. Of course, the bug cannot move
outside the room, so that coordinates that lead up a wall must be ignored, and a
new random combination formed. Each time a square is entered, the count for that
square is incremented so that a nonzero entry shows the number of times the bug
has landed on that square. When every square has been entered at least once, the
experiment is complete.
The crucial decision in solving this problem concerns the data representation. Fig
ure 2.15 shows the chess board represented as a two-dimensional array.
0 1 2 3 4 5 6 7
2 7 0
3 6 1
4 K
5 5 2
4 3
The eight possible moves of a knight on square (4, 2) are also shown in this figure.
In general, a knight may move to one of the squares (i - 2, 7 4- 1), (/ -1,74- 2),
(/ + 1,7 + 2), {i + 2,7 + 1), (z + 2,7 - 1), (z + 1,7 - 2), (z - 1,7 - 2), (z -2J- 1).
However, notice that if (z, j) is located near one of the board’s edges, some of
these possibilities could move the knight off the board, and, of course, this is not
permitted. We can represent easily the eight possible knight moves by two arrays
ktmove 1 and ktmove 2 as:
Additional Exercises 99
ktmove 1 ktmove 2
-2 1
-1 2
1 2
2 1
2 -I
1 -2
-1 -2
-2 -1