下来开始学习数据结构,这篇文章以及以后关于数据结构的文章,不会写太多的细节,比如一些性质等。更多的是关注一些常见的数据结构如何用python 代码实现。
学习的课本是数据结构与算法 python语言描述有兴趣的可以自己买来读读。
1 顺序表
python 的list 是一种采用分离式技术实现的动态顺序表,创建空表时不分配元素存储区,遇到insert 和append 要求加入元素,系统第一次分配能容纳4个元素的存储区,继续扩容时采用的策略使每次扩容的比率接近1.125倍。
只要采用等比扩容,就能保证尾端加入元素的append 的平均时间复杂性是O(1)
2 链接表
**## 单链表:**为了掌握一个表,只需要用一个变量保存着这个节点的首节点的引用,今后把这样的变量成为表头变量或者表头指针
定义一个单链表:
class LNode():
def __init__(self, elem, next_=None):
self.elem = elem
self.next_ = next_
(参数名字next_是为了避免与Python标准库的函数重名)
基本链表操作
1 创建空链表: 把相应的表头变量设置为空链接,Python 语言中将其设置为None
2 删除链表: 将表指针赋值为None,就抛弃了链表原有的所有节点,python 的解释器的存储管理系统会自动回收不用的存储
3 判断链表是否为空: 将表头变量的值与空链表进行比较
4 判断链表是否满: 一般不会满,除非程序用完了所有的存储空间
2.2 加入元素
表首端加入:
(1)创建一个新节点并保存数据
(2)把原链表的首节点的链接存入新节点的链接域next_
(3)修改表头变量,使之指向新节点,这个操作是新节点实际成为表头变量所指的节点,即表的首节点
q = LNode(13)
q.next = head.next
head = q
一般情况的插入: 想要在单链表里的某一个位置插入一个新节点,必须找到该位置之前的那个结点,因为新结点要插入到它的后面,需要修改它的next 域。
设变量pre 已指向要插入元素的前一个结点
(1)创建一个新结点,存入数据
(2)把pre 所指结点的next域的值存入新节点的链接域next , 这个操作将原表在pre 所指结点之后的一段链接到新结点之后
(3)修改pre的next 域,使之指向新结点
q = LNode(13)
q.next = pre.next
pre.next = q
2.3 删除元素
删除表首元素: 修改表头指针,使之指向第二个结点 (丢弃的结点将被Python 解释器自动回收)
head = head.next
一般情况下的元素删除: 一般情况下删除必须先找到要删除元素所在结点的前一结点。设用变量pre来指向
pre.next = pre.next.next
2.4 扫描 定位 遍历
由于单链表只有一个方向上的链接,开始情况下只有表头变量
在掌握中,所以对表内容的一切检查都只能从表头变量开始,沿着表中链接逐步进行,这种过程称为链表的扫描
p = head
while p is not None and 还需其他的条件:
对p所指结点里的数据做所需操作
p = p.next
循环中使用的辅助变量p 称为扫描指针。每个扫描循环必须用一个扫描指针作为控制变量
按下标定位: 链表首节点的元素看做下标0,确定第i个元素所在结点的操作称为按下标定位
p = head
while p is not None and i > 0:
i -=1
p = p.next
循环结束可能出现两种情况:或者扫描完表中所有节点还没有找到第i个结点,或者p所指结点就是所需。
通过检查P 是否为None 可以区分这两种情况。
显然,如果需要删除第K个结点,可以先将 i 设置为 k-1 ,循环后检查 i 是0 且p.next 不是None 就可以执行删除了
按元素定位: 假设需要在链表中找到满足谓词pred的元素,可以参考上面
p = head
while p is not None and not pred(p.elem):
p = p.next
循环结束时,p 是None, 或者为pred是True.
链表复杂度总结:
2.5 单链表类的实现
现在基于结点LNode定义一个单链表对象的类,在这种表对象里只有一个引用链接结点的_head域,初始化为None表示建立一个空表
判断表空的操作检查_head;在表头插入数据的操作是prepend,它把包含新元素的结点链接在最前面;操作pop删除表头结点并返回这个数据
(LNode 表示新的结点,LinkedListUnderflow 表示错误)
class LList:
def __init__(self):
self._head = None
def is_empty(self):
return self._head is None
def prepend(self):
self._head = LNode(elem, self._head)
def pop(self):
if self._head is None: # 空表
raise LinkedListUnderflow('in pop')
e = self._head.elem
self._head = self._head.next
return e
后端操作
在链表的最后插入元素,必须找到链表的最后一个结点。实现首先是一个扫描循环,找到相应的结点后把包含新元素的结点插入在其后。
def append(self, elem):
if self._head is None: # 链表为空
self._head = LNode(elem)
return
p = self._head
# 一直找p的下一个不为空的结点,直到为空,说明此时p是最后一个结点
while p.next is not None:
p = p.next
# 让最后一个结点的指针域指向新加入的结点
p.next = LNode(elem)
** 删除表最后的元素(结点):**要从单链表删除一个结点,必须找到它的前一个结点。在尾端删除的操作里,扫描循环应该找到表中倒数第二个结点,也就是找到p.next.next 为None 的p
在开始之前,需要处理两个特殊情况:
(1)如果表空没有可以返回的元素,应该引发异常;
(2) 表中只有一个元素需要特殊处理,因为这时应该需改表头指针,
(3)一般情况是先通过循环找到位置,取出最后结点的数据后将其删除
def pop_last(self):
if self._head is None: # 空表
raise LinkedListUnderflow('in pop_last')
p = self._head
while p.next is None: # 表中只有一个元素
# e 表示要返回的元素的值,
e = p.elem
self._head = None
return e
while p.next.next is not None: # 直到p.next 是最后结点
p = p.next
# 这里 e 表示最后一个元素的值
e = p.next.elem
# 把最后一个元素移除了,所以让它指向一个空,表示它现在是最后一个元素
p.next = None
return e