Assignment One

  1. Write a function to output the run length for each element in an array. Print the result on screen. For example, if the input is {1, 2, 2, 4, 4, 3, 3, 3}, the output should be shown as 1 1 2 2 4 2 3 3 on the screen. Specify the loop invariant and show how your program works.
  2. Write a function to interlace two integer arrays. For example, if the input is {1, 2, 3, 4} and {10, 11}, the result is {1, 10, 2, 11, 3, 4}.
  3. Write a faster method to compute square root. In our class, we have written a loop to compute the square root of integer N>=0 so that x^2 <= N && (x+1)^2 > N. Use the loop invariant {I: x is in range [a, b]} and the inverse loop condition {!B : a == b} to derive a faster solution. Try to half the size of the range during each loop. Hint: the initial range of [a, b] can be set to [0, N]. In the loop, let c = floor((a+b)/2), choose an interval [a, c-1], [c,c] or [c+1, b] so that x is guaranteed to be there.
  4. Euclid algorithm for Greatest Common Divisor (GCD). For two positive integers, if a > b, we have GCD(a, b) = GCD(a-b, b). Note that GCD(a,a) = a. Write a loop to compute GCD based on the above observations.
Solutions:
  1. class RunLength { public static void runlength(int[] a) { if (a == null) return; int k = 0; int rlen = 1; int curchar = a[0]; //{Loop invariant: rlen is the runlength of curchar ended at position k} while (k != a.length-1) { k++; if (a[k] == curchar) rlen++; else { System.out.println(curchar + " " + rlen); curchar = a[k]; rlen = 1; } } System.out.println(curchar + " " + rlen); } public static void main(String[] args) { int[] a = {1,1,2,3,3,4,5,5,5,6,6,8,9,9,9}; runlength(a); } }
  2. class Interlace { public static int[] interlace(int[] a, int[] b) { int[] c = new int[a.length + b.length]; int i = 0; int j = 0; int k = 0; //{ Loop invariant: [0,i) in a and [0,j) in b have been interaced in [0,k) in c } while (i != a.length || j != b.length) { if (i == a.length) { c[k] = b[j]; k++; j++; } else if (j == b.length) { c[k] = a[i]; k++; i++; } else { c[k] = a[i]; c[k+1] = b[j]; k+=2; i++; j++; } } return c; } public static void main(String[] args) { int[] a = {1,2,3,4,5,6,7}; int[] b = {10,11,12,13,14}; int[] c = interlace(a, b); for(int e: c) System.out.print(e + " "); } }
  3. class Sqrt { public static int sqrt(int n) { int a = 0; int b = n; //{ Loop invariant: sqrt(N) is in [a, b] } while (a <= b) { int c = (a+b)/2; if (c*c <= n && (c+1)*(c+1) > n) return c; if (c*c > n) b = c-1; else if ((c+1)*(c+1) <= n) a = c+1; } return -1; // We should never be here if n >= 0 } public static void main(String[] args) { System.out.println(sqrt(0)); System.out.println(sqrt(1111)); } }
  4. class Gcd { public static int gcd(int i, int j) { int a = i; int b = j; if (a < b) { int t = b; b = a; a = t; } //{ Loop invariant: gcd(a, b) = gcd(i, j) and a >= b } while (a != b) { int t = a - b; if (t > b) a = a - b; else { a = b; b = t; } } return a; } public static void main(String[] args) { System.out.println(gcd(25, 100)); System.out.println(gcd(256, 268)); } }

Assignment Two -- Recursion

This assignment includes three questions and one bonus question. The bonus question is not required. You still get full marks if you successfully complete the first three questions.
  1. In the class, we discussed the path finding problem in which a robot walks from the top left corner of a board to the right bottom corner. The robot can only move one step to the right or one step down. Now we put some obstacles on the way. Write a recursive program to print out all the possible paths by which the robot can reach the target.
  2. On a telephone key pad, each key corresponds to a number and a few letters (some have special characters). Given a sequence of numbers, we want to print out all the corresponding letter sequences. For example, "23" can be interpreted as "ad", "ae", "cf", etc.. Write a recursive function to achieve the goal.
  3. Write a recursive function to match regular expressions. In regular expressions, a "*" matches zero or any number of characters, a "." matches any single character. Thus "a*c" will match "ac" and "aabc", and "a.c" matches "abc". Your function should take a string and a regular expression as the input and return true if they match and otherwise return false.
  4. [Bonus question]: Write a function to solve the 8-puzzle. Some sample puzzles are at: http://www.tilepuzzles.com/default.asp?p=12.
Solutions:
  1. public class Path { public static void all_path(int[][] a, int r, int c, int N, int[][] board) { if (r == N-1 && c == N-1) { for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (board[i][j] == 0) System.out.print(a[i][j] + " "); else System.out.print("x "); } System.out.println(); } System.out.println(); return; } if (r < N-1 && board[r+1][c] == 0) { a[r+1][c] = 1; all_path(a, r+1, c, N, board); a[r+1][c] = 0; } if (c < N-1 && board[r][c+1] == 0) { a[r][c+1] = 1; all_path(a, r, c+1, N, board); a[r][c+1] = 0; } } public static void main(String[] s) { int[][] a = new int[5][5]; a[0][0] = 1; int[][] b = new int[5][5]; b[1][1] = 1; b[3][4] = 1; all_path(a, 0, 0, 5, b); } }
  2. class Tel { public static void num2str(char[] s, int k, int[] a, String[] dic) { if (a.length == 0) return; if (k == a.length) { for(char c: s) System.out.print(c); System.out.println(); return; } for(char c: dic[a[k]].toCharArray()) { s[k] = c; num2str(s, k+1, a, dic); } } public static void num2str(int[] a, String[] dic) { char[] s = new char[a.length]; num2str(s, 0, a, dic); } public static String[] build_dic() { String[] dic = new String[10]; dic[0] = " "; dic[1] = " "; dic[2] = "abc"; dic[3] = "def"; dic[4] = "ghi"; dic[5] = "jkl"; dic[6] = "mno"; dic[7] = "pqrs"; dic[8] = "tuv"; dic[9] = "wxyz"; return dic; } public static void main(String[] args) { String[] dic = build_dic(); int[] a = {1,2,3,4}; num2str(a, dic); } }
  3. class Reg { public static boolean match(String regex, String s) { if (regex.length() == 0) { if (s.length() != 0) return false; else return true; } if (regex.charAt(0) != '*') { if (s.length() == 0) return false; else if (regex.charAt(0) == s.charAt(0) || regex.charAt(0) == '.') return match(regex.substring(1), s.substring(1)); else return false; } else { if (s.length() == 0) return match(regex.substring(1), s); else return (match(regex.substring(1), s) || match(regex, s.substring(1))); } } public static void main(String[] args) { String reg = "**f*d."; String s = "afgfrtdd"; System.out.println(match(reg, s)); } } </li>

Assignment Three -- Abstract Data Types

  1. Write a function to reverse a singly lined list. For example, if a list is 1->2->3->4, the reversed list will be 4->3->2->1. Write both a loop version and a recursive version of the function. Your list should be able to contain arbitrary objects, not just integers.
  2. Write a function to merge two sorted linked lists into a sorted linked list. For example, given a list 1->4->9->10, and a list 5->8->11, the merged list will be 1->4->5->8->9->10->11. Write both an iterative implementation and a recursive one.
  3. Implement an abstract data type, circularly doubly linked list, using Java class. Doubly lined list have two pointers (references) in each node, one to the next node and the other to the previous one. Your list is also a circle: its tail node's next one is the head node and the head node's previous one is the tail node. Your data type should support insertion at the head, ordered insertion, item removal and finding specific items. Your data type also should also be iterable.
  1. public class List<Item> { private class Node<Item> { Item data; Node<Item> next; Node(Item v) {data = v;} } private Node<Item> head; public void insert(Item v) { Node<Item> t = new Node<Item>(v); t.next = head; head = t; } //Use iteration to reverse the list public void reverse() { if (head == null) return; if (head.next == null) return; Node<Item> p = head; Node<Item> q = head.next; // {loop invariant: list before node q have been reversed // p is right before q.} while (q != null) { Node<Item> t = q.next; q.next = p; p = q; q = t; } head.next = null; head = p; } // Recursion for reversion public Node<Item> reverse(Node<Item> h) { if (h == null) return h; if (h.next == null) return h; Node<Item> t = reverse(h.next); h.next.next = h; h.next = null; return t; } public void reverse2() { head = reverse(head); } public void print() { if (head == null) return; Node t = head; //{loop invariant: nodes before t have been printed out} while (t != null) { System.out.print(t.data + " "); t = t.next; } System.out.println(); } public static void main(String[] args) { List<Integer> list = new List<Integer>(); list.insert(3); list.insert(2); list.insert(1); list.reverse2(); list.print(); } }
  2. public class IntList { private Node head; public Node gethead() { return head;} public void insert(int v) { Node t = new Node(v); t.next = head; head = t; } public void merge(IntList h) { Node p = head; Node q = h.gethead(); Node w = new Node(0); Node t = w; // {loop invariant: nodes before p and q have been merged // into nodes of another list from w.next to t } while ( p != null || q != null) { if (p == null) { t.next = q; q = q.next; } else if (q == null) { t.next = p; p = p.next; } else { if (p.data < q.data) { t.next = p; p = p.next; } else { t.next = q; q = q.next; } } t = t.next; } t.next = null; head = w.next; } public Node merge(Node h1, Node h2) { if (h1 == null) return h2; if (h2 == null) return h1; if (h1.data < h2.data) { h1.next = merge(h1.next, h2); return h1; } else { h2.next = merge(h1, h2.next); return h2; } } public void merge2(IntList h) { head = merge(head, h.gethead()); } public void print() { if (head == null) return; Node t = head; //{loop invariant: nodes before t have been printed out} while (t != null) { System.out.print(t.data + " "); t = t.next; } System.out.println(); } public static void main(String[] args) { IntList list1 = new IntList(); list1.insert(7); list1.insert(4); list1.insert(2); IntList list2 = new IntList(); list2.insert(8); list2.insert(5); list2.insert(1); list2.insert(1); list1.merge(list2); list1.print(); } }

Assignment Four -- Sorting

  1. Implement a Java function to merge-sort a singly linked list. We assume that each node of the linked list contains an integer. The basic procedure is very similar to the mergesort on an array. Take advantage of the linked list structure to remove the extra memory usage.
  2. We have a pile of bolts and nuts. We know that they are in pairs. Our goal is to match the nuts and bolts quickly. One simple idea is to sort the bolts and nuts and then we can easily match them up. Unfortunately, we cannot compare the sizes between the nuts and we cannot compare the sizes between bolts. But we can tell whether a nut is bigger or a bolt is bigger by matching them up. Write a Java function to do the matching. You can use two integer arrays to represent the bolts and nuts. For instance, the bolts are [1,3,2] and the nuts are [3,2,1]. Your function should not compare between the nuts or bolts, but it can compare between a nut and a bold. The output should make both the bolts and nuts sorted. [Hint: the method is similar to quicksort].
  1. public class List { private class Node { int val; Node next; public Node(int v) { val = v; } } private Node head; public void pushfront(int v) { if (head == null) { head = new Node(v); return; } Node t = new Node(v); t.next = head; head = t; return; } public void print() { if (head == null) return; Node t = head; // {loop inv: nodes before t have been printed} while (t != null) { System.out.print(t.val + " "); t = t.next; } } private Node merge(Node a, Node b) { if (a == null) return b; if (b == null) return a; if (a.val < b.val) { a.next = merge(a.next, b); return a; } else { b.next = merge(b.next, a); return b; } } private Node get_head() { return head; } public void merge(List u) { Node h = merge(head, u.get_head()); head = h; } public Node findmid(Node a) { Node fast = a; Node slow = a; while (fast.next != null) { fast = fast.next; if (fast == null) break; if (fast.next == null) break; fast = fast.next; slow = slow.next; } return slow; } public Node mergesort(Node a) { if (a == null) return a; if (a.next == null) return a; Node m = findmid(a); Node t = m.next; m.next = null; return merge(mergesort(a), mergesort(t)); } public void mergesort() { head = mergesort(head); } public static void main(String[] args) { List list = new List(); list.pushfront(3); list.pushfront(7); list.pushfront(3); list.pushfront(20); list.mergesort(); list.print(); } }
  2. public class SortBoltsNuts { public static void match(int[] bolt, int[] nut, int start, int end) { if (start >= end) return; int p = partition(nut, bolt[start], start, end); partition(bolt, nut[p], start, end); match(bolt, nut, start, p-1); match(bolt, nut, p+1, end); } public static void swap(int[] a, int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; } public static int partition(int[] a, int v, int start, int end) { // assume v is always in a[] int k; for (k = start; k <= end; k++) { if ( a[k] == v ) break; } swap(a, k, start); int i = start+1; int j = end; // {loop invariant: a[start..i) <= v, a(j, end] > v } while (i <= j) { if (a[i] <= v) i++; else { swap(a, i, j); j--; } } swap(a, start, j); return j; } public static void main(String[] args) { int[] nut = {1,4,3}; int[] bolt = {4,3,1}; match(nut, bolt, 0, nut.length-1); for(int i = 0; i < nut.length; i++) System.out.println("(" + nut[i] + "," + bolt[i] + ")"); } }

Assignment Five -- Binary Search Tree

  1. Convert an ordered doubly linked list to a binary search tree. You can only use constant extra memory.
  2. Convert a binary search tree into an ordered doubly linked list. A binary search tree and a circular doubly linked list are conceptually built from the same type of nodes - a data field and two references to other nodes. Given a binary search tree, rearrange the references so that it becomes a circular doubly-linked list (in sorted order). Hint: create a circularly linked list A from the left subtree, a circularly linked list B from the right subtree, and make the root a one node circularly linked list. Them merge the three lists.
Solution:
  1. public class Node { int val; Node next; Node prev; Node(int v) { val = v; } } public class Pair<Item1, Item2> { Item1 first; Item2 second; Pair(Item1 f, Item2 s) {first = f; second = s;} } public class List2Tree { public static Pair<Node, Node> list2tree(Node h, int n) { // Convert a doubly linked list with head h and length n // to a tree. The return is the root of the tree and the // the (n+1) node in list h if (h == null) return new Pair<Node,Node>(null, null); if (n == 0) return new Pair<Node,Node>(null, h); int m = n/2; Pair<Node,Node> t = list2tree(h, m); Node left_root = t.first; Node root = t.second; root.prev = left_root; Pair<Node,Node> u = list2tree(root.next, n-m-1); root.next = u.first; return new Pair<Node,Node>(root, u.second); } public static Node insert(Node h, int n) { Node t = new Node(n); t.next = h; if (h != null) h.prev = t; return t; } public static void printlist(Node r) { while (r != null) { System.out.print(r.val + " "); r = r.next; } System.out.println(); } public static void printtree(Node r) { if (r == null) { System.out.print("X "); return;} System.out.print(r.val + " "); printtree(r.prev); printtree(r.next); } public static void main(String[] args) { Node list = insert(null, 8); list = insert(list, 7); list = insert(list, 6); list = insert(list, 5); list = insert(list, 4); list = insert(list, 3); list = insert(list, 2); list = insert(list, 1); printlist(list); Pair<Node,Node> p = list2tree(list, 8); printtree(p.first); System.out.println(); } }
  2. See http://cslibrary.stanford.edu/109/TreeListRecursion.html

Assignment Six

In this assignment, you will solve a small scale real problem. It is an open-ended assignment. You may implement anything you are interested in. You may form 2-people groups to complete this assignment. A solo project is also welcomed. In fact, I would expect a bit more for the group projects. Each group just needs to submit a single copy of code and a brief description about what features you have implemented.

If you do not want to choose, the default one is to implement a computer player for a connect-4 game or a Othello (reversi) game or some variations of these games such as 3D tic tac toe. Your computer player should try to win the game and each move should be derived as soon as possible.

You may use the following sample tic tac toe code in C++ as a starting point. Note that you have to write in Java though.

#include "stdio.h" void show_board(int b[3][3]) { printf(" "); for (int i = 0; i < 3; i++) { printf("%d ", i); } printf("\n ------\n"); for (int i = 0; i < 3; i++) { printf("%d | ", i); for (int j = 0; j < 3; j++) printf("%d ", b[i][j]); printf("\n"); } printf(" ======\n"); } void clear_board(int b[3][3]) { for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) b[i][j] = 0; } bool checkwin(int b[3][3], int r, int c) { if (b[r][0] == b[r][1] && b[r][1] == b[r][2] && b[r][0] > 0) return true; if (b[0][c] == b[1][c] && b[1][c] == b[2][c] && b[0][c] > 0) return true; if (r == c && b[0][0] == b[1][1] && b[1][1] == b[2][2] && b[1][1] > 0) return true; if (r+c == 2 && b[0][2] == b[1][1] && b[1][1] == b[2][0] && b[1][1] > 0) return true; return false; } bool isfull(int b[3][3]) { for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) if (b[i][j] == 0) return false; return true; } int play(int b[3][3], int turn, int &mr, int &mc ) { int result; // 1: undetermined, 2: computer wins, 0: user wins if (turn == 1) result = -10; else result = 10; for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) { if (b[i][j] == 0) { b[i][j] = turn+1; if (checkwin(b, i, j)) { b[i][j] = 0; mr = i; mc = j; if (turn == 1) return 2; else return 0; } if (isfull(b)) { b[i][j] = 0; mr = i; mc = j; return 1; } int u, v; int r = play(b, 1-turn, u, v); b[i][j] = 0; if (turn == 1 && r > result) { result = r; mr = i; mc = j; } if (turn == 0 && r < result) { result = r; mr = i; mc = j; } } } return result; } int main() { int board[3][3]; for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) board[i][j] = 0; show_board(board); int turn = 0; // 1: computer, 0: human player while (!isfull(board)) { if (turn == 0) { printf("Input (row, col): "); int r, c; scanf("%d %d", &r, &c); board[r][c] = 1; show_board(board); if (checkwin(board, r, c)) { printf("You win!\n"); return 0; } } else { int r, c; result = play(board, turn, r, c); board[r][c] = 2; show_board(board); if (checkwin(board, r, c)) { printf("Computer wins!\n"); return 0; } } turn = 1 - turn; } printf("It is a draw!\n"); return 0; }

The key is to implement the recursive method that generates the best move for a computer player given the current board configruation. In the tic tac toe example, backtracking exhausts the whole state tree. This may be too slow for a large board game such as the connect-4 or the Othello. There are a few things you can do to speed up the process:

  1. We can use a map to store the situations we have already evaluated during the search. When the same board configuration appears again, we can directly use the stored move instead of resorting to recursion. This avoids lots of duplicated computations. We probably should constrain the number of states that we store in the map. One scheme is to keep a record of the depth of the recursion, and we only store the states that correspond to shadow depths. These states are close to the top of the search tree. The reason to only keep the states near the top of the search tree is because these states are few and they usually require a long time to evalute in the recursion.
  2. We can further prune the search tree to speed up the process. One commonly used strategy is called the alpha-beta pruning. Let's use the tic tac toe program as an example to illustrate the concept. We introduce two more variables, alpha and beta, to represent the current best move score for the computer and the human player. As we discussed in the class, the computer wants to find a bigger alpha and the human player wants a small beta.

    Let's assume that it's computer's turn and it is trying to evaluate the next possible move. In the class, we use an exhaustive search method to get the lowest score for the human player's move; if we treat the current computer move state as a node in the search tree, we try all the human player nodes connected to this computer node. This is in fact not necessary if one human node gives a score (beta) lower than the current alpha, because this gurantees that the current computer move is not better than the previous ones and we thus can skip this computer node and preceed to the next one.

    Google alpha-beta pruning to see some discussions on the web.

  3. The above methods are exact methods, which gurantee the best move. In many situations, especially when there is a time constraint, we often just need a good enough move. Invent your own heuristics to obtain a good move without doing an expensive exhaustive search, or to allow ignore part of the search tree.