数据结构与算法(Algorithm)
1 时间复杂度(O)
- 在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间,常用大O符号表述;
- 相同大小的不同输入值仍可能造成算法的运行时间不同,因此我们通常使用算法的最坏情况复杂度,记为
T(n)
,定义为任何大小的输入n
所需的最大运行时间。 - 另一种较少使用的方法是平均情况复杂度,通常有特别指定才会使用。
2 查找
顺序查找(线性查找) | 依次查找 | O(n) |
---|---|---|
二分查找 | 位置取中 | O(logn) |
1.1 二分查找
def binary_search(li, val):
left = 0
right = len(li) - 1
while left <= right:
mid = (left + right) // 2
if li[mid] == val:
return mid
elif val > li[mid]: # 查找的值在mid右侧
left = mid + 1
else: # 查找的值在mid左侧
right = mid - 1
else:
return None
1.2 顺序查找
def linear_search(li, val):
for ind, v in enumerate(li):
if v == val:
return ind
else:
return None
3 排序
低效(O(n^2)) | 中效(O(nlogn)) | 其他 |
---|---|---|
冒泡排序 | 快速排序(最坏情况为O(n^2),即逆序列表) | 希尔排序 |
选择排序 | 堆堆序 | 计数排序 |
插入排序 | 归并排序 | 桶排序 |
基数排序 |
3.1 低效
(1)冒泡排序:
def bubble_sort(li):
for i in range(len(li) - 1): # 第i趟
for j in range(len(li) - i - 1):
if li[j] > li[j + 1]:
li[j], li[j + 1] = li[j + 1], li[j]
print(li)
(2)选择排序:
def select_sort(li):
for i in range(len(li) - 1):
min_loc = i
for j in range(i + 1, len(li)):
if li[min_loc] > li[j]:
min_loc = j
li[min_loc], li[i] = li[i], li[min_loc]
print(li)
(3)插入排序:
def insert_sort(li):
for i in range(1, len(li)): # 表示摸到的牌的下标
temp = li[i]
j = i - 1 # j指的是手里牌的下标
while j >= 0 and li[j] > temp:
li[j + 1] = li[j]
j -= 1
li[j + 1] = temp
print(li)
3.2 中效
(1)快速排序:
def partition(li, left, right): # 归位函数
temp = li[left]
while left < right:
while left < right and li[right] > temp: # 当比归位值大时,right位置减1
right -= 1
li[left] = li[right] # 将小值填补左边的空位
while left < right and li[left] < temp: # 当比归位值小时,left值加1
left += 1
li[right] = li[left] # 将大值填补右边的空位
li[left] = temp # 把temp归位
return left
def quick_sort(li, left, right): # 排序函数
if left < right:
mid = partition(li, left, right)
quick_sort(li, left, mid - 1)
quick_sort(li, mid + 1, right)
(2)堆堆序:
二叉树的存储方式:顺序存储、链式存储(数据结构用的式链式存储方式)
**堆:**一种特殊的完全二叉树结构
**大根堆:**一个完全二叉树,满足任一节点都比其孩子节点大
**小根堆:**一颗完全二叉树,满足任一节点都比其孩子节点小
def sift(li, low, high): # 调整函数
"""
:param li: 列表
:param low: 堆顶位置
:param high: 堆最后一个元素的位置
"""
i = low # 最开始指向根节点
j = 2 * i + 1 # j为左孩子
temp = li[low]
while j <= high: # 保证数据有效不越界
if j + 1 <= high and li[j + 1] > li[j]: # 保证有右孩子且右孩子更大
j = j + 1
if temp < li[j]:
li[i] = li[j]
i = j
j = 2 * i + 1
else:
li[i] = temp
break
else:
li[i] = temp
def heap_sort(li):
n = len(li)
for i in range((n - 2) // 2, -1, -1):
# i表示父节点的位置的下标
sift(li, i, n - 1)
# 1、建堆完成
for j in range(n - 1, -1, -1):
li[j], li[0] = li[0], li[j]
sift(li, 0, j - 1)
# 2、出数:堆顶元素为n-1的最大元素,将其依次放在列表末尾
问题应用:取n个值中前k个较大的值
def sift(li, low, high): # 调整函数
"""
:param li: 列表
:param low: 堆顶位置
:param high: 堆最后一个元素的位置
"""
i = low # 最开始指向根节点
j = 2 * i + 1 # j为左孩子
temp = li[low]
while j <= high: # 保证数据有效不越界
if j + 1 <= high and li[j + 1] < li[j]: # 保证有右孩子且右孩子更小
j = j + 1
if temp > li[j]:
li[i] = li[j]
i = j
j = 2 * i + 1
else:
li[i] = temp
break
else:
li[i] = temp
def top_k(li, k):
heap = li[0:k]
n = len(heap)
for i in range((n - 2) // 2, -1, -1):
# i表示父节点的位置的下标
sift(heap, i, n - 1)
print('建堆:', heap)
# 1、建堆完成
for i in range(k, len(li) - 1):
if heap[0] < li[i]:
heap[0] = li[i]
sift(heap, 0, k - 1)
print('遍历:', heap)
# 2、遍历列表,将数值依次与前k值中最小值比较,并按小根堆向下调整
for j in range(k - 1, -1, -1):
heap[0], heap[j] = heap[j], heap[0]
sift(heap, 0, j - 1)
# 3、出数,排序
return heap
(3)归并排序:
def merge(li, left, mid, right):#将有序列表归并为一个有序列表
i = left
j = mid + 1
temp = []
while i <= mid and j <= right: # 只要两边都有数
if li[i] < li[j]:
temp.append(li[i])
i += 1
else:
temp.append(li[j])
j += 1
while i <= mid:
temp.append(li[i])
i += 1
while j <= right:
temp.append(li[j])
j += 1
li[left:right + 1] = temp
def merge_sort(li, left, right):#通过递归,划分列表
if left < right:
mid = (left + right) // 2
merge_sort(li, left, mid)
merge_sort(li, mid + 1, right)
merge(li, left, mid, right)
3.3 其他
(1)希尔排序
原理:
"""
原数列 [8, 6, 4, 9, 7, 3, 0, 1, 5]
d=4 8 7 5 5 7 8
--->>> 3 6 --->>> 3 6
--->>> 4 0 --->>> 0 4
9 1 1 9
d=2 8 4 7 0 5 0 4 5 7 8
--->>> 3 9 6 1 --->>> 1 3 6 9
d=1 ---> 8 3 4 9 7 6 0 1 5 ---> 0 1 3 4 5 7 8 9
"""
代码:
def insert_sort_gap(li, gap):
for i in range(gap, len(li)): # 表示摸到的牌的下标
temp = li[i]
j = i - gap # j指的是手里牌的下标
while j >= 0 and li[j] > temp:
li[j + gap] = li[j]
j -= gap
li[j + gap] = temp
def shell_sort(li):
d = len(li) // 2
while d >= 1:
insert_sort_gap(li, d)
d = d // 2
(2)计数排序
原理:
- 知道列表的最大值,创建一个列表
- 将原列表的数值转换为新列表的下标,原列表中同一数值的个数作为新列表对应下标的索引值
- 按照新列表的下标及索引值,重新写入到原列表中-
def count_sort(li, max_count=100):
"""
:param li: 列表
:param max_count: 数值范围最大值
:return:
"""
count = [0 for _ in range(max_count + 1)]
for val in li:
count[val] += 1
li.clear()
for ind, val in enumerate(count):
for i in range(val):
li.append(ind)
**缺点:**需要已知列表最大值,如果最大值过大,则会创建一个很长的列表,占用很大的空间;对字母和小数不支持
(3)桶排序
def bucket_sort(li, n=100, max_num=10000):
buckets = [[] for _ in range(n)] # 建立桶
for val in li:
# 0->0号桶 86->0号桶 10000->99号桶
i = min(val // (max_num // n), n - 1) # 寻找值对应的桶号
buckets[i].append(val)
# 插入元素时,让其有序
# [0,5,6,4]->[0,4,5,6]
for j in range(len(buckets[i]) - 1, 0, -1):
if buckets[i][j] < buckets[i][j - 1]:
buckets[i][j - 1], buckets[i][j] = buckets[i][j], buckets[i][j - 1]
else:
break
sorted_li = []
for buc in buckets:
sorted_li.extend(buc)
return sorted_li
(4)基数排序
原理:
-
桶排序的一种,按照桶排序对数据进行输入输出,但是在元素分桶时,不再对其进行排序;
-
先按个位数进行桶排序,依次输出,在按十位数进行桶排序,依次输出,循环往复;
-
循环次数取决于最大数的位数
代码:
def radix_sort(li):
max_num = max(li) # 获取列表中最大的数值,以确定数值的位数,即循环次数
# 99 --> 2 188 -->3
it = 0 # 先按个位数进行排序
while 10 ** it <= max_num:
buckets = [[] for _ in range(10)]
# 186 第三位是1,应该放在1的桶里,(186//10^2)%10
for val in li:
i = (val // (10 ** it)) % 10
buckets[i].append(val)
li.clear()
for buc in buckets:
li.extend(buc)
it += 1
4 数据结构
***数据结构:***指相互之间存在一种或多种关系的数据元素的几何和该集合中数据元素之间的关系组成;即设计数据以何种方式组织并存储在计算机中。
分类:按照逻辑结构可分为线性结构、树结构、图结构**;
- **线性结构:**数据结构中的元素存在一对一的相互关系
- **树结构:**数据结构中的元素存在一对多的相互关系
- **图结构:**数据结构中的元素存在多对多的相互关系
4.1 线性结构
(1)列表
***数组:***根据地址以及数据类型来查找某一元素;假设数组中存储整型数据,在32位系统
中,一个整型数据占有4
个字节,已知数组a的首地址是100
,则a[2]可以通过 100+4*2
查找;
数值 | a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] |
---|---|---|---|---|---|---|---|
地址 | 100 | 104 | 108 | 112 | 116 | 120 | 124 |
数组长度不可变,是固定的;数组中存储的数据类型不可变,必须是统一的
***列表:***列表中存储的是数据的地址,通过地址指向对应的值
(2)栈
**栈(stack):**是一个数据集合,只能在一端插入或者删除操作的列表。
**特点:**后进先出LIFO(last-in, first-out)
基本操作:
- 进栈(压栈、入栈):push
- 出栈:pop
- 取顶栈:gettop
应用:括号匹配问题
class Stack():
def __init__(self):
self.stack = []
def push(self, elements):
self.stack.append(elements)
def pop(self):
return self.stack.pop()
def gettop(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
def is_empty(self):
return len(self.stack) == 0
"""
使用栈来解决括号匹配问题 ()[]{[()]} 匹配 {(}) 不匹配
思路:
1、遇到左括号,放在栈里,遇到右括号,判断是否匹配,匹配出栈,不匹配返回False
2、如果最后栈为空,则匹配成功,返回True,反之返回False
"""
def brace_match(s):
stack = Stack()
match = {')': '(', '}': '{', ']': '['}
for ch in s:
if ch in ['(', '{', '[']:
stack.push(ch)
else:
if stack.is_empty():
return False
elif stack.gettop() == match[ch]:
return True
else:
return False
if stack.is_empty():
return True
else:
return False
print(brace_match('([{}]){({[]})}'))
print(brace_match('([)]'))
运行结果:
True
False
(3)队列
**队列:**是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除。
特点:
-
进行插入的一端为**队尾(**rear),插入动作为进队或入队
-
进行删除的一端为队头(front),删除动作为出队
-
队列的性质:**先进先出(**First-in, First-out)
队列实现:不能使用列表简单实现,使用环形队列
- 队首指针前进1:
front = (front + 1) % MaxSize
- 队尾指针前进:
rear = (rear + 1) % MaxSize
- 空队列:
rear == front
;满队列:(rear + 1) % MaxSize == front
代码实现:
class Queue():
def __init__(self, size=100):
self.size = size
self.queue = [0 for _ in range(size)]
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
def pop(self):
if not self.is_empty():
self.front = (self.front + 1) % self.size
return self.queue[self.front]
else:
raise IndexError
# 判断队空
def is_empty(self):
return self.rear == self.front
# 判断队满
def is_filled(self):
return (self.rear + 1) % self.size == self.front
q = Queue(5)
for i in range(4):
q.push(i)
print(q.is_filled())
代码运行:
True
(4)使用栈和队列进行迷宫搜索
栈:深度优先,(回溯法)
队列:广度优先
创建一个空队列,将起点1放入队列,然后1只有一条路可走,因此1出列2进列,到3入列后由于有两条路可走,3出列4、5入列;随后先走4的方向4出列6入列,再5出列7入列,此时6、7在队列中,6又有了两个方向,此时6出列,8、9入列,此时队列中为7\8\9,以此规律依次类推,直到找到出口。
队列中存的不再是路径,而是现在考虑的路,分岔的中端。因此输出路径会比较麻烦。
maze = [
[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, 1, 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, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 1, 0, 1, 1, 0, 1],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
dirs = [
lambda x, y: (x + 1, y),
lambda x, y: (x - 1, y),
lambda x, y: (x, y - 1),
lambda x, y: (x, y + 1)
]
'''
使用栈,深度优先,回溯法,不保证路径一定是最短的
思路:
从上一个节点开始,任意找下一个能走的点,当找不到能走的点时,退回上一个点寻找是否有其他方向的点。使用栈存储当前路径。后进 先出,方便回退到上一个点'''
def maze_path(x1, y1, x2, y2):
stack = []
stack.append((x1, y1))
while len(stack) > 0:
curNode = stack[-1] # 当前的节点
if curNode[0] == x2 and curNode[1] == y2:
# 走到终点
for p in stack:
print(p)
return True
# 遍历(x,y)四个方向
for dir in dirs:
nextNode = dir(curNode[0], curNode[1])
if maze[nextNode[0]][nextNode[1]] == 0:
stack.append(nextNode)
maze[nextNode[0]][nextNode[1]] = 2 # 表示已经走过
break
else:
maze[nextNode[0]][nextNode[1]] = 2
stack.pop()
else:
print('没有路')
return False
"""
使用队列,保证是最短路径
思路:从一个节点开始,寻找所有接下来能继续走的点,继续不断寻找,直到找到出口。使用队列存储当前正在考虑的节点。
"""
def print_r(path):
curNode = path[-1]
real_path = []
while curNode[2] != -1:
real_path.append(curNode[0:2])
curNode = path[curNode[2]]
real_path.append(curNode[0:2])
real_path.reverse()
for node in real_path:
print(node)
def maze_path_queue(x1, y1, x2, y2):
queue = deque()
queue.append((x1, y1, -1))
path = []
while len(queue) > 0:
curNode = queue.popleft() # 当前的节点,队首出出队;注意,不要使用pop():pop()是队尾出,不符合队列的规则
path.append(curNode)
print(queue, '----->>>>', path)
if curNode[0] == x2 and curNode[1] == y2:
# 走到终点
print_r(path)
return True
# 遍历(x,y)四个方向
for dir in dirs:
nextNode = dir(curNode[0], curNode[1])
if maze[nextNode[0]][nextNode[1]] == 0:
queue.append((nextNode[0], nextNode[1], len(path) - 1)) # 后续节点进队,记录哪一个节点带她来的
maze[nextNode[0]][nextNode[1]] = 2 # 标记已经走过
print(queue, '----->>>>', path)
print('第一次'.center(40, '*'))
else:
print('没有路')
return False
maze_path(1, 1, 8, 8)
maze_path_queue(1, 1, 8, 8)
代码输出:
(1, 1)
(2, 1)
(3, 1)
(4, 1)
(5, 1)
(5, 2)
(5, 3)
(6, 3)
(6, 4)
(6, 5)
(7, 5)
(8, 5)
(8, 6)
(8, 7)
(8, 8)
(5) 链表
链表:由一系列节点组成的元素几何。每个节点包含两部分,数据域item和指向下一个节点的指针next。通过节点之间的相互连接最终串联成一个链表。
class Node():
def __init__(self, item):
self.item = item
self.next = None
a = Node(1)
b = Node(4)
c = Node(2)
a.next = b
b.next = c
print(a.next.item) # 结果为4
1)创建链表的方法:头插法、尾插法
-
头插法(列表倒序):
newNode.next=head
head=newNode
def create_linklist_head(li): head = Node(li[0]) for element in li[1:]: node = Node(element) node.next = head head = node return head
-
尾插法(列表正序):
tail.next=newNode
tail=newNode
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
2)链表的插入
3)链表的删除
4)双链表
双链表的插入:
双链表的删除:
(6)哈希表
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做**散列函数,存放记录的数组叫做散列表**
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数
哈希冲突
即不同key值产生相同的地址, H ( k e y 1 ) = H ( k e y 2 ) H(key~1~)=H(key~2~) H(key 1 )=H(key 2 ),比如我们上面说的存储3,6,9,p取3时,3 % 3 == 6 % 3 == 9 % 3,此时3,6,9都发生了hash冲突
链地址法
产生hash冲突后在存储数据后面加一个指针,指向后面冲突的数据,用链地址法则是下面这样:

4.2 树
(1)概念
树:是一种数据结构,它是由n(n>=1)
个有限结点组成一个具有层次关系的集合。
- **无序树:**树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;
- **有序树:**树中任意节点的子结点之间有顺序关系,这种树为有序树;
- **二叉树:**每个节点最多含有两个子树的树称为二叉树;
- **满二叉树:**叶节点除外的所有节点均含有两个子树的树被称为满二叉树;
- **完全二叉树:**有 2 k − 1 2^k-1 2k−1个节点的满二叉树称为完全二叉树;
- **哈夫曼树(最优二叉树):**带权路径最短的二叉树称为哈夫曼树或最优二叉树。
根节点:每个结点有零个或多个子结点;没有父结点的结点称为根结点;
**树的深度:**树中结点的最大层次;
**树的度:**一棵树中,最大的结点的度称为树的度;
**孩子节点/父节点:**一个结点含有的子树的根结点称为该结点的子结点;若一个结点含有子结点,则这个结点称为其子结点的父结点;
子树:
" alt=“满二叉树” style=“zoom: 80%;” />
(2)二叉树
二叉树的链式存储:将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来链接。
节点的定义:
class BiTreeNode:
def __init__(self, data):
self.data = data
self.lchild = None
self.rchild = None
(3)遍历表达法
遍历表达法有4种方法:先序遍历、中序遍历、后序遍历、层次遍历
例如右图:
其先序遍历(又称先根遍历)(根-左-右)
# 前序遍历
def pre_order(root):
if root:
print(root.data, end=',') # 先打印自己
pre_order(root.lchild) # 在遍历左子树
pre_order(root.rchild) # 最后遍历右子树
'''结果:''' E,A,C,B,D,G,F
其中序遍历(又称中根遍历)(左-根-右)(仅二叉树有中序遍历)
# 中序遍历
def in_order(root):
if root:
in_order(root.lchild) # 先遍历左子树
print(root.data, end=',') # 在打印自己
in_order(root.rchild) # 最后遍历右子树
'''结果:'''A,B,C,D,E,G,F
其后序遍历(又称后根遍历)(左-右-根)
# 后序遍历
def aft_order(root):
if root:
aft_order(root.lchild)
aft_order(root.rchild)
print(root.data, end=',')
'''结果:'''B,D,C,A,F,G,E
其层次遍历(同广度优先搜索)
def level_order(root):
from collections import deque
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)
'''结果:'''E,A,G,C,F,B,D
(4)二叉搜索树
**二叉搜索树:**一颗二叉树且满足性质:设 x x x是二叉树的一个节点。如果 y y y 是 是 是 x x x 左 子 树 的 一 个 节 点 , 那 么 左子树的一个节点,那么 左子树的一个节点,那么 y . k e y < = x . k e y y.key <=x.key y.key<=x.key ; 如 果 ;如果 ;如果 y y y 是 是 是 x x x 右 子 树 的 一 个 节 点 , 那 么 右子树的一个节点,那么 右子树的一个节点,那么 y . k e y > = x . k e y y.key >=x.key y.key>=x.key$;
二叉树搜索树的操作:插入、删除、查询
插入:
class BiTreeNode(object):
def __init__(self, data):
self.data = data
self.lchild = None # 左孩子
self.rchild = None # 右孩子
self.parent = None
class BST:
def __init__(self, li):
self.root = None
if li:
for val in li:
self.insert_no_rec(val)
# 使用递归方法进行查询时
# self.root = self.insert(self.root, 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
···
删除:
- 如果要删除的节点是叶子结点:直接删除;
- 如果要删除的节点只有一个子节点:将此节点的父节点与子节点链接,然后直接删除该节点;
- 如果要删除的节点有两个节点:将其右子树的的最小节点(该节点最多只有一个右孩子)删除,并替换当前节点。
class BST:
···
# 查询节点的值
def query_no_rec(self, val):
if self.root:
p = self.root
while True:
if p:
if val < p.data:
p = p.lchild
elif val > p.data:
p = p.rchild
else:
return p
else:
raise ValueError ('没有该节点,请重新输入!')
# 删除操作 -- 叶子结点
def _remove_node_1(self, node):
if not node.parent:
self.root = None
elif node == node.parent.lchild:
# node是它父亲的左孩子
node.parent.lchild = None
else:
node.parent.rchild = None
# 删除操作 -- 只有一个左节点
def _remove_node_21(self, 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):
if not node.parent:
self.root = node.rchild
node.rchild.parent = None
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.rchild and not node.lchild:
self._remove_node_1(node)
elif not node.rchild:
self._remove_node_21(node)
elif not node.lchild:
self._remove_node_22(node)
else:
# 两个孩子都有
min_node = node.rchild
while min_node.lchild:
min_node = min_node.lchild
node.data = min_node.data
if min_node.rchild:
self._remove_node_22(min_node)
else:
self._remove_node_1(min_node)
二叉搜索树的平均时间复杂度为 O ( l o g n ) O(logn) O(logn),最坏情况下,二叉树非常倾斜,和链表类似
(5)AVL树概念
AVL树是一颗自平衡的二叉搜索树,具有以下性质:
-
根的左右子树的高度之差的绝对值不能超过1
-
根的左右子树都是平衡二叉树
AVL树操作:插入
-
插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作来进行修正
-
插入一个节点后,只有==从插入节点到根节点的路径上的节点的平衡可能被改变==。我们需要找到第一个破坏了平衡条件的节点,称之为k。k的两颗子树的高度差2。
-
不平衡的出现可能有四种情况:
-
不平衡是由于对
k
的右孩子的右子树插入节点造成的:使用左旋进行平衡 ;
不平衡是由于对
k
的右孩子的右子树插入节点造成的:使用左旋进行平衡 ;
def rotate_left(self, p, c):
s2 = c.lchild
p.rchild = s2
if s2:
s2.parent = p
c.lchild = p
p.parent = c# upgrade your balance factor p.balance_factor = 0 c.balance_factor = 0 return c
-
不平衡是由于对
K
的左孩子的左子树插入节点造成的:使用右旋进行平衡;# 不平衡是由于对`K`的左孩子的左子树插入节点造成的:使用右旋进行平衡; def rotate_right(self, p, c): s2 = c.rchild p.lchild = s2 if s2: s2.parent = p c.rchild = p p.parent = c # upgrade you balance factor c.balance_factor = 0 p.balance_factor = 0 return c
-
不平衡是由于
k
的右孩子的左子树插入节点造成的:先右旋==>左旋进行平衡# 不平衡是由于`k`的右孩子的左子树插入节点造成的:先**右旋**==>**左旋**进行平衡 def rotate_right_left(self, p, c): g = c.lchild s3 = g.rchild s2 = g.lchild c.lchild = s3 if s3: s3.parent = c g.rchild = c c.parent = g p.rchild = s2 if s2: s2.parent = p g.lchild = p p.parent = g # upgrade the balance factor if g.balance_factor > 0: c.balance_factor = 0 p.balance_factor = -1 elif g.balance_factor < 0: c.balance_factor = 1 p.balance_factor = 0 else: # s1,s2,s3,s4都为None,实际插入的是G c.balance_factor = 0 p.balance_factor = 0 return g
-
不平衡是由于
k
的左孩子的右子树插入节点造成的:先左旋==>右旋进行平衡。
def rotate_left_right(self, p, c): g = c.rchild s3 = g.rchild s2 = g.lchild c.rchild = s2 if s2: s2.parent = c g.lchild = c c.parent = g p.lchild = s3 if s3: s3.parent = p g.rchild = p p.parent = g # upgrade the balance factor if g.balance_factor > 0: p.balance_factor = 0 c.balance_factor = -1 elif g.balance_factor < 0: p.balance_factor = 1 c.balance_factor = 0 else: # s1,s2,s3,s4都为None,实际插入的是G c.balance_factor = 0 p.balance_factor = 0 return g
-