Python 面向对象编程 + 基本数据结构实现【part 1】:链表、跳表
本文使用的 python
编译器:Spyder 3.3.6
Cpt1. 简单实现
定义对象:类 class
(这个定义是否与 C语言
中的结构体相似?)
class Test:
def pr(self):
self.height = 20
在这个大类里,就添加了 height
这个信息。关于如何使用这个类:
a = Test()
这样就对 a
附上了 Test
这个类的信息:(当然也可以定义空的类。)
注: 在这部中,貌似我并不能直接调用出里面 a.height
的值,只能调用出 a
这个类中,pr
这个子函数的信息:
>>> <__main__.Test object at 0x0000019E8E036E08> <bound method Test.pr of <__main__.Test object at 0x0000019E8E036E08>>
Cpt2. 现实编程中的实例实现步骤:
- 创建方法
__new__()
方法__init__()
方法- 注:两种方法都可以从父类继承,或是直接创建
- 类说明:
docstring
紧跟在类名行之后(就是自己写的对类进行解释的一个注释)查看类说明,用__doc__
来查看。(或者用help(a)
)
如:
Cpt3. 描述对象的特征
类的属性:
- 实例属性:
- 一般使用
__init__()
进行创建初始化直接使用。 - 直接使用即定义:
self.<属性名>
- 引用方法
self.<属性名>
Remark:self
指类实例化之后的类本身。- 属于同一个类,但是是不同实例,他们的属性值是不相关的(比如
a
,b
属于Test()
这个类,我改变a
中的实例属性值,b
的相应属性不改变)
输出结果:class Test: def __init__(self): '''这是实例属性''' self.a = 0 self.b = 10 def info(self): print('a:', self.a, 'b:', self.b) if __name__ == "__main__": clsa = Test() clsa.info() '''在实例外部可以进行类属性的添加,一般避免这么使用'' clsa.color = 'red' print(clsa.color)#直接用“.”可以调用
>>>a: 0 b: 10 >>>red
- 一般使用
- 类属性:属于同一类的不同实例,具有的相同的属性,就是给每个对象附上相同的值(写在
__init__()
函数的外部)。
可以在外部进行类属性的修改,修改后,相同类的不同实例的类属性都会改变。
- 私有属性:
__ab
,双下划线开头。不能在外部进行修改。但是直接访问a.__ab
可以访问这个值(python内部用别的变量名存储这个值)
- 特殊属性(这里不探讨了)
__doc__
注释__name__
名称__dict__
保存实例属性所有的实例名与值的一个字典__module__
所在模块名__base__
父类
Cpt4. 基本数据结构的实现:
① 链表的实现
python
中的链表与 C语言
中的链表形式上是相同的,都是由若干个节点,像一条链连接起来。每个节点包含:1. 当前节点存储的值;2. 下一个节点的位置。
注:链表与数组的不同在于,链表的存储地址不是连续的,就像在一堆彩虹糖(别的占用内存较大的数据结构)的间隙之间,放入几粒大米(链表的节点),因此只能顺着节点顺序访问,而不能抽取其中的一个值。
在 C语言
中,一般记录的是指针,也就是地址的位置,那么在 python
中,我们可以直接把一个数据结构赋值赋在前一个节点的 next
指针上。他的数据结构如下:
Step1. 首先定义单向链表节点
之前说过,__init__()
是对一个节点的初始化。然后里面如上所说,val
存储值,next
存储下一个节点的地址。为了使用方便,头节点并不像 C语言
中一样,将头结点的值的定义为空。采用 Leetcode 上面经常出现的定义方式:
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
Step2. 定义链表
因为很多算法题目中,都是预先确定好一个序列,然后我们从一个确定的序列构建链表进行做题。那我这里就写了一个从列表构建链表的函数:
class SingleLinkList(object):
def __init__(self, node = None):
self.head = node
def construct(self, nums):
if not nums:
return None
else:
for i in range(0, len(nums)):
if i == 0:
cur = ListNode(nums[i])
self.head = cur
else:
node = ListNode(nums[i])
cur.next = node
cur = cur.next
#这个是遍历并输出整个链表的子函数
def travel(self):
cur = self.head
print("head->", end = "")
while cur != None:
print(cur.val, end = "->")
cur = cur.next
print("end")
在使用的时候也非常简单,在主程序中先创建 SingleLinkList()
这个类,并直接调用这个类内函数就可以:
if __name__ == "__main__":
l1 = SingleLinkList()
nums1 = [1,2,4]
l1.construct(nums1)
这样我们就有了一个链表,而且在我们使用 l1
的时候,其实是 l1
开头这个链表的初始位置,也就是链表头节点。
Step3. 如何进行外部操作
对于怎么在类的外部添加对这个实例操作:就拿简单的合并两个有序链表来举例(Leetcode 21)。
我们可以先写好题解:(这里直接采用了递归的形式)
class solution(object):
def mergeTwoLists(self, l1, l2):
if l1 is None:
return l2
elif l2 is None:
return l1
elif l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2
然后主程序就长这样:在我们调用的时候,调用的是一个链表的头指针,我们的返回值也是一个头指针也就是 ListNode
这个树结构,因此不能直接调用 SingleLinkList()
类中的 travel()
函数,那只能通过找出我们返回值的位置到底在哪个指针上,再进行结果的输出。如下:
if __name__ == "__main__":
l1 = SingleLinkList()
l2 = SingleLinkList()
nums1 = [1,2,4]
nums2 = [1,3,4]
l1.construct(nums1)
l2.construct(nums2)
Res = solution()
res = Res.mergeTwoLists(l1.head, l2.head)
if res == l1.head:
l1.travel()
else:
l2.travel()
因此,整个能够运行的程序如下:
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
class SingleLinkList(object):
def __init__(self, node = None):
self.head = node
def construct(self, nums):
if not nums:
return None
else:
for i in range(0, len(nums)):
if i == 0:
cur = ListNode(nums[i])
self.head = cur
else:
node = ListNode(nums[i])
cur.next = node
cur = cur.next
def travel(self):
cur = self.head
print("head->", end = "")
while cur != None:
print(cur.val, end = "->")
cur = cur.next
print("end")
class solution(object):
def mergeTwoLists(self, l1, l2):
if l1 is None:
return l2
elif l2 is None:
return l1
elif l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2
if __name__ == "__main__":
l1 = SingleLinkList()
l2 = SingleLinkList()
nums1 = [1,2,4]
nums2 = [1,3,4]
l1.construct(nums1)
l2.construct(nums2)
Res = solution()
res = Res.mergeTwoLists(l1.head, l2.head)
if res == l1.head:
l1.travel()
else:
l2.travel()
以上
② 跳表的实现
跳表是链表数据结构的一种拓展,能够在
O
(
log
N
)
O(\log N)
O(logN) 时间复杂度下对一个有序链表进行访问(包括插入删除)。每一层上的节点通过一定概率向上构造一个节点,也就是
P
r
(
i
层
有
节
点
∣
i
−
1
层
有
节
点
)
=
1
Pr(i层有节点|i-1层有节点)=1
Pr(i层有节点∣i−1层有节点)=1,
P
r
(
i
−
1
层
有
节
点
∣
i
层
有
节
点
)
=
p
Pr(i-1层有节点|i层有节点)=p
Pr(i−1层有节点∣i层有节点)=p。
p
p
p 是人为定义的,为了方便起见,这里设置为
p
=
0.5
p=0.5
p=0.5 。
实现代码:
首先定义单个节点:包含值、右指针、下指针三个值。
# 引入几个全局变量
Maxlevel = 16
th = [pow(0.5, n) for n in range(Maxlevel, 0, -1)]
import random
# 构造单个节点
class Node(object):
def __init__(self, x = "inf"):
self.val = x
self.next = None
self.below = None
然后对整条链进行构造:因为不同层的头节点是始终存在的,因此将其通过一条向下的链,连接起来,这也就是我们的初始化步骤。
class Skiplist(object):
def __init__(self):
self.head = Node("inf")
cur = self.head
for i in range(0, Maxlevel):
cur.below = Node("inf")
cur = cur.below
补充代码:给定升序序列的列表,构造跳表construct()
;遍历跳表函数travel
:
从序列构造跳表construct()
这是我花时间花的最久的步骤,因为在构建时比较复杂,同时需要向下、向右构建链接。
遍历跳表 travel
相对简单一点,只需要逐层遍历即可。通过此,就可以完整的看到自己的链表实现的是否正确。
一般的操作函数search
, add
, erase
为什么需要一个根据列表来构造跳表的函数呢?答案来了,因为底下这些小函数用到的思路与那个的思路完全一致。
search()
函数
寻找,在每一层上找,如果找到了,输出True
;如果右边节点比target
大,那么如果本节点有下一层,就往下一层找,如果没有下一层,那就说明寻找完成,没有发现该节点,即条表中不存在该节点。
add()
函数
像 search()
函数一样,在每一层搜索到需要插入的位置,将所有的插入位置的指针写入一个列表curs
中。然后我们只需要对列表中的点进行操作就可以了:
- 先生成随机数,看需要从第几层开始插入。
- 如果当前指针没有后继
- 如果不需要插入,则往下走
- 如果需要插入,建立一个节点,建立起链接,并更新列表
curs
- 如果有后继指针,则如最简单的插入法,插入,并更新列表
curs
最后,建立向下的指针,这里需要注意:我们只能对更新过的指针建立向下链接。所以只需要判断当前这个指针的值是否为我们插入的值就可以了。
erase()
函数
先调用search()
函数,如果不在跳表中,直接输出False
,如果在条表中,如add()
函数中,先寻址,然后每层都删除就可。
完整代码如下:(包括主函数)
Maxlevel = 16
th = [pow(0.5, n) for n in range(Maxlevel, 0, -1)]
import random
class Node(object):
def __init__(self, x = "inf"):
self.val = x
self.next = None
self.below = None
class Skiplist(object):
def __init__(self):
self.head = Node("inf")
cur = self.head
for i in range(0, Maxlevel):
cur.below = Node("inf")
cur = cur.below
def construct(self, lst):
if not lst:
return None
else:
# 初始化头节点
self.head = Node("inf")
cur = self.head
heads = [None] * Maxlevel
for i in range(0, Maxlevel):
cur.below = Node("inf")
# 记录下每层的头节点
heads[i] = cur
cur = cur.below
# 开始从上往下加入数字
curs = heads
for item in lst:
pr = random.random()
lvnum = 0
flag = 0
for i in range(0, Maxlevel):
if (i < Maxlevel - 1) & (pr > th[i]):
lvnum = lvnum + 1
else:
if flag == 0:
temp = i
flag = 1
curs[i].next = Node(item)
curs[i] = curs[i].next
# 添加向下的链接
for j in range(temp, Maxlevel - 1):
curs[j].below = curs[j+1]
return self.head
def travel(self):
lv = self.head
lvnum = 0
while lv:
cur = lv
print("Level", lvnum, end = ": ")
while cur:
print(cur.val, end = "->")
cur = cur.next
print("end")
lv = lv.below
if not lv.below:
break
lvnum = lvnum + 1
def search(self, target):
"""
:type target: int
:rtype: bool
"""
cur = self.head
while cur:
while cur.next:
if cur.next.val > target:
break
elif cur.next.val == target:
return True
else:
cur = cur.next
cur = cur.below
return False
def add(self, num):
"""
:type num: int
:rtype: None
"""
cur = self.head
pos = []
while True:
if cur:
if cur.next:
if cur.next.val < num:
cur = cur.next
else:
pos.append(cur)
if cur.below:
cur = cur.below
else:
break
else:
pos.append(cur)
cur = cur.below
if not cur:
break
pr = random.random()
for i in range(0, Maxlevel):
if (i < Maxlevel - 1) & (pr > th[i]):
continue
else:
if pos[i].next == None:
pos[i].next = Node(num)
pos[i] = pos[i].next
else:
temp = pos[i].next
pos[i].next = Node(num)
pos[i] = pos[i].next
pos[i].next = temp
for i in range(0, Maxlevel-1):
if pos[i].val == num:
pos[i].below = pos[i+1]
def erase(self, num):
"""
:type num: int
:rtype: bool
"""
if not self.search(num):
return False
else:
cur = self.head
pos = []
while True:
if cur:
if cur.next:
if cur.next.val < num:
cur = cur.next
else:
pos.append(cur)
if cur.below:
cur = cur.below
else:
break
else:
pos.append(cur)
cur = cur.below
if not cur:
break
for i in range(0, Maxlevel):
if pos[i].next:
if pos[i].next.val != num:
continue
else:
pos[i].next = pos[i].next.next
return True
# Your Skiplist object will be instantiated and called as such:
if __name__ == "__main__":
obj = Skiplist()
obj.add(1)
obj.add(2)
obj.add(3)
obj.search(0)
obj.add(4)
obj.search(1)
obj.erase(0)
obj.erase(1)
obj.search(1)
lst = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
obj.construct(lst)
target = 10
param_1 = obj.search(target)
print("param_1 =", param_1)
num = 65
obj.add(num)
param_2 = obj.search(num)
print("param_2 =", param_2)
param_3 = obj.erase(num)
obj.travel()
param_4 = obj.search(num)
print("param_4 =", param_4)