480 likes | 755 Views
CHAPTER 4: Linked Structures. Java Software Structures: Designing and Using Data Structures Third Edition John Lewis & Joseph Chase. Chapter Objectives. Describe the use of references to create linked structures Compare linked structures to array-based structures
E N D
CHAPTER 4: Linked Structures Java Software Structures: Designing and Using Data Structures Third Edition John Lewis & Joseph Chase
Chapter Objectives • Describe the use of references to create linked structures • Compare linked structures to array-based structures • Explore the techniques for managing a linked list • Discuss the need for a separate node to form linked structures • Implement a stack collection using a linked list
References as Links • There are many ways to implement a collection • In chapter 3 we explored an array-based implementation of a stack collection • A linked structure uses object reference variables to link one object to another • Recall that an object reference variable stores the address of an object • In that sense, an object reference is a pointer to an object
Self-Referential Objects • A Person object, for instance, could contain a reference variable to another Person object: public class Person { private String name; private String address; private Person next; // a link to another Person object // whatever else }
Linked Lists • This type of reference can be used to form a linked list, in which one object refers to the next, which refers to the next, etc. • Each object in a list is often generically called a node • A linked list is a dynamic data structure in that its size grows and shrinks as needed, unlike an array, whose size is static or fixed • Java objects are created dynamically when they are instantiated
Non-linear Structures • A linked list, as the name implies, is a linear structure • Object references also allow us to create non-linear structures such as hierarchies and graphs
Managing Linked Lists • The references in a linked list must be carefully managed to maintain the integrity of the structure • Special care must be taken to ensure that the entry point into the list is maintained properly • The order in which certain steps are taken is important • Consider inserting and deleting nodes in various positions within the list
Elements without Links • The problem with self-referential objects is that they must "know" they are part of a list • A better approach is to manage a separate list of nodes that also reference the objects stored in the list • The list is still managed using the same techniques • The objects stored in the list need no special implementation to be part of the list • A generic list collection can be used to store any kind of object
Sentinel nodes • There are variations on the implementation of linked lists that may be useful in particular situations • One such solution is the use of sentinel nodes or dummy nodes on either end of the list • This practice eliminates the special cases of inserting or deleting the first or last node
Doubly Linked Lists • Another useful variation is a doubly linked list • In a doubly linked list each node has a reference to both the next and previous nodes in the list • This makes traversing the list easier
Implementing a stack with links • We can use a linked list to implement our stack collection from chapter 3 • First, however, we will need to create a LinearNode class to represent a node in the list
The LinearNode class /** * @author Lewis and Chase * * Represents a node in a linked list. */ package jss2; public class LinearNode<T> { /** reference to next node in list */ private LinearNode<T> next; /** element stored at this node */ private T element; /** * Creates an empty node. */ public LinearNode() { next = null; element = null; }
The LinearNode class (continued) /** * Creates a node storing the specified element. * @param elem element to be stored */ public LinearNode (T elem) { next = null; element = elem; } /** * Returns the node that follows this one. * @return LinearNode<T> reference to next node */ public LinearNode<T> getNext() { return next; } /** * Sets the node that follows this one. * @param node node to follow this one */
The LinearNode class (continued) public void setNext (LinearNode<T> node) { next = node; } /** * Returns the element stored in this node. * @return T element stored at this node */ public T getElement() { return element; } /** * Sets the element stored in this node. * @param elem element to be stored at this node */ public void setElement (T elem) { element = elem; } }
LinkedStack /** * @author Lewis and Chase * * Represents a linked implementation of a stack. */ package jss2; import jss2.exceptions.*; import java.util.Iterator; public class LinkedStack<T> implements StackADT<T> { /** indicates number of elements stored */ private int count; /** pointer to top of stack */ private LinearNode<T> top; /** * Creates an empty stack. */ public LinkedStack() { count = 0; top = null; }
LinkedStack – the push operation /** * Adds the specified element to the top of this stack. * @param element element to be pushed on stack */ public void push (T element) { LinearNode<T> temp = new LinearNode<T> (element); temp.setNext(top); top = temp; count++; }
LinkedStack – the pop operation /** * Removes the element at the top of this stack and returns a * reference to it. Throws an EmptyCollectionException if the stack * is empty. * @return T element from top of stack * @throws EmptyCollectionException on pop from empty stack */ public T pop() throws EmptyCollectionException { if (isEmpty()) throw new EmptyCollectionException("Stack"); T result = top.getElement(); top = top.getNext(); count--; return result; }
LinkedStack – the other operations • Using a linked implementation, the peek operation is implemented by returning a reference to top • The isEmpty operation returns true if the count of elements is 0, and false otherwise • The size operation simply returns the count of elements in the stack • The toString operation can be implemented by simply traversing the linked list.
Analysis of Stack Operations • Like our ArrayStack operations, the LinkedStack operations work on one end of the collection and are generally efficient • The push and pop operations, for the linked implementation are O(1) • Likewise, the other operations are also O(1)
Using Stacks - Traversing a Maze • A classic use of a stack is to keep track of alternatives in maze traversal or other trial and error algorithms • Using a stack in this way simulates recursion • Recursion is when a method calls itself either directly or indirectly
Using Stacks - Traversing a Maze • Run-time environments keep track of method calls by placing an activation record for each called method on the run-time stack • When a method completes execution, it is popped from the stack and control returns to the method that called it • Which is now the activation record on the top of the stack
Using Stacks - Traversing a Maze • In this manner, we can traverse a maze by trial and error by using a stack to keep track of moves that have not yet been tried
The Maze class /** * @author Lewis and Chase * * Represents a maze of characters. The goal is to get from the * top left corner to the bottom right, following a path of 1's. */ import jss2.*; public class Maze { /** * constant to represent tried paths */ private final int TRIED = 3; /** * constant to represent the final path */ private final int PATH = 7;
The Maze class (continued) /** * two dimensional array representing the grid */ private int [][] grid = { {1,1,1,0,1,1,0,0,0,1,1,1,1}, {1,0,0,1,1,0,1,1,1,1,0,0,1}, {1,1,1,1,1,0,1,0,1,0,1,0,0}, {0,0,0,0,1,1,1,0,1,0,1,1,1}, {1,1,1,0,1,1,1,0,1,0,1,1,1}, {1,0,1,0,0,0,0,1,1,1,0,0,1}, {1,0,1,1,1,1,1,1,0,1,1,1,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,1,1,1,1,1,1} }; /** * push a new attempted move onto the stack * @param x represents x coordinate * @param y represents y coordinate * @param stack the working stack of moves within the grid * @return StackADT<Position> stack of moves within the grid */
The Maze class (continued) private StackADT<Position> push_new_pos(int x, int y, StackADT<Position> stack) { Position npos = new Position(); npos.setx(x); npos.sety(y); if (valid(npos.getx(),npos.gety())) stack.push(npos); return stack; } /** * Attempts to iteratively traverse the maze. It inserts special * characters indicating locations that have been tried and that * eventually become part of the solution. This method uses a * stack to keep track of the possible moves that could be made. * @return boolean returns true if the maze is successfully traversed */
The Maze class (continued) public boolean traverse () { boolean done = false; Position pos = new Position(); Object dispose; StackADT<Position> stack = new LinkedStack<Position>(); stack.push(pos); while (!(done)) { pos = stack.pop(); grid[pos.getx()][pos.gety()] = TRIED; // this cell has been tried if (pos.getx() == grid.length-1 && pos.gety() == grid[0].length-1) done = true; // the maze is solved else { stack = push_new_pos(pos.getx(),pos.gety() - 1, stack); stack = push_new_pos(pos.getx(),pos.gety() + 1, stack); stack = push_new_pos(pos.getx() - 1,pos.gety(), stack); stack = push_new_pos(pos.getx() + 1,pos.gety(), stack); } }
The Maze class (continued) return done; } /** * Determines if a specific location is valid. * @param row int representing y coordinate * @param column int representing x coordinate * @return boolean true if the given coordinate is a valid move */ private boolean valid (int row, int column) { boolean result = false; /** Check if cell is in the bounds of the matrix */ if (row >= 0 && row < grid.length && column >= 0 && column < grid[row].length) /** Check if cell is not blocked and not previously tried */ if (grid[row][column] == 1) result = true; return result; }
The Maze class (continued) /** * Returns the maze as a string. * @return String representation of the maze grid */ public String toString () { String result = "\n"; for (int row=0; row < grid.length; row++) { for (int column=0; column < grid[row].length; column++) result += grid[row][column] + ""; result += "\n"; } return result; } }
The MazeSearch class /** * @author Lewis and Chase * * Demonstrates a simulation of recursion using a stack. */ public class MazeSearch { /** * Creates a new maze, prints its original form, attempts to * solve it, and prints out its final form. * @param args array of Strings */ public static void main (String[] args) { Maze labyrinth = new Maze(); System.out.println (labyrinth); if (labyrinth.traverse ()) System.out.println ("The maze was successfully traversed!"); else System.out.println ("There is no possible path."); System.out.println (labyrinth); } }
The Position class /** * @author Lewis and Chase * * Represents a single position in a maze of characters. */ public class Position { /** x coordinate */ private int x; /** y coordinate */ private int y; /** * Constructs a position and sets the x & y coordinates to 0,0. */ Position () { x = 0; y = 0; }
The Position class (continued) /** * Returns the x-coordinate value of this position. * @return int the x-coordinate of this position */ public int getx() { return x; } /** * Returns the y-coordinate value of this position. * @return int the y-coordinate of this position */ public int gety() { return y; }
The Position class (continued) /** * Sets the value of the current position's x-coordinate. * @param a value of x-coordinate */ public void setx(int a) { x = a; } /** * Sets the value of the current position's x-coordinate. * @param a value of y-coordinate */ public void sety(int a) { y = a; } }
The java.util.Stack Class • The Java Collections framework defines a Stack class with similar operations • It is derived from the Vector class and therefore has some characteristics that are not appropriate for a pure stack • The java.util.Stack class has been around since the original version of Java, and has been retrofitted to meld with the Collections framework