Merge pull request #224 from email2liyang/master

scala trees
This commit is contained in:
wangzheng0822 2019-01-14 10:54:10 +08:00 committed by GitHub
commit 04d8cdb6bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 894 additions and 0 deletions

View File

@ -0,0 +1,61 @@
package ch23_binary_tree
import scala.collection.mutable
class Node[T](var data: T, var left: Option[Node[T]], var right: Option[Node[T]])
class BinaryTree[T] {
def preOrder(root: Option[Node[T]]): String = {
val result = new StringBuilder
if (root.isDefined) {
result.append(root.map(_.data.toString).get)
result.append(preOrder(root.get.left))
result.append(preOrder(root.get.right))
}
result.mkString
}
def inOrder(root: Option[Node[T]]): String = {
val result = new StringBuilder
if (root.isDefined) {
result.append(inOrder(root.get.left))
result.append(root.map(_.data.toString).get)
result.append(inOrder(root.get.right))
}
result.mkString
}
def postOrder(root: Option[Node[T]]): String = {
val result = new StringBuilder
if (root.isDefined) {
result.append(postOrder(root.get.left))
result.append(postOrder(root.get.right))
result.append(root.map(_.data.toString).get)
}
result.mkString
}
def levelOrder(root: Option[Node[T]]): String = {
val result = new StringBuilder
val queue = new mutable.Queue[Node[T]]()
if (root.isDefined) {
queue += root.get
while (!queue.isEmpty) {
val node = queue.dequeue()
result.append(node.data.toString)
if (node.left.isDefined) {
queue += node.left.get
}
if (node.right.isDefined) {
queue += node.right.get
}
}
}
result.mkString
}
}

View File

@ -0,0 +1,148 @@
package ch24_binary_search_tree
import ch23_binary_tree.{BinaryTree, Node}
import scala.util.control.Breaks._
class BinarySearchTree(var root: Option[Node[Int]]) extends BinaryTree[Int] {
def insert(data: Int): Node[Int] = {
val dataNode = new Node(data, None, None)
root match {
case None => root = Some(dataNode)
case Some(_) => {
var p = root
breakable {
while (p.isDefined) {
if (data > p.get.data) {
p.get.right match {
case None => {
p.get.right = Some(dataNode)
break
}
case Some(_) => p = p.get.right
}
}
if (data < p.get.data) {
p.get.left match {
case None => {
p.get.left = Some(dataNode)
break
}
case Some(_) => p = p.get.left
}
}
}
}
}
}
dataNode
}
def find(data: Int): Option[Node[Int]] = {
var p = root
breakable {
while (p.isDefined) {
if (data > p.get.data) {
p = p.get.right
} else if (data < p.get.data) {
p = p.get.left
} else {
//find the value
break
}
}
}
p
}
def delete(data: Int): Unit = {
//there are 3 scenarios
//1: data is leaf node
//2: data has one child node
//3: data has two child nodes, we need to find out the smallest node from right branch
var p = root
var pp: Option[Node[Int]] = None //parent node of deleted node
//find matching node to delete
breakable {
while (p.isDefined) {
if (data > p.get.data) {
pp = p
p = p.get.right
} else if (data < p.get.data) {
pp = p
p = p.get.left
} else {
//find the value
break
}
}
}
if (p.isEmpty) {
//find nothing
return
}
//now we find the node to delete
//scenario 3
if (p.get.left.isDefined && p.get.right.isDefined) {
//need to find out the smallest node in right branch
var minPP = p
var minP = p.get.right
while (minP.get.left.isDefined) {
minPP = minP
minP = minP.get.left
}
//assign the smallest value in the right branch to the node to be deleted
p.get.data = minP.get.data
//now problems becomes delete the minP in the tree
//minP must not have any left child node
//minP may or may not have right child node
//it will fall back to scenario 1 or 2
p = minP
pp = minPP
}
//child is the child of p
var child: Option[Node[Int]] = None
if (p.get.left.isDefined) {
child = p.get.left
} else if (p.get.right.isDefined) {
child = p.get.right
}
//starting the node deletion
pp match {
case None => root = child
case Some(parentNode) => {
if (parentNode.left == p) {
parentNode.left = child
} else if (parentNode.right == p) {
parentNode.right = child
}
}
}
}
def height(): Int = {
_height(root)
}
private[this] def _height(nodeOpt: Option[Node[Int]]): Int = {
nodeOpt match {
case None => 0
case Some(node) => {
if (node.left.isEmpty && node.right.isEmpty) {
1
} else {
scala.math.max(_height(node.left), _height(node.right)) + 1
}
}
}
}
}

View File

@ -0,0 +1,95 @@
package ch28_heap
import scala.util.control.Breaks._
class Heap(val capacity: Int, var elementCount: Int = 0) {
def this(arrayParam: Array[Int], bottomUp: Boolean) = {
this(arrayParam.length + 1)
if (bottomUp) {
arrayParam.foreach(this.insert)
} else {
//copy data into array of heap
for (i <- arrayParam.indices) {
array(i + 1) = arrayParam(i)
elementCount = arrayParam.length
}
for (i <- elementCount / 2 + 1 to 1 by -1) {
heapifyTopDown(i, elementCount - 1)
}
}
}
require(capacity > 0, "capacity should be > 0")
val array: Array[Int] = new Array[Int](capacity)
def insert(data: Int): Unit = {
if (elementCount == capacity - 1) {
throw new IllegalStateException("heap full")
}
elementCount += 1
array(elementCount) = data
//heapify bottom up
//compare the element with it's parent node i/2 until parent node > child node
//this will make sure the root element of the tree is the biggest value
var i = elementCount
while (i / 2 > 0 && array(i) > array(i / 2)) {
val temp = array(i)
array(i) = array(i / 2)
array(i / 2) = temp
i = i / 2
}
}
def removeMax(): Int = {
require(elementCount > 0, "heap is empty")
val result = array(1)
array(1) = array(elementCount)
elementCount -= 1
heapifyTopDown(1, elementCount)
result
}
//heapify from top to bottom
//start from the top to compare with it's child nodes
//swap if child node > parent node
//stop at child node <= parent node
private[this] def heapifyTopDown(startIndex: Int, stopIndex: Int) = {
var pointer = startIndex
breakable {
while (true) {
var maxPos = pointer
if (pointer * 2 <= stopIndex && array(pointer * 2) > array(maxPos)) {
maxPos = pointer * 2
}
if (pointer * 2 <= stopIndex && array(pointer * 2 + 1) > array(maxPos)) {
maxPos = pointer * 2 + 1
}
if (maxPos == pointer) {
break
}
//swap the parent and child
val temp = array(pointer)
array(pointer) = array(maxPos)
array(maxPos) = temp
//start a new round
pointer = maxPos
}
}
}
}
object Heap {
def heapSort(array: Array[Int]): Array[Int] = {
val result = new Array[Int](array.length)
val heap = new Heap(array, true)
for (i <- result.length - 1 to 0 by -1) {
result(i) = heap.removeMax()
}
result
}
}

View File

@ -0,0 +1,46 @@
package ch29_heap_solutions
import java.io.{BufferedWriter, File, FileWriter}
import scala.collection.mutable
import scala.io.Source
import scala.util.control.Breaks._
object FileMerger {
/**
* each given file has sorted String as content, we need to merge them together
*
* @param smallFiles - small files with sorted content
* @return merged file
*/
def mergeFiles(smallFiles: List[File]): File = {
//init output file
val output = File.createTempFile("merged-file", ".txt")
val writer = new BufferedWriter(new FileWriter(output))
//init small top heap
val priorityQueue = new mutable.PriorityQueue[(Char, Source)]()(Ordering.by((_: (Char, Source))._1).reverse)
val sources = smallFiles.toArray.map(smallFile => Source.fromFile(smallFile))
//init fill the priority queue from each file
sources.foreach(source => priorityQueue.enqueue((source.next(), source)))
breakable {
while (true) {
val next = priorityQueue.dequeue()
val output: Char = next._1
val source = next._2
writer.append(output)
if (source.hasNext) {
priorityQueue.enqueue((source.next(), source))
}
//determine the end of merge
if (sources.forall(!_.hasNext) && priorityQueue.isEmpty) {
break
}
}
}
writer.close()
output
}
}

View File

@ -0,0 +1,42 @@
package ch29_heap_solutions
import scala.collection.mutable
class MiddleNumberKeeper(val percent: Double) {
def this() = this(0.5)
val bigTop = new mutable.PriorityQueue[Int]()
val smallTop = new mutable.PriorityQueue[Int]()(scala.math.Ordering.Int.reverse)
def put(num: Int): Unit = {
if (smallTop.nonEmpty && num >= smallTop.head) {
smallTop += num
adjustHeap()
return
}
//for any other scenario, we just put the item to bitTop then adjustHeap
bigTop += num
adjustHeap()
}
def get(): Option[Int] = {
bigTop.headOption
}
private[this] def adjustHeap(): Unit = {
val totalLength = smallTop.length + bigTop.length
//deal with bigTop
while (bigTop.length.doubleValue() / totalLength - percent > 0.0001) {
//move item from bigTop to smallTop
smallTop += bigTop.dequeue()
}
//deal with smallTop
while (smallTop.length.doubleValue() / totalLength - (1.0D - percent) > 0.0001) {
bigTop += smallTop.dequeue()
}
}
}

View File

@ -0,0 +1,30 @@
package ch29_heap_solutions
import scala.collection.mutable
/**
* keep the top k items in the the class
*/
class TopKItemsKeeper(val itemsToKeepCount: Int) {
//have a smallest value top heap
val queue = new mutable.PriorityQueue[Int]()(scala.math.Ordering.Int.reverse)
def put(item: Int): Unit = {
if (queue.length < itemsToKeepCount) {
queue += item
return
}
//queue already have the k items
if (item.compareTo(queue.head) > 0) {
queue.dequeue()
queue += item
}
}
def get(): List[Int] = {
queue.clone().dequeueAll
}
}

View File

@ -0,0 +1,89 @@
package ch31_graph
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.util.control.Breaks._
class Graph(vertex: Array[String]) {
require(vertex.nonEmpty, "nonEmpty vertex required")
val adjacency = new Array[mutable.MutableList[String]](vertex.length)
for (i <- Range(0, vertex.length)) {
adjacency(i) = new mutable.MutableList[String]()
}
def addEdge(startNode: String, endNode: String): Unit = {
adjacency(vertex.indexOf(startNode)) += endNode
adjacency(vertex.indexOf(endNode)) += startNode
}
def getEdges(node: String): Array[String] = {
adjacency(vertex.indexOf(node)).toArray
}
def breathFirstSearch(startNode: String, destNode: String): Option[Array[String]] = {
var result: Option[Array[String]] = None
val queue = new mutable.Queue[String]()
val visited = new mutable.HashSet[String]()
val explored = new ArrayBuffer[String]()
//put starting node into the queue
queue += startNode
breakable {
while (queue.nonEmpty) {
val node = queue.dequeue()
if (!visited.contains(node)) {
explored += node
visited += node
if (node.equals(destNode)) {
result = Some(explored.toArray)
break
}
queue ++= adjacency(vertex.indexOf(node))
}
}
}
result
}
def depthFirstSearch(startNode: String, destNode: String): Option[Array[String]] = {
var found = false
val visited = new mutable.HashSet[String]()
val explored = new ArrayBuffer[String]()
def _dfs(start: String): Unit = {
if (found) {
return
}
if (!visited.contains(start)) {
visited += start
explored += start
val destsForStart: mutable.MutableList[String] = adjacency(vertex.indexOf(start))
breakable {
for (i <- destsForStart.indices) {
val node = destsForStart(i)
if (node.equals(destNode)) {
found = true
if (!explored.contains(node)) {
explored += node
}
break()
}
_dfs(node)
}
}
}
}
_dfs(startNode)
if (found) {
Some(explored.toArray)
} else {
None
}
}
}

View File

@ -0,0 +1,24 @@
package ch32_matching
import scala.util.control.Breaks._
object BruteForce {
def firstIndexOf(main: Array[Char], sub: Array[Char]): Int = {
require(main != null, "main array required")
require(sub != null, "sub array required")
require(main.length >= sub.length, "sub array should be small than main array")
var result = -1
breakable {
for (i <- 0 until (main.length - sub.length)) {
if (main.slice(i, i + sub.length) sameElements sub) {
result = i
break
}
}
}
result
}
}

View File

@ -1,6 +1,7 @@
package ch11_sorts
import ch12_sorts.{MergeSort, QuickSort}
import ch28_heap.Heap
import org.scalatest.{FlatSpec, Matchers}
import scala.util.Random
@ -53,6 +54,7 @@ class SortsTest extends FlatSpec with Matchers {
timed("selectionSort", Sorts.selectionSort, array.clone())
timed("mergeSort", MergeSort.mergeSort, array.clone())
timed("quickSort", QuickSort.quickSort, array.clone())
timed("heapSort", Heap.heapSort, array.clone())
}
def reportElapsed(name: String, time: Long): Unit = println(name + " takes in " + time + "ms")

View File

@ -0,0 +1,50 @@
package ch23_binary_tree
import org.scalatest.{BeforeAndAfterEach, FlatSpec, Matchers}
class BinaryTreeTest extends FlatSpec with BeforeAndAfterEach with Matchers {
/*
* A
* / \
* B C
* / \ / \
* D E F G
*
*/
var root: Option[Node[String]] = None
val tree = new BinaryTree[String]
override def beforeEach() {
val D = new Node[String]("D", None, None)
val E = new Node[String]("E", None, None)
val B = new Node[String]("B", Some(D), Some(E))
val F = new Node[String]("F", None, None)
val G = new Node[String]("G", None, None)
val C = new Node[String]("C", Some(F), Some(G))
val A = new Node[String]("A", Some(B), Some(C))
root = Some(A)
}
override protected def afterEach(): Unit = {
root = None
}
behavior of "BinaryTreeTest"
it should "postOrder" in {
tree.postOrder(root) should equal("DEBFGCA")
}
it should "inOrder" in {
tree.inOrder(root) should equal("DBEAFCG")
}
it should "preOrder" in {
tree.preOrder(root) should equal("ABDECFG")
}
it should "levelOrder" in {
tree.levelOrder(root) should equal("ABCDEFG")
}
}

View File

@ -0,0 +1,76 @@
package ch24_binary_search_tree
import org.scalatest.{FlatSpec, Matchers}
class BinarySearchTreeTest extends FlatSpec with Matchers {
/*
* 33
* / \
* 17 50
* / \ / \
* 13 18 34 58
* \ \ / \
* 16 25 51 66
* / \ \
* 19 27 55
*
*/
behavior of "BinarySearchTreeTest"
it should "insert" in {
val tree = new BinarySearchTree(None)
val nums = Array(33, 17, 50, 13, 18, 34, 58, 16, 25, 51, 66, 19, 27, 55)
nums.foreach(tree.insert)
tree.inOrder(tree.root) should equal(nums.sorted.mkString(""))
tree.preOrder(tree.root) should equal("3317131618251927503458515566")
tree.postOrder(tree.root) should equal("1613192725181734555166585033")
tree.levelOrder(tree.root) should equal("3317501318345816255166192755")
}
it should "find " in {
val tree = new BinarySearchTree(None)
val nums = Array(33, 17, 50, 13, 18, 34, 58, 16, 25, 51, 66, 19, 27, 55)
nums.foreach(tree.insert)
nums.foreach(num => {
assert(tree.find(num).isDefined)
tree.find(num).get.data should equal(num)
})
assert(tree.find(100).isEmpty)
}
it should "delete" in {
val tree = new BinarySearchTree(None)
val nums = Array(33, 17, 50, 13, 18, 34, 58, 16, 25, 51, 66, 19, 27, 55)
nums.foreach(tree.insert)
tree.delete(13)
tree.inOrder(tree.root) should equal(nums.sorted.tail.mkString(""))
tree.delete(18)
tree.inOrder(tree.root) should equal("1617" + nums.sorted.slice(4, nums.size).mkString(""))
tree.delete(66)
tree.inOrder(tree.root) should equal("1617" + nums.sorted.slice(4, nums.size - 1).mkString(""))
}
it should "calc height of a tree -1" in {
val tree = new BinarySearchTree(None)
val nums = Array(33, 17, 50, 13, 18, 34, 58, 16, 25, 51, 66, 19, 27, 55)
nums.foreach(tree.insert)
tree.height() should equal(5)
}
it should "calc height of a tree -2" in {
val tree = new BinarySearchTree(None)
val nums = Array(33, 17, 50, 13, 18, 34, 88).sorted
nums.foreach(tree.insert)
tree.height() should equal(7)
}
it should "calc height of a tree -3" in {
val tree = new BinarySearchTree(None)
val nums = Array(33).sorted
nums.foreach(tree.insert)
tree.height() should equal(1)
}
}

View File

@ -0,0 +1,41 @@
package ch28_heap
import org.scalatest.{FlatSpec, Matchers}
class HeapTest extends FlatSpec with Matchers {
behavior of "HeapTest"
it should "insert and removeMax" in {
val nums = Array(33, 27, 21, 16, 13, 15, 19, 5, 6, 7, 8, 1, 2, 12)
val heap = new Heap(nums.length + 1)
nums.foreach(heap.insert)
heap.removeMax() should equal(33)
heap.removeMax() should equal(27)
}
it should "build heap from array - bottom up" in {
val nums = Array(33, 27, 21, 16, 13, 15, 19, 5, 6, 7, 8, 1, 2, 12)
val heap = new Heap(nums, true)
heap.removeMax() should equal(33)
heap.removeMax() should equal(27)
}
it should "build heap from array - top down" in {
val nums = Array(33, 27, 21, 16, 13, 15, 19, 5, 6, 7, 8, 1, 2, 12)
val heap = new Heap(nums, false)
heap.removeMax() should equal(33)
heap.removeMax() should equal(27)
}
it should "sort array" in {
val nums = Array(33, 27, 21, 16, 13, 15, 19, 5, 6, 7, 8, 1, 2, 12)
val result = Heap.heapSort(nums)
result.mkString("") should equal(nums.sorted.mkString(""))
}
}

View File

@ -0,0 +1,53 @@
package ch29_heap_solutions
import java.io.{BufferedWriter, File, FileWriter}
import org.scalatest.{FlatSpec, Matchers}
import scala.io.Source
import scala.util.Random
class FileMergerTest extends FlatSpec with Matchers {
behavior of "FileMergerTest"
it should "mergeFiles" in {
val num = 10
val contentCount = 10
val random = Random.alphanumeric
val files = new Array[File](num)
for (i <- Range(0, num)) {
val file = File.createTempFile(i + "-small", ".txt")
files(i) = file
val writer = new BufferedWriter(new FileWriter(file))
val content = random.take((i + 1) * contentCount).toArray.slice(i * contentCount, (i + 1) * contentCount)
writer.write(content.sorted)
writer.flush()
writer.close()
}
println("small files below")
files.foreach(printFile)
val mergedFile = FileMerger.mergeFiles(files.toList)
val raw = Source.fromFile(mergedFile).toArray
raw should equal(raw.sorted)
raw.length should equal(num * contentCount)
println("")
println("merged file below")
printFile(mergedFile)
//clean up
files.foreach(_.delete())
mergedFile.delete()
}
def printFile(file: File): Unit = {
val source = Source.fromFile(file)
source.getLines().foreach(println)
}
}

View File

@ -0,0 +1,28 @@
package ch29_heap_solutions
import org.scalatest.{FlatSpec, Matchers}
class MiddleNumberKeeperTest extends FlatSpec with Matchers {
behavior of "MiddleNumberKeeperTest"
it should "get middle of the array" in {
val numKeeper = new MiddleNumberKeeper()
for (i <- Range(0, 10)) {
numKeeper.put(i)
}
numKeeper.get().get should equal(4)
}
it should "get 90% position of the array" in {
val numKeeper = new MiddleNumberKeeper(0.9)
for (i <- Range(0, 9)) {
numKeeper.put(i)
}
numKeeper.put(9)
numKeeper.get().get should equal(8)
}
}

View File

@ -0,0 +1,25 @@
package ch29_heap_solutions
import org.scalatest.{FlatSpec, Matchers}
import scala.util.Random
class TopKItemsKeeperTest extends FlatSpec with Matchers {
behavior of "TopKItemsKeeperTest"
it should "put and get top K from the keeper" in {
val length = 50
val k = 5
val topKItemsKeeper = new TopKItemsKeeper(k)
val nums = new Array[Int](length)
for (i <- Range(0, length)) {
nums(i) = Random.nextInt
}
nums.foreach(topKItemsKeeper.put)
val ordering = scala.math.Ordering.Int.reverse
topKItemsKeeper.get().toArray.sorted(ordering) should equal(nums.sorted(ordering).slice(0, k))
}
}

View File

@ -0,0 +1,64 @@
package ch31_graph
import org.scalatest.{FlatSpec, Matchers}
class GraphTest extends FlatSpec with Matchers {
/*
0 - 1 - 2
| | |
3 - 4 - 5
| |
6 - 7
*/
behavior of "GraphTest"
def initGraph: Graph = {
val num = 8
val vertex = new Array[String](num)
for (i <- Range(0, num)) {
vertex(i) = i.toString
}
val graph = new Graph(vertex)
graph.addEdge("0", "1")
graph.addEdge("1", "2")
graph.addEdge("0", "3")
graph.addEdge("1", "4")
graph.addEdge("2", "5")
graph.addEdge("3", "4")
graph.addEdge("4", "5")
graph.addEdge("4", "6")
graph.addEdge("5", "7")
graph.addEdge("6", "7")
graph
}
it should "construct the graph" in {
val graph: Graph = initGraph
graph.getEdges("0").sorted should equal(Array("1", "3"))
graph.getEdges("1").sorted should equal(Array("0", "2", "4"))
graph.getEdges("2").sorted should equal(Array("1", "5"))
graph.getEdges("3").sorted should equal(Array("0", "4"))
graph.getEdges("4").sorted should equal(Array("1", "3", "5", "6"))
graph.getEdges("5").sorted should equal(Array("2", "4", "7"))
graph.getEdges("6").sorted should equal(Array("4", "7"))
graph.getEdges("7").sorted should equal(Array("5", "6"))
}
it should "do breath first search in graph" in {
val graph: Graph = initGraph
graph.breathFirstSearch("0", "4").get should equal(Array("0", "1", "3", "2", "4"))
graph.breathFirstSearch("1", "5").get should equal(Array("1", "0", "2", "4", "3", "5"))
assert(graph.breathFirstSearch("1", "8").isEmpty)
}
it should "do depth first search in graph" in {
val graph: Graph = initGraph
graph.depthFirstSearch("0", "4").get should equal(Array("0", "1", "2", "5", "4"))
graph.depthFirstSearch("1", "5").get should equal(Array("1", "0", "3", "4", "5"))
assert(graph.depthFirstSearch("1", "8").isEmpty)
}
}

View File

@ -0,0 +1,20 @@
package ch32_matching
import org.scalatest.{FlatSpec, Matchers}
import scala.util.Random
class BruteForceTest extends FlatSpec with Matchers {
behavior of "BruteForceTest"
it should "find firstIndexOf a sub string" in {
val random = Random.alphanumeric
val main = random.take(1000).toArray
val index = Random.nextInt(950)
val sub = random.take(1000).toArray.slice(index, index + 50)
BruteForce.firstIndexOf(main, sub) should equal(index)
}
}