Chapter 8: Arrays (Single and Double Dimensional)
In the middle of the 1950s, a team at IBM led by John Backus built the first compiler for FORTRAN, a language meant for people doing heavy numerical work. Those users had a problem that had nothing to do with mathematics. A scientist working with two hundred readings could not sensibly invent two hundred variable names, and even if she did, no loop could visit them, because a loop can change a number but it cannot change a name.
FORTRAN’s answer was the DIMENSION statement. You declared one name and said how many cells it stood for, and from then on you reached a cell by writing the name with a number beside it. The number could be held in a variable. That single change is what made loops and data fit together, and almost every language since has kept the idea.
One detail took longer to settle. Should the first cell be number one, as counting normally begins, or number zero? In 1982 the computer scientist Edsger W. Dijkstra circulated a short note arguing that numbering ought to start at zero, because it makes the arithmetic of ranges come out cleanly. Java agrees with him, which is why your first element lives at index 0 — and why so many first programs run one step too far.
Diagram
Fig. 8.0-1 – a long horizontal strip of ten equal memory cells under one common name, labelled with indices 0 to 9 below the cells and sample marks inside them; a single variable named i drawn as a small box with an arrow pointing at the cell it currently selects; caption area showing the same cells reachable by changing i rather than by changing the name
This chapter will answer each of these questions as it goes.
8.1 Why a Single Variable Is Not Enough
A class teacher enters the marks of forty students, adds them, finds the average, and then wants to know how many students scored above that average. The first two tasks are easy. The third one quietly changes everything, because it cannot be done until every mark has already been seen — and to compare a mark with the average, the mark must still be there when the average is known.
That is the real problem this chapter solves. It is not about handling many values. It is about handling many values that all have to stay.
One variable holds one value
A variable of a primitive type names exactly one location in memory. Assigning to it replaces whatever was there. Consider this fragment, which reads ten numbers using a Scanner object named in:
int m = 0;
for (int i = 1; i <= 10; i++)
{
m = in.nextInt();
}Ten numbers are typed, ten assignments happen, and at the end m holds the tenth. The other nine are gone. They were never lost by mistake; they were overwritten, one by one, exactly as the program instructed. Any question that needs to look back — the average, the highest, the count above average — is now unanswerable.
Ten variables are worse, not better
The obvious repair is to use ten variables: m1, m2, m3, and so on. For ten values this is merely tedious. For forty it is unworkable, and for a number decided while the program runs it is impossible.
The deeper objection is not length. It is that a name is fixed when the program is compiled, while a value can change while the program runs. A loop can count i from 1 to 10, but no loop can turn the name m1 into the name m2. So a set of separate variables cannot be visited by a loop at all, and every operation on them — reading, adding, comparing, printing — must be written out by hand, once per variable.
What is needed is a way to keep the name fixed and let a number select which value we mean, because a number can be held in a variable, and a variable can be changed by a loop.
One name, many cells
That is exactly what an array provides. An array is a block of memory holding a fixed number of values of the same type, all reachable through one common name, with a number attached to that name deciding which one is meant. Each individual value in the block is called an element.
If the array is named m, then m[0], m[1], m[2] … are its elements, and the number in the brackets may be written as a literal or supplied by a variable. Because it may be supplied by a variable, a loop can now do the walking:
- to read forty marks, run the loop and let the loop counter choose the cell;
- to add them, run the loop again over the same cells;
- to count those above the average, run it a third time — and the marks are all still there.
An array is Java’s simplest structured data type: a single type built out of many values of one other type, held together as one object.
Every part of that sentence does work. Fixed number — the size is decided when the array is created and cannot grow afterwards. Same data type — an array of int holds only int values. One name — there is a single identifier for the whole collection. An integer position — and it is that integer, not a name, that makes loops possible.
Diagram
Fig. 8.1-1 – two panels side by side. Left panel: five separate boxes labelled m1, m2, m3, m4, m5, each holding one mark, with a crossed-out arrow from a loop counter i showing that i cannot select among them. Right panel: one contiguous strip of five cells under the single name m, holding the same marks, positions 0 to 4 written below the cells, and an arrow from the loop counter i pointing to the cell at position i, showing that changing i moves the arrow
| Feature | Separate variables | Array |
|---|---|---|
| Naming | one name for each value | one name for the whole collection |
| Selecting a value | by writing the name | by an integer position that a variable can supply |
| Use with a loop | not possible | natural; the loop counter selects the element |
| Number of values | must be decided and written out in the source code | size fixed when the array is created, but the code does not change with it |
| Types held | each variable may be of a different type | every element is of the same type |
| Code length | grows with the number of values | stays the same however many values there are |
What arrays are good at, and what they are not
Two properties matter throughout this chapter. The first is a strength: reaching an element by its position takes the same small amount of work whatever the position is, so m[499] is found as fast as m[1]. Nothing is counted or skipped over; the position is turned into an address by arithmetic. Section 8.9 shows exactly how that arithmetic works.
The second is an honest limitation, and it is really three. An array cannot change its size after it is created. It cannot hold a mixture of types. And because the elements sit in consecutive positions, putting a new value into the middle or taking one out of the middle forces the rest to shift, one cell at a time — you will write that shifting yourself in section 8.7.
None of this makes arrays a poor choice. It makes them a specific choice: right for a known quantity of same-typed data that must be visited repeatedly, wrong for a collection that keeps growing and shrinking unpredictably.
8.2 The Single Dimensional Array
When every element of an array is selected by a single position number, the array is called a single dimensional array. It is the array in its plainest form: one row of cells, one number to pick a cell. Everything else in this chapter is built on it, including the two-dimensional table you will meet in section 8.8.
8.2.1 Declaring and Creating an Array
Bringing an array into existence takes two distinct steps, and Java lets you write them together or apart.
The first step declares a variable that is allowed to refer to an array of a stated type. The second step actually creates the block of cells and fixes how many there are. The new operator does the creating.
int marks[] = new int[10];Read it in pieces. int is the type of every element. marks is the name of the whole collection. The empty pair of brackets after the name says that marks is an array variable rather than a single int. new int[10] sets aside ten int cells in memory and hands back a way of reaching them, which is stored in marks.
The brackets may sit on either side of the name, and both forms mean exactly the same thing:
int marks[] = new int[10];
int[] marks2 = new int[10];Placing the brackets next to the type — int[] marks2 — reads more naturally as “an array of int called marks2”, and is the form you will see most often in modern code. This book uses whichever form makes a particular line clearest; neither is more correct than the other.
Diagram
Fig. 8.2-1 – anatomy of the statement int marks[] = new int[10]; with labelled callouts pointing to each part: ‘int’ labelled element type, ‘marks’ labelled name of the array, ‘[ ]’ labelled marks the variable as an array, ‘new’ labelled creates the block in memory, ‘[10]’ labelled number of cells; below the statement, a strip of ten empty cells with positions 0 to 9 written underneath
Declaring now, creating later
The two steps can also be separated, which is useful when the size is not known until the program runs:
int marks[];
int n = in.nextInt();
marks = new int[n];Here the size comes from a value typed by the user. Java evaluates the expression inside the brackets at the moment new runs, so any int expression is allowed — a literal, a variable, or a calculation. The size is fixed at that moment and cannot change afterwards.
The variable holds a reference, not the cells
An array in Java is an object. The variable marks does not contain the ten values; it contains a reference — a way of reaching the block where the ten values live. This is why the declaration and the creation are separate steps at all: until new runs, the variable refers to nothing, and its value is the special literal null.
Diagram
Fig. 8.2-2 – a small box labelled marks on the left containing an arrow-like reference value, an arrow from it leading to a separate contiguous block of ten cells on the right labelled positions 0 to 9; a caption line indicating that the variable holds the reference while the block holds the elements
That distinction has one practical consequence worth noticing early. Writing b = a between two array variables does not copy any elements; it makes b refer to the same block as a, so a change made through b is visible through a. Copying elements has to be done element by element, with a loop.
8.2.2 Subscripts and the Range 0 to n–1
The number written in brackets after the array name is called the subscript, or equivalently the index, of the element. The name together with its subscript — marks[3] — is called a subscripted variable, and it behaves in every way like an ordinary variable of the element type. It can be read, assigned to, passed to a method, or used in an expression.
marks[0] = 78;
marks[1] = marks[0] + 5;
System.out.println(marks[1]);In Java the subscripts of an array of size n run from 0 to n – 1. An array created as new int[10] therefore has valid subscripts 0 to 9. There is no marks[10].
The reason is that a subscript is not a count of elements; it is a distance from the beginning of the block. The first element is at the beginning, so its distance is zero. The second is one cell further along, so its distance is one. Counting distances rather than positions is what makes the arithmetic behave: the number of elements between subscripts i and j is simply j – i, with no adjustment, and a loop written as
for (int i = 0; i < marks.length; i++)visits every element exactly once, with the bound stated plainly and no + 1 or – 1 anywhere.
8.2.3 Default Values of Array Elements
An ordinary local variable must be given a value before it is used; the compiler refuses to read one that has not been assigned. Array elements are different. Because an array is an object created by new, Java fills every cell with a default value at the moment of creation, and those values may be read straight away.
The defaults are fixed by the element type.
| Element type | Default value | Printed as |
|---|---|---|
byte, short, int |
zero | 0 |
long |
zero | 0 |
float |
zero | 0.0 |
double |
zero | 0.0 |
char |
the character with code 0 | nothing visible |
boolean |
false | false |
String and any other object type |
no object at all | null |
The last row is worth pausing on. String is not a primitive type, so an array of String does not contain strings; it contains references, and until you put something in each cell, every reference is null. Creating new String[5] therefore creates no strings whatever — only five empty places where strings may later be referred to.
The program below shows the defaults for four common types.
8.2.4 Initialising an Array at the Point of Declaration
When the values are known while the program is being written, they can be listed inside braces, separated by commas. This is called an initialiser list.
int fare[] = {12, 15, 15, 20, 26, 30};Two things happen automatically. The array is created — no new is needed — and its size is taken from the number of values in the list, here six. The values are stored in the order written, so fare[0] is 12 and fare[5] is 30.
The same form works for any element type. Characters go in single quotes and strings in double quotes:
char grade[] = {'A', 'B', 'C', 'D', 'E'};
String subject[] = {"English", "Physics", "Chemistry", "Computer Science"};An initialiser list is only allowed in a declaration. Once the variable exists, values must be placed one at a time, or a fresh array must be created and assigned.
8.2.5 Arrays of Other Data Types
Nothing about the syntax changes when the element type changes. The type appears twice — once in the declaration and once after new — and the two must agree.
| To hold | Written as |
|---|---|
| integers | int m[] = new int[10]; |
| real numbers of single precision | float m[] = new float[10]; |
| real numbers of double precision | double m[] = new double[10]; |
| single characters | char ch[] = new char[10]; |
| truth values | boolean flag[] = new boolean[10]; |
| strings | String name[] = new String[10]; |
An array is strictly of one type. marks[2] = 'A'; is not an error in an int array — the character is converted to its code — but marks[2] = "A"; is, because a String cannot be turned into an int by the compiler. Mixed data needs either separate arrays that run in step, or a class of your own with one field per item of information.
8.2.6 The length Attribute
Every array carries its own size with it. Writing marks.length gives the number of elements in marks, and it is available wherever the array is available — including inside a method that received the array as an argument and knows nothing else about it.
int fare[] = {12, 15, 15, 20, 26, 30};
System.out.println("Number of fares : " + fare.length);
System.out.println("Last subscript : " + (fare.length - 1));This prints Number of fares : 6 and Last subscript : 5. The distinction is the one from section 8.2.2 again: length counts the elements, while the highest legal subscript is one less than that count.
Using length rather than a number typed into the loop is a small habit with a large payoff. A loop written as for (i = 0; i < fare.length; i++) stays correct when the initialiser list grows or shrinks; a loop written as i < 6 silently becomes wrong.
8.3 Putting Values into an Array
An array created by new is real but empty of anything useful — every cell holds the default value for its type. Something has to put the actual data in. There are three routes, and they differ in when the values become known.
The initialiser list of section 8.2.4 is the earliest: the values are known while the program is being written. The three routes in this section cover everything later than that — values the program works out for itself, values handed to it when it is started, and values typed while it is running.
8.3.1 By Assignment
The plainest method is to assign to one subscripted variable at a time.
int stock[] = new int[4];
stock[0] = 25;
stock[1] = 12;
stock[2] = 40;
stock[3] = 7;This is honest but pointless for anything larger than a handful of cells, and it repeats the weakness of separate variables that section 8.1 set out to remove. Assignment becomes powerful only when the subscript is a variable and the value is calculated:
for (int i = 0; i < stock.length; i++)
{
stock[i] = 0;
}One statement now fills every cell, whatever the size of the array. And since the loop counter is available inside the loop, the value stored may depend on the position it is stored at — which is how tables, sequences and patterns get built.
| Pass | i |
i + 1 |
number * (i + 1) |
cell filled |
|---|---|---|---|---|
| 1 | 0 | 1 | 7 | table[0] = 7 |
| 2 | 1 | 2 | 14 | table[1] = 14 |
| 3 | 2 | 3 | 21 | table[2] = 21 |
| 4 | 3 | 4 | 28 | table[3] = 28 |
| 5 | 4 | 5 | 35 | table[4] = 35 |
| 6 | 5 | 6 | 42 | table[5] = 42 |
| 7 | 6 | 7 | 49 | table[6] = 49 |
| 8 | 7 | 8 | 56 | table[7] = 56 |
| 9 | 8 | 9 | 63 | table[8] = 63 |
| 10 | 9 | 10 | 70 | table[9] = 70 |
Result: the loop ends when i reaches 10, because 10 < table.length is false. Nothing is printed by this loop; the trace shows that ten passes fill ten cells, subscripts 0 to 9, and that the value in a cell is not the same thing as the subscript of that cell.
8.3.2 Through the Argument of main
Every Java program starts at main, and main receives an array. Look again at the header you have written many times:
public static void main(String args[])args is an array of String. It holds the command-line arguments — the extra words typed after the class name when the program is launched. Running a program as
java ArgSum 12 8 25 5
gives args a length of 4, with args[0] holding the text "12", args[1] holding "8", and so on. Nothing is read from the keyboard while the program runs; the data arrives with the program itself.
Two points decide how such data is used. First, the elements are text even when they look like numbers, so "12" must be converted before it can be added to anything. Integer.parseInt does that conversion, and returns the int the text represents. Second, the program is told how many arguments it received by args.length, so it need not be written for a fixed count.
Passing an array to a method directly
In a development environment such as BlueJ, a static method can also be invoked on its own from a menu, with its argument typed into a dialogue box. Because of this you may see a class written with a header such as public static void main(int m[]), the values being supplied in braces as {1, 2, 3, 4} when the method is called.
This is worth understanding for what it is: a convenience of the environment for calling a method, not the way a Java program is normally started. When the Java runtime launches a class by itself, it looks for main with a String array parameter and nothing else. A class whose only main takes an int array will compile, but it cannot be started in the usual way.
8.3.3 Reading Values with the Scanner Class
Most programs need values that are not known until they are running. The Scanner class of the java.util package reads them from the keyboard.
import java.util.Scanner;A Scanner object is created once, attached to the standard input stream, and then asked repeatedly for the next value:
Scanner in = new Scanner(System.in);
int n = in.nextInt();Each call reads one item and moves past it, so a loop calling nextInt() fills an array one cell per pass. Which method to call depends on the type wanted.
| Method | Reads and returns |
|---|---|
nextInt() |
the next whole number, as an int |
nextLong() |
the next whole number, as a long |
nextDouble() |
the next real number, as a double |
next() |
the next word, up to the next space, as a String |
nextLine() |
the rest of the current line, as a String |
nextBoolean() |
the next true or false, as a boolean |
Diagram
Fig. 8.3-1 – flow: value typed at the keyboard → standard input stream (System.in) → Scanner object named in → in.nextInt() returns one whole number → assignment to marks[i] → the cell at subscript i inside the array block
There is no method for reading a single character. A character is obtained by reading a word and taking its first letter, which is the source of a common mistake.
One further caution about mixing methods. nextInt() stops as soon as the number ends and leaves the rest of the line, including the end-of-line mark, still waiting. A nextLine() called immediately afterwards therefore returns that leftover remainder rather than the next line typed. Reading a whole line after a number needs an extra nextLine() to clear what is left behind.
Choosing a way to supply values
| Feature | Assignment in the program | Command-line argument | Scanner input |
|---|---|---|---|
| When the values are known | while the program is written, or computed as it runs | when the program is launched | while the program is running |
| Where they come from | the source code, or a calculation | the words typed after the class name | the keyboard |
| Type of the incoming data | already the correct type | always text; must be converted | the type asked for by the method called |
| Who decides how many | the programmer, or the loop | whoever launches the program; read from args.length |
usually read as the first input |
| Suits | tables, patterns, fixed data, clearing an array | short fixed inputs supplied once | data supplied by a person as the program runs |
8.4 Traversing an Array
Visiting every element of an array exactly once, in order, is called a traversal. Almost everything an array is used for is a traversal, or several: totalling, averaging, counting, searching, printing, comparing, copying, sorting. Learning to write one correctly is worth more than learning any single algorithm in this chapter.
A traversal has the same shape every time:
for (int i = 0; i < a.length; i++)
{
// do something with a[i]
}The counter starts at the first subscript, the condition stops it just past the last, and the body works on one element. What changes from one problem to the next is only the body — and whether one pass is enough.
8.4.1 Summing and Averaging the Elements
To total an array, keep a variable outside the loop that carries the running sum, and add one element to it on each pass:
int total = 0;
for (int i = 0; i < a.length; i++)
{
total = total + a[i];
}The variable total must be created before the loop and must start at 0, because it has to survive from one pass to the next and must begin from a known value. A variable used this way — accumulating a result across the passes of a loop — is doing the same work in every array algorithm you will meet.
The average follows from the total, but the division needs care. Both total and a.length are of type int, and dividing one int by another in Java produces an int, with the fractional part discarded.
Why some questions need two passes
Now return to the problem that opened this chapter: how many marks lie above the average? The average is not known until every mark has been added, and each mark has to be compared with it afterwards. So the array must be walked twice — once to total, once to compare. This is exactly the work that cannot be done while the values are being read, and it is the reason the marks had to be stored in the first place.
Diagram
Fig. 8.4-1 – one strip of eight cells holding marks, with two labelled sweeps drawn beneath it. Sweep 1, an arrow left to right, labelled ‘accumulate total’; a box below it showing total growing. Sweep 2, a second arrow left to right, labelled ‘compare each mark with the average’; a box below it showing count growing. A note between the sweeps stating that the average becomes known only when sweep 1 ends
8.4.2 Finding the Maximum and the Minimum
You cannot know the largest value in an array until you have seen them all, so this too is a traversal. The method is to hold a “best so far” and improve it whenever a better candidate appears.
The smallest element is found by the same method with the comparison reversed, and both can be tracked in one traversal. Notice that the loop starts at 1, not 0: element 0 has already been used to set the starting values, and comparing it with itself would achieve nothing.
Diagram
Fig. 8.4-2 – a strip of seven cells holding the values -2, 3, 5, -6, 1, 4, 0 with subscripts 0 to 6 beneath. Above the strip, a marker labelled ‘max so far’ drawn at subscript 0, then moved to subscript 1, then to subscript 2, with the value beside it changing from -2 to 3 to 5; the marker then stays over subscript 2 for the remaining cells, showing that no later element is larger
| Pass | i |
temp[i] |
temp[i] > max? |
max, maxDay after |
temp[i] < min? |
min, minDay after |
|---|---|---|---|---|---|---|
| start | – | – | – | -2, 0 | – | -2, 0 |
| 1 | 1 | 3 | yes | 3, 1 | no | -2, 0 |
| 2 | 2 | 5 | yes | 5, 2 | no | -2, 0 |
| 3 | 3 | -6 | no | 5, 2 | yes | -6, 3 |
| 4 | 4 | 1 | no | 5, 2 | no | -6, 3 |
| 5 | 5 | 4 | no | 5, 2 | no | -6, 3 |
| 6 | 6 | 0 | no | 5, 2 | no | -6, 3 |
Result: the output is Highest : 5 on day 3, Lowest : -6 on day 4, Range : 11. The trace shows the point of the method — max and min are each replaced only twice in seven passes, yet every element is still examined, because a value cannot be dismissed without being compared.
8.4.3 Staying Inside the Bounds
Every array access in Java is checked as the program runs. If a subscript falls outside the range 0 to length - 1, the program stops immediately with an ArrayIndexOutOfBoundsException, naming the offending value. Section 8.2.2 showed the classic way of producing one; the concern here is the choice of loop bounds while traversing.
Not every traversal covers the whole array, and the header must say which elements are wanted.
| Loop header | Elements visited | Typical use |
|---|---|---|
for (i = 0; i < n; i++) |
all, first to last | totalling, counting, printing |
for (i = n - 1; i >= 0; i--) |
all, last to first | printing in reverse, shifting values to the right |
for (i = 1; i < n; i++) |
all except the first | the body compares with a value already taken from a[0] |
for (i = 0; i < n - 1; i++) |
all except the last | the body touches a[i + 1] as well as a[i] |
The last row is the one that catches people. Whenever the body of a loop mentions a[i + 1], the largest i allowed is n - 2, so the condition must be i < n - 1. Written as i < n, the final pass reaches a[n], which does not exist. You will meet this exact situation in bubble sort, where neighbouring elements are compared.
Reverse traversal deserves the same attention. The counter starts at n - 1, because that is the last valid subscript, and the condition is i >= 0 rather than i > 0, or element 0 is never reached.
8.5 Searching an Array
Searching means finding out whether a particular value is present in an array and, if it is, where. The value being looked for is called the search key, and a search should report the position rather than a bare yes or no, because the position is what lets the program do anything useful afterwards — print the matching record, change it, remove it.
There are two standard methods, and the difference between them is not merely speed. One works on any array at all. The other buys its speed by demanding something in return.
8.5.1 Linear Search
The obvious method is to look at the elements one after another until the key turns up or the array runs out. It is called linear search, or equally sequential search, and it is nothing more than a traversal with a comparison in the body.
Two details in that algorithm matter. The variable position starts at −1 because −1 is not a legal subscript, so it can stand for “not found” without being mistaken for an answer. And the search stops as soon as it succeeds; in Java the loop is left with break. A search that keeps going after a match wastes time and, worse, will report the last occurrence instead of the first.
Diagram
Fig. 8.5-1 – a strip of eight cells holding 417, 205, 388, 129, 476, 350, 291, 502 with subscripts 0 to 7 beneath; an arrow moving left to right stopping at subscript 4; the cells at subscripts 0 to 3 marked ‘compared, no match’, the cell at subscript 4 marked ‘match — search stops’, and the cells at subscripts 5 to 7 marked ‘never examined’
| Pass | i |
code[i] |
code[i] == key? |
position after |
loop continues? |
|---|---|---|---|---|---|
| start | – | – | – | −1 | – |
| 1 | 0 | 417 | no | −1 | yes |
| 2 | 1 | 205 | no | −1 | yes |
| 3 | 2 | 388 | no | −1 | yes |
| 4 | 3 | 129 | no | −1 | yes |
| 5 | 4 | 476 | yes | 4 | no, break |
Result: the output is 476 found at subscript 4. The trace shows that five comparisons were enough and the last three elements were never looked at — but had the key been 502, all eight would have been needed, and had it been 300, all eight would have been spent proving it absent.
8.5.2 Binary Search
Think about how you find a name in a printed telephone directory. Nobody starts at the first page. You open it somewhere in the middle, see which half the name must be in, and throw the other half away. Binary search is exactly that method, and it is available for one reason only: the directory is in order.
The array is examined between two markers, first and last, which begin at the two ends. The element midway between them is compared with the key. Three outcomes are possible, and each is decisive:
- the middle element equals the key — the search is over;
- the middle element is smaller than the key — the key, if present, must lie to the right, so
firstmoves tomid + 1; - the middle element is larger than the key — the key, if present, must lie to the left, so
lastmoves tomid - 1.
Whichever happens, roughly half the remaining elements are eliminated by a single comparison, without any of them being examined. When first passes last there is nothing left between the markers, and the key is absent.
Diagram
Fig. 8.5-2 – three stacked strips of the same nine cells holding 12, 25, 34, 47, 56, 63, 78, 85, 91 with subscripts 0 to 8. Strip 1: first at 0, last at 8, mid at 4 (value 56), the cells 0 to 4 shaded out and labelled ‘discarded, 56 < 78’. Strip 2: first at 5, last at 8, mid at 6 (value 78), labelled ‘match’. Strip 3 shows the count: two comparisons against a possible nine
| Pass | first |
last |
mid |
price[mid] |
Comparison with 50 | Marker moved |
|---|---|---|---|---|---|---|
| 1 | 0 | 8 | 4 | 56 | 56 > 50 | last becomes 3 |
| 2 | 0 | 3 | 1 | 25 | 25 < 50 | first becomes 2 |
| 3 | 2 | 3 | 2 | 34 | 34 < 50 | first becomes 3 |
| 4 | 3 | 3 | 3 | 47 | 47 < 50 | first becomes 4 |
| – | 4 | 3 | – | – | first <= last is false |
loop ends |
Result: position is still −1, so the output is 50 is not in the list. Four comparisons settled a question about nine elements, and the failure is detected the moment first overtakes last — the interval has become empty, and an empty interval cannot contain anything.
8.5.3 Choosing Between the Two Searches
The two methods answer the same question and are not interchangeable.
| Feature | Linear search | Binary search |
|---|---|---|
| Requirement on the data | none; sorted or unsorted | the array must be in order |
| Elements examined | one after another from the start | only the middle of a shrinking interval |
| Effect of one comparison | rules out one element | rules out about half the remaining elements |
| Best case | 1 comparison, \(O(1)\) | 1 comparison, \(O(1)\) |
| Worst case | \(n\) comparisons, \(O(n)\) | about \(\log_2 n\) comparisons, \(O(\log n)\) |
| Suits | small arrays, or data that arrives unordered | large arrays that are already kept in order |
| Cost of preparation | none | the array must be sorted, which itself takes work |
The last two rows decide most real choices. Binary search is faster per search but is not free, because keeping the data in order costs something. For a single search of an unsorted array, sorting it first and then searching is slower than simply looking through it. For an array that will be searched again and again — a price list, an index, a directory — the sorting is paid for once and every later search is cheap.
Both methods have been written here for numbers, compared with < and >. The same two algorithms apply unchanged to an array of text; only the comparison changes, since two String values are ordered by the compareTo() method rather than by the relational operators.
8.6 Sorting an Array
Sorting means rearranging the elements of an array so that they run in order — ascending or descending, numerically for numbers and alphabetically for text. It is worth doing for its own sake, because ordered data is easier to read, and worth doing for what it enables: binary search, merging, finding duplicates, ranking.
The syllabus covers three techniques — bubble sort, selection sort and insertion sort. All three produce exactly the same final array. What differs is the strategy: what each one has achieved when it is halfway through, how many comparisons it makes, and how much it moves the data about. That is the real subject of this section.
Every technique here works through a series of passes, one sweep of the data each, and every one of them relies on the swap — exchanging the values in two cells. A swap needs a third variable, because a direct assignment would destroy one of the values before it could be saved:
t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;All three techniques below are shown ascending, on the same six values — 29, 13, 48, 7, 35, 21 — so that their behaviour can be compared honestly at the end.
8.6.1 Bubble Sort
Bubble sort compares neighbours. It walks along the array comparing each element with the one after it, exchanging the pair whenever they are in the wrong order. One such sweep does not sort the array, but it does one thing reliably: the largest value meets every element it passes, wins every comparison, and is carried all the way to the last cell.
That is the whole idea. After the first pass the largest value is in its final place; after the second pass the second largest is in place; and so on. Each pass may therefore stop one cell earlier than the last, since the tail of the array is already settled.
Note the inner bound carefully: j runs only to n-2-i, because the body compares a[j] with a[j+1] and the largest subscript touched must still be n-1. This is the situation section 8.4.3 warned about.
Diagram
Fig. 8.6-1 – six cells holding 29, 13, 48, 7, 35, 21 with subscripts 0 to 5. Five curved brackets drawn above successive neighbouring pairs (0-1, 1-2, 2-3, 3-4, 4-5), each labelled with the comparison made and marked ‘swap’ or ‘no swap’ for the first pass; below the strip, the result of the pass 13, 29, 7, 35, 21, 48 with the final cell shaded and labelled ‘largest value now settled’
Pass (i) |
Comparisons made | Exchanges | Array at end of pass |
|---|---|---|---|
1 (i = 0) |
5 | 4 | 13 29 7 35 21 48 |
2 (i = 1) |
4 | 2 | 13 7 29 21 35 48 |
3 (i = 2) |
3 | 2 | 7 13 21 29 35 48 |
4 (i = 3) |
2 | 0 | 7 13 21 29 35 48 |
5 (i = 4) |
1 | 0 | 7 13 21 29 35 48 |
Result: the array is in ascending order, and 15 comparisons were made in all. The trace exposes a real weakness — the data was already sorted at the end of pass 3, yet passes 4 and 5 ran anyway, comparing values that could not possibly be out of order. Bubble sort as written has no way of noticing that it has finished early.
8.6.2 Selection Sort
Selection sort takes the opposite approach. Instead of nudging values along by repeated exchanges, it looks for the value that belongs in a given position and puts it there in one move.
Think of the array as two regions: a sorted region growing from the left, and an unsorted region occupying the rest. To extend the sorted region by one cell, find the smallest value in the unsorted region and exchange it with the first unsorted cell. Repeat until the unsorted region holds one element, which must already be the largest.
Finding the smallest is the method of section 8.4.2, applied to part of the array rather than all of it.
Diagram
Fig. 8.6-2 – three stacked strips of six cells. Strip 1: 29 13 48 7 35 21, the whole strip labelled ‘unsorted’, an arrow marking 7 at subscript 3 as the minimum, and a double-headed arrow showing the exchange with subscript 0. Strip 2: 7 13 48 29 35 21, cell 0 shaded and labelled ‘sorted region’, the rest labelled ‘unsorted’, minimum 13 already in place at subscript 1. Strip 3: 7 13 21 29 35 48, whole strip shaded and labelled ‘sorted’
Pass (i) |
Unsorted region scanned | Smallest found | min |
Exchange | Array at end of pass |
|---|---|---|---|---|---|
1 (i = 0) |
13 48 7 35 21 | 7 | 3 | a[0] with a[3] |
7 13 48 29 35 21 |
2 (i = 1) |
48 29 35 21 | 13 | 1 | none needed | 7 13 48 29 35 21 |
3 (i = 2) |
29 35 21 | 21 | 5 | a[2] with a[5] |
7 13 21 29 35 48 |
4 (i = 3) |
35 48 | 29 | 3 | none needed | 7 13 21 29 35 48 |
5 (i = 4) |
48 | 35 | 4 | none needed | 7 13 21 29 35 48 |
Result: the output is 7 13 21 29 35 48 with Exchanges made : 2. Fifteen comparisons were still made, exactly as in bubble sort, but the data moved only twice — the trade selection sort offers is many comparisons in exchange for very little movement.
8.6.3 Insertion Sort
Insertion sort works the way most people arrange a hand of playing cards. The cards already held are kept in order; each new card is compared with them from the right, the larger ones are pushed along to make room, and the new card is dropped into the gap.
The array is again seen as a sorted region on the left and an unsorted region on the right, but the sorted region is built differently. Selection sort chooses which value goes into the next position; insertion sort takes whatever value comes next and works out where it goes.
The element being placed must be saved in a temporary variable first, because the shifting is about to overwrite its cell.
Diagram
Fig. 8.6-3 – the array 7 13 29 48 35 21 with subscripts 0 to 5; cells 0 to 3 shaded as ‘already in order’; the value 35 at subscript 4 lifted out into a box labelled temp; a curved arrow showing 48 shifted from subscript 3 to subscript 4; a downward arrow dropping temp into the gap at subscript 3; the resulting strip 7 13 29 35 48 21 shown below
Pass (i) |
temp |
Elements shifted right | Placed at subscript | Array at end of pass |
|---|---|---|---|---|
1 (i = 1) |
13 | 29 | 0 | 13 29 48 7 35 21 |
2 (i = 2) |
48 | none | 2 | 13 29 48 7 35 21 |
3 (i = 3) |
7 | 48, 29, 13 | 0 | 7 13 29 48 35 21 |
4 (i = 4) |
35 | 48 | 3 | 7 13 29 35 48 21 |
5 (i = 5) |
21 | 48, 35, 29 | 2 | 7 13 21 29 35 48 |
Result: the array is sorted after 11 comparisons and 8 shifts. Pass 2 is the interesting one: 48 was already larger than everything to its left, so the while loop did not run at all and the element was simply written back where it came from. That is why nearly sorted data is cheap for this method — most passes end after one comparison.
8.6.4 Comparing the Three Sorting Techniques
All three techniques ran on the same six values and produced the same answer. The differences lie in the accounting.
One further property is worth naming. A sorting method is called stable if two equal values keep their original relative order. This matters when the data carries more than one field — sorting a list of students by marks, where two students have the same mark, a stable method leaves them in the order they were already in. Bubble sort and insertion sort only ever move a value past a strictly greater one, so both are stable. Selection sort exchanges distant elements and can jump one equal value over another, so it is not.
| Feature | Bubble sort | Selection sort | Insertion sort |
|---|---|---|---|
| Basic action | exchange neighbours that are out of order | find the smallest and put it in place | shift the larger elements and insert |
What is settled after pass k |
the k largest values, at the right-hand end |
the k smallest values, at the left-hand end |
the first k+1 elements, in order among themselves |
| Comparisons on the six values used here | 15 | 15 | 11 |
| Comparisons, worst case | \(n(n-1)/2\) | \(n(n-1)/2\) | \(n(n-1)/2\) |
| Comparisons, best case | \(n(n-1)/2\) | \(n(n-1)/2\) | \(n-1\) |
| Movement of data | many exchanges; 8 on this data | at most \(n-1\) exchanges; 2 on this data | shifting, not exchanging; 8 shifts on this data |
| Effect of nearly sorted data | none, unless the method is modified to stop early | none | large; the method becomes close to \(O(n)\) |
| Stable | yes | no | yes |
| Cost | \(O(n^2)\) | \(O(n^2)\) | \(O(n^2)\) worst, \(O(n)\) best |
Read the table as a set of trade-offs rather than a ranking. Selection sort is the one to choose when moving data is expensive and comparing it is cheap, since it moves each element at most once per pass. Insertion sort is the one to choose when the data is likely to be nearly in order already, because only then does its best case appear. Bubble sort is the simplest to write and the easiest to get right, and it is the natural place to start — but on this evidence it does the most work for the least reason.
The one thing all three share is the shape of their worst case. Each performs about \(n^2/2\) comparisons, so multiplying the data by 10 multiplies the work by roughly 100. That is what \(O(n^2)\) means in practice, and it is why sorting a genuinely large collection needs methods beyond this chapter.
8.7 Insertion, Deletion and Merging
Searching and sorting leave an array the same size. The three operations in this section change what the array holds — a value goes in, a value comes out, two lists become one — and each of them runs into the fact established in section 8.1: an array cannot grow.
The way round it is to separate two different numbers. The capacity of an array is how many cells it has, fixed by new. The number of elements actually in use is something the program keeps track of itself, in an ordinary int variable, usually called n. An array of capacity 10 holding 6 values has four spare cells at the end, and those spare cells are what make insertion possible at all.
From here on, n means the count of elements in use, and the elements occupy subscripts 0 to n - 1. Everything from n to capacity - 1 is spare.
8.7.1 Inserting an Element at a Given Position
To put a new value at subscript pos, the cell at pos must first be emptied — and emptying it means moving everything from pos onwards one place to the right.
The direction of that movement is the whole difficulty. Copying a[pos] into a[pos + 1] first would destroy the value already in a[pos + 1] before it had been saved, and the same value would then be copied all the way along. The shifting must therefore begin at the last element and work backwards, so that every cell is copied into a cell whose contents are no longer needed.
Diagram
Fig. 8.7-1 – a strip of ten cells, the first six holding 14, 27, 33, 46, 58, 61 and the last four drawn empty and labelled ‘spare capacity’; subscripts 0 to 9 beneath. Three curved arrows above the strip showing 61 moving from subscript 5 to 6, 58 from 4 to 5 and 46 from 3 to 4, numbered 1, 2, 3 to show the order of the copies; a downward arrow dropping the value 40 into the vacated cell at subscript 3
| Pass | j |
Copy performed | Array (subscripts 0 to 6) after the copy |
|---|---|---|---|
| start | – | – | 14 27 33 46 58 61 0 |
| 1 | 5 | a[6] = a[5] |
14 27 33 46 58 61 61 |
| 2 | 4 | a[5] = a[4] |
14 27 33 46 58 58 61 |
| 3 | 3 | a[4] = a[3] |
14 27 33 46 46 58 61 |
| after loop | 2 | a[3] = 40 |
14 27 33 40 46 58 61 |
Result: seven elements in ascending order. Notice that a value appears twice at the end of each pass — that is the copy in transit, and it is overwritten on the next pass. The last such duplicate, at subscript 3, is removed by the assignment of the new value.
8.7.2 Deleting an Element from a Given Position
Deletion is the same movement in reverse. The element at pos is removed by copying every later element one place to the left, over the gap, and then reducing n by 1.
Here the copying must run forwards, from pos upwards, for the same reason insertion runs backwards: each cell is copied into one whose contents are no longer needed.
Diagram
Fig. 8.7-2 – a strip of five cells holding 8, 19, 23, 31, 45 with subscripts 0 to 4; the cell at subscript 1 marked with a cross and labelled ‘to be deleted’; three curved arrows showing 23 moving from subscript 2 to 1, 31 from 3 to 2 and 45 from 4 to 3, numbered 1, 2, 3; the cell at subscript 4 shown shaded and labelled ‘no longer in use, though the value 45 is still there’
To delete a value rather than a position, search for it first — linear search from section 8.5.1 returns exactly the subscript this algorithm needs, and returns −1 when there is nothing to delete.
8.7.3 Merging Two Sorted Arrays
Merging combines two arrays that are already sorted into a third array that is also sorted. If the first holds m elements and the second holds p, the result holds exactly m + p.
The method rests on one observation. If both lists are in ascending order, then the smallest value not yet used must be at the front of one list or the other — it cannot be hiding further along, because everything further along is larger. So it is enough to compare the two front elements, take the smaller, and move that list’s marker forward by one.
Eventually one list runs out. Whatever is left in the other is already in order and larger than everything taken so far, so it is simply copied across.
Diagram
Fig. 8.7-3 – two source strips one above the other, the first holding 4, 11, 19, 26 and the second holding 7, 9, 22, 30, 41, each with a marker labelled i and j at its current position; a third, longer strip below labelled c with a marker labelled k. Arrows from the two markers meeting at a comparison symbol, and a single arrow from the comparison into the cell at k, showing that one value is copied per step and only one marker advances
| Step | i |
j |
a[i] |
b[j] |
Value copied to c[k] |
k after |
|---|---|---|---|---|---|---|
| 1 | 0 | 0 | 4 | 7 | 4, from a |
1 |
| 2 | 1 | 0 | 11 | 7 | 7, from b |
2 |
| 3 | 1 | 1 | 11 | 9 | 9, from b |
3 |
| 4 | 1 | 2 | 11 | 22 | 11, from a |
4 |
| 5 | 2 | 2 | 19 | 22 | 19, from a |
5 |
| 6 | 3 | 2 | 26 | 22 | 22, from b |
6 |
| 7 | 3 | 3 | 26 | 30 | 26, from a |
7 |
| tail | 4 | 3 | – | 30, 41 | 30 then 41, from b |
9 |
Result: c holds 4 7 9 11 19 22 26 30 41. After step 7 the marker i reached 4, which is a.length, so the first while loop ended and the second array’s remaining two elements were copied straight across without any further comparison.
A last observation ties this section to the previous one. The nine merged values could also have been obtained by copying both arrays into one and sorting it — but that would cost \(O(n^2)\) with the methods of section 8.6, while merging costs \(O(m + p)\). When the order is already there, throwing it away and rebuilding it is the expensive way round.
8.8 The Double Dimensional Array
A single dimensional array is a list. A great deal of ordinary data is not a list but a table: the marks of forty students in five subjects, the fares between six stations, the rainfall of twelve months over ten years. Such data has two directions, and it needs two numbers to pick out one value — which row, and which column.
An array whose elements are selected by two subscripts is called a double dimensional array, and the table it represents is usually called a matrix. Almost everything already learnt carries over: the subscripts still start at 0, the elements are still all of one type, and the size is still fixed at creation.
8.8.1 Creating a Double Dimensional Array
The declaration carries two pairs of brackets, and new is given two sizes:
int m[][] = new int[3][4];This creates a table of 3 rows and 4 columns — twelve cells in all. Row subscripts run from 0 to 2 and column subscripts from 0 to 3, so the twelve elements are m[0][0] to m[2][3]. The first subscript is always the row and the second always the column, and the two are not interchangeable: m[2][3] exists in this array while m[3][2] does not.
Diagram
Fig. 8.8-1 – a grid of 3 rows and 4 columns; column subscripts 0, 1, 2, 3 written above the columns and row subscripts 0, 1, 2 written to the left of the rows; each cell labelled with its full name m[0][0], m[0][1] … m[2][3]; the cell m[2][3] outlined and labelled ‘row 2, column 3 — the last element’
Values may also be listed at the point of declaration, one inner pair of braces per row:
int m[][] = {{5, 8, 2, 9}, {7, 1, 6, 3}, {4, 0, 8, 2}};The number of inner groups fixes the number of rows, and the number of values in each group fixes the number of columns. Written out over three lines, as it usually is, the code takes the shape of the table itself.
Any element type may be used, and the pattern of the declaration never changes.
| To hold | Written as |
|---|---|
| whole numbers | int m[][] = new int[3][4]; |
| real numbers | double m[][] = new double[3][4]; |
| single characters | char ch[][] = new char[3][4]; |
| truth values | boolean flag[][] = new boolean[3][4]; |
| pieces of text | String name[][] = new String[3][4]; |
The default values are the ones listed in Table 8.2, so a new int matrix begins full of zeros and a new String matrix begins full of null.
How many rows, how many columns
Both counts are available from the array itself, but they are asked for differently:
int rows = m.length;
int cols = m[0].length;m.length gives the number of rows. m[0].length gives the number of columns in row 0. The wording is deliberate.
8.8.2 Storing and Displaying the Elements
One subscript needs one loop; two subscripts need two, one inside the other. The outer loop chooses a row and the inner loop runs across that row’s columns, so the elements are visited row by row.
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
// work with m[i][j]
}
}Displaying a matrix uses the same pair of loops with one addition: a println() at the end of each row, after the inner loop has finished, so that the next row starts on a new line. Leaving it out prints the entire table as one long line — a mistake that is easy to see and easy to correct.
8.8.3 Working Row-wise and Column-wise
The two loops can be nested either way round, and swapping them changes what the traversal means.
With i outside and j inside, the elements are visited across row 0, then across row 1, and so on. Reverse the nesting — j outside and i inside — and the elements are visited down column 0, then down column 1. The array is unchanged; only the order of visiting differs, and that order is what makes a row total or a column total possible.
Diagram
Fig. 8.8-2 – two copies of the same 3 by 4 grid. The left copy has a snaking arrow running left to right across row 0, then row 1, then row 2, labelled ‘row-wise: i outside, j inside’. The right copy has a snaking arrow running top to bottom down column 0, then column 1, then 2, then 3, labelled ‘column-wise: j outside, i inside’; the cells are numbered 1 to 12 in each copy to show the differing order of visits
To total a row, the accumulator must be reset at the start of each row and reported at its end — that is, inside the outer loop but outside the inner one. Placing it in the wrong loop is the standard mistake: created before both loops, it accumulates the whole table; created inside the inner loop, it never holds more than one element.
8.8.4 Square Matrices and Their Diagonals
A matrix with as many rows as columns is a square matrix, and the number of rows is called its order. A square matrix of order 4 holds 16 elements, subscripted [0][0] to [3][3].
Only a square matrix has diagonals, and it has two.
The principal diagonal runs from the top left corner to the bottom right. An element lies on it when its row subscript equals its column subscript, so its members are exactly those cells m[i][j] for which i == j. It is also called the left or leading diagonal.
The secondary diagonal runs from the top right corner to the bottom left. For a matrix of order n, an element lies on it when i + j == n - 1. It is also called the right or trailing diagonal.
Both conditions are worth checking against the corners. In a matrix of order 4, i + j == 3 selects m[0][3], m[1][2], m[2][1] and m[3][0] — the top right cell, the bottom left cell, and the two between them. When the order is odd the centre element satisfies both conditions and lies on both diagonals, which is something to allow for when the two sums are being added together.
Diagram
Fig. 8.8-3 – a 4 by 4 grid with row subscripts 0 to 3 on the left and column subscripts 0 to 3 above. The four cells where i equals j are shaded in one tone and labelled ‘principal diagonal, i = j’; the four cells where i + j equals 3 are shaded in a second tone and labelled ‘secondary diagonal, i + j = n − 1’; each shaded cell annotated with its subscript pair
Symmetry
A square matrix is called symmetric when it is unchanged by reflecting it in its principal diagonal — that is, when m[i][j] equals m[j][i] for every pair of subscripts. The matrix above is not symmetric: m[0][1] is 7 while m[1][0] is 9. Testing for symmetry needs a full traversal and a boolean that starts at true and is set to false the moment any mismatched pair is met.
8.9 Address Calculation in Arrays
Section 8.1 claimed that reaching m[499] costs no more than reaching m[1]. This section shows why, and in doing so explains how a table of rows and columns is stored in a memory that has neither.
Memory is a long sequence of numbered storage locations, each holding one byte, and the number of a location is its address. An array occupies a contiguous block of these locations — one element after another, with nothing in between. Two facts follow, and everything in this section comes from them: the position of the first element is known, and every element takes the same number of bytes.
The address of the first element of the block is called the base address, usually written \(B\). The number of bytes occupied by one element is the size of the element type, usually written \(w\).
| Element type | Bytes occupied |
|---|---|
byte |
1 |
short |
2 |
char |
2 |
int |
4 |
float |
4 |
long |
8 |
double |
8 |
These sizes are fixed by the Java language and do not vary from one machine to another, which is one of the reasons a Java program behaves identically everywhere.
8.9.1 Address of an Element in a Single Dimensional Array
Consider an array of int, so \(w = 4\), whose base address \(B\) is 2000. The first element occupies the four bytes beginning at 2000, the second the four beginning at 2004, the third the four beginning at 2008. The element at subscript i therefore begins \(4 \times i\) bytes after the start of the block.
\[\text{Address of } a[i] = B + w \times (i - LB)\]
Here \(LB\) is the lower bound — the smallest legal subscript. In Java the lower bound is always 0, which reduces the formula to
\[\text{Address of } a[i] = B + w \times i\]
The subtraction of \(LB\) is kept in the general form because several other languages allow arrays to be subscripted from 1, or from any value the programmer chooses. What the formula really computes is how many elements lie before this one, and multiplying that count by the element size gives the distance in bytes.
For the array above, the element at subscript 5 begins at \(2000 + 4 \times 5 = 2020\), and the element at subscript 499 begins at \(2000 + 4 \times 499 = 3996\).
Diagram
Fig. 8.9-1 – a horizontal block of memory divided into equal cells of four bytes each; the leftmost cell labelled a[0] with the address 2000 written beneath it, the next cells labelled a[1] at 2004, a[2] at 2008, a[3] at 2012; a bracket spanning from the start of the block to the start of a[3] labelled ‘3 elements × 4 bytes = 12’; the general formula B + w × i shown alongside
Notice what the formula does not do. It does not count along the array, and it does not look at any element on the way. It performs one multiplication and one addition, and the size of the subscript makes no difference to that work.
8.9.2 Row Major Address Calculation
Memory has one dimension; a matrix has two. Storing a matrix therefore means choosing an order in which to lay its elements out in a single line, and there are two standard choices.
Row major order stores the matrix one complete row at a time: the whole of row 0, then the whole of row 1, and so on. It is the order in which we read a printed page, and it is the order used by most languages.
For a matrix of \(R\) rows and \(C\) columns, how many elements come before m[i][j]? Every row before row i is complete, contributing \(i \times C\) elements, and within row i there are j elements to the left. So
\[\text{Address of } m[i][j] = B + w \times \left[ (i - LBR) \times C + (j - LBC) \right]\]
where \(LBR\) and \(LBC\) are the lower bounds of the row and column subscripts and \(C\) is the number of columns. With Java’s lower bounds of 0 this becomes
\[\text{Address of } m[i][j] = B + w \times (i \times C + j)\]
Diagram
Fig. 8.9-2 – a 3 by 4 grid at the top with cells labelled m[0][0] to m[2][3]; below it a single long strip of twelve memory cells; arrows carrying the grid cells into the strip in the order m[0][0], m[0][1], m[0][2], m[0][3], m[1][0] … m[2][3]; the strip annotated ‘row 0’, ‘row 1’, ‘row 2’ in three brackets, and the leftmost cell marked with the base address B
Take an int matrix of 3 rows and 4 columns with base address 1000, so \(w = 4\) and \(C = 4\).
m[0][0]lies at \(1000 + 4 \times (0 \times 4 + 0) = 1000\).m[1][2]lies at \(1000 + 4 \times (1 \times 4 + 2) = 1000 + 24 = 1024\).m[2][3]lies at \(1000 + 4 \times (2 \times 4 + 3) = 1000 + 44 = 1044\), which is the last of the twelve elements.
The number of columns appears in the formula and the number of rows does not. That is not an oversight: to skip over a complete row you need to know how long a row is, and a row is \(C\) elements long. How many rows exist below is irrelevant to how far you must travel.
When the subscripts do not start at zero
Questions are often set on arrays whose subscripts begin somewhere other than 0, and the general form handles them without modification. Suppose A[5][1] is the first element of an array declared as A[5...10][1...8], stored in row major order with a base address of 1500 and 2 bytes per element. Then
- the number of rows is \(10 - 5 + 1 = 6\) and the number of columns is \(C = 8 - 1 + 1 = 8\);
- the address of
A[8][5]is \(1500 + 2 \times \left[ (8 - 5) \times 8 + (5 - 1) \right] = 1500 + 2 \times 28 = 1556\).
Work the two subtractions first, and the rest is arithmetic. The commonest slip is forgetting to subtract the lower bounds, which shifts every answer by a fixed amount.
8.9.3 Column Major Address Calculation
Column major order makes the opposite choice: the whole of column 0 is stored first, then the whole of column 1, and so on. Fortran uses this order, and it appears in numerical libraries descended from it.
The reasoning is the mirror image. Every column before column j is complete, contributing \(j \times R\) elements, and within column j there are i elements above. So
\[\text{Address of } m[i][j] = B + w \times \left[ (j - LBC) \times R + (i - LBR) \right]\]
where \(R\) is the number of rows. With lower bounds of 0,
\[\text{Address of } m[i][j] = B + w \times (j \times R + i)\]
Now it is the number of rows that appears, for the same reason as before — a column is \(R\) elements long.
Diagram
Fig. 8.9-3 – the same 3 by 4 grid at the top with cells labelled m[0][0] to m[2][3]; below it a single long strip of twelve memory cells; arrows carrying the grid cells into the strip in the order m[0][0], m[1][0], m[2][0], m[0][1], m[1][1] … m[2][3]; the strip annotated ‘column 0’, ‘column 1’, ‘column 2’, ‘column 3’ in four brackets, and the leftmost cell marked with the base address B
For the same int matrix of 3 rows and 4 columns with base address 1000, and now \(R = 3\):
m[1][2]lies at \(1000 + 4 \times (2 \times 3 + 1) = 1000 + 28 = 1028\), against 1024 in row major order.m[2][3]lies at \(1000 + 4 \times (3 \times 3 + 2) = 1000 + 44 = 1044\) — the same address as before, because the last element is last in either order.
For the earlier example A[5...10][1...8] with a base address of 1500 and 2 bytes per element, the address of A[8][5] in column major order is \(1500 + 2 \times \left[ (5 - 1) \times 6 + (8 - 5) \right] = 1500 + 2 \times 27 = 1554\).
The full picture is easiest to see side by side. The table below gives the offset of each element of a 3 by 4 matrix — that is, the number of elements stored before it — under both orders.
| Element | Offset, row major | Offset, column major |
|---|---|---|
m[0][0] |
0 | 0 |
m[0][1] |
1 | 3 |
m[0][2] |
2 | 6 |
m[0][3] |
3 | 9 |
m[1][0] |
4 | 1 |
m[1][1] |
5 | 4 |
m[1][2] |
6 | 7 |
m[1][3] |
7 | 10 |
m[2][0] |
8 | 2 |
m[2][1] |
9 | 5 |
m[2][2] |
10 | 8 |
m[2][3] |
11 | 11 |
Every element appears exactly once in each column of the table, and the offsets 0 to 11 are used exactly once each. The two orders store the same data in the same space; they disagree only about which element goes where.
| Feature | Row major order | Column major order |
|---|---|---|
| Order of storage | one complete row after another | one complete column after another |
Elements before m[i][j] |
\(i \times C + j\) | \(j \times R + i\) |
| Which size appears in the formula | the number of columns, \(C\) | the number of rows, \(R\) |
| Elements that end up adjacent in memory | those next to each other in a row | those above and below each other in a column |
| Traversal that runs straight through memory | row-wise: i outside, j inside |
column-wise: j outside, i inside |
| Used by | most languages, including C and Java’s own conventions | Fortran and libraries derived from it |
8.10 Summary
Why arrays exist
- A single variable holds one value, and each assignment destroys the previous one, so data that must be revisited cannot be kept in one variable. Separate variables do not help either, because a name is fixed when the program is compiled while a loop can only change a value — no loop can turn one name into another.
- An array is a collection of a fixed number of elements of the same data type, stored under one name, each selected by an integer position. Because the position can be held in a variable, a loop can visit every element.
- Arrays are the right choice for a known quantity of same-typed data that must be visited repeatedly. Their limitations are real: the size cannot change after creation, the elements must share one type, and inserting or removing a value in the middle forces the rest to shift.
Creating and using a single dimensional array
- Declaring an array variable only creates a name; the
newoperator creates the block of cells and fixes its size. The size may be any integer expression, so it can be decided while the program runs, but it cannot change afterwards. - An array variable holds a reference to the block, not the elements themselves. Assigning one array variable to another gives the same block a second name; it does not copy anything.
- The subscripts of an array of
nelements run from 0 to n − 1, because a subscript records the distance of an element from the start of the block rather than its place in a count. There is no element at subscriptn, and every access outside the range stops the program with anArrayIndexOutOfBoundsException. - Every cell is filled at creation with the default value of its type: zero for the integer types, 0.0 for the real types, the character with code 0 for
char, false forboolean, and null forStringand every other object type. - Values known while the program is written can be given in an initialiser list, which creates the array and takes its size from the number of values listed. An initialiser list is allowed only in a declaration.
- Every array carries its own size in the
lengthattribute, written without brackets. A loop bounded bylengthstays correct when the array’s size changes.
Getting data into an array
- Values may be assigned in the program, one at a time or computed inside a loop from the subscript; supplied as command-line arguments, which arrive in the
Stringarray received bymainand must be converted from text before use; or read while the program runs with theScannerclass of thejava.utilpackage. Scannersupplies one method per type read. There is no method for a single character, which is obtained by reading a word and taking its first character.
Traversal
- Visiting every element exactly once, in order, is a traversal, and it is the shape of nearly every array operation. A variable that carries a result across the passes of the loop must be created before the loop and given a starting value.
- Some questions need more than one traversal. An average cannot be compared with the individual values until every value has been added, so counting the elements above the average requires two passes over the same data.
- The largest and smallest elements are found by holding a best-so-far and improving it. The starting value must be an element that is actually in the array; starting a maximum at zero gives a wrong answer for data that is entirely negative.
- The bounds of a traversal must match what the body touches. A body that reads the element after the current one may only run while the subscript is below n − 1.
- A single traversal of
nelements costs \(O(n)\), where \(n\) is the number of elements. Two traversals of the same array still cost \(O(n)\), because a constant multiplier does not change how the work grows.
Searching
- Linear search, also called sequential search, compares the key with each element in turn and stops at the first match. It assumes nothing about the order of the data. Its best case is 1 comparison, its worst case is \(n\) comparisons where \(n\) is the number of elements, and its cost is \(O(n)\).
- Binary search requires the data to be in order. It compares the key with the middle element of the portion still under consideration and discards half of that portion at every step, because the ordering proves that the discarded half cannot contain the key. Its worst case is about \(\log_2 n\) comparisons and its cost is \(O(\log n)\).
- Applied to unsorted data, binary search does not fail loudly; it reports a value absent that is in fact present, because the reasoning that justifies discarding a half is no longer valid.
- A search should report the position, and −1 serves as the report of failure because it can never be a legal subscript.
Sorting
- Bubble sort compares neighbouring elements and exchanges those in the wrong order, so each pass carries the largest remaining value to the end. It always makes n(n − 1)/2 comparisons and cannot notice that the data is already sorted unless it is modified to do so.
- Selection sort finds the smallest element of the unsorted region and exchanges it with the first cell of that region. It always makes n(n − 1)/2 comparisons but performs at most n − 1 exchanges, so it moves data very little.
- Insertion sort keeps the front of the array in order and places each following element by shifting the larger elements one cell to the right. Its best case is data already in order, needing n − 1 comparisons and no shifting, which makes it \(O(n)\) on nearly sorted data.
- All three are \(O(n^2)\) in the worst case, where \(n\) is the number of elements: multiplying the data by ten multiplies the work by roughly a hundred.
- A sorting method is stable if equal values keep their original relative order. Bubble sort and insertion sort are stable; selection sort is not, because it exchanges distant elements.
Insertion, deletion and merging
- Because an array cannot grow, insertion is possible only in an array whose capacity exceeds the number of elements in use. The program keeps that count itself, and the elements occupy the subscripts below it.
- Inserting at a position means shifting every later element one cell to the right, and the shifting must run backwards from the last element; running it forwards copies one value over the whole tail of the list.
- Deleting at a position means shifting every later element one cell to the left and reducing the count. Nothing is erased — the last value remains in its cell and becomes unreachable only because the count no longer includes it.
- Both operations move up to \(n\) elements and therefore cost \(O(n)\), where \(n\) is the number of elements in use; both are cheapest at the end of the list and dearest at the front.
- Merging combines two sorted arrays into a sorted third by repeatedly taking the smaller of the two front elements, then copying whatever remains of the list that has not been exhausted. Each element is handled once, so the cost is \(O(m + p)\) for arrays of \(m\) and \(p\) elements — far less than discarding the order and sorting the combined data.
Double dimensional arrays
- A double dimensional array arranges elements in rows and columns, each element selected by two subscripts, the first for the row and the second for the column. The number of rows is the array’s own length; the number of columns is the length of a row.
- Processing a matrix needs two nested loops. With the row subscript on the outside the elements are visited row-wise; exchanging the nesting visits them column-wise, and that choice is what distinguishes a row total from a column total.
- A square matrix has as many rows as columns, and only a square matrix has diagonals. An element lies on the principal diagonal when its two subscripts are equal, and on the secondary diagonal when they add up to one less than the order. When the order is odd, the centre element lies on both.
- A square matrix is symmetric when the element at row i, column j equals the element at row j, column i for every pair of subscripts.
- Visiting every cell of a matrix of \(r\) rows and \(c\) columns costs \(O(r \times c)\), which for a square matrix of order \(n\) is \(O(n^2)\) — the cost of the quantity of data, not of the nesting.
Address calculation
- An array occupies a contiguous block of memory. The address of the first element is the base address, and every element occupies the same number of bytes, fixed by its type.
- In a single dimensional array, the address of the element at subscript i is the base address plus the element size multiplied by (i − lower bound). In Java the lower bound is always 0.
- This is a calculation, not a search: it costs the same for any subscript, so array access is constant time, \(O(1)\). That property is what allows binary search to jump straight to a middle element, and it is also why every element must be of the same size and therefore of the same type.
- A matrix must be laid out in a memory that has only one direction. Row major order stores one complete row after another, and the number of elements before the element at row i, column j is i × C + j, where C is the number of columns. Column major order stores one complete column after another, and the count is j × R + i, where R is the number of rows.
- The formula for row major order contains the number of columns and the formula for column major order contains the number of rows, because each must skip a whole row or a whole column at a time. Both orders use the same space and place the first and last elements identically; they disagree about everything between.
8.11 Key Terms
- array — a collection of a fixed number of elements of the same data type, stored under one name, each selected by an integer position.
- base address — the address of the memory location at which the first element of an array is stored.
- binary search — a search of sorted data that compares the key with the middle element of the portion still under consideration and discards half of it at every step.
- bubble sort — a sorting technique that repeatedly compares neighbouring elements and exchanges those that are in the wrong order.
- capacity — the number of cells an array has, fixed when it is created, as distinct from the number of cells currently in use.
- column major order — a layout that stores a matrix in memory one complete column after another.
- command-line argument — a word typed after the class name when a program is launched, received by
mainas an element of itsStringarray. - default value — the value automatically placed in every cell of an array when it is created, decided by the element type.
- double dimensional array — an array whose elements are arranged in rows and columns and selected by two subscripts.
- element — one individual value stored in an array.
- flag — a
booleanvariable used to record whether some condition has occurred, such as a successful search. - in place — describing an operation that rearranges the elements of an existing array without creating a second one.
- index — another name for a subscript.
- initialiser list — a list of values in braces, given in a declaration, which creates an array and takes its size from the number of values listed.
- insertion sort — a sorting technique that keeps the front of the array in order and places each following element into it by shifting the larger elements to the right.
- linear search — a search that compares the key with each element in turn from the beginning until a match is found or the array is exhausted.
- matrix — a table of values arranged in rows and columns, held in a double dimensional array.
- merging — combining two sorted arrays into a single sorted array containing all their elements.
- pass — one complete sweep of a sorting or searching method through the data.
- principal diagonal — in a square matrix, the cells running from the top left corner to the bottom right, where the row and column subscripts are equal.
- reference — the value held in an array variable, identifying the block of memory where the elements are stored.
- row major order — a layout that stores a matrix in memory one complete row after another.
- search key — the value being looked for during a search.
- secondary diagonal — in a square matrix of order n, the cells running from the top right corner to the bottom left, where the two subscripts add up to n − 1.
- selection sort — a sorting technique that finds the smallest element of the unsorted region and exchanges it with the first cell of that region.
- sequential search — another name for linear search.
- single dimensional array — an array whose elements are selected by a single subscript.
- sorting — rearranging the elements of an array so that they run in ascending or descending order.
- square matrix — a matrix with as many rows as columns; the number of rows is called its order.
- stable — describing a sorting method that leaves equal values in their original relative order.
- structured data type — a type built out of many values of another type, held together and used as one thing.
- subscript — the integer written in brackets after an array name to select one element, measured as a distance from the start of the block and beginning at 0.
- subscripted variable — an array name together with a subscript, which behaves like an ordinary variable of the element type.
- swap — the exchange of the values held in two cells, performed with the help of a temporary variable.
- traversal — visiting every element of an array exactly once, in order.
8.12 Points to Ponder
This chapter argued that numbering from 0 is the natural choice, because a subscript measures distance from the start of the block rather than position in a count. Yet the same choice is behind a large share of the errors beginners make, and a few languages number their arrays from 1 instead. If a language numbered from 1, which of the calculations in this chapter would become simpler and which would become clumsier — and is “easier to learn” the same thing as “easier to get right”?
Bubble sort and selection sort made exactly the same number of comparisons on the same six values, but bubble sort moved data eight times and selection sort twice. For the small
intarrays in this chapter that difference is invisible. What would the elements have to be like for movement to become the expensive part of sorting, and how would that change which of the three techniques you would choose?Binary search depends completely on the data being sorted, does not check that it is, and returns a confidently wrong answer when it is not. Checking would cost a full traversal — more than the search itself. Should an algorithm verify the assumption it rests on when the check costs more than the work, or is stating the assumption clearly the responsibility of whoever calls it?
Deleting an element from an array leaves the value sitting in the last cell, unreachable only because the count no longer includes it. Is “deleted” an honest description of what happened? Consider what it would take to make the value genuinely gone, and whether any program would be worth the extra work.
Java checks every subscript as the program runs and stops with an error when one falls outside the range; some languages perform no such check and run a little faster for it. The check catches a fault at the moment it is committed instead of allowing damage to surface somewhere unrelated. How would you decide what that certainty is worth, and does the answer depend on what the program is for?
Row major and column major layouts store identical values, occupy identical space, and require identical numbers of operations to traverse — yet on real machines a traversal that runs against the storage order can be noticeably slower. If two programs perform the same number of steps on the same data, what could possibly make one of them slower, and what does that suggest about counting operations as a measure of cost?
8.13 Objective Questions
- An array is created as
int a[] = new int[8];. The valid subscripts of this array are: [1 mark]
- 1 to 8
- 0 to 8
- 0 to 7
- 1 to 7
- Which of the following gives the number of elements in a single dimensional array
a? [1 mark]
a.length()a.lengthlength(a)a.size()
- An array is created as
String name[] = new String[4];and nothing is assigned to it. The value ofname[0]is: [1 mark]
- an empty piece of text
- a single space
0null
- Which of the following can be applied only to data that is already in order? [1 mark]
- Linear search
- Binary search
- Bubble sort
- Selection sort
- A matrix has R rows and C columns and is stored in row major order. The number of elements stored before
m[i][j]is: [1 mark]
- i × R + j
- i × C + j
- j × R + i
- j × C + i
- Assertion (A): Selection sort performs at most n − 1 exchanges while sorting n elements. Reason (R): Selection sort makes n(n − 1)/2 comparisons whatever the arrangement of the data. Choose the correct option: [1 mark]
- Both A and R are true and R is the correct explanation of A.
- Both A and R are true but R is not the correct explanation of A.
- A is true but R is false.
- A is false but R is true.
- Assertion (A): Binary search cannot be relied upon when the elements of the array are not in order. Reason (R): After a single comparison, binary search discards half of the elements still under consideration without examining them. Choose the correct option: [1 mark]
- Both A and R are true and R is the correct explanation of A.
- Both A and R are true but R is not the correct explanation of A.
- A is true but R is false.
- A is false but R is true.
- Match each item in column A with the statement in column B that describes it. [1 mark]
| Column A | Column B |
|---|---|
| a. Linear search | i. combines two sorted arrays into a single sorted array |
| b. Binary search | ii. performs at most n − 1 exchanges for n elements |
| c. Selection sort | iii. examines the elements one after another from the beginning |
| d. Merging | iv. halves the portion still under consideration at every step |
Fill in the blank: in a square matrix of order n, the element
m[i][j]lies on the secondary diagonal when ____________. [1 mark]State whether the following is true or false: deleting an element from an array reduces the capacity of the array. [1 mark]
8.14 Check Your Concepts
Define an array, and state any two limitations that follow from the definition. [2 marks]
State the default value placed in an element of a newly created array of each of these types:
int,double,booleanandString. [2 marks]What is a subscripted variable? Give one example and state its type, for an array declared as
double rate[] = new double[5];. [2 marks]An array is created as
new int[n]. Give the range of its valid subscripts, and explain why the largest of them is n − 1 rather than n. [2 marks]Differentiate between linear search and binary search, giving any three points of difference. [3 marks]
Differentiate between
lengthas used with an array andlength()as used with aString, and state what happens if the two are interchanged. [3 marks]Differentiate between bubble sort and selection sort on the basis of the number of comparisons and the amount of data movement each performs. [3 marks]
An
intmatrix of 4 rows and 6 columns is stored from base address 1200, each element occupying 4 bytes. Calculate the address ofm[2][3]in row major order and again in column major order, showing your working, and state the total number of bytes the matrix occupies. [4 marks]For a square matrix of order 5, name the five elements of the secondary diagonal. Explain why a single loop is enough to total that diagonal, and give the expression that produces the column subscript from the row subscript. [4 marks]
A value is to be inserted into the middle of a list held in an array. Explain, with a small worked example of your own, why the shifting must begin at the last element and work backwards, and state what the list looks like if it is done the other way round. [4 marks]
The array 8, 15, 23, 34, 47, 56, 68, 79 is searched for the value 56 using binary search. Show the values of the lower marker, the upper marker and the middle subscript at every step, state how many comparisons are made, and explain how the method would have detected the value 60 as absent. [6 marks]
Compare bubble sort, selection sort and insertion sort with respect to their basic action, their behaviour on data that is already in order, and their worst-case cost in big O notation. State which of the three you would choose for a nearly sorted list, giving your reason. [6 marks]
8.15 Output & Debugging
- Give the output of the following fragment, and state in one line what it computes in general. [2 marks]
int a[] = {6, 2, 9, 4};
int s = 0;
for (int i = 0; i < a.length; i++)
{
if (i % 2 == 0)
{
s = s + a[i];
}
}
System.out.println(s);- State the exact line printed by the fragment below, give the true average of the four marks, name the fault that accounts for the difference, and rewrite the offending statement correctly. [3 marks]
int m[] = {70, 65, 82, 60};
int total = 0;
for (int i = 0; i < m.length; i++)
{
total = total + m[i];
}
int average = total / m.length;
System.out.println("Average = " + average);- The fragment below compiles but does not run to completion. State what happens and at which value of
i, name the error reported, and correct the loop so that all five cells are filled. [2 marks]
int p[] = new int[5];
for (int i = 1; i <= 5; i++)
{
p[i] = i * i;
}- Give the output of the following fragment, and name the set of elements whose total it produces. [3 marks]
int m[][] = {{3, 5, 7}, {2, 4, 6}, {8, 1, 9}};
int t = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i + j == 2)
{
t = t + m[i][j];
}
}
}
System.out.println(t);- The fragment below is meant to sort the array into ascending order but stops with an error on its very first pass. Identify the single fault, correct it, and explain why the order of the two conditions matters. [3 marks]
int a[] = {9, 4, 7};
int i, j, temp;
for (i = 1; i < a.length; i++)
{
temp = a[i];
j = i - 1;
while (a[j] > temp && j >= 0)
{
a[j + 1] = a[j];
j--;
}
a[j + 1] = temp;
}- The array below is searched for the value 31 by binary search. List, in order, the elements that are compared with the key, and state how many comparisons are made. [2 marks]
int v[] = {5, 11, 18, 24, 31, 40, 52};8.16 Programming Practice
Write a class that stores ten whole numbers in an array and displays the second largest of them without sorting the array and without using a second array. Submit the complete class and a variable description table. Sample input: 34, 78, 12, 90, 45, 67, 23, 90, 56, 81 — note that the largest value occurs twice, and state in a comment how your program treats that case. [4 marks]
Write a class that stores ten whole numbers in an array, sorts them into descending order using bubble sort, and displays the whole array at the end of every pass, one pass to a line, followed by the sorted array. Submit the complete class. [4 marks]
An array of capacity 30 holds
nwhole numbers in its firstncells, arranged in ascending order. Write a program that readsn(with 3 ≤ n ≤ 25) and thennumbers, then reads one further number and inserts it at its correct place so that the array remains in ascending order, and finally displays the enlarged list. The program must not sort the array again, and must display a suitable message if there is no spare cell. Submit an algorithm in pseudocode and the complete class. [6 marks]Write a program that reads two arrays of sizes
mandp, each already in ascending order, and produces a third array containing allm + pvalues in ascending order, without sorting the third array at any stage. Display all three arrays. Submit an algorithm in pseudocode, the complete class, and a variable description table. [6 marks]Write a program that reads the order
nof a square matrix (with 2 < n < 10, an unsuitable value producing a message and no further processing) and then reads n × n whole numbers into it. Display the matrix in its proper shape, the sum of the principal diagonal, the sum of the secondary diagonal, and a statement of whether the matrix is symmetric. Submit the complete class and a variable description table. Test with the order 3 and the values 4, 7, 2 / 7, 5, 9 / 2, 9, 6, and again with an order of your own choosing that is not symmetric. [6 marks]Write a single method with the header
public static int countAbove(int a[], int limit)which returns how many elements ofaare strictly greater thanlimit. Submit the method only, together with one line stating what your method returns when the array holds no such element. [4 marks]