数据结构和算法
第一部分,查找和排序
查找:顺序查找和二分查找
排序:
牛逼三人组(快速排序,堆排序,归并排序)
lowB三人组(冒泡,插入,选择)
其他排序(希尔,计数,基数,桶)
顺序查找和二分查找
# 顺序查找和二分查找
def linear_search(li,var):
for i in li:
if var==i:
return True
else:
return False
def binary_search(li,var):
left = 0
right = len(li)-1
mid = (left+right)//2
while left < right:
if var > li[mid]:
left = mid+1
mid = (left+right)//2
if var < li[mid]:
right = mid-1
mid = (left+right)//2
if var == li[mid]:
return True
else:
return False
a=[1,2,3,4,5,6,7,8,9]
linear_search(a,10)
False
low B排序三人组:冒泡排序,插入排序,选择排序
# low B排序三人组:冒泡排序,插入排序,选择排序
#冒泡排序: 依次前后对比,前大于后,则换,一轮后最后一个位置为最大值,然后开始下一轮,n-1轮后全部有序
def bubble_sort(li):
for i in range(len(li)-1,-1,-1):
for j in range(i):
if li[j] > li[j+1]:
li[j],li[j+1] = li[j+1],li[j]
print(li)
# 选择排序:类似冒泡排序,首先选择列表的第一位的数据,依次和后面的数据尽心对比,如果大,就交换,一轮结束,第一位的为最小
def select_sort(li):
for i in range(len(li)):
for j in range(i,len(li)):
if li[i] > li[j]:
li[i],li[j] = li[j],li[i]
print(li)
# 插入排序:类似与扑克牌,取一张,排一张
def insert_sort(li):
for i in range(1,len(li)):
tem = li[i]
j=i-1
while j >=0 and li[j] > tem:
li[j+1] =li[j]
j-=1
li[j+1] = tem
print(li)
b=[2,1,3,7,33,6,7,8,5]
insert_sort(b)
[1, 2, 3, 5, 6, 7, 7, 8, 33]
牛B三人组:快速排序,堆排序,归并排序
1、快速排序原理:
- 取任意一个元素使得元素归位,也就是,左边所有数比它小,右边所有数比其大,
- 对归位后元素位置的左边和右边依次进行归位,直到全部元素都归位。
2、堆排序:
堆是一种特殊的二叉树:
大根堆:任意父节点大于子节点
小根堆:任意父节点小于子节点
堆的向下调整:假设某个节点z的左右子树都满足大根堆,但是该节点不满足堆,将该节点和左右子节点中较大的替换,实现堆的有序,然后检查调整后的子节点的结构是否满足堆,不满足的话,依次向下进行调整,直到整个堆有序。
- 将列表建成堆,(堆是一种完全二叉树)首先对堆从下往上依次进行向下调整,使得整个堆有序
- 堆顶元素为最大值,将其存储,得到有序组第一个值
- 将堆最后一个位置的元素放置堆顶,经过向下调整,再次得到有序堆,在把堆顶元素存储,得到有序组的第二个值,依次进行,直到堆为空。
3、归并排序原理:还是递归实现
- 假如一个序列,左边一部分有序,右边一部分有序,可以通过归并将其整体有序
- 对于一个完整的列表,最小到单个元素是有序的,就可以通过归并和递归的思想将其整体有序 -->
```python
# 快速排序实现:元素归位+递归
# 首先某一个元素归位
def partition(li,left,right): #归位
tmp = li[left]
while left < right:
while left < right and li[right] >= tmp:
right -=1
li[left] = li[right]
while left < right and li[left] <= tmp:
left +=1
li[right] = li[left]
li[left] = tmp
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)
return li
b=[2,1,3,4,1,1,4,5,6,3]
quick_sort(b,0,len(b)-1)
[1, 1, 1, 2, 3, 3, 4, 4, 5, 6]
# 堆排序,原理二叉树+向下调整+循环
# 建堆,
# 取最上面元素,将最下面元素换到堆顶
# 向下调整成完全二叉树
# 重复直到树变成空。
# 先建立向下调整函数
def sift(data,low,high):
# 假设只有堆顶元素无序
i = low
j = 2*i+1
tmp = data[low]
while j <= high:
if j+1 <=high and data[j+1]>data[j]:
j = j+1
if data[i] < data[j]:
data[i],data[j] = data[j],data[i]
i = j
j = 2*i+1
else:
break
# if data[j+1]:
# if data[j] > data[j+1]: #左孩子大于右孩子
# if data[i] < data[j]:
# data[i],data[j] = data[j],data[i]
# i=j
# j=2*i+1
# else: #右孩子大于左孩子
# if data[i] < data[j+1]:
# data[i],data[j+1] = data[j+1],data[i]
# i = j+1
# j = 2*i+1
# else:
# if data[i] < data[j]:
# data[i],data[j] = data[j],data[i]
return data
# 排序函数
def dui_sort(data,low,high):
for i in range(high//2,-1,-1):
sift(data,i,high)
print(data)
while high > 0:
data[high],data[low] = data[low],data[high]
high -= 1
sift(data,low,high)
return data
def head_sort(data):
n=len(data)
for i in range((n-2)//2,-1,-1):
sift(data,i,n-1)
for i in range(n-1,-1,-1):
data[0],data[i]=data[i],data[0]
sift(data,0,i-1)
return data
b=[2,9,8,7,6,5,4,3]
dui_sort(b,0,len(b)-1)
[9, 7, 8, 3, 6, 5, 4, 2]
[2, 3, 4, 5, 6, 7, 8, 9]
#归并排序
# 归并函数
def merge(data,left,mid,right):
#假设left-mid,mid-right各自有序,将其合并有序
tmp=[]
i = left
j = mid+1
while i <= mid and j <= right:
if data[i] > data[j]:
tmp.append(data[j])
j += 1
else:
tmp.append(data[i])
i += 1
if i > mid: # 左边先结束
for var in data[j:right+1]:
tmp.append(var)
if j > right: # 右边先结束
for var in data[i:mid+1]:
tmp.append(var)
data[left:right+1] = tmp
return data
#归并排序
def merge_sort(data,left,right):
if left < right: #至少两个元素
mid = (left + right) // 2
merge_sort(data,left,mid)
merge_sort(data,mid+1,right)
merge(data,left,mid,right)
return data
b = [3,2,1,4,5,2,6,6,3,8]
merge_sort(b,0,len(b)-1)
[1, 2, 2, 3, 3, 4, 5, 6, 6, 8]
其他排序方法:希尔排序,计数排序,桶排序,基数排序
1、希尔排序原理:插入排序变形,分组插入排序
- 首先取整数d1=n/2,将元素分为d1个组,每组相邻元素间距为d1,各组内进行插入排序
- 取第二个整数d2=d1/2,重复进行插入排序
- 每趟并不会使得某些元素有序,而是使得整体数据越来越有序,最后一趟使得所有数据语序
2、 计数排序原理:
- 数数据中的每个数有几个,遍历统计,最后按照统计的数排好序
- 快,时间复杂度为O(n),但是有很大局限,
- 需要知道最大数,还得都是整数,还得额外开一个同样大小的空间
3、桶排序原理:计数排序的升级
- 先将元素分在不同的桶中,对桶中的元素排序,对元素范围大的适用
- 效率取决于数的分布,均匀效果会好,平均效率是O(n+k),最差O(n^2*k)
4、基数排序原理:
- 对于多关键字排序:比如要求员工表按照薪资排序,薪资相同的按照年龄排序,
- 就可以先按照年龄排序,再按照薪资排序,最后的顺序就满足要求,这个是基于排序是稳定排序
- 多位整数的排序也可以看成过关键字排序:位数多的大,相同位数的最高位大的大。
- 所谓稳定排序,是指同样的数字,之前在前面的排完序还是在前面。
# 希尔排序:比堆排序稍慢
def insert_sort_gap(li,gap):
for i in range(gap,len(li)):
tmp = li[i]
j = i - gap
while j >= 0 and li[j] > tmp:
li[j+gap] = li[j]
j -= gap
li[j+gap] = tmp
def shell_sort(li):
d = len(li)//2
while d >= 1:
insert_sort_gap(li,d)
d//=2
li = [3,2,1,6,4,2,6]
shell_sort(li)
print(li)
[1, 2, 2, 3, 4, 6, 6]
# 计数排序
def count_sort(li,max_count):
count = [0 for _ in range(max_count+1)]
for var in li:
count[var] += 1
li.clear()
for ind,var in enumerate(count):
for i in range(var):
li.append(ind)
li = [3,1,2,4,1,5,6,3,6,4]
count_sort(li,6)
print(li)
[1, 1, 2, 3, 3, 4, 4, 5, 6, 6]
# 桶排序:
def bucket_sort(li,n=100,max_num=10000):
# n表示桶数
# max_num表示最大值
bucket = [[] for _ in range(n)]
for var in li:
i = min(var // (max_num // n), n-1)
bucket[i].append(var)
for j in range(len(bucket[i])-1, 0 ,-1):
if bucket[i][j] < bucket[i][j-1]:
bucket[i][j],bucket[i][j-1] = bucket[i][j-1],bucket[i][j]
else:
break
sorted_li = []
for buc in bucket:
sorted_li.extend(buc)
return sorted_li
import random
li = [random.randint(0,10000) for i in range(100)]
li=bucket_sort(li)
print(li)
[39, 98, 156, 164, 537, 662, 718, 1082, 1115, 1248, 1279, 1326, 1345, 1372, 1516, 1545, 1578, 1749, 1945, 2060, 2160, 2232, 2329, 2388, 2620, 2661, 2900, 3096, 3104, 3163, 3382, 3403, 3429, 3491, 3828, 3884, 3908, 3947, 3962, 4054, 4343, 4506, 4646, 4694, 4843, 4869, 4913, 5031, 5108, 5263, 5387, 5649, 5799, 5811, 5894, 6018, 6171, 6230, 6450, 6474, 6475, 6609, 6658, 6692, 6700, 6711, 6787, 6879, 6947, 7062, 7094, 7172, 7222, 7385, 7403, 7493, 7525, 7570, 7571, 7592, 7831, 8023, 8095, 8187, 8212, 8430, 8436, 8643, 8683, 8814, 8870, 8897, 9024, 9197, 9265, 9274, 9324, 9330, 9779, 9782]
# 基数排序
# 对整数排序,按照个位,十位等的大小进行排序
def redix_sort(li):
max_num=max(li)
ti=0
while 10**ti<=max_num:
buckets=[[] for _ in range(10)]
for var in li:
digit=(var//10**ti)%10
buckets[digit].append(var)
li.clear()
for buc in buckets:
li.extend(buc)
ti+=1
return li
a=[21,32,14,53,63,17,77,898,456,363]
print(a)
[21, 32, 14, 53, 63, 17, 77, 898, 456, 363]
redix_sort(a)
[14, 17, 21, 32, 53, 63, 77, 363, 456, 898]
def twoSum( nums, target) :
for i in range(0,len(nums)-2):
for j in range(i+1,len(nums)-1):
if nums[i]+nums[j]==target:
return [i,j]
a=list(range(100))
random.shuffle(a) #打乱a
print(a)
[94, 85, 64, 40, 15, 8, 75, 89, 41, 73, 30, 34, 57, 63, 86, 48, 47, 60, 3, 62, 95, 92, 77, 46, 79, 74, 70, 10, 51, 23, 84, 66, 56, 32, 42, 43, 22, 83, 25, 24, 58, 61, 7, 33, 9, 54, 27, 65, 11, 36, 90, 6, 99, 69, 98, 4, 93, 59, 2, 5, 38, 87, 72, 45, 16, 96, 39, 37, 44, 53, 26, 14, 17, 67, 52, 50, 97, 29, 13, 19, 68, 49, 78, 55, 28, 18, 81, 80, 1, 88, 21, 31, 82, 20, 71, 76, 35, 0, 12, 91]
a=np.random.randint(100,size=20)
print(a)
[68 41 10 56 14 78 9 79 31 49 82 83 39 84 76 25 60 63 9 84]
数据结构和算法:
逻辑结构和物理结构
逻辑结构分为:线性结构,树结构,图结构
列表,栈,队列,链表,哈希表,树,二叉树,AVL树
贪心算法,动态规划
# 32位机器上面,一个整数占了4个字节,一个整数占32位也就是四个字节,一个地址占四个字节。
# c++的数组:
# 元素是连续存储在内存中,取数直接从内存中取,
# 数组中的元素种类必须相同,长度必须固定,因为要在开数组的同时确定元素位置和大小
# python中的列表:
# 元素随机存储在内存中,列表中保存元素地址,再通过地址寻找元素,这也使得列表中元素种类可以不同,长度也可以变化。
栈:先进后出
# 编写栈:
#栈性质是先进后出
class Stack():
def __init__(self):
self.stack=[]
#入栈
def push(self,element):
self.stack.append(element)
#出栈
def pop(self):
return self.stack.pop()
#获取栈顶元素:
def get_pop(self):
if len(self.stack)>0:
return self.stack[-1]
else:
return None
#判断栈是否为空
def is_empty(self):
return len(self.stack)==0
#栈的应用:括号匹配问题
#给定一个字符串,其中包括大中小括号,求字符串中的括号是否匹配
#{}[()] 匹配
# ({)}不匹配
def brace_match(s):
match={']':'[',')':'(','}':'{'}
stack=Stack()
for ch in s:
if ch in {'(','{','['}: #左括号进栈
stack.push(ch)
elif match[ch]==stack.get_pop():
stack.pop()
else:
return False
if stack.is_empty():
return True
else:
return False
s='[](){({)}}'
brace_match(s)
False
队列:先进先出
#队列:先进先出,为了保证空间可以重复利用,将队列的队首和队尾链接在一起成环,这样就可以节约空间
# 为了节约空间可以将队列设计成环形队列,为了区分队空和队满,front位置不放元素,当front和rear相等,则队空,rear=front-1时认为队满
class Queue():
def __init__(self,size):
self.queue=[0 for _ in range(size)]
self.size=size
self.front=0 #对首
self.rear=0 #队尾
def push(self,element):
if not self.is_fill():
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_fill(self):
return (self.rear+1)%self.size==self.front
# 队列有包
from collections import deque
m=Queue(5)
m.push(1)
m.push(2)
m.push(3)
m.pop()
1
#(x1,y1)=(1,1),(x2,y2)=(15,15)
#栈走迷宫,深度优先,
def maze_path(maze,x1,y1,x2,y2):
#[x1,y1]表示迷宫入口,[x2,y2表示迷宫出口],迷宫中0表示路,1表示墙
#先将栈建好,将迷宫入口压栈,栈内的值表示走迷宫的路径
stack=[]
stack.append((x1,y1))
# 建立取根据当前节点取下一个节点的遍历列表
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)]
#当栈的长度大于0,可以循环
while len(stack)>0:
# 取得当前节点
curnode=stack[-1]
#拿到当前节点,首先判断节点是否为终点,若是,打印路径返回True
if curnode[0]==x2 and curnode[1]==y2:
for p in stack:
print(p)
return True
#根据当前节点取下一节点
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 #2表示已经走过
break
#若不能走:则标志迷宫中的节点并且出栈
else:
maze[nextnode[0]][nextnode[1]]=2
stack.pop()
#当栈的长度等于零,表明没有路可以走出去
else:
return False
# maze=[
# [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
# [1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1],
# [1,0,1,0,1,1,1,1,1,1,1,0,1,0,1,0,1],
# [1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1],
# [1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1],
# [1,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,1],
# [1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1],
# [1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1],
# [1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1],
# [1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1],
# [1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,1,1],
# [1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1],
# [1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,0,1],
# [1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1],
# [1,1,1,0,1,0,1,1,1,0,1,1,1,1,1,0,1],
# [1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1],
# [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
# ]
maze=[
[1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,1,1],
[1,0,1,1,0,0,0,1],
[1,0,1,1,0,1,0,1],
[1,0,1,1,1,1,1,1],
[1,0,0,0,0,1,1,1],
[1,1,1,1,0,0,0,1],
[1,1,1,1,1,1,1,1]
]
res=maze_path(maze,1,1,15,15)
# 队列走迷宫:广度优先
from collections import deque
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 print_r(path):
realpath=[]
curnd=path[-1]
while curnd!=path[0]:
realpath.append(curnd)
curnd=path[curnd[2]]
realpath.append(path[0])
realpath.reverse()
return realpath
def maze_path_que(maze,x1,y1,x2,y2):
# 先定义好队列,路径,将迷宫入口压栈
queue=deque()
path=[]
queue.append((x1,y1,-1))
maze[x1][y1]=2
#只要队列有数据就循环
while len(queue) > 0:
#定义当前节点,并记录路径
curnode=queue.popleft()
path.append(curnode)
#设置终止条件,也就是到了迷宫终点就停止循环
if curnode[0]==x2 and curnode[1]==y2:
print(print_r(path))
return True
#根据当前节点计算下一节点,如果下一节点为0,则将下一节点压入队列,
#标志迷宫表示走过该位置
for dir in dirs:
nextnode=dir(curnode[0],curnode[1])
if maze[nextnode[0]][nextnode[1]]==0:
queue.append((*nextnode,len(path)-1))
maze[nextnode[0]][nextnode[1]]=2
print('没有路')
return False
maze_path_que(maze,1,1,6,6)
没有路
False
# 关于while和for后面接else:
# 循环体被break或者return,则else不执行,其他情况继续执行
链表主要功能:插入,查找
# 链表实现哈希表
# 首先封装好链表
class LinkList():
# 先定义节点
class Node():
def __init__(self,item):
self.item = item
self.next = None
# 定义迭代器
class Lkiter():
def __init__(self,node):
self.node = node
def __next__(self):
if self.node:
curnode = self.node
self.node = curnode.next
return curnode.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,item):
# 头插法:
item_node = self.Node(item)
if not self.head:
self.head = item_node
else:
item_node.next = self.head
self.head = item_node
# # 尾插法
# item_node = self.Node(item)
# if not self.head:
# self.head = item_node
# self.tail = item_node
# else:
# self.tail.next = item_node
# self.tail = item_node
def extend(self,li):
for ele in li:
self.append(ele)
def find(self,obj):
for n in self:
if n == obj:
return True
else:
return False
def __iter__(self):
return self.Lkiter(self.head)
def __repr__(self):
return '<<'+','.join(map(str,self))+'>>'
lk = LinkList()
lk.extend([1,2,3])
lk.append(2)
print(lk)
<<2,3,2,1>>
哈希表:插入,查找
#python 的集合和字典都是哈希表
class HashTable:
def __init__(self,size = 101):
self.size = size
self.T = [LinkList() for i in range(self.size)]
def h(self,k):
return k % self.size
def insert(self,k):
i = self.h(k)
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(1)
ht.insert(2)
ht.insert(3)
print(ht.T[1])
<<1>>
二叉树:遍历,插入,查找,删除
# 二叉树的遍历
def pre_order(root): #前序遍历
if 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)
if len(queue)>0:
cur = queue.popleft()
if cur.lchild:
lchild = cur.lchild
if cur.rchild:
rchild = cur.rchild
queue.append(lchild)
queue.append(rchild)
print(cur.data,end = ',')
# 二叉搜索树建立,只要功能:插入,查找,删除
# 是二叉树的一种应用,特征是左子节点<节点<右子节点,通过这个特征可以进行数字的查找和排序
class BiTnode():
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 var in li:
self.insert_no_rec(var)
# 插入功能,两种方法:递归和非递归
def insert(self,node,obj): #递归做法
if not node: #如果节点为空
node = BiTnode(obj)
elif obj < node.data:
node.lchild = self.insert(node.lchild,obj)
node.lchild.parent = node
elif obj > node.data:
node.rchild = self.insert(node.rchild,obj)
node.rchild.parent = node
return node
def insert_no_rec(self,obj): # 非递归做法
p = self.root
if not p: #如果根节点为空
self.root = BiTnode(obj)
return
while True:
if obj < p.data:
if p.lchild: #左子树为非空
p = p.lchild
else: #左子树为空
p.lchild = BiTnode(obj)
p.lchild.parent = p
elif obj > p.data:
if p.rchild:
p = p.rchild
else:
p.rchild = BiTnode(obj)
p.rchild.parent = p
else:
return
#定义遍历:前序遍历,中序遍历,后续遍历
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 query(self,node,obj): #递归做法
if not node:
return None
if obj == node.data:
return node
elif obj < node.data:
return self.query(node.lchild,obj)
else:
return self.query(node.rchild,obj)
def query_no_rec(self,obj): #非递归做法
p = self.root
while p:
if p.data < obj:
p = p.rchild
elif p.data > obj:
p = p.lchild
else:
return p
else:
return None
# 删除操作,先判断删除的是什么位置的节点,并先按照各自的情况编写内部函数,以便直接掉用
def __remove_node1__(self,node):
#情况1,node就是叶节点,
if not node.parent:
self.root = None
if node == node.parent.lchild:
node.parent.lchild = None
else:
node.parent.rchild = None
def __remove_node21__(self,node):
#情况21:删除的node只有一个左孩子
if not node.parent: #根节点
self.root = node.lchild
node.lchild.parent = None
if 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_node22__(self,node):
# 情况22:node只有一个右孩子
if not node.parent:
self.root = node.rchild
node.rchild = None
if 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
#完整删除操作
#分为三种情况:1、删除的是叶节点,就直接删除
# 2、删除的节点只含有一个子节点,那么按照上面的函数将其子节点和其父节点进行链接
# 3、删除的节点包含两个子节点:用右子树的最左侧的节点N(也就是右子树的最小值)进行更换,并将N的子节点和N的父节点进行链接
def delete(self,obj):
p = self.query_no_rec(obj)
if p: #树里面存在节点
if not p.lchild and not p.rchild: #情况1,叶节点
self.__remove_node21__(p)
elif not p.lchild: #情况22:node只有右节点
self.__remove_node22__(p)
elif not p.rchild: #情况21:只有左节点
self.__remove_node21__(p)
else: #情况3:有两个节点
# 先找到右子树的最左侧节点node_l,将其值付给node
node_l = p.rchild
while node_l.lchild:
node_l = node_l.lchild
p.data = node_l.data
#如果node_l有右子节点,那么将其子节点和父节点链接,将node的子节点和P的子节点进行链接
if node_l.rchild:
self.__remove_node22__(node_l)
else:
self.__remove_node1__(node_l)
else:
return 'Error Delete'
tree = BST([1,4,3,2,5,6,7,9,8])
tree.in_order(tree.root)
print('')
tree.delete(4)
tree.in_order(tree.root)
1,2,3,4,5,6,7,8,9,
1,2,3,5,6,7,8,9,
tree.delete(10)
'Error Delete'
tree.delete(111)
'Error Delete'
if None:
print(1)
AVL树是一颗自平衡的二叉树,为了解决二叉树最坏情况下非常倾斜的情况,解决方法就是随机化插入
1、性质包括:
- 根的左右子树的高度差的绝对值不超过1
- 根的左右子树都是平衡二叉树
2、做法就是定义一个balace_factor用来记录每个节点的左右子树高度差
插入一个节点后只有从插入节点到根节点的路径上的节点的平衡被破坏,我们需要找到第一个被破坏平衡的节点,称为K,K的左右子子树高度差为2
3、维持树的平衡就是通过旋转,不平衡的出现右四种情况:
- 1、左旋:由于对右孩子的右子树插入导致
- 2、右旋:对左孩子的左子树插入导致
- 3、先左旋后右旋:左孩子的右子树
- 4、先右旋后左旋:右孩子的左子树
class Avlnode(BiTnode):
def __init__(self,val):
BiTnode.__init__(self,val)
self.bf = 0
class AVLTree():
def __init__(self,li):
self.root = None
for val in li:
self.insert_no_rec(val)
def insert_no_rec(self,val):
#1、插入
p = self.root
if not p:
self.root = Avlnode(val)
return
else:
while True:
if val < p.data:
if p.lchild:
p = p.lchild
else:
p.lchild = Avlnode(val)
p.lchild.parent = p
node = p.lchild
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:
break
# 2、更新bf
while node.parent:
#循环的终止条件是父节点不存在
if node.parent.lchild == node:
# 如果node节点是父节点的左孩子
if node.parent.bf > 0:
# 父节点原来是1,插入后是0
node.parent.bf = 0
break
elif node.parent.bf == 0:
# 父节点原来是0,插入后是-1
node.parent.bf = -1
node = node.parent
continue
else:
# 父节点原来是-1,插入后是-2,需要旋转使其平衡
# g = node.parent.parent
# x = node.parent
if node.bf < 0:
#左孩子的左子树导致不平衡
n = self.rotate_right(node.parent,node)
elif node.bf > 0:
# node.bf>0,左孩子的右子树导致不平衡
n = self.rotate_left_right(node.parent,node)
break
else:
# node节点是父节点的右孩子
if node.parent.bf > 0:
#父节点原来是1,插入后是2,需要旋转
# g = node.parent.parent
# x = node.parent
if node.bf > 0:
#右孩子的右子树导致不平衡
n = self.rotate_right(node.parent,node)
elif node.bf < 0:
# 右孩子的左子树导致不平衡
n = self.rotate_right_left(node.parent,node)
break
elif node.parent.bf == 0:
# 夫节点原来时0,插入后是1,
node.parent.bf = 1
node = node.parent
continue
else:
# 父节点原来是-1,插入后是0
node.parent.bf = 0
break
# n.parent = g
# if g:
# if x == g.lchild:
# g.lchild = n
# else:
# g.rchild = n
# break
# else:
# self.root = n
# break
def rotate_left(self,p,c):
#左旋:右孩子的右子树进行插入导致不平衡
s2 = c.lchild
p.rchild = s2
if s2:
s2.parent = p
c.lchild = p
c.parent = p.parent
# 注意这个不能放在if里面,因为不管p.parent是否存在,c.parent = p.parent
if p.parent:
if p.parent.lchild == p:
p.parent.lchild = c
else:
p.parent.rchild = c
else:
self.root = c
p.parent = c
# 更新bf
#只有插入导致不平衡进行旋转时,bf更新是这个结果,删除导致的不平衡还有其他情况
c.bf = 0
p.bf = 0
return c
def rotate_right(self,p,c):
# 右旋:左孩子的左子树导致不平衡
s2 = c.rchild
p.lchild = s2
if s2:
s2.parent = p
c.rchild = p
c.parent = p.parent
if p.parent:
if p == p.parent.lchild:
p.parent.lchild = c
else:
p.parent.rchild = c
else:
self.root = c
p.parent = c
#更新bf
c.bf = 0
p.bf = 0
return c
def rotate_left_right(self,p,c):
# 左旋后右旋:左孩子的右子树插入导致不平衡
g = c.rchild
s2 = g.lchild
c.rchild = s2
if s2:
s2.parent = c
c.parent = g
g.lchild = c
s3 = g.rchild
p.lchild = s3
if s3:
s3.parent = p
g.rchild = p
g.parent = p.parent
if p.parent:
if p == p.parent.lchild:
p.parent.lchild = g
else:
p.parent.rchild = g
else:
self.root = g
p.parent = g
#更新bf
if g.bf > 0:
c.bf = -1
p.bf = 0
elif g.bf < 0:
c.bf = 0
p.bf = 1
else:
c.bf = 0
p.bf = 0
g.bf = 0
return g
def rotate_right_left(self,p,c):
# 右旋后左旋:右孩子的左子树插入导致不平衡
g = c.lchild
s2 = g.lchild
p.rchild = s2
if s2:
s2.parent = p
g.lchild = p
g.parent = p.parent
if p.parent:
if p == p.parent.lchild:
p.parent.lchild = g
else:
p.parent.rchild = g
else:
self.root = g
p.parent = g
s3 = g.rchild
c.lchild = s3
if s3:
s3.parent = c
c.parent = g
g.rchild = c
#更新bf
if g.bf > 0:
c.bf = 0
p.bf = -1
elif g.bf < 0:
p.bf = 0
c.bf = 1
else:
# 插入的是g
c.bf = 0
p.bf = 0
g.bf = 0
return g
avl = AVLTree([9,8,7,6,5,4,3,2,1])
in_order(avl.root)
1,2,3,4,5,6,7,8,9,
pre_order(avl.root)
6,4,2,1,3,5,8,7,9,