数据结构(链表 哈希表 二叉树 AVL树)
链表
链表是由一系列节点组成的元素集合。每个节点包含两部分,数据域item和指向下一个节点的指针next。
通过节点之间的相互连接next,最终串联成一个链表。
# 手动创建链表
class Node:
def __init__(self, item):
self.item = item
self.next = None
a = Node(1)
b = Node(2)
c = Node(3)
a.next = b
b.next = c
print(a.next.next.item) # 3
创建列表
头插法(倒叙插)

# 手动创建链表
class Node: def __init__(self, item): self.item = item self.next = None
# 创建链表 头插法
def create_linklist_head(li):
head = Node(li[0])
for element in li[1:]:
node = Node(element)# 创建节点
node.next = head # 把头连起来
head = node
return head
尾插法(正序插)

# 手动创建链表
class Node: def __init__(self, item): self.item = item self.next = None
# 尾插法
def create_linklist_tail(li):
head = Node(li[0]) # 头指向Node(li[0])
tail = head # 尾指向Node(li[0])
for element in li[1:]:
node = Node(element) # 创建节点
tail.next = node # 把尾巴连起来
tail = node # 新的node成为tail
return head
链表节点的插入
插入p节点 先链接4和2(如果先链接1和4的话原来的链表就断了) 断开1和2 再链接1和4

p.next = curNode.next # 先链接4和2
curNode.next = p # 再链接1和4
链表节点的删除*(把p节点删掉 先链接1和2 要不链表就断了 找不到下一节点了)

p = curNode.next
curNode.next = curNode.next.next # 或者 curNode.next = p.next 链接1和2
del p # 再删掉p
双链表
双链表的每个节点有两个指针:一个指向后一个节点,另一个指向前一个节点。
如何建立双链表?

# 双链表
class Node(object): def __init__(self, item=None): self.item = item self.next = None self.prior = None
双链表节点的插入:

p.next = curNode.next # 链接2和3
curNode.next.prior = p # 再链接3和2
p.prior = curNode # 链接2和1
curNode.next = p # 链接1和2
双链表节点的删除:

p = curNode.next
curNode.next = p.next # 链接1和3 断开1和2
p.next.prior = curNode # 链接3和1 断开2和1
del p
哈希表
哈希表一个通过哈希函数来计算数据存 储位置的数据结构,通常支持如下操作:
insert(key, value):插入键值对(key,value)
get(key):如果存在键为key的键值对则返回其value,否则返回空值
delete(key):删除键为key的键值对
直接寻址表

当关键字的全域U比较小时,直接寻址是一种简单而有效的方法。
直接寻址技术缺点:
当域U很大时,需要消耗大量内存,很不实际
如果域U很大而实际出现的key很少,则大量空间被浪费
无法处理关键字不是数字的情况
哈希
直接寻址表:key为k的元素放到T列表的k位置上
改进直接寻址表:哈希(Hashing)
构建大小为m的寻址表T
key为k的元素放到h(k)位置上(h(k)是一个函数,其将域U映射到表T[0,1,...,m-1])
哈希表(Hash Table,又称为散列表)
是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
假设有一个长度为7的哈希表,哈希函数h(k)=k%7。元素集合{14,22,3,5}的存储方式如下图。

14对7取余数0 存到0位置上 , 22对7取余1存到1的位置上,3对7取余3存到3的位置上。。。(对7取余只能是六个位置)
哈希冲突
由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置上的情况,这种情况叫做哈希冲突。
比如h(k)=k%7, h(0)=h(7)=h(14)=...
解决哈希冲突——开放寻址法
开放寻址法:如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值。
线性探查:如果位置i被占用,则探查i+1, i+2,……
(查的时候怎么查呢 也是同理 探查直到遇到空的位置停止 要是没有那就是表里没有此值)
二次探查:如果位置i被占用,则探查i+12,i-12,i+22,i-22,……
二度哈希:有n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2,h3,……
解决哈希冲突——拉链法
拉链法:
哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。

哈希表——常见哈希函数*
除法哈希法: h(k) = k % m
乘法哈希法: h(k) = floor(m*(A*key%1))
全域哈希法: ha,b(k) = ((a*key + b) mod p) mod m a,b=1,2,...,p-1
哈希表的应用——集合与字典
字典与集合都是通过哈希表来实现的。 a = {'name': 'Alex', 'age': 18, 'gender': 'Man'}
使用哈希表存储字典,通过哈希函数将字典的键映射为下标:
假设h('name') = 3, h('age') = 1, h('gender') = 4,
则哈希表存储为[None, 18, None, 'Alex', 'Man']
如果发生哈希冲突,则通过拉链法或开发寻址法解决


1、远程服务器一个文件 本地一个文件 有可能下到80%断点了 重启的时候先判断我的文件是不是下完了 比较自己的MD5值和服务器的md5值是不是一样
2、百度云可能秒传,因为服务器上存储好多文件,你上传的文件可能是从别的地方下载的也有可能之前就被别人上传过去了,我上传这个电影的时候 软件先算一下这个md5值 如果一样表示已经有人传过了,直接就把别人传的移到你的云盘目录里就行了


二叉树
二叉树的 链式 存储:将二叉树的节点定义为一个对象,节点之间通过 类似链表 的链接方式来连接。
节点定义:
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
print(root.lchild.rchild.data) # c

二叉树的遍历方式:
前序遍历:EACBDGF
中序遍历:ABCDEGF (先将E放中间左右是AG ,再把分别把AG放中间A的左边没有右边是C G的左边没有右边是F,再把CF放中间 C左边B右边D F左右没有)
后序遍历:BDCAFGE (先左后右 左后自己 每次是根在最后 AGE , CAFGE , BDCAFGE)
层次遍历:EAGCFBD
#! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/3/24 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 print(root,lchild.rchild.data) # 前序遍历 def pre_order(root): if root: # 如果root不是空 print(root.data, end=',') # 打印自己 pre_order(root.lchild) pre_order(root.rchild) # 中序遍历 def in_order(root): if root: in_order(root.lchild) print(root.data, end=',') in_order(root.rchild) # 后序遍历 def post_order(root): if root: post_order(root.lchild) post_order(root.rchild) print(root.data, end=',') # 层次遍历 def level_order(root): 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) level_order(root)
二叉搜索树

二叉搜索树是一颗二叉树且满足性质:
设x是二叉树的一个节点。如果y是x左子树的一个节点,那么y.key ≤ x.key;
如果y是x右子树的一个节点,那么y.key ≥ x.key。
二叉搜索树的操作:查询(查询只是查树的深度就能找到结果)、插入(同查询)、删除
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): # 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: # 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
二叉搜索树——删除操作
1.如果要删除的节点是叶子节点:直接删除

2.如果要删除的节点只有一个孩子:将此节点的父亲与孩子连接,然后删除该节点。

3. 如果要删除的节点有两个孩子:将其左子树最大节点或右子树的最小节点替换当前节点(该节点最多有一个右孩子)并删除替换前的替换当前节点

#! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/3/24 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)# 1直接删除 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(nlgn)。
最坏情况下,二叉搜索树可能非常偏斜。

解决方案:
随机化插入
AVL树
AVL树:
AVL树是一棵自平衡的二叉搜索树。

左子树的高度减去右子树的高度
AVL树具有以下性质:(不会说是一边是0个一边是1万个 保持大概减半)
根的左右子树的高度之差的绝对值不能超过1 根的左右子树都是平衡二叉树
AVL树——插入
插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作来进行修正。
插入一个节点后,只有从插入节点到根节点的路径上的节点的平衡可能被改变。我们需要找出第一个破坏了平衡条件的节点,称之为K。K的两颗子树的高度差2。
不平衡的出现可能有4种情况
1.不平衡是由于对K的左孩子的左子树插入导致的:右旋

2.不平衡是由于对K的右孩子的右子树插入导致的:左旋

3.不平衡是由于对K的右孩子的左子树插入导致的:右旋-左旋

4.不平衡是由于对K的左孩子的右子树插入导致的:左旋-右旋


#! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/3/24 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)

浙公网安备 33010602011771号