数据结构
数据结构是指互相之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。
分类
逻辑分类:线性结构;树结构;图结构
列表
列表中的元素是如何存储的:顺序存储
列表的基本操作:按下标查找、插入元素、删除元素...
这些操作的很少见复杂度时多少:查找O(1)、查找 删除O(n)
栈
栈(Stack)是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表。
栈的特点:后进先出LIFO(last-in-first-out)
栈的概念:栈顶、栈底
栈的基本操作:
- 进栈(压栈):push
- 出栈:pop
- 取栈顶:gettop()
class Stack:
def __init__(self):
self.stack = []
def push(self,ele):
self.stack.append(ele)
def pop(self):
if not self.is_empty():
self.stack.pop()
def get_top(self):
if not self.is_empty():
return self.stack[-1]
def is_empty(self):
return len(self.stack)==0
class Check_symbol:
def __init__(self,check_str):
self.res = self.__check(check_str)
@property
def result(self):
return self.res
def __check(self,check_str):
stack = Stack()
for v in check_str:
match = {"}":"{","]":"[","}":"{"}
if v in {"{","[","("}:
stack.push(v)
else:
if v not in match:
return False
if stack.get_top() == match[v]:
stack.pop()
else:
return False
if stack.is_empty():
return True
else:
return False
c = Check_symbol("")
print(c.result)
队列
队列(Queue)是一个数据结合,仅允许在列表的一端进行插入,另一端进行删除。
进行插入的一端称为队尾(rear),插入动作称为进队或入队。
进行删除的一端称为队头(front),删除动作称为出队。
队列的性质:先进先出(First-in,First-out)
队列的实现
环形队列
class Queue:
def __init__(self,size):
self.queue = [0 for _ in range(size+1)]
self.size = size+1
self.rear = 0
self.front = 0
def push(self,element):
if not self.is_filled():
self.rear = (self.rear+1) % self.size
self.queue[self.rear] = element
else:
raise IndexError("queue is filled.")
def pop(self):
if not self.is_empty():
self.front = (self.front+1) % self.size
return self.queue[self.front]
else:
raise IndexError("queue is empty.")
# 判断队空
def is_empty(self):
return self.rear == self.front
# 判断队满
def is_filled(self):
return (self.rear+1) % self.size == self.front
q = Queue(4)
q.push(1)
q.push(2)
q.push(3)
q.push(4)
print(q.pop())
print(q.pop())
print(q.pop())
print(q.pop())
迷宫问题
给一个二维列表,表示迷宫(0表示通道,1表示围墙)。给出算法,求一条走出迷宫的路径。
解法一:栈 -- 深度优先搜索
回溯法
思路:从一个节点开始,任意找下一个能走的点,当找不到能走的点时,退回上一个点寻找是否有其他方向的点。
使用栈存储当前路径
解法二:队列 -- 广度优先
思路:从一个节点开始,找出所有能走的点,当前节点出列,并记录该节点信息(由哪个节点走到),上层节点全部出列后 开始下一层循环,直到找到出口位置,并从记录中找到该路线位置信息。
广度优先可以找到最优解。
from collections import deque
maze_obj = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 1, 0, 1],
[1, 1, 0, 0, 0, 0, 1, 1, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
dirs = [
lambda point: (point[0] + 1, point[1]),
lambda point: (point[0], point[1] + 1),
lambda point: (point[0] - 1, point[1]),
lambda point: (point[0], point[1] - 1),
]
def print_path(path):
end_point = path[-1] # 出口点
realpath = [] # 实际路径
while end_point[-1] != -1:
realpath.append(end_point)
end_point = path[end_point[-1]]
loop_count = 0
for curIndex in range(len(realpath) - 1, -1, -1):
print(realpath[curIndex][0])
loop_count+=1
print("loop_count:",loop_count)
def maze_path(maze, start_point, end_point):
maze[start_point[0]][start_point[1]] =2
q = deque()
q.append((start_point, 0))
path = []
path.append((start_point, -1))
while len(q) > 0:
n = len(q)
# for i in range(n):
# print(q)
curNode = q.popleft()
path.append(curNode)
curPoint = curNode[0]
if curPoint == end_point:
# print(path)
print_path(path)
return True
for fun in dirs:
nextPoint = fun(curPoint)
if maze[nextPoint[0]][nextPoint[1]] == 0:
q.append((nextPoint, len(path) - 1))
maze[nextPoint[0]][nextPoint[1]] = 2 # 标记已经走过
else:
print("没有出路")
return True
maze_path(maze_obj, (1, 1), (7, 8))
哈希表
哈希表通过一个哈希函数来计算数据存储位置的数据结构,通常支持如下操作:
- insert(key,value):插入键值对
- get(key):如果存在键为key的键值对则返回其value,否则范围空值
- delete(key):删除键为key的键值对
哈希表(Hash Table,又称为散列表),是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
假设有一个长度为7的哈希表,哈希函数h(k)=k%7。元素集合{14,22,3,5}的存储方式如下:
i:[ 0, 1, 2, 3, 4, 5, 6]
v:[14, 22, null, 2, 3, null, 5, null]
哈希冲突
使用上诉方式可能造成哈希冲突,比如再向哈希表中插入一个key=0的元素,按照h(k)算法 应该放在i=0的位置上,而i=0的位置上已经有值了。
解决哈希冲突
开放寻址法
开放寻址法:如果哈希函数返回的位置已经有值了,则可以向后探查新的位置来存储这个值。
- 线性探查:如果位置i被占用,则探查i+1, i+2, ...
- 二次探查:如果位置i被占用,则探查i+1^2, i-1^2, i+2^2, i-2^2, ...
- 二度哈希:有n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2(k),h3(k)
拉链法
拉链法:哈希表每个位置都链接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。
常见哈希函数
- 除法哈希法: h(k) = k%m
- 乘法哈希法: h(k) = floor(m(Akey%1))
- 全域哈希法:
'''
哈希表--拉链法
'''
class LinkList:
class Node:
def __init__(self, item):
self.item = item
self.next = None
class LinkListIterator:
def __init__(self, node):
self.node = node
def __iter__(self):
return self
def __next__(self):
if self.node:
cur_node = self.node
self.node = self.node.next
return cur_node.item
else:
raise StopIteration
def __init__(self, iterable=None):
self.head = None
self.tail = None
if iterable:
self.extend(iterable)
def append(self, obj):
node = self.Node(obj)
if not self.head:
self.head = node
self.tail = node
else:
self.tail.next = node
self.tail = node
def extend(self, iterable):
for obj in iterable:
if not self.find(obj):
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):
return "<"+",".join(map(str, self))+">"
class HashTable:
def __init__(self, maxsize=100):
self.maxsize = maxsize
self.T = [LinkList() for _ in range(maxsize)]
def insert(self, k):
i = self.h(k)
if self.find(k):
print("Duplicated Insert.")
else:
self.T[i].append(k)
def h(self, k):
return k % self.maxsize
def find(self, k):
i = self.h(k)
return self.T[i].find(k)
h = HashTable()
h.insert(1)
h.insert(1)
h.insert(101)
print(",".join(map(str,h.T)))
树
树是一种数据结构 比如:目录结构
树是一种可以递归定义的数据结构
树是由n个节点组成的集合:
- 如果n=0,那这是一棵空树
- 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。
一些概念
根节点,叶子节点
树的深度
树的度
孩子节点,父节点
子树
class Node:
def __init__(self, name):
self.name = name
self.children = []
self.parent = None
def __repr__(self):
return self.name
class Tree:
def __init__(self, rootName):
self.root = Node(rootName)
self.curNode = self.root
def append(self, name):
appendNode = Node(name)
self.curNode.children.append(appendNode)
appendNode.parent = self.curNode
def getChildren(self, name):
print(self.curNode.children)
for children in self.curNode.children:
if children.name == name:
self.curNode = children
return self.curNode
else:
print("name is wrong!")
return None
doc = Tree("html")
doc.append("head")
doc.append("body")
body = doc.getChildren("body")
print("--》 ",body)
二叉树
二叉树的子节点最多只有两个,分别为 左节点 右节点。
class BiTreeNode:
def __init__(self, data):
self.data = data
self.lchild = Node #左节点
self.rchild = Node # 右节点
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("child node data:",e.lchild.rchild.data)
二叉搜索树
二叉树中,如果全部左子节点的值小于其父节点的值,全部右子节点的值大于其父节点的值,则该二叉树为二叉搜索树。
class BITreeNode:
def __init__(self, val):
self.val = val
self.lchild = None # 左节点
self.rchild = None # 右节点
self.parent = None # 父节点
class BST:
def __init__(self, val):
self.root = BITreeNode(val)
def insert(self, node, val):
'''插入 递归解法'''
# print(val,f"node:{node}")
if not node:
node = BITreeNode(val)
elif val < node.val:
node.lchild = self.insert(node.lchild, val)
node.lchild.parent = node
elif val > node.val:
node.rchild = self.insert(node.rchild, val)
node.rchild.parent = node
return node
def insert_no_res(self, val):
'''插入 循环解法'''
node = self.root
if not node:
self.root = BITreeNode(val)
return
while True:
if val < node.val:
if node.lchild:
node = node.lchild
else:
node.lchild = BITreeNode(val)
node.lchild.parent = node
break
elif val > node.val:
if node.rchild:
node = node.rchild
else:
node.rchild = BITreeNode(val)
node.rchild.parent = node
break
else:
break
def pre_order(self, node):
'''前序遍历'''
if node:
print(node.val, end=",")
self.pre_order(node.lchild)
self.pre_order(node.rchild)
def in_order(self, node):
'''中序遍历'''
if node:
self.in_order(node.lchild)
print(node.val, end=",")
self.in_order(node.rchild)
def post_order(self, node):
'''后序遍历'''
if node:
self.post_order(node.lchild)
self.post_order(node.rchild)
print(node.val, end=",")
def level_order(self, node):
'''层次遍历'''
from collections import deque
queue = deque()
queue.append(node)
while queue:
node = queue.popleft()
print(node.val, end=",")
if node.lchild:
queue.append(node.lchild)
if node.rchild:
queue.append(node.rchild)
def query(self, node, val):
'''查询,返回节点实体'''
if node:
if node.val == val:
return node
else:
if node.lchild and val < node.val: # 若目标值小于节点值,则往节点左节点查找
return self.query(node.lchild, val)
if node.rchild and val > node.val:
return self.query(node.rchild, val) # 若目标值大于节点值,则往节点右节点查找
return None
def excute(self, node, val):
if self.query(node, val):
return True
else:
return False
def delete(self, val):
'''
删除一共有三种情况:
1.删除的节点为叶子节点:
把该节点删除,父节点对应其的向下关系删除
2.删除的节点有一个子节点:
把该节点删除,父节点对应的向下关系对应到唯一的子节点
3.删除的节点有两个子节点:
把该节点删除,父节点对应的向下关系对应到左节点下的最大值节点或者对应到右节点下的最小值节点
:param val:
:return:
'''
del_node = self.query(self.root, val)
if del_node: # 如果有节点
children_count = self.get_children_count(del_node)
if children_count == 0 or children_count == 1:
self.upd_parent_relation(del_node)
elif children_count == 2:
self.upd_parent_left_relation(del_node)
del_node = None
else:
return False
def get_children_count(self, node):
'''查看节点有多少子节点'''
return (1 if node.lchild else 0) + (1 if node.rchild else 0)
def upd_parent_relation(self, node):
new_node = node.lchild
if not node.lchild:
new_node = node.rchild
if node.parent.lchild == node: # 若待删除节点为父节点的左子节点
node.parent.lchild = new_node # 更新父节点的左子链关系
else: # 若为右子节点
node.parent.rchild = new_node # 更新右子链关系
def upd_parent_left_relation(self, node):
new_node = node.lchild
while new_node.rchild:
new_node = new_node.rchild
if node.parent.lchild == node: # 若待删除节点为父节点的左子节点
node.parent.lchild = new_node # 更新父节点的左子链关系
else: # 若为右子节点
node.parent.rchild = new_node # 更新右子链关系
b = BST(5)
b.insert_no_res(4)
b.insert_no_res(6)
b.insert_no_res(3)
b.insert_no_res(2)
b.level_order(b.root)
print()
# print(b.excute(b.root, 2))
c = b.delete(4)
# print(b.excute(b.root, 2))
b.level_order(b.root)
print()
b.pre_order(b.root)
AVL树
AVL树:AVL树是一种自平衡的二叉搜索树。
AVL树:
- 根的左右子树的高度之差的绝对值不能超过1
- 根的左右子树都是平衡二叉树