学习了王争老师的《数据结构与算法之美》课程后,发现单纯的阅读和听语音吸收知识的效率太低,于是准备开辟一个博客专栏,专门用来讲述自己在学习这门课程中的一些心得体会。
本专栏所有代码全部使用 Scala 语言编写,之所以选择 Scala,一方面是因为 Scala 是深入学习大数据必不可少的语言,二是因为如果选择 Java ,Python等语言,类似的博客太多了,同质化可能比较严重,本专栏也是对我 Scala 语言的一次历练。
本专栏从数组开始,之前的一些理论知识例如算法复杂度分析等,网上有太多的同类型博客,就不献丑了,本专栏主要还是以代码分享与逻辑详解为主。
个人认为,如果能够很好的手写一个 ArrayList,数组这块的知识基本上就能掌握的不错了。真正的ArrayList包含了不少诸如 子列表、迭代器等知识,我觉得和数组这块内容不太相关,而且代码写多了看起来也复杂,故只展示核心的查询、新增、插入、删除逻辑,理解好这块内容对于学习数组这种数据结构帮助很大。
先直接展示个人手写的简单版本的 ArrayList。
代码展示
import java.util.StringJoiner
//注意Manifest要加上,不然会出现编译错误
class MySimpleArrayList[T: Manifest](initSize: Int = 10) {
//底层数组
private var array: Array[T] = new Array[T](initSize)
//数组最大容量
private var capacity: Int = initSize
//list的大小
var size: Int = 0
//链表末尾添加元素
def add(t: T): Unit = {
//检查并扩容
checkAndExpand()
array(size) = t
size += 1
}
//下标 k 插入元素
def insert(t: T, k: Int): Unit = {
checkRange(k)
//检查并扩容
checkAndExpand()
//倒序遍历插入----------------------------------------------1
var index: Int = size
while (index > k) {
array(index) = array(index - 1)
index -= 1
}
//第 k 项覆盖
array(k) = t
//大小增加
size += 1
}
//删除下标为 k的元素
def delete(k: Int): Unit = {
checkRange(k)
//正序遍历删除----------------------------------------------2
var index: Int = k
while (index < size - 1) {
array(index) = array(index + 1)
index += 1
}
size -= 1
}
//获取下标为 k 的元素
def get(k: Int): T = {
checkRange(k)
array(k)
}
//方便打印
override def toString: String = {
val sj: StringJoiner = new StringJoiner(",")
for (i <- 0 until size) {
sj.add(array(i).toString)
}
"[" + sj.toString + "]"
}
//检查是否越界
private def checkRange(k: Int): Unit = {
if (k >= size || k < 0) {
throw new ArrayIndexOutOfBoundsException("数组下标越界:" + k)
}
}
//检查
private def checkAndExpand(): Unit = {
if ((size + 1) > capacity) {
expand()
}
}
//数组扩容
private def expand(): Unit = {
val temp = array
capacity = capacity + (capacity >> 2)
array = new Array[T](capacity)
System.arraycopy(temp, 0, array, 0, size)
}
}
上面的代码包含一些不足之处,比如数组大小超过 Int.MaxValue 的临界处理,以及删除导致的空间浪费等问题,这些个人感觉问题不是很大,为避免复杂化,故不实现。
测试
object Main extends App {
val list: MySimpleArrayList[Int] = new MySimpleArrayList[Int](4)
list.add(0)
list.add(1)
list.add(2)
println(list.size)
println(list.toString)
list.add(3)
list.add(4)
list.add(5)
println(list.size)
println(list.toString)
list.add(6)
list.add(7)
println(list.size)
println(list.toString)
list.insert(21, 3)
println(list.size)
println(list.toString)
println(list.get(3))
list.delete(3)
println(list.size)
println(list.toString)
println(list.get(3))
list.delete(10)
}
输出
3
[0,1,2]
6
[0,1,2,3,4,5]
8
[0,1,2,3,4,5,6,7]
9
[0,1,2,21,3,4,5,6,7]
21
8
[0,1,2,3,4,5,6,7]
3
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 数组下标越界:10
...
说明
总体上采取的思想是动态扩容。
数组是一个线性表结构,为了保证连续存储,如果遇到新增或者删除一个元素的情况下就得考虑数组数据迁移的事情,但是如果每次更新都整个迁移会极大的影响性能,故每次按照固定倍数1.5 倍扩容数组,一旦填充满了空间不够再进行扩容,这样不可避免会造成空间浪费,这也是一种空间换时间的思想。
JDK1.8 版本的 ArrayList 源码使用的是 System.arraycopy()来实现MySimpleArrayList中 1和 2 的的功能,System.arraycopy()使用的native方法更加高效,但是隐藏了实现细节,不方便去学习掌握 算法逻辑,故我在 1 和 2 中使用了倒序/正序遍历来达到同样的效果,实现逻辑也更加直观。
当然上面的代码如果有错误或者不足的地方,欢迎来评论区和我讨论,感谢观看!下一节主要讲链表内容。
个人网站: