关于第一章学习已经介绍完了。
- 【一起学数据结构与算法分析】第一篇:取第K大数的两种方法比较
- 【一起学数据结构与算法分析】第二篇:字谜游戏
- 【一起学数据结构与算法分析】第三篇:递归的4条设计原则
- 【一起学数据结构与算法分析】第四篇:泛型漫谈
- 【一起学数据结构与算法分析】第五篇:处理include标签
接下来我们就尝试来解一解书本上的习题。
同学们,上阔~
1.1 编写一个程序解决选择问题。令k = N / 2 。画出表格显示程序对于N种不同的值的运行时间。
参见上面第一个链接,就不再赘述了,但是按题目要求要补充运行时间(k = N / 2),N大家酌量取值。
N (冒泡) | N(k排) | 运行时间(ms) |
---|---|---|
1000 | 27 | 1 |
10000 | 518 | 56 |
100000 | 55340 | 7469 |
1.2 编写一个程序求解字谜问题。
参见上面第二个链接
1.3 只使用处理I/O的printDigit方法,编写一种方法以输出任意Double型量(可以是负的)。
这个问题其实可以分四步解决:
- 变号
即把负数变正数并输出负号,就不知道题意说只使用printDigit方法,那么print("-")方法能否使用,如果不能用就不输入吧。 - 输入整数部分
递归实现,可参数书本printOut方法的实现。 - 输入小数点
没什么好讲的。 - 输出小数点后面的数
这地方尤其注意精度问题,如:
print(10.103 - 10) // output 0.10299999999999976 rather than 0.103.
那么想求后面的1可以这样:把10.103乘以10得101.03,把10.103取整再乘以10为100,再把101.03 - 100得到的结果再取整,就是1了,后面递归类推直到相减的结果为0为止,这样就避免了原来的double值在中间计算时损失精度,看网上什么+0.5眼花缭乱的。
奉上完整代码:
@Test
fun testPrintDouble() {
printDouble(-0.10839)
}
/**
* Print every digit of a double value by method [printDigit].
*/
private fun printDouble(value: Double) {
var positiveValue = value
// Print and change sign if needed.
if(value < 0){
print("-")
positiveValue = -value
}
// Print int part recursively.
val intValue = value.toInt()
printInt(intValue)
print(".")
// Print double part recursively.
printPositiveDouble(intValue, positiveValue)
}
private fun printInt(value: Int) {
if(value >= 10) {
printInt(value / 10)
}
printDigit(value % 10)
}
private fun printPositiveDouble(intValue: Int, value: Double) {
if(intValue - value != 0.0) {
val value10Times = value * 10
print((value10Times - intValue * 10).toInt())
printPositiveDouble(value10Times.toInt(), value10Times)
}
}
private fun printDigit(number: Int) {
print(number)
}
1.4 处理include标签
见上面第五个链接。
1.5 编写一种递归方法,它返回数N的二进制表示中1的个数。利用这样的事实:如果N是奇数,那么其1的个数等于N/2的二进制表示中1的个数加1。
如果知道实数的二进制存储,这个问题就很简单了。
比如整数17的二进制为:
0000 0000 0000 0000 0000 0000 0001 0001
整数8的二进制为:
0000 0000 0000 0000 0000 0000 0000 1000
那么我们将整数的二进制右移1位再与1做位与操作就可以了。其中,递归的基点应是value = 0。
/**
* Output the count of "1" from N's binary.
* @param value is the number to be calculated.
* @param sum of "1" performed by bit.
*/
private fun getOneCount(value: Int): Int {
if(value != 0) {
return (value and 1) + getOneCount(value shr 1)
} else {
return 0
}
}
我们分别把17和8代入,发现17中1的个数为2,8中1的个数为1,确实如题干所示。
辣么,我们再回头想想为什么N比N/2的个数少1呢?
因为N是奇数的话,低位肯定是1,而N/2是N的二进制右移一位,正好把低位的1移走了,所以少了1个。
1.6 编写带有下列声明的例程: public void permute(String str) ; private void permute(char[], int low, int high); 第一个例程是个驱动程序,它调用第二个例程并显示String str中的字符的所有排列。如果str是"abc",那么它输出的串是abc,acb,bac,bca,cab和cba。第二个例程使用递归。
其实就是数学上排列组件问题,那怎么用递归的方法求呢?在文章开头的第三个链接中我们学习了递归的几个原则,其中最重要的是找到递推的规则和基点。
首先我们找下规则:
一个字母:不用处理,直接输出;
两个字母:比如a和b,排列组件为ab和ba
三个字母:如题干所示。
……
所以很容(ma)易(fan)地找到这个规律和基准:
依次把数列中每个元素都放在第一个位置,然后把第二个元素到最后元素构成的新数列再按上述处理,直到新数列中只有一个元素为止。
语言描述还是太啰嗦,我们简单地的画个图:
上图中红色均为已被固定的元素。
/**
* Permute array from range of [low] to [high] recursively.
* @param array to be permuted.
* @param low is the start of permute range.
* @param high is the end of permute range.
*/
private fun permute(array: CharArray, low: Int, high: Int) {
if(low == high) {
println(array.contentToString())
return
}
for(i in low .. high) {
swap(array, low, i)
// Permute from the next position.
permute(array, low + 1, high)
// Restore array to the original against previous swap.
swap(array, i, low)
}
}
完整代码请参考git中ArrangeCharsTest。
1.7-1.12
请参见习题资料,不再赘述。
1.13 设计一个泛型类Collection,它存储Object对象的集合(在数组中),以及该集合的当前大小。提供public方法isEmpty、makeEmpy、insert、remove和isPresent。方法isPresent(x)当且仅当在该集合中存在(由equals定义)等于x的一个Object时返回true。
其实就是对Collections的ADT的定义,老朽这边先写个简单的,没有考虑优化、并发等方面,完整版大家可以直接看官方List等实现。
/**
* Array contains data with type Any.
*/
private var elements: Array<Any?> = arrayOfNulls<Any>(INIT_SIZE)
/**
* Actual number of elements in collection.
*/
private var size = 0
/**
* Check if collection is empty or not.
*/
fun isEmpty(): Boolean{
return size == 0
}
/**
* Clear all elements from collection.
*/
fun makeEmpty() {
for(i in 0 until size) {
elements[i] = null
}
size = 0
}
/**
* Insert element to collection.Note that expand array if its space not enough.
*/
fun insert(element: T?){
if(size >= elements.size) {
elements = elements.copyOf(size + INCREMENT_SPACE)
}
elements[size] = element
size ++
}
/**
* Remove element from collection even having multiple same values.
*/
fun remove(element: T?) {
if(isEmpty()) {
return
}
// Create a new array to receive the values not equals the removing element.
val newArray = arrayOfNulls<Any?>(elements.size)
var pointer = 0
elements.forEach {
if(it != element) {
newArray[pointer ++ ] = it
}
}
// Clear unnecessary references.
makeEmpty()
elements = newArray
size = pointer + 1
}
/**
* Detect whether collection contains the assigned element.
* @param element to be checked.
* @return true if collection contains, otherwise not.
*/
fun isPresent(element: T?): Boolean {
elements.forEach {
// Different from java or other language.
if(element == it) {
return true
}
}
return false
}
1.14 设计一个泛型类OrderedCollection,它存储Comparable的对象的集合(在数组中),以及该集合的当前大小。提供public方法isEmpty、makeEmpty、insert、remove、findMin和findMax。findMin和findMax分别返回该集合中最小的和最大的Comparable对象的引用(如果该集合为空,则返回null)。
首先写一个比较的接口:
interface Comparable<T> {
fun compareTo(value: T?): Int
}
然后把泛型改下:
class MyOrderedCollection<T> where T: Comparable<T>
最后加几个方法:
/**
* Return the min value inside of collection, return null if collection empty.
*/
fun findMin(): T? {
if(isEmpty()) {
return null
}
var minElement: T? = null
elements.forEach {
if(it == null) {
minElement = null
} else if(it.compareTo(minElement) < 0){
minElement = it as T
}
}
return minElement
}
1.15 定义一个Rectangle类,该类提供getLength和getWidth方法。利用图1-18中的findMax例程编写一种main方法,该方法创建一个Rectangle数组并首先找出依面积最大的Rectangle对象,然后找出依周长最大的Rectangle对象。
定义一个Rectangle:
/**
* Rectangle with only length and width.
*/
data class Rectangle(private val length: Int, private val width: Int) {
fun getLength(): Int = length
fun getWidth(): Int = width
}
定义一个比较接口:
/**
* An interface for comparing two values.
*/
interface Comparator<T> {
fun compare(lhs: T, rhs: T): Int
}
分别定义面积比较和周长比较:
/**
* Compare two rectangles with area.
*/
class AreaComparator: Comparator<Rectangle> {
override fun compare(lhs: Rectangle, rhs: Rectangle): Int {
return lhs.getLength() * lhs.getWidth() - rhs.getLength() * rhs.getWidth()
}
}
/**
* Compare two rectangles with circumstance.
*/
class CircumstanceComparator: Comparator<Rectangle> {
override fun compare(lhs: Rectangle, rhs: Rectangle): Int {
return (lhs.getLength() + lhs.getWidth()) - (rhs.getLength() + rhs.getWidth())
}
}
使用书上的findMax例程:
/**
* Find the max from array with rule in comparator.
*/
fun findMax(array: Array<Rectangle>, cmp: Comparator<Rectangle>): Rectangle? {
if(array.isEmpty()) {
return null
}
var maxIndex = 0
for(i in array.indices) {
if(cmp.compare(array[i], array[maxIndex]) > 0) {
maxIndex = i
}
}
return array[maxIndex]
}
.再定义main方法:
@Test
fun main() {
val array = arrayOf(
Rectangle(3, 5),
Rectangle(6, 2),
Rectangle(9, 1)
)
println("max area:" + findMax(array, AreaComparator()))
println("max circumstance:" + findMax(array, CircumstanceComparator()))
}
好了,第一章的习题部分就讲完了,水平有限,有错误的地方可以直接在评论区留言。
码字不易,觉得不错就点个赞加个star吧。