1 CSC 427: Data Structures and Algorithm Analysis Fall 2010 Brute force approach KISS vs....

30
1 CSC 427: Data Structures and Algorithm Analysis Fall 2010 Brute force approach KISS vs. generality HW2 examples: League Standings, Enigma, Musical Themes exhaustive search: string matching generate & test: N-queens, TSP, Knapsack inheritance & efficiency • ArrayList SortedArrayList

Transcript of 1 CSC 427: Data Structures and Algorithm Analysis Fall 2010 Brute force approach KISS vs....

1

CSC 427: Data Structures and Algorithm Analysis

Fall 2010

Brute force approach KISS vs. generality HW2 examples: League Standings, Enigma, Musical Themes exhaustive search: string matching generate & test: N-queens, TSP, Knapsack inheritance & efficiency

• ArrayList SortedArrayList

2

Brute force

many problems do not require complex, clever algorithms a brute force (i.e., straightforward) approach may suffice

consider the exponentiation application

simple, iterative version: ab = a * a * a * … * brecursive version: ab = ab/2 * ab/2

while the recursive version is more efficient, O(log N) vs. O(N), is it really worth it?

brute force works fine when the problem size is small only a few instances of the problem need to be solved need to build a prototype to study the problem

League Standings

consider the league standings problem from HW2 a Map is a natural structure for keeping team-record pairs biggest problem was displaying team records in decreasing order (by wins)

oTreeMap makes it easy to get the teams in alphabetical orderoto order by score, need to sort the teams by number of wins

3

elegant, generalizable, efficient solution: create a Record class that encapsulates the team name and win/loss record make this class Comparable, so that a team with more wins is > can then put Records in a list and sort

Record class

4

class Record implements Comparable<Record> { private String teamName; private int numWins; private int numLosses; public Record(String name) { this.teamName = name; this.numWins = 0; this.numLosses = 0; } public void addWin() { this.numWins++; } public void addLoss() { this.numLosses++; } public int compareTo(Record other) { if (other.numWins == this.numWins) { return this.teamName.compareTo(other.teamName); } else { return other.numWins - this.numWins; } }

public String toString() { return this.teamName + " " + this.numWins + "-" + this.numLosses; }}

Record class provides methods for adding wins & losses

the compareTo method looks first at number of winsif same number of wins, looks next at team name

the toString method makes it easy to display a Record

Standings class

5

public class Standings { public static void main(String[] args) { TreeMap<String, Record> teams = new TreeMap<String, Record>();

Scanner input = new Scanner(System.in);

String team1 = input.next(); int score1 = input.nextInt(); String team2 = input.next(); int score2 = input.nextInt();

while (score1 >= 0 || score2 >= 0) { if (!teams.containsKey(team1)) { teams.put(team1, new Record(team1)); } if (!teams.containsKey(team2)) { teams.put(team2, new Record(team2)); } if (score1 > score2) { teams.get(team1).addWin(); teams.get(team2).addLoss(); } else { teams.get(team1).addLoss(); teams.get(team2).addWin(); }

team1 = input.next(); score1 = input.nextInt(); team2 = input.next(); score2 = input.nextInt(); }

main method for solving the problem is fairly simplesince Records are Comparable, can use Collections.sort to order them

ArrayList<Record> standings = new ArrayList<Record>();

for (String teamName : teams.keySet()) { standings.add(teams.get(teamName)); } Collections.sort(standings);

for (Record t : standings) { System.out.println(t); } System.out.println("DONE"); }}

How efficient is this solution?

let G = the number of games and T = the number of teams

• while loop executes G times, each time through mustread in teams & scorescheck to see if teams already stored in Mapadd record to Map if not already storeddetermine which team won/lostupdate records of both teams in Map

• sorting the records requiresconstructing an empty ArrayListgetting the keySet for the Mapgetting each record and storing in the Arraylistsorting the ArrayList

• displaying the sorted records

6

Simpler version?

is the development of a new Record class really necessary?

is there a (reasonable) upper limit on the number of games? e.g., suppose we knew/guessed there were at most 100 games

we could avoid code complexity by making ~100 passes through the teams

traverse teams, displaying all teams with 100 wins, then…traverse teams, displaying all teams with 99 wins, then…traverse teams, displaying all teams with 98 wins, then ……traverse teams, displaying all teams with 0 wins

how inefficient is this?

7

StandingsBrute class

8

public class StandingsBrute { public static void main(String[] args) { TreeMap<String, Integer> wins = new TreeMap<String, Integer>(); TreeMap<String, Integer> losses = new TreeMap<String, Integer>();

Scanner input = new Scanner(System.in);

String team1 = input.next(); int score1 = input.nextInt(); String team2 = input.next(); int score2 = input.nextInt();

int numGames = 0; while (score1 >= 0 || score2 >= 0) { if (!wins.containsKey(team1)) { wins.put(team1, 0); losses.put(team1, 0); } if (!wins.containsKey(team2)) { wins.put(team2, 0); losses.put(team2, 0); } if (score1 > score2) { wins.put(team1, wins.get(team1)+1); losses.put(team2, losses.get(team2)+1); } else { wins.put(team2, wins.get(team2)+1); losses.put(team1, losses.get(team1)+1); } numGames++;

team1 = input.next(); score1 = input.nextInt(); team2 = input.next(); score2 = input.nextInt(); }

simpler versionone maps for wins, another for lossesmust keep track of num games

for (int i = numGames; i >= 0; i--) { for (String s : wins.keySet()) { int numWins = wins.get(s); if (numWins == i) { System.out.println(s + " " + wins.get(s) + "-" + losses.get(s)); } } } System.out.println("DONE"); }}

Incremental improvements?

remove a record from the Map after displaying impact on efficiency?

9

before traversing the keySet to display all teams with W wins, call containsValue(W) to make sure some exist impact on efficiency?

make a pass throught the keySet and store win totals in an ArrayListthen sort the win totals and traverse looking only for those totals

impact on efficiency?

Enigma problem

if we really wanted to model an Enigma machine, would want to define classes that model the components

Rotor, Backplate, …

that way, could easily generalize to interchangeable rotors, variable numbers of rotors, etc.

if the goal is to just solve this problem, can go much simpler represent each rotor and backplate as a String connect letters using indexOf and charAt methods rotate rotors using substring and concatenation

10

Enigma class

11

public class Enigma { private String innerRotor; private String middleRotor; private String backplate;

public Enigma(String inner, String middle, String back){ this.middleRotor = middle; this.innerRotor = inner; this.backplate = back; }

public String encode(String message){ String middleCopy = this.middleRotor; String innerCopy = this.innerRotor; String encMessage = ""; for(int i = 0; i < message.length(); i++){ String temp = message.substring(i,i + 1); int tempPos = innerCopy.indexOf(temp); String buffer = backplate.substring(tempPos, tempPos + 1);

tempPos = middleCopy.indexOf(buffer); encMessage += backplate.substring(tempPos, tempPos+1);

innerCopy = rotate(innerCopy); if (innerCopy.equals(this.innerRotor)) { middleCopy = rotate(middleCopy); } } return encMessage; }

private String rotate(String rotor){ return rotor.substring(rotor.length()-1,rotor.length()) + rotor.substring(0,rotor.length()-1); } . . .

here, chose to have some generality rotors and backplate are

fields, initialized by a constructor

encode and rotate are methods

as a result, would be easy to add additional functionality encode multiple messages add a decode method create a GUI

Enigma class (cont.)

12

. . .

public static void main(String[] args) { Scanner input = new Scanner(System.in); String inner = input.nextLine(); String middle = input.nextLine(); String back = input.nextLine(); String message = input.nextLine();

Enigma coder = new Enigma(inner, middle, back); String coded = coder.encode(message.substring(0, message.length()-1)); System.out.println(coded + "."); }}

what is the purpose of the substring?

why is "." added to the end of the coded message?

by designing the solution with fields and methods, can easily add a GUI

Musical Themes problem

this was by far the trickiest solution to code, but also most concise

checking to see whether two sequences of notes are similar is fairly straightforward

can make use of char substraction 'B' – 'A' = 1

need to systematically try subsequences of the score for matches could start with the longest possible match, shorten until find a match could start with shortest possible match, lengthen until no more matches

ABCEGDEFAC ABCEGDEFAC ABCEGDEFAC ABCEGDEFAC

ABCEGDEFAC ABCEGDEFAC

ABCEGDEFAC

13

Themes class

14

public class Themes { private static boolean matches(String seq1, String seq2) { for (int i = 0; i < seq1.length() - 1; i++) { int diff1 = (7 + seq1.charAt(i + 1) - seq1.charAt(i)) % 7; int diff2 = (7 + seq2.charAt(i + 1) - seq2.charAt(i)) % 7; if (diff1 != diff2) { return false; } } return true; }

private static String findMatch(String seq) { for (int matchSize = seq.length() / 2; matchSize > 1; matchSize--) { int stop1 = seq.length() - (2 * matchSize); for (int start1 = 0; start1 <= stop1; start1++) { int stop2 = seq.length() - matchSize; for (int start2 = start1 + matchSize; start2 <= stop2; start2++) { String sub1 = seq.substring(start1, start1 + matchSize); String sub2 = seq.substring(start2, start2 + matchSize); if (Themes.matches(sub1, sub2)) { return sub1; } } } } return seq.substring(0, 1); }

public static void main(String[] args) { Scanner input = new Scanner(System.in); int numThemes = input.nextInt(); for (int i = 0; i < numThemes; i++) { System.out.println(Themes.findMatch(input.next())); } }}

matches: determines

whether two sequences of same length are similar

uses % op

findMatch: tries every

possible pair of themes

starts with longest, goes left-to-right

TRICKY, but concise

Exhaustive search

in the worst case, the themes program must try (almost) every possible pair of subsequences for a match for long short approach, if no matches of size >= 2, can simply return first char for short->long approach, can stop each pass as soon as a match is found

15

related example: string matching consider the task of the String indexOf method

find the first occurrence of a desired substring in a string this problem occurs in many application areas, e.g., DNA sequencing

CGGTAGCTTGCCTAGGAGGCTTCTCATAGAGCTCGATCGGTACG…

TAGAG

Exhaustive string matching

the brute force/exhaustive approach is to sequentially search

CGGTAGCTTGCCTAGGAGGCTTCTCATAGAGCTCGATCGGTACG…CGGTAGCTTGCCTAGGAGGCTTCTCATAGAGCTCGATCGGTACG…CGGTAGCTTGCCTAGGAGGCTTCTCATAGAGCTCGATCGGTACG…CGGTAGCTTGCCTAGGAGGCTTCTCATAGAGCTCGATCGGTACG……CGGTAGCTTGCCTAGGAGGCTTCTCATAGAGCTCGATCGGTACG…

16

public static int match(String seq, String desired) { for (int start = 0; start <= seq.length() – desired.length(); start++) { String sub = seq.substring(start, start+desired.length()); if (sub.equals(desired)) { return start; } } return -1;}

efficiency of search? we can do better (more later)

Generate & test

sometimes exhaustive algorithms are referred to as "generate & test" can express algorithm as generating each candidate solution systematically,

testing each to see if the candidate is actually a solution

musical themes: try seq.substring(0, seq.length()/2)if no match, try seq.substring(0, seq.length()/2 – 1)if no match, try seq.substring(1, seq.length()/2)if no match, try seq.substring(2, seq.length()/2+1)…

string matching: try seq.substring(0, desired.length())if no match, try seq.substring(1, desired.length()+1)if no match, try seq.substring(2, desired.length()+2)…

17

18

Generate & test: N-queens

given an NxN chess board, place a queen on each row so that no queen is in jeopardy

generate & test approach systematically generate every possible

arrangement test each one to see if it is a valid solution

this will work (in theory), but the size of the search space may be prohibitive

4x4 board

8x8 board

= 1,820 arrangements

= 131,198,072 arrangements

4

16

8

64

4! = 24 arrangements

8! = 40,320 arrangements

again, we can do better (more later)

nP-hard problems: traveling salesman

there are some problems for which there is no known "efficient" algorithm (i.e., nothing polynomial) known as nP-hard problems

generate & test may be the only option

19

Traveling Salesman Problem: A salesman must make a complete tour of a given set of cities (no city visited twice except start/end city) such that the total distance traveled is minimized.

example: find the shortest tour given this map

generate & test try every possible route

efficiency?

50

80

70

40

10

10

20

1 2

3 4

5

90

xkcd: Traveling Salesman Problem comic

20

a dynamic programming approach (more later) can improve performance slightly, but still intractable for reasonably large N

nP-hard problems: knapsack problem

another nP-hard problem:

Knapsack Problem: Given N items of known weights w1,…,wN and values v1,…,vN and a knapsack of capacity W, find the most value subset of items that fit in the knapsack.

example: suppose a knapsack with capacity of 50 lb. Which items do you take?

tiara $5000 3 lbscoin collection $2200 5 lbsHDTV $2100 40 lbslaptop $2000 8 lbssilverware $1200 10 lbsstereo $800 25 lbsPDA $600 1 lbclock $300 4 lbs

generate & test solution: try every subset & select the one with greatest value

21

22

Dictionary revisited

recall the Dictionary class earlier

the ArrayList add method simply appends the item at the end O(1)

the ArrayList contains method performs sequential search O(N)

this is OK if we are doing lots of adds and few searches

import java.util.List;import java.util.ArrayList;import java.util.Scanner;import java.io.File;

public class Dictionary { private List<String> words; public Dictionary() { this.words = new ArrayList<String>(); } public Dictionary(String filename) { this();

try { Scanner infile = new Scanner(new File(filename)); while (infile.hasNext()) { String nextWord = infile.next(); this.words.add(nextWord.toLowerCase()); } } catch (java.io.FileNotFoundException e) { System.out.println("FILE NOT FOUND"); } } public void add(String newWord) { this.words.add(newWord.toLowerCase()); } public void remove(String oldWord) { this.words.remove(oldWord.toLowerCase()); } public boolean contains(String testWord) { return this.words.contains(testWord.toLowerCase()); }}

23

Timing dictionary searches

we can use our StopWatch class to verify the O(N) efficiency

dict. size insert time 38,621 401 msec 77,242 612 msec144,484 1123 msec

dict. size search time

38,621 1.10 msec 77,242 2.61 msec144,484 5.01 msec

execution time roughly doubles as dictionary size doubles

import java.util.Scanner;import java.io.File;

public class DictionaryTimer { public static void main(String[] args) { System.out.println("Enter name of dictionary file:"); Scanner input = new Scanner(System.in); String dictFile = input.next();

StopWatch timer = new StopWatch();

timer.start(); Dictionary dict = new Dictionary(dictFile); timer.stop();

System.out.println(timer.getElapsedTime()); timer.start(); for (int i = 0; i < 100; i++) { dict.contains("zzyzyba"); } timer.stop(); System.out.println(timer.getElapsedTime()/100.0); }}

24

Sorting the list

if searches were common, then we might want to make use of binary search this requires sorting the words first, however

we could change the Dictionary class to do the sorting and searching a more general solution would be to extend the ArrayList class to SortedArrayList could then be used in any application that called for a sorted list

recall:public class java.util.ArrayList<E> implements List<E> { public ArrayList() { … } public boolean add(E item) { … } public void add(int index, E item) { … } public E get(int index) { … } public E set(int index, E item) { … } public int indexOf(Object item) { … } public boolean contains(Object item) { … } public boolean remove(Object item) { … } public E remove(int index) { … } …}

25

SortedArrayList (v.1)

using inheritance, we only need to redefine what is new add method sorts after adding; indexOf uses binary search no additional fields required

big-Oh for add? big-Oh for indexOf?

import java.util.ArrayList;import java.util.Collections;

public class SortedArrayList<E extends Comparable<? super E>> extends ArrayList<E> { public SortedArrayList() { super(); } public boolean add(E item) { super.add(item); Collections.sort(this); return true; } public int indexOf(Object item) { return Collections.binarySearch(this, (E)item); }}

26

SortedArrayList (v.2)

is this version any better? when? big-Oh for add? big-Oh for indexOf?

import java.util.ArrayList;import java.util.Collections;

public class SortedArrayList<E extends Comparable<? super E>> extends ArrayList<E> { public SortedArrayList() { super(); } public boolean add(E item) { // NOTE: COULD REMOVE THIS METHOD AND super.add(item); // JUST INHERIT THE ADD METHOD FROM return true; // ARRAYLIST AS IS } public int indexOf(Object item) { Collections.sort(this); return Collections.binarySearch(this, (E)item); }}

27

SortedArrayList (v.3)

if insertions and searches are mixed, sorting for each insertion/search is extremely inefficient instead, could take the time to insert each item into its correct position big-Oh for add? big-Oh for indexOf?

import java.util.ArrayList;import java.util.Collections;

public class SortedArrayList<E extends Comparable<? super E>> extends ArrayList<E> { public SortedArrayList() { super(); } public boolean add(E item) { int i; for (i = 0; i < this.size(); i++) { if (item.compareTo(this.get(i)) < 0) { break; } } super.add(i, item); return true; } public int indexOf(Object item) { return Collections.binarySearch(this, (E)item); }}

search from the start vs. from the end?

28

Dictionary using SortedArrayList

note that repeated calls to add serve as insertion sort

dict. size insert time 38,621 29.2 sec 77,242 127.9 sec144,484 526.2 sec

dict. size search time

38,621 0.0 msec 77,242 0.0 msec144,484 0.1 msec

insertion time roughly quadruples as dictionary size doubles; search time is trivial

import java.util.Scanner;import java.io.File;import java.util.Date;

public class DictionaryTimer { public static void main(String[] args) { System.out.println("Enter name of dictionary file:"); Scanner input = new Scanner(System.in); String dictFile = input.next();

StopWatch timer = new StopWatch();

timer.start(); Dictionary dict = new Dictionary(dictFile); timer.stop();

System.out.println(timer.getElapsedTime()); timer.start(); for (int i = 0; i < 100; i++) { dict.contains("zzyzyba"); } timer.stop(); System.out.println(timer.getElapsedTime()/100.0); }}

29

SortedArrayList (v.4)if adds tend to be done in groups (as in loading the dictionary)

it might pay to perform lazy insertions & keep track of whether sorted big-Oh for add? big-Oh for indexOf?

if desired, could still provide addInOrder method (as before)import java.util.ArrayList;import java.util.Collections;

public class SortedArrayList<E extends Comparable<? super E>> extends ArrayList<E> { private boolean isSorted;

public SortedArrayList() { super(); this.isSorted = true; } public boolean add(E item) { this.isSorted = false; return super.add(item); } public int indexOf(Object item) { if (!this.isSorted) { Collections.sort(this); this.isSorted = true; } return Collections.binarySearch(this, (E)item); }}

30

Timing the lazy dictionary on searchesmodify the Dictionary class to use the lazy SortedArrayList

dict. size insert time 38,621 340 msec 77,242 661 msec144,484 1113 msec

dict. size 1st search 38,621 10 msec 77,242 61 msec144,484 140 msec

dict. size search time 38,621 0.0 msec 77,242 0.0 msec144,484 0.1 msec

import java.util.Scanner;import java.io.File;import java.util.Date;

public class DictionaryTimer { public static void main(String[] args) { System.out.println("Enter name of dictionary file:"); Scanner input = new Scanner(System.in); String dictFile = input.next(); StopWatch timer = new StopWatch()

timer.start(); Dictionary dict = new Dictionary(dictFile); timer.stop(); System.out.println(timer.getElapsedTime()); timer.start(); dict.contains("zzyzyba"); timer.stop(); System.out.println(timer.getElapsedTime());

timer.start(); for (int i = 0; i < 100; i++) { dict.contains("zzyzyba"); } timer.stop(); System.out.println(timer.getElapsedTime()/100.0); }}