Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Package Title: Lab Questions Course Title: Big Java, Late Objects Chapter Number: 14 Sorting and Searching Question type: Essay 1.1) When evaluating sorting algorithms, there many factors that might affect how fast an algorithm will run when it is programmed for a real machine: CPU speed, language used, type of data being sorted, type of operating system, and the number of concurrent processes running at the same time. For this reason, comparing actual running times of an algorithm is ineffectual for making judgments about how good the algorithm might be. To effectively make a judgment that is free of real world constraints, computer scientists, when comparing sorting algorithms, often simply count the number of comparisons an algorithm makes. In this exercise, we will add a counter to the MergeSorter program to help us measure the number of comparisons of array elements being made while the routine is completing the sort. The MergeSorter code is listed below. private static void merge(int[] first, int[] second, int[] a) { int iFirst = 0; // Next element to consider in the first array int iSecond = 0; // Next element to consider in the second array int j = 0; // Next open position in a // As long as neither iFirst nor iSecond is past the end, move // the smaller element into a while (iFirst < first.length && iSecond < second.length) { if (first[iFirst] < second[iSecond]) { a[j] = first[iFirst]; iFirst++; } else { a[j] = second[iSecond]; iSecond++; } j++; } // Note that only one of the two loops below copies entries // Copy any remaining entries of the first array while (iFirst < first.length) { a[j] = first[iFirst]; iFirst++; j++; } // Copy any remaining entries of the second half while (iSecond < second.length) { a[j] = second[iSecond]; iSecond++; j++; Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 1 } } The behavior we want to monitor occurs in this section of code, if (first[iFirst] < second[iSecond]) { a[j] = first[iFirst]; iFirst++; } else { a[j] = second[iSecond]; iSecond++; } It is in the above section that we compare array elements and decide which element becomes part of our merged and sorted array. By counting the number of comparisons here, we obtain a measurement that helps us gauge the speed of the algorithm. Add code that will count each comparison, maintaining the result in an integer counter. Supply a method getCounter to retrieve the counter. Supply a second method called resetCounter to set the counter to 0. The code below supports creating and swapping elements in arrays of varying sizes. /** This class contains utility methods for array manipulation. */ public class ArrayUtil { /** Creates an array filled with random values. @param length the length of the array @param n the number of possible random values @return an array filled with length numbers between 0 and n - 1 */ public static int[] randomIntArray(int length, int n) { int[] a = new int[length]; for (int i = 0; i < a.length; i++) { a[i] = (int) (Math.random() * n); } return a; } /** Swaps two entries of an array. @param a the array @param i the first position to swap @param j the second position to swap */ public static void swap(int[] a, int i, int j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 2 } } Modify the main program below so you can test sorting arrays with sizes 10000, 20000, …,90000. /** This program demonstrates the merge sort algorithm by sorting an array that is filled with random numbers. */ public class MergeSortDemo { public static void main(String[] args) { int[] a = ArrayUtil.randomIntArray(10000, 10000); MergeSorter.resetCounter(); MergeSorter.sort(a); System.out.println("Array size: 10000; comparisons: " + MergeSorter.getCounter()); } } What are your results? Answer: Array Array Array Array Array Array Array Array Array Array size: size: size: size: size: size: size: size: size: size: 10000; 10000; 20000; 30000; 40000; 50000; 60000; 70000; 80000; 90000; comparisons: comparisons: comparisons: comparisons: comparisons: comparisons: comparisons: comparisons: comparisons: comparisons: 120501 120467 260952 408611 561808 718284 877312 1039097 1203642 1369517 Title: Lab 14.1.1 Add counter to merge sort to count comparisons made Difficulty: Medium Section Reference 1: 14.4 Merge Sort 2.1) In this lab, you will implement the bubble sort algorithm. The bubble sort is so called because it compares adjacent items, "bubbling" the smaller one up toward the beginning of the array. By comparing all pairs of adjacent items starting at the end of the array, the smallest item is guaranteed to reach the beginning of the array at the end of the first pass. The second pass begins again at the end of the array, ultimately placing the second smallest item in the second position. During the second pass, there is no need to compare the first and second items, because the smallest element is guaranteed to be in the first position. Bubble sort takes at most n - 1 passes for an array of n items. During the first pass, n - 1 pairs need to be compared. During the second pass, n - 2 pairs need to be compared. During the ith pass, n - i pairs need to be compared. During the last pass, n - (n - 1) or one pair needs to be compared. If, during any pass, no two adjacent items need to be interchanged, the array is in order and the sort can terminate. If it continues, no further interchanges will occur. Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 3 What is the code of your BubbleSorter class? Answer: /** This class sorts an array, using the bubble sort algorithm. */ public class BubbleSorter { private int[] a; /** Constructs a bubble sorter. @param anArray the array to sort */ public BubbleSorter(int[] anArray) { a = anArray; } /** Sorts the array managed by this bubble sorter. */ public void sort() { boolean swapped = true; for (int i = a.length - 1; i > 0 && swapped; i--) { swapped = false; for (int j = a.length - 1; j >= a.length - i; j--) { if (a[j] < a[j - 1]) { swap(j, j - 1); swapped = true; } } } } /** Swaps two entries of the array. @param i the first position to swap @param j the second position to swap */ private void swap(int i, int j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; } } Title: Lab 14.2.1 Implementing bubble sort Difficulty: Medium Section Reference 1: 14.1 Selection Sort Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 4 2.2) Describe the efficiency of the bubble sort algorithm and represent it in big-Oh notation. Answer: Bubble sort performs the following number of iterations (for an array of size n): n–1 n–2 n–3 ... 2 1 = n * ((n – 1) / 2) = (n2 – n) / 2 Which means that bubble sort has O(n2) performance. Title: Lab 14.2.2 Analyzing the big-Oh efficiency of bubble sort Difficulty: Hard Section Reference 1: 14.3 Analyzing the Performance of the Selection Sort Algorithm 2.3) A variation of the bubble sort algorithm starts each pass at the beginning of the list, interchanging items as needed so that a larger item "sinks" below a smaller item following each comparison. (Such a sort might be called a "rock sort," but this term is not in common usage.) Implement this variation for sorting an array. What is the code for your sort method? Answer: public void sort() { boolean swapped = true; for (int i = 0; i < a.length - 1 && swapped; i++) { swapped = false; for (int j = 0; j < a.length - i - 1; j++) { if (a[j] > a[j + 1]) { swap(j, j + 1); swapped = true; } } } } Title: Lab 14.2.3 Implement a variation of bubble sort Difficulty: Medium Section Reference 1: 14.1 Selection Sort Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 5 3.1) We want to sort a list of cities by name; however, if there are two cities with the same name, then we use the state name as tie breaker. Implement a City class and a constructor public City(String name, String state) Also supply a toString method; we will need it for testing. Have your City class implement the Comparable<City> interface. What is the code of your City class? Answer: public class City implements Comparable<City> { private String name; private String state; public City(String name, String state) { this.name = name; this.state = state; } public String getName() { return name; } public String getState() { return state; } public int compareTo(City other) { if (name.compareTo(other.name) == 0) { return state.compareTo(other.state); } else { return name.compareTo(other.name); } } public String toString() { return "City[" + name + "," + state + "]"; } } Title: Lab 14.3.1 Write City class implementing Comparable interface Difficulty: Medium Section Reference 1: 14.8 Sorting and Searching in the Java Library Section Reference 2: 14.8.3 Comparing Objects Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 6 3.2. Write a CitySortDemo class that populates an ArrayList<City> with cities and then sorts it. What is the code of your class? Remember that you can use Collections.sort to sort an array list. Answer: import java.util.Collections; import java.util.ArrayList; public class CitySortDemo { public static void main(String[] args) { ArrayList<City> cities = new ArrayList<City>(); cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new City("Anchorage", "Alaska")); City("Little Rock", "Arkansas")); City("Fairbanks", "Alaska")); City("Bumblebee", "Arizona")); City("Springfield", "Oregon")); City("Kodiak", "Alaska")); City("Bloomington", "Illinois")); City("Pine Bluff", "Arkansas")); City("Flagstaff", "Arizona")); City("Phoenix", "Arizona")); City("Bloomington", "Indiana")); City("Tucson", "Arizona")); City("Springfield", "Illinois")); City("Juneau", "Alaska")); City("Arkadelphia", "Arkansas")); Collections.sort(cities); for (City c : cities) { System.out.println(c); } } } Title: Lab 14.3.2 Demo class for sorting cities Difficulty: Medium Section Reference 1: 14.8 Sorting and Searching in the Java Library Section Reference 2: 14.8.3 Comparing Objects 3.3 Now we want to sort lists of cities first by state name, then by city name, without modifying the implementation of the City class. That means you cannot redefine the compareTo method. Instead, define a CityComparator class that implements the Comparator<City> interface. public class CityComparator implements Comparator<City> { public int compare(City a, City b) { . . . } Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 7 } What is the code of your CityComparator class? Answer: import java.util.Comparator; public class CityComparator implements Comparator<City> { public int compare(City a, City b) { if (a.getState().compareTo(b.getState()) == 0) { return a.getName().compareTo(b.getName()); } else { return a.getState().compareTo(b.getState()); } } } Title: Lab 14.3.3 Comparator class for cities Difficulty: Medium Section Reference 1: 14.8 Sorting and Searching in the Java Library Section Reference 2: 14.8.3 Comparing Objects Section Reference 3: Special Topic 14.5 The Comparator Interface 3.4) Write a CitySortDemo2 class that populates an ArrayList<City> with cities and then sorts it, using a CityComparator. What is the code of your class? Answer: import java.util.Collections; import java.util.ArrayList; public class CitySortDemo2 { public static void main(String[] args) { ArrayList<City> cities = new ArrayList<City>(); cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new cities.add(new City("Anchorage", "Alaska")); City("Little Rock", "Arkansas")); City("Fairbanks", "Alaska")); City("Bumblebee", "Arizona")); City("Springfield", "Oregon")); City("Kodiak", "Alaska")); City("Bloomington", "Illinois")); City("Pine Bluff", "Arkansas")); City("Flagstaff", "Arizona")); City("Phoenix", "Arizona")); City("Bloomington", "Indiana")); City("Tucson", "Arizona")); Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 8 cities.add(new City("Springfield", "Illinois")); cities.add(new City("Juneau", "Alaska")); cities.add(new City("Arkadelphia", "Arkansas")); Collections.sort(cities, new CityComparator()); for (City c : cities) { System.out.println(c); } } } Title: Lab 14.3.4 Demo for sorting cities with Comparator Difficulty: Medium Section Reference 1: 14.8 Sorting and Searching in the Java Library Section Reference 2: 14.8.3 Comparing Objects 4.1) In this lab, you will implement a “guess your name” game where the computer finds a name in a sorted list of names. The computer will use this method to “guess” your last name: This Does Does Does Does program tries to guess your last name, but you have to give some hints. your name come before "MYERSCOUGH" in the dictionary? (Y/N) Y your name come before "ISENBERG" in the dictionary? (Y/N) Y your name come before "DIMAGGIO" in the dictionary? (Y/N) N your name come before "GALLUP" in the dictionary? (Y/N) N . . . Is your name "HORSTMANN"? (Y/N) Y Start out with a program that reads all names from the "last names" file at http://www.census.gov/genealogy/www/data/1990surnames/names_files.html. information. Sort the names, using Collections.sort. Discard the statistical Use this code outline: public class NameGuesser { private ArrayList<String> lastNames = new ArrayList<String>(); public void readNames() throws IOException, MalformedURLException { URL url = new URL("http://www.census.gov/genealogy/names/dist.all.last"); Scanner in = new Scanner(url.openStream()); . . . } } What is the code for reading all names and sorting them? Answer: public void readNames() throws IOException, MalformedURLException { Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 9 // Read the last names URL url = new URL("http://www.census.gov/genealogy/names/dist.all.last"); Scanner in = new Scanner(url.openStream()); while (in.hasNext()) { // Read the last name String lastName = in.next(); lastNames.add(lastName); // Ignore the statistical information in.nextDouble(); in.nextDouble(); in.nextInt(); } in.close(); // Sort the names Collections.sort(lastNames); } Title: Lab 14.4.1 Read names from census web site and sort them Difficulty: Easy Section Reference 1: 14.8 Sorting and Searching in the Java Library 4.2) Now you will implement the method that guesses the name, called guessName. At the beginning of the search, set low to 0 and high to the maximum index of the names list. Set mid to the average of low and high, then ask the user if the name comes before the list entry at index mid. Depending on the answer, update low or high. If low == high, ask if the name matches. What is the code for your guessName method? Answer: public { int int int void guessName() low = 0; high = lastNames.size() - 1; mid = (low + high) / 2; Scanner in = new Scanner(System.in); System.out.println("This program tries to guess your last name," + " but you have to give some hints."); String reply; boolean done = false; while (!done) { System.out.println("Does your name come before \"" + lastNames.get(mid) + "\" in the dictionary? (Y/N)"); reply = in.next(); if (reply.equalsIgnoreCase("Y")) { high = mid - 1; Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 10 } else { low = mid + 1; } mid = (low + high) / 2; if (low >= high) { done = true; } } System.out.println("Is your name \"" + lastNames.get(mid) + "\"? (Y/N)"); reply = in.next(); if (reply.equalsIgnoreCase("N")) { System.out.println("Sorry, your last name is not in the file."); } } Title: Lab 14.4.2 Complete guessName method Difficulty: Medium Section Reference 1: 14.6 Searching 4.3) How many entries does your names list have? How many guesses does your program need to make until it has guessed the user's name or given up? (Use big-Oh for your answer.) Answer: How many entries does your names list have? 88799 How many guesses does your program need to make until it has guessed the user's name or given up? At each guess, the program is either successful and finds the name, or if unsuccessful, is able to discard at least half the names depending on whether the guess was too high or too low. It operates the way you do when you look up a telephone number in the telephone book: You take a guess at where the name might be, look at the names on the page, and decide which half of the book to give up on. How many times can we discard half of the book or half of the list of names? Think about this: How many times could you spend half the money in your bank account before you were broke? Not many. That’s why the technique is effective. If you had two dollars in your account and had to spend in dollar increments, you would spend $1 and then $1 before you were broke. If you had $4, you would spend $2, $1, and then finally $1. With $8 the spending pattern is $4, $2, $1 and $1. (Note that 2 = 21 , 4 = 22, and 8 = 23.) You seem to be able to spend half your money one time more than the base two exponent associated with the amount of money in your account. This exponent is precisely log(n) when n is the amount of money you have or the number of names you want to look up. Computer scientists would call this an O(log(n)) algorithm. Title: Lab 14.4.3 Assess efficiency of name guessing Difficulty: Easy Section Reference 1: 14.7 Problem Solving: Estimating the Running Time of an Algorithm Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 11 5) In Chapter 13 we developed a class of static methods that manipulated ArrayLists of Integers. These methods included even, odd, and merge (see Lab 13.1.5 – 13.1.7). Recall that merge combines two sorted lists into a single sorted list by comparing elements at the head of each list to decide which element appears next in the sorted result. In combination, these three methods can be used to implement a recursive mergeSort method for ArrayLists. Consider the unsorted list x = {12, 5, 9, 4, 21, 3, 33, 49, 18}. Applying even to x we can get list a = {12, 9, 21, 33, 18} and applying odd to x we can get list b = {5, 4, 3, 49}. Because lists a and b are smaller in size than x, we can recursively mergeSort a and b to obtain sorted lists c = {9, 12, 18, 21, 33} and d = {3, 4, 5, 49}. Remember that we are using recursion here, so we don’t have to show how mergeSort(c) and mergeSort(d) will work, we just have to show how the solutions to these smaller problems can be combined to sort list a. Applying merge to sorted lists c and d solves the problem of sorting x. Of course, this has to be written using logic that will stop the recursion when the lists are empty or contain a single item. Write the code that will construct a mergeSort method with the following header: public static ArrayList<Integer> mergeSort(ArrayList<Integer> tList) Use the following code as a test harness for mergeSort: import java.util.ArrayList; public class ListMethodsRunner { public static void main(String[] args) { ArrayList<Integer> list1 = new ArrayList<Integer>(); list1.add(2); list1.add(3); list1.add(5); list1.add(9); list1.add(22); list1.add(38); list1.add(56); list1.add(4); list1.add(7); list1.add(8); list1.add(23); list1.add(37); ArrayList<Integer> tempList = ListMethods.mergeSort(list1); if (tempList.size() == 0) { System.out.println("The list is empty"); } else { for (Integer i : tempList) { System.out.println(i); } } } } Answer: Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 12 public static ArrayList<Integer> mergeSort(ArrayList<Integer> tList) { ArrayList<Integer> list = ListMethods.deepClone(tList); if ((list.size() == 0) || (list.size() == 1)) { return list; } else { ArrayList<Integer> evenList = ListMethods.even(list); ArrayList<Integer> oddList = ListMethods.odd(list); ArrayList<Integer> eList = mergeSort(evenList); ArrayList<Integer> oList = mergeSort(oddList); return merge(eList, oList); } } Here is the ListMethods class from Chapter 13: import java.util.ArrayList; public class ListMethods { public static ArrayList<Integer> makeList(int n) { ArrayList<Integer> tempList = null; if (n <= 0) // The smallest list we can make { tempList = new ArrayList<Integer>(); } else // All other lists are created here { tempList = makeList(n - 1); // Solve the smaller problem tempList.add(n); // Use it to solve the larger problem return tempList; } return tempList; } public static ArrayList<Integer> reverseList(ArrayList<Integer> tList) { ArrayList<Integer> list = ListMethods.deepClone(tList); if ((list.size() == 1) || (list.size() == 0)) { return list; } else { } return list; } public static ArrayList<Integer> deepClone(ArrayList<Integer> tList) { ArrayList<Integer> list = new ArrayList<Integer>(); for (Integer i : tList) { list.add(new Integer(i)); } Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 13 return list; } public static ArrayList<Integer> even(ArrayList<Integer> tList) { ArrayList<Integer> list = ListMethods.deepClone(tList); if ((list.size() == 0) || (list.size() == 1)) { return list; } else { Integer tempInt = list.remove(0); list.remove(0); // Throw away the second element list = ListMethods.even(list); list.add(0, tempInt); } return list; } public static ArrayList<Integer> odd(ArrayList<Integer> tList) { ArrayList<Integer> list = ListMethods.deepClone(tList); if ((list.size() == 0) || (list.size() == 1)) { return list; } else { list.remove(0); // Throw away the first element Integer tempInt = list.remove(0); list = ListMethods.odd(list); list.add(0,tempInt); } return list; } public static ArrayList<Integer> merge(ArrayList<Integer> tList1, ArrayList<Integer> tList2) { ArrayList<Integer> list1 = ListMethods.deepClone(tList1); ArrayList<Integer> list2 = ListMethods.deepClone(tList2); ArrayList<Integer> tempList; // The list we will return if (list1.size() == 0) { return list2; } else if (list2.size() == 0) { return list1; } else { Integer lastElement1 = list1.get(list1.size() - 1); Integer lastElement2 = list2.get(list2.size() - 1); if (lastElement1.compareTo(lastElement2) < 0) { // Remove the largest element in either list lastElement2 = list2.remove(list2.size() - 1); // Now merge the lists recursively Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 14 tempList = ListMethods.merge(list1, list2); // Add back the largest element tempList.add(lastElement2); } else { // Remove the largest element in either list lastElement1 = list1.remove(list1.size() - 1); // Now merge the lists recursively tempList = ListMethods.merge(list1,list2); // Add back the largest element tempList.add(lastElement1); } } return tempList; } } Title: Lab 14.5 Merge sort with ArrayList<Integer> static methods Difficulty: Medium Section Reference 1: 14.5 Analyzing the Merge Sort Algorithm 6.1) Imagine facing a rectangular formation of soldiers with m soldiers in each row and n soldiers in each column. We rearrange the soldiers by height following these rules in sequence: The Soldier Sort Each column of soldiers is sorted from tallest to shortest moving from back to front. Each row of soldiers is sorted from tallest to shortest moving left to right. The surprising fact is that after sorting each row, the soldiers are still sorted in each column. Following this procedure produces a formation of soldiers that is pleasing and looks more uniform than a formation in which the soldiers are randomly placed. Examining the formation we see the tallest soldier in the back, left, and the shortest soldier in the front right. The chart below represents an array of 10 rows and 15 columns of integers in the range 100 – 199. The values were randomly generated and placed in an array, and the array was “Soldier sorted”. Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 15 Series1 200 180 160 140 120 100 80 60 40 20 0 S15 S13 S9 S7 S3 S1 9 S5 5 S11 1 Series2 Series3 Series4 Series5 Series6 Series7 Series8 Series9 Series10 Series11 Series12 Series13 Series14 Series15 The code below generates a 10 by 15 integer array with data similar to the data in the chart above. public class SoldierRunner { public static void main(String[] args) { final int ROWSIZE = 10; final int COLSIZE = 15; int[][] formation = new int[ROWSIZE][COLSIZE]; for (int i = 0; i < ROWSIZE; i++) { for (int j = 0; j < COLSIZE; j++) { formation[i][j] = (int)(Math.random() * 100) + 100; } } // Print the initial formation System.out.println("------------Initial formation------------------"); for (int i = 0; i < ROWSIZE; i++) { for (int j = 0; j < COLSIZE; j++) { System.out.print(formation[i][j] + " "); } System.out.println(); } } } Add the code necessary to soldier sort the array and print the sorted formation. Use merge sort to sort each row and each column. Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 16 Answer: import java.util.Collections; public class SoldierSorter { public static void main(String[] args) { final int ROWSIZE = 10; final int COLSIZE = 15; int[][] formation = new int[ROWSIZE][COLSIZE]; for (int i = 0; i < ROWSIZE; i++) { for (int j = 0; j < COLSIZE; j++) { formation[i][j] = new Integer((int) (Math.random() * 100) + 100); } } // Print the initial formation System.out.println("------------Initial formation------------------"); for (int i = 0; i < ROWSIZE; i++) { for (int j = 0; j < COLSIZE; j++) { System.out.print(formation[i][j] + " "); } System.out.println(); } // Merge sort by rows for (int i = 0; i < ROWSIZE; i++) { MergeSorter.sort(formation[i]); } // Merge sort by cols for (int i = 0; i < COLSIZE; i++) { int[] temp = new int[ROWSIZE]; for (int j = 0; j < ROWSIZE; j++) { temp[j] = formation[j][i]; } MergeSorter.sort(temp); for (int j = 0; j < ROWSIZE; j++) { formation[j][i] = temp[j]; } } // Print the final formation System.out.println("---------------Final formation------------------"); for (int i = 0; i < ROWSIZE; i++) { for (int j = 0; j < COLSIZE; j++) { System.out.print(formation[i][j] + " "); } System.out.println(); } } Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 17 } Title: Lab 14.6.1 Soldier sorting an array Difficulty: Medium Section Reference 1: 14.5 Analyzing the Merge Sort Algorithm Lab Manual Chapter 14 © John Wiley & Sons, Inc. All rights reserved. 18