课件的教学逻辑是,先学会Python的基本语法,学会使用List列表和Dict字典这两个Python内置的抽象数据类型。然后开始讲解什么是抽象数据类型,先搞了Stack,Queue,Deque这三个简单的,让我们理解怎么用List实现它们。再讲怎么实现List,虽然Python里List已经实现,但还是“再造了一遍轮子”,使用链表实现了List和OrderedList。之后在递归里讲解了动态规划(算法),在讲排序查找(算法)时介绍了ADT Map映射(同样,用List来实现Map,“再造了一遍轮子”)。最后开始讲树和图。
现在想想,这样是比较合理的,代码量和难度循序渐进。
但由于对这些数据结构/抽象数据类型之间的关系没理清楚,当时可是一头雾水!
我一开始学List的时候就有点不明白列表是什么,姑且当作Python里的数组来看待。学到Stack,Queue,Deque还算清楚,学到List就晕了,前面学了一遍List怎么又学一遍List?明明前面不是用了List来实现Stack等,怎么后面又说还需要实现什么有序列表和无序列表?虽然这个课件上说明白了,List在Python里是实现的,在某些语言中没有,所以这里是“假装没实现”自己实现一遍。但是我当时脑子里其他语言就是C,List之于Python就是数组之于C,C里明明有数组啊!于是懵圈了。
最不明白的是,课件用链表实现完后来了一句“链表实现的List,跟Python内置的列表数据类型,性能上还有差距,主要是因为Python内置的列表数据类型是基于数组来实现的,并进行了优化”。啥?列表不是数组啊?那Python数组是什么?我们没学啊?
我学新东西比较慢,加上当时计算概论用的是C(而且类和对象那边没怎么学,链表也不甚熟悉,好像就是结构体一通连),脑子里都是C的概念,生搬硬套到Python上面来,搞混了相关概念,所以出现这种状况。事实上,直到昨天我也没有搞明白这件事(或者说忘了当时的这个疑惑)。
直到今天我一边在Leetcode上刷题,一边又浏览课件才突然回忆起这个困惑。程序设计课开始使用C++,我接触到vector后,便多使用vector,而很少使用普通的数组了,毕竟麻烦又容易出错。现在看来就很清楚了,List之于Python,应该对标vector之于C++,都是动态数组。vector以数组形式实现,连续内存存储,支持[]运算符。
WiKi简介:vector设计之初即是为了改善C语言原生数组的种种缺失与不便,而欲提供一种更有效、更安全的数组。vector的使用接口刻意模拟C语言原生数组,较明显的差异在于存储器管理,原生数组必须在宣告数组的时候明确指定数组长度,但是vector不需要指定,而是会在运行期依据状况自我调整长度,动态增大容量。
C++ primer的作者说到,在实际的编程中,作为程序员应该避免用到低级数组和指针,而更应该多用高级的vector和迭代器(在程序强调速度的情况下,可以在类类型的内部使用数组和指针)。连C++都这么说,Pythoner就快乐地使用List别想这有的没的了!(误)
此外,这样的混编模式使得我对算法是什么数据结构是什么理解不是很清楚。个人还是比较喜欢数算A的方法,非常“坚定”地以数据结构为顺序来介绍然后算法。第一章把算法和数据结构之间的关系和课程的设计逻辑就讲清楚了。但我并不是觉得数算B如何如何不好,只是数算B使用Python后在语法部分占用较多课时,并且因为课程要求较低和课外加分活动、作业太多,相比数算A课程不够硬不够系统。课程虽然有趣也有用,但有点喧宾夺主的意味。该训练的一些东西糊里糊涂过去了,笔试占比不高甚至还能拿到高分,但依旧没有达到“数据结构与算法”的课程要求。
我大一时一直没太“搞明白”这之间的区别,我的理解是,数据结构就是计算机里的一套“存储组织数据”的“模板”,抽象数据类型就是“对实际问题进行抽象”得到的“模型”。实际问题—>数学模型—>计算机数据结构。反正本文课件上都叫ADT(抽象数据模型),序列(数组?)链表什么的是数据结构。
WiKi教科书:序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。Python有6个内置的序列类型,但最常见的是列表和元组。
后来我简单地认为,抽象数据类型ADT像是个封装好的类,是一种用来存储处理数据的数学模型。然后这俩概念课件上有时似乎也混着用(XX既是ADT又是数据结构)。诶,搞这文字游戏干什么,反正知道大概指代的是个什么东西,会用会实现才是正经事。
参考依据数据结构与算法--ADT:https://www.atjiang.com/data-structures-using-python-ADT/
下面是数算A的课件介绍:
结构:实体 + 关系
数据结构:按照逻辑关系(线性、非线性)组织起来的一批数据,按一定的存储方法(顺序、链接、索引、散列)把它存储在计算机中,在这些数据上定义了一个运算(数据操作,C++里是函数)的集合。
抽象数据类型ADT (Abstract Data Type):定义了一组运算的数学模型,与物理存储结构无关(只关心逻辑结构与运算,隐藏运算实现的细节和内部数据结构),使软件系统建立在数据之上(面向对象)。
Stack():创建一个空栈,其中不包含任何数据项
push(item):将item数据项加入栈顶,无返回值
pop():将栈顶数据项移除,返回栈顶的数据项,栈被修改
peek():“窥视” 栈顶数据项,返回栈顶的数据项但不移除,栈不被修改
isEmpty():返回栈是否为空栈
size():返回栈中有多少个数据项
Queue():创建一个空队列对象,返回值为Queue对象;
enqueue(item):将数据项item添加到队尾,无返回值;
dequeue():从队首移除数据项,返回值为队首数据项,队列被修改;
isEmpty():测试是否空队列,返回值为布尔值;
size():返回队列中数据项的个数。
Deque():创建一个空双端队列
addFront(item):将item加入队首
addRear(item):将item加入队尾
removeFront():从队首移除数据项,返回值为移除的数据项
removeRear():从队尾移除数据项,返回值为移除的数据项
isEmpty():返回deque是否为空
size():返回deque中包含数据项的个数
List():创建一个空列表
add(item):添加一个数据项到列表中,假设item原先不存在于列表中
remove(item):从列表中移除item,列表被修改, item原先应存在于表中
search(item):在列表中查找item,返回布尔类型值
isEmpty():返回列表是否为空
size():返回列表包含了多少数据项
append(item):添加一个数据项到表末尾,假设item原先不存在于列表中
index(item):返回数据项在表中的位置
insert(pos, item):将数据项插入到位置pos,假设item原先不存在与列表中,同时原列表具有足够多个数据项,能让item占据位置pos
pop():从列表末尾移除数据项,假设原列表至少有1个数据项
pop(pos):移除位置为pos的数据项,假设原列表存在位置pos
OrderedList():创建一个空的有序表
add(item):在表中添加一个数据项,并保持整体顺序,此项原不存在
remove(item):从有序表中移除一个数据项,此项应存在,有序表被修改
search(item):在有序表中查找数据项,返回是否存在的布尔值
isEmpty():是否空表
size():返回表中数据项的个数
index(item):返回数据项在表中的位置,此项应存在
pop():移除并返回有序表中最后一项,表中应至少存在一项
pop(pos):移除并返回有序表中指定位置的数据项,此位置应存在
BinaryTree():创建仅有根节点的二叉树
insertLeft/insertRight():将新节点插入二叉树中,作为其直接的左/右子节点
get/setRootVal():则取得或返回根节点
getLeft/RightChild():返回左/右子树
BinaryHeap():创建一个空二叉堆对象
insert(k):将新key加入到堆中
findMin():返回堆中的最小项,最小项仍保留在堆中
delMin():返回堆中的最小项,同时从堆中删除
isEmpty():返回堆是否为空
size():返回堆中key的个数
buildHeap(list):从一个key列表创建新堆
Map():创建一个空映射
put(key, val):将key-val关联对加入映射中,如果key已经存在,则将val替换原来的旧关联值
get(key):给定key,返回关联的数据值,如不存在,则返回None
del:通过del map[key]的语句形式删除key-val关联
len():返回映射中key-val关联的数目
in:通过key in map的语句形式,返回key是否存在于关联中,布尔值
Graph():创建一个空的图
addVertex(vert):将一个顶点Vertex对象加入图中
addEdge(fromVert, toVert):添加一条有向边
addEdge(fromVert, toVert, weight):添加一条带权的有向边
getVertex(vertKey):查找名称为vertKey的顶点
getVertices():返回图中所有顶点列表
in:按照vert in graph的语句形式,返回顶点是否存在图中True/False