数据结构
链表
链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
定义:链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是由一系列节点组成的元素集合。每个节点包含两部分,数据域item和指向下一个节点的指针next。通过节点之间的相互连接,最终串成一个链表。
创建链表:
头插法
尾插法
class Node:
"""
创建链表
"""
def __init__(self, item):
self.item = item
self.next = None
def create_linklist_head(li):
"""
头插法
:param li:
:return:
"""
head =Node(li[0]) # 列表第一个值为头
for element in li[1:]: # 列表除了头之外的元素
node = Node(element) # 新的链表元素
node.next = head # 新的链表插到头部,
head = node # 头部为新的链表元素
return head
def create_linklist_tail(li):
head = Node(li[0]) # 初始头结点
tail = head # 开始尾结点和头结点相同
for element in li[1:]:
node = Node(element)
tail.next = node # 新的链表插到尾部
tail = node # 尾部位新的链表元素
return head
def print_linklist(lk):
"""
遍历链表
:param lk:
:return:
"""
while lk: # 当lk不为None时进行循环
print(lk.item, end=',')
lk = lk.next
lk_head = create_linklist_head([1,2,3,6,8,15])
lk_tail = create_linklist_tail([1,2,3,6,8,15])
print_linklist(lk_head)
print_linklist(lk_tail)
双向链表:
一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
顺序表(列表/数组)与链表
链表在插入和删除的操作上明显快于顺序表
链表的内存可以更灵活的分配
链表这种链式存储的数据结构对树和图结构有很大启发
哈希表(散列表)
哈希表是通过哈希函数来计算数据存储位置的数据结构,通常支持如下操作:
* insert(key, value):插入键值对
* get(key):如果存在键为key的键值对则返回value,否则返回空值
* delete(key):删除键为key的键值对
哈希表是一种线性的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
注意:由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一位置上的情况,这种情况叫做哈希冲突
解决hash冲突
1.开放寻址法:
2.拉链法:
hash表的实现:
class LinkList:
class Node:
def __init__(self, item=None):
self.item = item
self.next = None
class LinkListIterator: # 迭代器
def __init__(self, node):
self.node = node
def __next__(self):
if self.node: # 如果node不是空
cur_node = self.node
self.node = cur_node.next # 更新node指向下一个链表
return cur_node.item # 返回当前链表存的数据
else:
raise StopIteration
def __iter__(self):
return self
def __init__(self, iterable=None):
self.head = None
self.tail = None
if iterable:
self.extend(iterable)
def append(self, obj):
s = LinkList.Node(obj) # 创建队列
if not self.head: # 如果没有队列
self.head = s # 把第一个设置为队列
self.tail = s # 把第一个设置为尾巴
else: # 如果不是空的
self.tail.next = s # 接上原本尾巴的next
self.tail = s # 获得新的尾巴
def extend(self, iterable):
for obj in iterable:
self.append(obj)
def find(self, obj):
for n in self:
if n == obj:
return True
else:
return False
def __iter__(self):
return self.LinkListIterator(self.head) # 创建迭代器
def __repr__(self):
# print调用__repr__。map(str, self)把可迭代对象转换成字符串
return "<<" + ", ".join(map(str, self)) + ">>" # map(func,iterable)通过函数映射可迭代对象
# 类似于集合的结构
class HashTable:
def __init__(self, size=101):
self.size = size # hash表长度
self.T = [LinkList() for i in range(self.size)] # 存放下标的地方
def h(self, k):
# hash函数
return k % self.size
def insert(self, k):
i = self.h(k) # hash值
if self.find(k): # 用于去重
print("Duplicated Insert.") # 有这个值了
else:
self.T[i].append(k)
def find(self, k):
i = self.h(k)
return self.T[i].find(k)
ht = HashTable()
ht.insert(0)
ht.insert(1)
ht.insert(3)
ht.insert(102)
ht.insert(508)
print(",".join(map(str, ht.T)))
print(ht.find(203))
hash表的应用:
1.字典与集合都是通过hash表实现的
2.MD5:曾经是密码学中常用的hash函数,把任意长度数据映射成128位的hash值
应用:
* 文件的hash值,用来验证下载的文件是否完整
* 云存储服务商利用它判断用户要上传的文件是否已经存在到服务器上,从而实现秒传的功能,同时避免存储过多的相同文件
* 如今安全性重要的场合推荐使用SHA-2等新的更安全的hash函数。应用:SHA-2比特币
树
树的概念:python算法–堆排序学前必备知识
二叉树的性质(特性)
性质1: 在二叉树的第i层上至多有2^(i-1)个结点(i>0)
性质2: 深度为k的二叉树至多有2^k - 1个结点(k>0)
性质3: 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
性质4:具有n个结点的完全二叉树的深度必为 log2(n+1)
性质5:对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)
二叉树的链式存储:
将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来链接
二叉树节点:
class Node(object):
"""节点类"""
def __init__(self, elem=-1, lchild=None, rchild=None):
self.elem = elem # 本身
self.lchild = lchild # 左孩子
self.rchild = rchild # 右孩子
二叉树的遍历(重点)
四种主要的遍历思想为:
深度优先遍历:
1.前序遍历:根结点 —> 左子树 —> 右子树
2.中序遍历:左子树—> 根结点 —> 右子树
3.后序遍历:左子树 —> 右子树 —> 根结点
广度优先遍历:
4.层次遍历:只需按层次遍历即可
面试题:通过二叉树前序遍历和中序遍历写出二叉树和它的后序遍历
1.前序遍历可以确认第一个元素为根
2.再到中序遍历中找到根的左右两侧子树
3.再通过前序遍历分出左右子树,找到首个左右子树,再重复1、2操作,检查每一个元素带入中序遍历中,找到左右子树的左边和右边得到对应的子树
4.得到二叉树,写出后序遍历,后序遍历最后一个元素为根元素,每一个子树的根为最后一个元素
左右子树不断划分
二叉树遍历(递归实现)
from collections import deque
class BiTreeNode:
def __init__(self, data):
self.data = data
self.lchild = None # 左孩子
self.rchild = None # 右孩子
a = BiTreeNode("A")
b = BiTreeNode("B")
c = BiTreeNode("C")
d = BiTreeNode("D")
e = BiTreeNode("E")
f = BiTreeNode("F")
g = BiTreeNode("G")
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f
root = e
def pre_order(root):
"""
前序遍历
:param root:
:return:
"""
if root: # 是否有root值
print(root.data, end=',') # 先打印自己
pre_order(root.lchild) # 递归左子树
pre_order(root.rchild) # 递归右子树
def in_order(root):
"""
中序遍历
:param root:
:return:
"""
if root:
in_order(root.lchild)
print(root.data, end=',')
in_order(root.rchild)
def post_order(root):
"""
后序遍历
:param root:
:return:
"""
if root:
post_order(root.lchild)
post_order(root.rchild)
print(root.data, end=',')
def level_order(root):
"""
层次遍历
:param root:
:return:
"""
queue = deque()
queue.append(root)
while len(queue) > 0: # 只要队不空
node = queue.popleft() # 出队
print(node.data, end=',')
if node.lchild: # 如果有左孩子
queue.append(node.lchild)
if node.rchild: # 如果有右孩子
queue.append(node.rchild)
pre_order(root)
###二叉搜索树
二叉搜索树是一颗二叉树且满足:左边的所有节点比父节点小,右边的所有节点比父节点大
二叉搜索树的操作:查询、插入、删除
import random
class BiTreeNode:
def __init__(self, data):
self.data = data
self.lchild = None # 左孩子
self.rchild = None # 右孩子
self.parent = None
class BST:
def __init__(self, li=None):
self.root = None
if li:
for val in li:
self.insert_no_rec(val)
def insert(self, node, val):
# 插入递归方法
if not node:
node = BiTreeNode(val)
elif val < node.data:
node.lchild = self.insert(node.lchild, val)
node.lchild.parent = node
elif val > node.data:
node.rchild = self.insert(node.rchild, val)
node.rchild.parent = node
return node
def insert_no_rec(self, val):
# 插入非递归方法
p = self.root
if not p: # 空树
self.root = BiTreeNode(val)
return
while True:
if val < p.data:
if p.lchild:
p = p.lchild
else: # 左孩子不存在
p.lchild = BiTreeNode(val)
p.lchild.parent = p
return
elif val > p.data:
if p.rchild:
p = p.rchild
else:
p.rchild = BiTreeNode(val)
p.rchild.parent = p
return
else:
return
def query(self, node, val):
# 查询递归版本
if not node: # 递归终止条件
return None
if node.data < val:
return self.query(node.rchild, val)
elif node.data > val:
return self.query(node.lchild, val)
else:
return node
def query_no_rec(self, val):
# 查询非递归版本
p = self.root
while p:
if p.data < val:
p = p.rchild
elif p.data > val:
p = p.lchild
else:
return p
return None
def pre_order(self, root):
if root:
print(root.data, end=',')
self.pre_order(root.lchild)
self.pre_order(root.rchild)
def in_order(self, root):
if root:
self.in_order(root.lchild)
print(root.data, end=',')
self.in_order(root.rchild)
def post_order(self, root):
if root:
self.post_order(root.lchild)
self.post_order(root.rchild)
print(root.data, end=',')
def __remove_node_1(self, node):
# 情况1:node是叶子节点
if not node.parent:
self.root = None
if node == node.parent.lchild: # node是它父亲的左孩子
node.parent.lchild = None
else: # 右孩子
node.parent.rchild = None
def __remove_node_21(self, node):
# 情况2.1:node只有一个左孩子
if not node.parent: # 根节点
self.root = node.lchild
node.lchild.parent = None
elif node == node.parent.lchild:
node.parent.lchild = node.lchild
node.lchild.parent = node.parent
else:
node.parent.rchild = node.lchild
node.lchild.parent = node.parent
def __remove_node_22(self, node):
# 情况2.2:node只有一个右孩子
if not node.parent:
self.root = node.rchild
elif node == node.parent.lchild:
node.parent.lchild = node.rchild
node.rchild.parent = node.parent
else:
node.parent.rchild = node.rchild
node.rchild.parent = node.parent
def delete(self, val):
if self.root: # 不是空树
node = self.query_no_rec(val)
if not node: # 不存在
return False
if not node.lchild and not node.rchild: # 1. 叶子节点
self.__remove_node_1(node)
elif not node.rchild: # 2.1 只有一个左孩子
self.__remove_node_21(node)
elif not node.lchild: # 2.2 只有一个右孩子
self.__remove_node_22(node)
else: # 3. 两个孩子都有
min_node = node.rchild
while min_node.lchild:
min_node = min_node.lchild
node.data = min_node.data
# 删除min_node
if min_node.rchild:
self.__remove_node_22(min_node)
else:
self.__remove_node_1(min_node)
tree = BST([1, 4, 2, 5, 3, 8, 6, 9, 7])
tree.in_order(tree.root)
print("")
tree.delete(4)
tree.delete(1)
tree.delete(8)
tree.in_order(tree.root)
二叉搜索树进行搜索的时间复杂度为O(logn)
最坏情况下,二叉搜索树可能非常偏斜,比如:所有节点都比根节点大,就会退化成线性结构
解决方案:1.随机化插入 2.AVL树
AVL树
AVL树:一颗自平衡二叉搜索树
AVL树性质:
根的左右子树的高度之差的绝对值不能超过1
根的左右子树都是平衡二叉树
插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作进行修正
旋转操作:插入一个节点后,只有从插入节点到根节点的路径上的节点的平衡可能改变。我们需要找到第一个破坏平衡条件的节点,称之为K。K的两颗子树的高度差2。
不平衡的出现可能有4中情况:
1.右旋: 左左
2.左旋: 右右
3.右旋-左旋: 右左
3.左旋-右旋: 左右
from bst import BiTreeNode, BST # 导入二叉搜索树
class AVLNode(BiTreeNode):
def __init__(self, data):
BiTreeNode.__init__(self, data)
self.bf = 0
class AVLTree(BST):
def __init__(self, li=None):
BST.__init__(self, li)
def rotate_left(self, p, c):
s2 = c.lchild
p.rchild = s2
if s2:
s2.parent = p
c.lchild = p
p.parent = c
p.bf = 0
c.bf = 0
return c
def rotate_right(self, p, c):
s2 = c.rchild
p.lchild = s2
if s2:
s2.parent = p
c.rchild = p
p.parent = c
p.bf = 0
c.bf = 0
return c
def rotate_right_left(self, p, c):
g = c.lchild
s3 = g.rchild
c.lchild = s3
if s3:
s3.parent = c
g.rchild = c
c.parent = g
s2 = g.lchild
p.rchild = s2
if s2:
s2.parent = p
g.lchild = p
p.parent = g
# 更新bf
if g.bf > 0:
p.bf = -1
c.bf = 0
elif g.bf < 0:
p.bf = 0
c.bf = 1
else: # 插入的是g
p.bf = 0
c.bf = 0
return g
def rotate_left_right(self, p, c):
g = c.rchild
s2 = g.lchild
c.rchild = s2
if s2:
s2.parent = c
g.lchild = c
c.parent = g
s3 = g.rchild
p.lchild = s3
if s3:
s3.parent = p
g.rchild = p
p.parent = g
# 更新bf
if g.bf < 0:
p.bf = 1
c.bf = 0
elif g.bf > 0:
p.bf = 0
c.bf = -1
else:
p.bf = 0
c.bf = 0
return g
def insert_no_rec(self, val):
# 1. 和BST一样,插入
p = self.root
if not p: # 空树
self.root = AVLNode(val)
return
while True:
if val < p.data:
if p.lchild:
p = p.lchild
else: # 左孩子不存在
p.lchild = AVLNode(val)
p.lchild.parent = p
node = p.lchild # node 存储的就是插入的节点
break
elif val > p.data:
if p.rchild:
p = p.rchild
else:
p.rchild = AVLNode(val)
p.rchild.parent = p
node = p.rchild
break
else: # val == p.data
return
# 2. 更新balance factor
while node.parent: # node.parent不空
if node.parent.lchild == node: # 传递是从左子树来的,左子树更沉了
# 更新node.parent的bf -= 1
if node.parent.bf < 0: # 原来node.parent.bf == -1, 更新后变成-2
# 做旋转
# 看node哪边沉
g = node.parent.parent # 为了连接旋转之后的子树
x = node.parent # 旋转前的子树的根
if node.bf > 0:
n = self.rotate_left_right(node.parent, node)
else:
n = self.rotate_right(node.parent, node)
# 记得:把n和g连起来
elif node.parent.bf > 0: # 原来node.parent.bf = 1,更新之后变成0
node.parent.bf = 0
break
else: # 原来node.parent.bf = 0,更新之后变成-1
node.parent.bf = -1
node = node.parent
continue
else: # 传递是从右子树来的,右子树更沉了
# 更新node.parent.bf += 1
if node.parent.bf > 0: # 原来node.parent.bf == 1, 更新后变成2
# 做旋转
# 看node哪边沉
g = node.parent.parent # 为了连接旋转之后的子树
x = node.parent # 旋转前的子树的根
if node.bf < 0: # node.bf = 1
n = self.rotate_right_left(node.parent, node)
else: # node.bf = -1
n = self.rotate_left(node.parent, node)
# 记得连起来
elif node.parent.bf < 0: # 原来node.parent.bf = -1,更新之后变成0
node.parent.bf = 0
break
else: # 原来node.parent.bf = 0,更新之后变成1
node.parent.bf = 1
node = node.parent
continue
# 链接旋转后的子树
n.parent = g
if g: # g不是空
if x == g.lchild:
g.lchild = n
else:
g.rchild = n
break
else:
self.root = n
break
tree = AVLTree([9, 8, 7, 6, 5, 4, 3, 2, 1])
tree.pre_order(tree.root)
print("")
tree.in_order(tree.root)
AVL数的应用:B树
B树是一颗自平衡的多路搜索树。常用于数据库的索引
发展路线
二叉树—->二叉搜索树(小的左/大的右,实现类似二分的效果)—-遇到偏斜严重的问题—->AVL树(保证自平衡,在插入或删除中需要维护平衡进行旋转)