Graphs, Searching, and Boggle (PA4)

Ace your homework & exams now with Quizwiz!

Connected Graphs

An undirected graph is connected if there is a path from every vertex to every other vertex

Depth-First Search

Depth-first search visits a node, and then recursively does depth-first search on all nodes from that node. In this sense, it will go all the way down a certain path once, and then backtrack to visit other nodes.

boggleutil: BoardDie

Each dice on the Board is represented by a "BoardDie" object, which contains pointers to all adjacent dice and a value that denotes the string on the current dice. It also has a vector<int> coordinate value, which is just a vector of size 2 that equals the coordinate of the node on the board. There is also a "visited" bool that checks if a die has already been visited.

Adjacency Lists

Each vertex in the graph contains a list of vertices adjacent to it. In a weighted graph, this would also contain the weight of the graph. Therefore, the storage required for this one is |V| + |E|. Example:: V0: {V1, V2} means V0 is connected to V1 and V2

Kruskal's Algorithm

Kruskals algorithm starts with a forest of single nodes, and joins them together as we go. Start with a forest, where each node is a vertex. Create a priority queue containing each EDGE, ordered by edge weight. Pop the smallest weight edge (Edge from v1 to v2) If v1 and v2 are already part of the same tree (There is a path between them), pop the next edge. Otherwise, join the two vertices with an edge. Pop the next edge. Continue until all nodes are connected and in the same tree.

boggleutil: Board

The Board is an object that contains a 2-D vector, where each element is a pointer to a BoardDie: vector<vector< BoardDie*> >. The size of the outermost vector should be the number of rows, and the size of the inner vector should be the number of columns. Therefore, there are row*col number of BoardDie pointers. When the board is first created, we are given a width, height, and a board layout (a multidimensional array of strings, where each string is the value on the dice). In the constructor, I have two for loops that iterate through the rows and columns. Then, inside the loops, I create a new BoardDie with the correct coordinate values and the correct letter. After that, I loop through again each BoardDie, and set the adjacency pointers of the dice to all dice around it (n, ne, e, se, s, sw, w, nw).

Searching With A Queue

This is a breadth first search of an unweighted graph. Each node should have both a "distance" and a "prev" field. Start by setting every node's distance to Infinity (max possible distance, so any other distance will beat it out) and previous values to null. Add the source node to a queue. Now, every time a node in the queue is visited, add all adjacent nodes to the queue with a distance of current distance + 1. If this new distance is greater than the distance already there, do not add the node to the queue.

Depth-First Properties

You have to keep track of which edges have been traversed You need to know which node to backtrack to one you hit a dead end (This can be done with a stack). When you visit a node, you aren't sure if you've found the shortest path or not.

Breadth-First Properties

You need to keep track of which nodes you've visited You need to make sure you visit all nodes adjacent to a node before visiting any others (this can be done with a queue, just add the adjacent nodes to a queue each time). The first time you visit a node, you can be sure you've found the shortest path to it.

boggleutil: LexiconNode

A LexiconNode is a node that contains 26 pointers to other nodes, labeled a-z (The alphabet). It also has a "value" field, which is the character that the current node is holding, and a "isWord" (also called "end") boolean that denotes whether or not the current node marks the end of a word.

Cycle

A cycle in a directed graph is a path in which the first and the last vertices are the same.

Strongly Connected Graphs

A directed graph is strongly connected if, for every vertex, there is a path to every other vertex

Weakly Connected Graphs

A directed graph is weakly connected if the undirected version of it would be connected but it is not strongly connected.

Completely Connected Graphs

A graph is completely connected if there is an edge from every vertex to every other vertex.

Sparse Graph

A graph with "close to" |V| edges is considered "sparse", which means there are roughly the same number of vertices as there are edges. This means that each vertex is likely only connected to one other vertex by a single edge.

Dense Graph

A graph with "close to" |V|^2 edges is considered "dense", which means that there would be roughly an edge for every single vertex to every other vertex. For example, if there are 5 vertices and 25 edges, this means there is an edge from every vertex to every other vertex.

Greedy Algorithms

A greedy algorithm is one that, at each stage, commits to the best option AT THAT STAGE, with no concern for whatever may happen in the future. They require no backtracking or second guessing. I.e. making change in the U.S, we always use the fewest number of coins possible by starting with large numbers, and it works. i.e. If I need change for 21 cents, I would start with 2 dimes and then a penny as the next best. BUT if there was a 12 cent coin and we were trying to get change for 21 cents, the greedy algorithm would, at the first stage, give a single 12 cent coin, and now there are 9 cents remaining, which can only be made with a nickel and 4 pennies. In this way, the greedy algorithm would fail.

Paths

A path from v to w in a graph G is a sequence of vertices in V such that all vertices between v and w are connected by an edge (Or "Are in E")

Simple Cycle

A simple cycle is a simple path and a cycle.

Simple Path

A simple path is when all the vertices are different, except the first and the last.

Spanning Trees (Undirected/Unweighted)

A spanning tree contains all vertices, has no cycles, and is connected. It's called "spanning" because it connects all the graph's vertices. It's also called a "Tree" because there are no cycles.

Graph

A structure that consists of elements called "nodes" or "vertices" and a set of connections called "edges". They are not necessarily hierarchical or sequential. This is denoted as: G = (V, E), which is a set of vertices V and a set of edges E. Therefore, the number of vertices is |V| and the number of edges is |E|

Adjacency Matrix

An adjacency matrix is a 2D array, where the value at [i][j] denotes whether or not vertex i is connected by an edge to vertex j. For an unweighted graph, this value is "1" if there is an edge, and "0" if there is not. For a weighted graph, this value is the weight of the edge between the vertices. There are always |V| rows and |V| columns in the matrix. These are very space inefficient for sparse graphs, since most vertices aren't connected to each other.

Boggle!

Boggle had two main files - "boggleplayer" and "boggleutil". BogglePlayer did all the actual work, boggleutil just specified some classes that we're going to use - such as the board and the lexicon.

Comparison between searches

Both breadth-first and depth-first ultimately visit all nodes, but breath first is much easier and better to implement.

Breadth First Search

Breadth-first search visits a node, and then all nodes adjacent to that node, and then all nodes adjacent to that node, and etc. Picture going one level at a time through a tree.

NP-Complete Intro

Find shortest path in unweighted graph using breadth search: O(|V| + |E|) worst case. Find shortest path in weighted graphs: Dijkstras algorithm, O(|E| log |V|) worst case. Minimum cost spanning tree: O(|E|log|V|) worst case. All these algorithms can be solved in reasonable time. However, not all problems can be!

NP-Complete

For many graph problems, the best known algorithms to solve them are exponential O(2^|V|). In this sense, the only way to solve them is literally by solving every single option possible. However, nobody can prove there aren't better algorithms, and lots of people are trying. Coming up with a solution would make you famous.

The Traveling Salesman

Given a weighted graph (Distances between cities), find the shortest path that visits every single vertex (city) once. There is know known algorithm to solve this problem other than to test every single option, which gets incredibly massive the more cities we have. Thus, it is NP-Complete

Directed Acyclic Graph (DAG)

If a directed graph has NO POSSIBLE CYCLES, it is a DAG. All trees are DAGS.

boggleplayer: getAllValidWords

I did this a super hacky way. I traverse through the Lexicon, every time I find a "isWord" boolean that's true, I check if the word is on the board using isOnBoard. The disadvantage of this is that it takes FOREVER since we have to go through every word in the english language to check if it's on the board. But it works. Just not extra credit. This allows the computer player to find all the words on the board.

Directed Graph

In a directed graph, if there is an edge going from vertex v to vertex w, then (v,w) is an ordered pair.

Weighted Graphs

In a weighted graph, each edge has a "weight" or a "cost" applied to each edge.

Undirected Graphs

In an undirected graph, if there is an edge going from vertex v to vertex w, then (v,w) is an unordered pair (Can also be written as (w,v)).

Shortest Path Problems

Shortest path algorithms try to find the fastest way to get from point A to point B, by finding the shortest path from the source to EVERY OTHER VERTEX.

boggleutil: Lexicon

The Lexicon is a multiway try containing LexiconNode's, each of which has 26 pointers to other LexiconNode's. To insert into the Lexicon, we first start with a word. Now, beginning with the very first letter of that word, find the corresponding child pointer in the root node. (For example, if the word is "Hello", we first want to find the "h" node pointer in the root). If the node doesn't already exist (i.e. the "h" child pointer is null), create a new node there with the letter value and set the current node equal to that. After each insertion, iterate once more through the string and continue down the trie. Once there are no more letters in the string to insert, set the "isWord" variable of the current node to true, showing that it is the end of word. We can now find the word by following the root down to that letter. For find(), just search each letter in the world and follow the trie down until there are no more letters. If at any point there is a null pointer or the final letter does not have "isWord" marked as true, find is false.

Length of a path

The length of a path is the number of EDGES in the path.

Finding an Unweighted Spanning Tree

There are likely many spanning trees for an undirected graph. As long as all nodes are connected and there are no cycles, it's a spanning tree. You can use Breadth-First search starting at any node to achieve this.

Spanning Trees (Weighted)

There is only one spanning tree with the smallest weight, or cost. This can be helpful to find because it can give the shortest path from any node to any other node.

Dijkstra's Algorithm

This algorithm is for weighted graphs, and there can be no negative weights. For this algorithm, we will use a priority queue, where each element is a (v, cost) pair, where v is the vertex and cost is the current distance from the source. Just like before, each node will have a dist and a prev field. There is now also a "done" field. Set all dist fields to INFINITY and all prev fields to null. Put the very first node into a PRIORITY QUEUE sorted based on the distance. Pop the first vertex from the priority queue (This should be the one with the smallest cost). Is the done field of this vertex marked true? Pop the next vertex. Otherwise, for each node adjacent to the popped node, check whether the new cost would be less than the adjacent node's current cost. If the cost is less than, update the "dist" field in the adjacent node and add the pair to the priority queue (Where distance is the cost). Pop the next node.

Prim's Algorithm for Spanning Trees

This algorithm is very similar to Dijkstras Each vertex will have a "done, prev, and cost" field. Initialize the values just like before. Pick any vertex and update the values. Now, add all adjacent nodes to a priority queue based on cost. Set done to true. Pop the first node in the priority queue (Smallest cost) Is the done field marked true? Then there's already an edge connecting the two vertices, and we can't use it. Pop the next value Otherwise, accept the edge between the current and adjacent node. Mark the node as done, and set the "prev" field to show the path. Add all adjacent nodes again to the priority queue. The result should be a spanning tree with the lowest cost.

boggleplayer: isOnBoard(string& word)

This one's definitely trickier. I use this method as a gateway to a visitDice(BoardDie* currDie, string word, vector<int>& dice) method that I create, that will handle recursion. First, I call visitDice on the root, with the original word, and an empty dice vector. VisitDice will first check if there are any characters remaining in the word. If there aren't, it will return true. This is the only case that returns true. It will then check if currDie exists. if it doesn't, it will return. It then checks if currDie has already been visited. If it has, it will return. At this point, we know that the current die hasn't been visited, that there's letters left in the word, and that the current die exists. Therefore, we can conclude that we are on track to making a word. Mark visited to true. Now we need to compare the dice's current value to the next character/string in the word, to see if they match. Some dice have more than one character, so the first thing I do is get the length of the string on the dice. I then extract that many characters from the string. I.e. if the current dice reads "ing" and my word is "ingot", it will extract the "ing" from Ingot. If the current dice just has "h", then it'll only extract the next character from the word. Then, it checks for a comparison, to see if the dice's character matches the word's next character. If they DO NOT match, it sets the current node's visited value to false (since It wont be used) and returns. Finally, the recursion. I call visitDice on every single adjacent node to the dice, and pass in a substring of the word that is all remaining letters in the word. If any of those recursive calls returns true (That is, we went all the way until there were no letters left), then I know that the current dice is a path to a word. At each stage of recursion I insert the coordinate of the current die into the dice vector, such that when the recursion ends, it should contain the coordinates of the entire path. Done. We'll definitely go over this in person.

boggleplayer: isInLexicon(string& word)

isInLexicon simply checks if word is in the Lexicon. Starting with the root, I just call find on my Lexcion and let it search for the word.


Related study sets

Restaurant Tyler Menu Test 08/21

View Set

3.4.3 Pre-Cal Graphing Polynomial Functions

View Set

BIO60 Lab Exam 1: Intro to Scientific Method

View Set

physics and human affairs final uark

View Set

PSYCHOLOGY CHAPTER 12: Social Psychology

View Set