[编程 | Phthon | 02] Python3常用算法整理
本文内容和图片基本是从网上搜索整理而来,部分代码是从网上搜索、整理并测试的,部分代码是本人实现的,仅供大家学习参考。
1 排序算法
1.1 冒泡排序(Bubble Sort)
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。
如下图所示:
每轮扫描时,依次比较相邻的两个元素,然后将大的交换到后面去,这样一轮下来,大的元素就像泡沫一样上浮到了最上面。
排除掉后面已经排好序的元素,开始下一轮扫描。
代码实现:
def bubbleSort(self,arr):
"""
冒泡算法,平均时间复杂度最好为O(n),最坏为O(n^2)。\n
arr为同类型的字符串列表、数字列表、字符列表等。\n
"""
if not isinstance(arr,list):
raise TypeError('arr must be a list')
for i in range(1,len(arr)):
for j in range(0,len(arr)-i):
if arr[j] > arr[j+1]:
arr[j],arr[j+1] = arr[j+1],arr[j]
return arr
从动图中可以看到,每轮经过两两比较后,就将最大的排到了末尾。
下面是测试输出:
排序前的列表:['a', 's', 'f', 's', '2', '3', 's', 'k', 'f', 'd', 'j', 'l']
排序后的列表:['2', '3', 'a', 'd', 'f', 'f', 'j', 'k', 'l', 's', 's', 's']
排序前的列表:[5, 60, 3, 2, 1, 65, 2, 0, 8, 8065]
排序后的列表:[0, 1, 2, 2, 3, 5, 8, 60, 65, 8065]
排序前的列表:['ab', 'test', 'tee', 'zoo', 'data']
排序后的列表:['ab', 'data', 'tee', 'test', 'zoo']
后续都用同样的字符串测试。
1.2 选择排序(Selection Sort)
选择排序是一种简单直观的排序算法,适用于较小的数据规模,不占用额外内存。和冒泡算法不同,比较后不用两量交换位置,因此效率高于冒泡排序。
如下图所示:
排序时,将序列分成已排序和未排序的两组列表(其实都是这样分的)。
每轮扫描依次对比,挑选出最小元素,一轮比较结束后将最小元素存放到已排序列表的末尾。
针对未排序列表,开始下一轮最小元素挑选。
代码实现:
def selectionSort(self,arr):
'''
选择排序算法,时间复杂度为O(n^2)。\n
arr为同类型的字符串列表、数字列表、字符列表等。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
for i in range(len(arr) - 1):
minIndex = i
for j in range(i + 1, len(arr)):
if arr[j] < arr[minIndex]:
minIndex = j
if i != minIndex:
arr[i], arr[minIndex] = arr[minIndex], arr[i]
return arr
按照1.1中的测试字符串检测,输出正常。
1.3 插入排序(Insertion Sort)
插入排序类似于玩扑克牌一样,也是一种简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
如下图所示:
从蓝色未排序序列里挑选待排序的元素;
针对已排序元素,从后向前扫描,依次两两比较,如比前面的小,则将已排序元素往后挪,如比前面的大,则插入。
开始下一轮扫描和插入。
代码实现:
def insertionSort(self,arr):
'''
插入排序,平均时间复杂度最好为O(n),最坏为O(n^2)
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
for i in range(1,len(arr)):
preIndex = i-1
current = arr[i]
while preIndex >= 0 and arr[preIndex] > current:
arr[preIndex+1] = arr[preIndex]
preIndex-=1
arr[preIndex+1] = current
return arr
按照1.1中的测试字符串检测,输出正常。
1.4 希尔排序(Shell Sort)
希尔排序也称递减增量排序算法,是对插入排序的一种改进版本。希尔排序先将整个待排序的序列分割为若干子序列分别进行插入排序,子序列中的元素由相隔若干增量的元素组成,最后再对整个序列执行插入排序。
如下图所示:
确定增量因子为2,增量因子不同,耗时不同,并且不稳定。
将10个元素分成10/2=5组,每组里的元素对比,按从小到大交换位置。
再将10个元素分成5/2=2组,每组里的元素对比,按从小到大交换位置。
直到最后分成一组,对比后结束排序。
代码实现:
def shellSort(self,arr):
'''
希尔排序,插入排序的改进版本,时间复杂度为O(n(logn)^2)。\n
k为每个子序列的元素个数,希尔排序是不稳定的。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
k = 3 # k不同,耗时不稳定
n = len(arr)
gap = n//k
while gap > 0:
for i in range(gap, n):
while i >= gap and arr[i] < arr[i - gap]:
arr[i], arr[i - gap] = arr[i - gap], arr[i]
i -= gap
gap //= k
return arr
按照1.1中的测试字符串检测,输出正常。
1.5 归并排序(Merge Sort)
归并排序是将多个有序表合并成一个新的有序表,即把待排序序列递归分为若干个子序列,每个子序列排序后再递归合并为有序序列。
如下图所示:
将序列一拆二、二拆四地分成多个子序列。
每两个子序列排序,然后合并成一个大序列后再排序再递归合并。
代码实现:
def mergeSort(self,arr):
'''
归并排序,将列表arr递归拆分,排序后依次合并,分而治之。\n
时间复杂度为O(nlogn),归并排序是稳定的。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
if(len(arr)<2):
return arr
middle = len(arr)//2
left, right = arr[0:middle], arr[middle:]
return self.__merge(self.mergeSort(left), self.mergeSort(right))
def __merge(self,left,right):
result = []
while left and right:
if left[0] <= right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
while left:
result.append(left.pop(0))
while right:
result.append(right.pop(0))
return result
按照1.1中的测试字符串检测,输出正常。
1.6 快速排序(Quick Sort)
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
如下图所示:
挑选一个基准值,可以是中间项。
根据基准项,将列表分割成3个列表,左边的列表元素都比基准小,右边的都比基准大,中间的和基准项相等。
递归对左右列表做同样的处理,直到列表元素只有1个。
代码实现:
def quickSort(self,arr):
'''
快速排序,以列表中间项为基准分割列表,递归处理。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
if len(arr) < 2:return arr
pivot = arr[ len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return self.quickSort(left) + middle + self.quickSort(right)
1.7 堆排序(Heap Sort)
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。
二叉树概念
二叉树:是有n个节点的有限集合,对于非空二叉树,每个节点最多只有左右两个子节点。
满二叉树:每层的节点数都达到最大值。譬如对k层的二叉树来说,第h层的子节点都是
完全二叉树:编号为1~n的每个节点都与同深度的满二叉树一一对应,这棵二叉树称为完全二叉树。
完全二叉树其实就是同深度的满二叉树去掉了最底层末尾几个节点而已,有些地方满二叉树仅要求每个节点的子节点为0个或2个,这里的满二叉树要求不一样。
堆
堆是按顺序存储的完全二叉树。
大根堆:每个子节点都比父节点小。
小根堆:每个子节点都比父节点大。
因为列表的序号都是从0开始,对于总数为n,序号为i的节点,只要其子节点都存在,则有:
如下图所示:
首先建立好初始堆。
依次递归地将a[0]交换到末尾已排序队列的开头,重建堆。
代码实现:
def __heapify(self,arr, n, i):
'''
堆排序被调函数
'''
largest = i
left = 2 * i + 1
right = left + 1
#pdb.set_trace()
if left < n and arr[i] < arr[left]:
largest = left
if right < n and arr[largest] < arr[right]:
largest = right
if largest != i:
arr[i],arr[largest] = arr[largest],arr[i]
self.__heapify(arr, n, largest)
def heapSort(self,arr):
'''
堆排序,先建立大根堆,然后依次递归置换出大元素后并重建大根堆。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
n = len(arr)
# 建立大根堆。
for i in range(n//2-1, -1, -1):
self.__heapify(arr, n, i)
# 依次将大根交换到末尾已排序序列,然后重建大根堆。
# 注意此时大根堆的总数依次减1,避免影响到交换出去的元素。
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
self.__heapify(arr, i, 0)
return arr
下面是一个例子的处理过程:
待处理的列表:[5, 60, 3, 2, 1, 65, 2, 0, 8, 8065]
处理好的大根堆:[8065, 60, 65, 8, 5, 3, 2, 0, 2, 1]
==>处理第 9元素:
交换前:[8065, 60, 65, 8, 5, 3, 2, 0, 2, 1]
交换后:[1, 60, 65, 8, 5, 3, 2, 0, 2, 8065]
==>处理第 8元素:
交换前:[65, 60, 3, 8, 5, 1, 2, 0, 2, 8065]
交换后:[2, 60, 3, 8, 5, 1, 2, 0, 65, 8065]
==>处理第 7元素:
交换前:[60, 8, 3, 2, 5, 1, 2, 0, 65, 8065]
交换后:[0, 8, 3, 2, 5, 1, 2, 60, 65, 8065]
==>处理第 6元素:
交换前:[8, 5, 3, 2, 0, 1, 2, 60, 65, 8065]
交换后:[2, 5, 3, 2, 0, 1, 8, 60, 65, 8065]
==>处理第 5元素:
交换前:[5, 2, 3, 2, 0, 1, 8, 60, 65, 8065]
交换后:[1, 2, 3, 2, 0, 5, 8, 60, 65, 8065]
==>处理第 4元素:
交换前:[3, 2, 1, 2, 0, 5, 8, 60, 65, 8065]
交换后:[0, 2, 1, 2, 3, 5, 8, 60, 65, 8065]
==>处理第 3元素:
交换前:[2, 2, 1, 0, 3, 5, 8, 60, 65, 8065]
交换后:[0, 2, 1, 2, 3, 5, 8, 60, 65, 8065]
==>处理第 2元素:
交换前:[2, 0, 1, 2, 3, 5, 8, 60, 65, 8065]
交换后:[1, 0, 2, 2, 3, 5, 8, 60, 65, 8065]
==>处理第 1元素:
交换前:[1, 0, 2, 2, 3, 5, 8, 60, 65, 8065]
交换后:[0, 1, 2, 2, 3, 5, 8, 60, 65, 8065]
[0, 1, 2, 2, 3, 5, 8, 60, 65, 8065]
1.8 计数排序(Couting Sort)
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中,这个数组空间其实也可以看作桶,因为统计的时候依赖于新开辟数组的序号,所以一般用计数数组来排序数字。
如下图所示:
根据序列中的min和max,构建一个元素总数为max-min+1的bucket。
遍历列表,依次将元素堆放到对应的bucket上去。
从小到大遍历bucket,计数为0的项忽略,重复项重复输出,得到的就是排序后的列表。
如果min和max的差远远超过列表总数,则bucket中存在的无用项就会特别多。
代码实现:
def countingSortNum(self,arr):
'''
桶排序,传入的参数arr必须是数字列表。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
numMin,numMax = arr[0],arr[0]
for i in arr:
if numMin > i: numMin = i
if numMax < i: numMax = i
a = [ arr.count(i) for i in range(numMin,numMax+1)]
return [ i+numMin for i in range(len(a)) for x in range(a[i])]
def countingSortChar(self,arr):
'''
桶排序改进版,支持单字符的排序
'''
# 这里将字符列表映射到了对应的ord列表。
# 并用到了list的min和map方法求最值。
ordChar = list(map(ord,arr))
numMin=min(ordChar)
# 构造一个bucket并做好统计
a = [ordChar.count(i) for i in range(numMin,max(ordChar)+1)]
return [ chr(i+numMin) for i in range(len(a)) for x in range(a[i])]
1.9 桶排序(Bucket sort)
桶排序和计数排序类似,它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。前面提到,在计数排序中,如果最值相差较大,远远超过了列表总数,则构建的bucket里存在很多无用项。为了解决这资源浪费的问题,我们就可以使用桶排序,桶的数量最好和列表项数一致。
代码实现:
def bucketSort(self,arr):
'''
计数排序,两两比较,比其它元素大就加1。\n
譬如比元素a小的元素有5个,则元素a放在位置为5的bucket上。\n
参数arr必须为同类型的列表。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
numLen = len(arr)
bucket = [0] * numLen
for i in range(numLen):
numGreater,numEqual = 0,0
for j in range(numLen):
if arr[i] > arr[j]:numGreater += 1
if arr[i] == arr[j]:numEqual += 1
for k in range(numGreater,numGreater + numEqual):
bucket[k] = arr[i]
return bucket
在上述代码中,针对每个元素,我们依次和其他元素做两两比较,如比k个元素大,我们则将其归入k这个桶中,最大的元素,可能比其他n-1个元素都大,则将其归入n-1这个桶。这样,桶的元素总量就控制得比较小,不会造成资源浪费。
1.10 基数排序(Radix Sort)
基数排序是一种非比较型整数排序算法,其原理很简单,说白了就是先后按位数上的数字做比较:
求出列表里数字最大的位数k,并构建一个从0到9的bucket。
先比较个位数,个位数相同的放置到同一个桶中,然后按桶的顺序将列表排序。
再比较十位数,十位数相同的放置到同一个桶中,然后按桶的顺序将列表排序。
依次类推,直到比较完最高位k,然后按桶的顺序输出的排序就是最终排序。
代码实现:
'''
基数排序,支持正整数列表的排序。
'''
def radixSort(arr):
"""基数排序"""
# 当前位数
digit = 0
# 最长位数
maxDigit = len(str(max(arr)))
while digit < maxDigit:
#初始化桶数组
bucket =[[] for _ in range(10)]
for x in arr:
bucket[int(x / (10**digit)) % 10].append(x)
arr.clear()
for x in bucket: # 放回原序列
for y in x:
arr.append(y)
digit += 1
return arr
假设待排序的数组是[5,60,3,2,1,65,2,0,8,8065],我们来分析下执行过程:
第一个while循环结束后,maxDigit为4。
第二个while循环共执行4次。
digit等于1前,bucket=[[60, 0], [1], [2, 2], [3], [], [5, 65, 8065], [], [], [8], []],arr=[60, 0, 1, 2, 2, 3, 5, 65, 8065, 8]。
digit等于2前,bucket=[[0, 1, 2, 2, 3, 5, 8], [], [], [], [], [], [60, 65, 8065], [], [], []],arr=[0, 1, 2, 2, 3, 5, 8, 60, 65, 8065]。
digit等于3前,bucket=[[0, 1, 2, 2, 3, 5, 8, 60, 65, 8065], [], [], [], [], [], [], [], [], []],arr=[0, 1, 2, 2, 3, 5, 8, 60, 65, 8065]
digit等于4前,bucket=[[0, 1, 2, 2, 3, 5, 8, 60, 65], [], [], [], [], [], [], [], [8065], []],arr=[0, 1, 2, 2, 3, 5, 8, 60, 65, 8065]
循环结束,返回arr。
2 查找算法
2.1 顺序查找(Sequential Search)
顺序查找又称为线性查找,是一种最简单的查找方法。适用于线性表的顺序存储结构和链式存储结构。该算法的时间复杂度为O(n)。
示例代码:
def sequentialSearch(self,arr,key):
'''
顺序查找,从列表arr中找到元素key。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
for i in arr:
if i == key:
return i
return False
当arr很大时,遍历较慢,平均查找时间较大。
2.2 二分查找(Binary Search)
二分查找针对的是有序序列,查找过程中间开始,如果待查找元素小于中间值,则递归处理左边的列表。有时候查找的深度过多,也会影响速度。
示例代码:
def binarySearch(self,arr=[],key=""):
'''
二分查找,从列表arr中找到元素key,arr为从小到大排列好的列表。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
low,high = 0,len(arr)-1
while low <= high:
mid = (low + high)//2
if key < arr[mid]:
high= mid - 1
elif key > arr[mid]:
low = mid + 1
else:
return arr[mid]
return -1
2.3 插值查找
插值查找与二分查找的原理类似,区别就在于mid值的选取。插值算法在列表数量较大且元素分布比较均匀的情况下,性能较高,而对于分布不均匀的数据,则不太适用。
mid值的选取公式:
mid = low + ( (key - a[low]) * (high - low) / (a[high] - a[low]) )
对于方差过大的列表,mid算来算去基本每次都是挨着low,从而降低了二分查找的效率。
另外,由于mid计算的时候牵涉到数字计算,所以本算法不支持字符列表。
示例代码:
def binarySearch(self,arr,key):
'''
插值查找,从列表arr中找到元素key,arr为从小到大排列好的正数列表。\n
基于二分查找,区别在于mid中间值的选取。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
low,high = 0,len(arr)-1
while low <= high:
mid = low + (key - arr[low]) * (high -low)//(arr[hign]-arr[low])
if key < arr[mid]:
high= mid - 1
elif key > arr[mid]:
low = mid + 1
else:
return arr[mid]
return -1
2.4 斐波那契查找
斐波那契查找就是在二分查找的基础上根据斐波那契数列进行分割的。在斐波那契数列找一个等于略大于查找表中元素个数的数F[n],将原查找表扩展为长度为F[n] (如果要补充元素,则补充重复最后一个元素,直到满足F[n]个元素),完成后进行斐波那契分割,即F[n]个元素分割为前半部分F[n-1]个元素,后半部分F[n-2]个元素,找出要查找的元素在那一部分并递归,直到找到。
示例代码:
def __fib(self,num):
'''
返回fib数列第num项。
'''
if num < 2:
return num
else:
return self.__fib(num - 1) + self.__fib(num - 2)
def fibonacciSearch(self, arr,key):
'''
斐波那契查找,必须在有序序列arr里查找key。
'''
if not isinstance(arr,list):
raise TypeError('arr must be a list')
low,high = 0,len(arr) - 1
k = 0
while self.__fib(k) < len(arr):
k += 1
while low <= high:
mid = min(low + self.__fib(k-1),len(arr)-1)
if key < arr[mid]:
high = mid -1
k -= 1
elif key > arr[mid]:
low = mid +1
k -= 2
else:
return arr[mid]
return None
2.5 分块查找
分块查找是二分查找和顺序查找的一种改进方法,分块查找由于只要求索引表是有序的,对块内节点没有排序要求(块内无序,块间有序),因此特别适合于节点动态变化的情况。
分块查找实现的关键是要根据原始列表构造出一个索引表,这个索引表存储的是分块信息,每个分块可以无序,但前一块里的所有元素都必须比后一块里的元素小(递增序列)。但既然能做好分块,分块时顺便将原始列表转化为有序列表,也是顺便的事情。
分块查找要求把一个大的线性表分解成若干块,每块中的节点可以任意存放,但块与块之间必须排序。假设是按关键码值非递减的,那么这种块与块之间必须满足已排序要求,实际上就是对于任意的i,第i块中的所有节点的关键码值都必须小于第i+1块中的所有节点的关键码值。还要建立一个索引表(索引表中为每一块都设置索引项,每一个索引项都包含两个内容)。
2.6 哈希查找
哈希查找就是利用哈希表的映射关系来迅速查找的一种方法,时间复杂度可以提升到O(1)。
关键是哈希算法的确定,很多示例里用的都是余数法,但这样还要解决可能存在的key冲突。
示例代码:
def hashSearch(self,arr,key):
'''
哈希查找,hash函数结果存在的冲突就比较低。
'''
dic = {}
arr = list(set(arr))
for i in range(len(arr)):
dic[hash(arr[i])] = arr[i]
if hash(key) in dic:
return key
return -1
2.7 树表查找
2.7.1 二叉树查找
使用二叉树查找的前提是需要先根据待查找的列表创建一棵二叉树,注意,这不是前面所提的大根堆。二叉树查找的次数和树的深度有关,所以会用B树或红黑树之类的算法来避免深度过大的问题。
二叉树有如下特点:
如左右子节点都存在,则左子节点小于当前值,右子节点大于当前值。
当前值左边的子孙节点,都比其小;当前值右边的子孙节点,都比当前值大。
最初本来是去调整无序列表的节点顺序来实现二叉树,后来发现根值未知的情况下,这种调整比较艰难。
所以,后来改变了思路,先将无序列表去重排序,然后可以求出二叉树的深度depth。
思路1:直接以第
思路2:创建取值范围为0~(
示例代码:
def __buildBinaryTree(self,arr):
'''
创建二叉树。\n
构造二叉树的难点在于根的确定,本文思路:
1. 先构造一个同深度depth的满二叉树;
2. 原序列去重排序并补齐;
3. 待排序序列依次根据满二叉树的序号来提取值。
当然,也可以直接将2^(depth-1)当作根来创建二叉树。
'''
import math
arr = list(set(arr))
n = len(arr)
depth = math.floor(math.log2(n)) + 1
# 创建维度为depth的满二叉树,其值从0到pow(2,depth)-2
fbt = [pow(2,depth-i-1)+j*pow(2,depth-i)-1 for i in range(depth) for j in range(pow(2,i))]
# 排序
result = self.bucketSort(arr)
n = len(result)
# 补齐为满二叉树,也可判断元素类型分别处理,譬如数字依次加1,字符依次加'a'
result.extend([result[-1]*i for i in range(2,pow(2,depth)-len(result)+1)])
return [result[fbt[i]] for i in range(len(result))]
def binaryTreeSearch(self,arr,key):
'''
二叉树查找。
'''
n=len(arr)
result = self.__buildBinaryTree(arr)
if key > max(arr):
return -1
# 递归查找
i = 0
while i < n:
numLeft,numRight = 2*i + 1, 2*i + 2
if result[i] == key:
return result[i]
elif result[i] > key:
i = numLeft
else:
i = numRight
return -1
2.7.2 2-3树查找
在二叉树中,一个节点最多只有2个子节点,而2-3树不一样。
在2-3树中,一个节点可以有2个键值和3个子节点,左边的子孙节点都比自己小,右边的子孙节点都比自己大,中间的子节点其键值都位于该节点的两个键之间。
一颗完美的2-3查找树,所有的叶节点到根节点的距离都是相同的。
2.7.3 B树查找
B树是一种平衡的多路查找树,2-3树和2-3-4树是B树的一种特例,2-3树也称为3阶B树,2-3-4树也称为4阶B树。
一个M(M>2)阶的非空B树具有如下性质:
根节点子树数为[2,M]。
非叶子节点最多有M棵子树。
非根非叶节点的子树数为[M/2,M]。
关键字数量要满足[m/2]-1
非叶子节点的关键字个数等于子树数量减1。
所有叶节点到根节点的距离相等。
示例代码:
'''
本例子来自自算法网。
调整了print以适应python 3.0,198行__spilt函数内的middle,改成了//。
'''
class Entity(object):
'''数据实体'''
def __init__(self,key,value):
self.key = key
self.value = value
class Node(object):
'''B树的节点'''
def __init__(self):
self.parent = None
self.entitys = []
self.childs = []
def find(self,key):
'''通过key查找并返回一个数据实体'''
for e in self.entitys:
if key == e.key:
return e
def delete(self,key):
'''通过key删除一个数据实体,并返回它和它的下标(下标,实体)'''
for i,e in enumerate(self.entitys):
if e.key == key:
del self.entitys[i]
return (i,e)
def isLeaf(self):
'''判断该节点是否是一个叶子节点'''
return len(self.childs) == 0
def addEntity(self,entity):
'''添加一个数据实体'''
self.entitys.append(entity)
self.entitys.sort(key=lambda x:x.key)
def addChild(self,node):
'''添加一个子节点'''
self.childs.append(node)
node.parent = self
self.childs.sort(key=lambda x:x.entitys[0].key)
class Tree(object):
'''B树'''
def __init__(self,size=6):
self.size = size
self.root = None
self.length = 0
def add(self,key,value=None):
'''插入一条数据到B树'''
self.length += 1
if self.root:
current = self.root
while not current.isLeaf():
for i,e in enumerate(current.entitys):
if e.key > key:
current = current.childs[i]
break
elif e.key == key:
e.value = value
self.length -= 1
return
else:
current = current.childs[-1]
current.addEntity(Entity(key,value))
if len(current.entitys) > self.size:
self.__spilt(current)
else:
self.root = Node()
self.root.addEntity(Entity(key,value))
def get(self,key):
'''通过key查询一个数据'''
node = self.__findNode(key)
if node:
return node.find(key).value
def delete(self,key):
'''通过key删除一个数据项并返回它'''
node = self.__findNode(key)
if node:
i,e = node.delete(key)
#在节点不是叶子节点时需要做修复(取对应下标的子节点的最大的一个数据项来补)
if not node.isLeaf():
child = node.childs[i]
j,entity = child.delete(child.entitys[-1].key)
node.addEntity(entity)
while not child.isLeaf():
node = child
child = child.childs[j]
j,entity = child.delete(child.entitys[-1].key)
node.addEntity(entity)
self.length -= 1
return e.value
def isEmpty(self):
return self.length == 0
def __findNode(self, key):
'''通过key值查询一个数据在哪个节点,找到就返回该节点'''
if self.root:
current = self.root
while not current.isLeaf():
for i, e in enumerate(current.entitys):
if e.key > key:
current = current.childs[i]
break
elif e.key == key:
return current
else:
current = current.childs[-1]
if current.find(key):
return current
def __spilt(self,node):
'''
分裂一个节点,规则为:
1、中间的数据项移到父节点
2、新建一个右兄弟节点,将中间节点右边的数据项移到新节点
'''
middle = len(node.entitys) // 2
top = node.entitys[middle]
right = Node()
for e in node.entitys[middle + 1:]:
right.addEntity(e)
for n in node.childs[middle + 1:]:
right.addChild(n)
node.entitys = node.entitys[:middle]
node.childs = node.childs[:middle + 1]
parent = node.parent
if parent:
parent.addEntity(top)
parent.addChild(right)
if len(parent.entitys) > self.size:
self.__spilt(parent)
else:
self.root = Node()
self.root.addEntity(top)
self.root.addChild(node)
self.root.addChild(right)
if __name__ == '__main__':
t = Tree(4)
t.add(20)
t.add(40)
t.add(60)
t.add(70,'c')
t.add(80)
t.add(10)
t.add(30)
t.add(15,'python')
t.add(75,'java')
t.add(85)
t.add(90)
t.add(25)
t.add(35,'c#')
t.add(50)
t.add(22,'c++')
t.add(27)
t.add(32)
print(t.get(15))
print(t.get(75))
print(t.delete(35))
print(t.delete(22))
t.add(22,'lua')
print(t.get(22))
print(t.length)
执行后输出:
python
java
c#
c++
lua
16
2.7.4 B+树查找
B+树是B树的变体,其定义基本和B树相同。
除了:
非叶子节点的子树数与关键字个数相同。
所有关键字都在叶节点中出现,命中的都是叶节点。
所有叶子节点增加了一个链指针。
非叶子节点就相当于叶节点的稀疏索引,叶节点相当于存储关键字的数据层。B+树索引适合于文件索引系统。
另外还有B*索引,在B+树的基础上,所有非叶节点增加了一个指向其兄弟的指针。
2.7.5 R树查找
R树在数据库等领域做出的功绩是非常显著的,很好地解决了在高维空间的搜索等问题。它把B树的思想很好的扩展到了多维空间,采用了B树分割空间的思想,并在添加、删除操作时采用合并、分解结点的方法,保证树的平衡性。因此,R树就是一棵用来存储高维数据的平衡树。
在此不赘述,需要了解的可以去搜索下。
2.7.6 红黑树查找
参考CSDN博主liu_coding理解红黑树并实现(https://blog.csdn.net/net_wolf_007/article/details/79706498)。
也可参考:
红黑树是一棵二叉树, 有五大特征:
节点要么是红色,要么是黑色。
根节点是黑色的.
每个叶节点都是黑色的空节点(或null)。
每个红色节点的两个子节点都是黑色的(相连的两个节点不能都是红色的)。
从任一个节点到其每个叶子节点的所有路径都是包含相同数量的黑色节点。