在这小节的代码中,因为对数器器中需要用到随机数,所以导入随机库函数
import random
给定一个数组arr, 和一个数num, 请把小于等于num的数放在数组的左边, 大于num的数放在数组的右边。
1、要求额外空间复杂度O(1), 时间复杂度O(N)
2、对数器方法要求额外空间复杂度O(N)), 时间复杂度O(N)
def LessMoreNum_right(array,num):
less=[]
more=[]
equal=[]
for i in range(len(array)):
if array[i]<num:
less.append(array[i])
elif array[i]==num:
equal.append(array[i])
elif array[i]>num:
more.append(array[i])
return less+equal+more
因为原题只要是将小于num的数移到num的做出即可,并不要求左侧是有序的,所以解题方法有很多种,此时不能直接采用判断新的解题答案和遍历方法得到的结果一致。
结合荷兰国旗问题来求解,主函数入口
#结合荷兰国旗问题来求解,主函数入口
def Netherlands(array,num):
if array==None:
return False
elif len(array)==1 and array[0]!=num:
return False
else:
partition(array=array,left=0,right=len(array)-1,num=num)
荷兰国旗问题处理过程
#交换数组两个下标的数值
def swap(array,index1,index2):
item=array[index1]
array[index1]=array[index2]
array[index2]=item
#荷兰国旗问题处理过程
def partition(array,left,right,num):
lessIndex=left-1
moreIndex=right+1
index=left
while index<moreIndex:
if array[index]<num:
lessIndex=lessIndex+1
swap(array=array,index1=index,index2=lessIndex)
index=index+1
elif array[index]==num:
index=index+1
elif array[index]>num:
moreIndex = moreIndex - 1
swap(array=array, index1=index, index2=moreIndex)
return array
给定一个数组arr, 和一个数num, 请把小于num的数放在数组的左边, 等于num的数放在数组的中间, 大于num的数放在数组的右边。
要求额外空间复杂度O(1), 时间复杂度O(N)
例子: 给定数组:[2, 3, 1, 9, 7, 6, 1, 4, 5],给定一个值4,那么经过处理原数组可能得一种情况是:[2, 3, 1, 1, 4, 9, 7, 6, 5],需要注意的是,小于4的部分不需要有序,大于4的部分也不需要有序,返回等于4部分的左右两个下标,即[4, 4]
之所以在两种情况下,进行交换后,对i的处理不同,原因如下:
当遇到>情况的时候,进行数值交换后,此时array[i]的数值在之前的数组遍历的时候没有遇到过,所以在下一步操作中要对其进行判断,所以没有进行i++操作。同理可得,在遇到<情况下,交换数值后,因为此时的array[i]的数值是之前已经遍历过的,所以不需要再进行判断了,直接i++即可。
#交换数组两个下标的数值
def swap(array,index1,index2):
item=array[index1]
array[index1]=array[index2]
array[index2]=item
#荷兰国旗问题处理过程
def partition(array,left,right,num):
lessIndex=left-1
moreIndex=right+1
index=left
while index<moreIndex:
if array[index]<num:
lessIndex=lessIndex+1
swap(array=array,index1=index,index2=lessIndex)
index=index+1
elif array[index]==num:
index=index+1
elif array[index]>num:
moreIndex = moreIndex - 1
swap(array=array, index1=index, index2=moreIndex)
return array
经典快排就是将序列中比尾元素小的移动到序列左边,比尾元素大的移动到序列右边,对以该元素为界的左右两个子序列(均不包括该元素)重复此操作。
首先我们要考虑的是对给定的一个数,如何将序列中比该数小的移动到左边,比该数大的移动到右边。
思路: 利用一个辅助指针small,代表较小数的右边界(初始指向首元素前一个位置),遍历序列每次遇到比该数小的数就将其与arr[small+1]交换并右移small,最后将该数与arr[small+1]交换即达到目的。 对应算法如下:
#交换数组两个下标的数值
def swap(array,index1,index2):
item=array[index1]
array[index1]=array[index2]
array[index2]=item
#经典快排
#改进的荷兰国旗问题,返回列表中元素等于最后一位array[R]的下标索引的上下界限
#在荷兰国旗问题的基础上,将 num=array[R],即数组的最后一位
def partition2(array,left,right):
num=array[right]
less=left-1
more=right+1
index=left
while index<more:
if array[index]==num:
index=index+1
elif array[index]<num:
less=less+1
swap(array,less,index)
index=index+1
elif array[index]>num:
more=more-1
swap(array,more,index)
num_index=[less+1,more-1]
return num_index
#经典快排
def quick_sort(array,left,right):
if left<right:
num_index=partition2(array,left,right)
quick_sort(array,left,num_index[0]-1)
quick_sort(array,num_index[1]+1,right)
return array
经典排序的时间复杂度与数据状况有关,如果每一次partition时,尾元素都是序列中最大或最小的,那么去除该元素序列并未如我们划分为样本量相同的左右两个子序列,而是只安排好了一个元素(就是去掉的那个元素),这样的话时间复杂度就是O(n-1+n-2+……+1)=O(n^2) ;但如果每一次partition时,都将序列分成了两个样本量相差无几的左右两个子序列,那么时间复杂度就是O(nlogn)(使用Master公式求解)。
比较一下经典排序和使用荷兰国旗问题改进后的经典排序,不难发现,后者一次partition能去除一个以上的元素(等于arr[endIndex]的区域), 而前者每次partition只能去除一个元素,这里的去除相当于安排(排序)好了对应元素的位置。因此后者比经典排序更优,但是优化不大,只是常数时间内的优化,实质上的效率还是要看数据状况(最后的情况为O(nlogn),最坏的情况为O(n^2))。
#经典快速排序,结合荷兰国旗问题,从小到大排序
# 主函数入口
def QuickSort(array,left,right):
if left==right:
return array[left]
if left<right:
index=Partition(array=array, left=left, right=right)
QuickSort(array, left, index[0]-1)
QuickSort(array, index[1]+1, right)
return array
#交换数组两个下标的数值
def swap(array,index1,index2):
item=array[index1]
array[index1]=array[index2]
array[index2]=item
#荷兰国旗问题处理过程,放回array列表中等于num的下标索引
def Partition(array,left,right):
num=array[right]
lessIndex = left - 1
moreIndex = right + 1
index = left
while index < moreIndex:
if array[index] < num:
lessIndex = lessIndex + 1
swap(array=array, index1=index, index2=lessIndex)
index = index + 1
elif array[index] == num:
index = index + 1
elif array[index] > num:
moreIndex = moreIndex - 1
swap(array=array, index1=index, index2=moreIndex)
index=[lessIndex+1,moreIndex-1]
return index
上面谈到了快排的短板是依赖数据状况,那么我们有没有办法消除这个依赖,让他成为真正的O(nlogn)呢?
事实上,为了让算法中的操作不依托于数据状况(如快排中每一次partition取尾元素作为比较,这就没有规避样本的数据状况,如果尾元素是最大或最小值就成了最坏情况)常常有两种做法:
总结: 随机快排是在经典快排的基础上进行修改。他们之间有以下不同点。
在这里插入代码片#随机快速排序,从小到大排序
# 主函数入口
def RandomQuickSort(array,left,right):
if left==right:
return array[left]
if left<right:
index=Partition(array=array, left=left, right=right)
RandomQuickSort(array, left, index[0]-1)
RandomQuickSort(array, index[1]+1, right)
return array
#交换数组两个下标的数值
def swap(array,index1,index2):
item=array[index1]
array[index1]=array[index2]
array[index2]=item
def Partition(array,left,right):
num=array[random.randint(left,right)]
lessIndex = left - 1
moreIndex = right + 1
index = left
while index < moreIndex:
if array[index] < num:
lessIndex = lessIndex + 1
swap(array=array, index1=index, index2=lessIndex)
index = index + 1
elif array[index] == num:
index = index + 1
elif array[index] > num:
moreIndex = moreIndex - 1
swap(array=array, index1=index, index2=moreIndex)
index=[lessIndex+1,moreIndex-1]
return index
经数学论证,由于每一轮partition选出的作为比较对象的数是随机的,即序列中的每个数都有1/n的概率被选上,那么该算法时间复杂度为概率事件,经数学论证该算法的数学期望为O(nlogn)。虽然说是数学期望,但在实际工程中,常常就把随机快排的时间复杂度当做O(nlogn)。
堆结构就是将一颗完全二叉树映射到数组中的一种存储方式。
国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
在实际工程中,用数组来构建完全二叉树,假设一个下标索引为Index的节点,它有左右儿子节点和父母节点,那么他们之间的下标索引满足下面这个关系:
堆就是用数组实现的二叉树,所有它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。
堆的常用方法:
堆的分类:堆分为两种,大根堆和小根堆,两者的差别在于节点的排序方式。
**定义:**当堆的每一颗子树(包括树本身)的最大值就是其结点时称为大根堆;相反,当堆的每一颗子树的最小值就是其根结点时称为小根堆。其中大根堆的应用较为广泛,是一种很重要的数据结构。
例子:
这是一个大根堆,因为每一个父节点的值都比其子节点要大。10 比 7 和 2 都大。7 比 5 和 1都大。
heapInsert和heapify
大根堆最重要的两个操作就是heapInsert和heapify,前者是当一个元素加入到大根堆时应该自底向上与其父结点比较,若大于父结点则交换;后者是当堆中某个结点的数值发生变化时,应不断向下与其孩子结点中的最大值比较,若小于则交换。
建立大根堆heapInsert
#交换数组两个下标的数值
def swap(array,index1,index2):
item=array[index1]
array[index1]=array[index2]
array[index2]=item
#建立大根堆,向上调整
def HeapInsert(array,index):
parentIndex=(index-1)//2
while parentIndex>=0 and array[index]>array[parentIndex]:
swap(array=array,index1=index,index2=parentIndex)
index=parentIndex
parentIndex = (index - 1) // 2
建立大根堆算法复杂度是O(N)
对于一个索引下标为i的节点,假设它前面i-1个节点已经是大根堆,那么对i节点与根节点之间的树高度为logi,那么对i节点进行大根堆排序就是这个节点所在的层与根节点之间的树高度,即log i
所以算法复杂度为:
N=log1+log2+log3+…+logN
堆调整heapify
假设在已经建立好的大根堆上,节点index上的数值突然变小,找到左右儿子节点,和这两个数最大的交换,从而不断往下沉,从而重新变为大根堆。
#交换数组两个下标的数值
def swap(array,index1,index2):
item=array[index1]
array[index1]=array[index2]
array[index2]=item
#堆调整,向下调整
def HeapIfy(array,index,heapSize):
LeftSonIndex=2*index+1
RightSonIndex=2*index+2
while LeftSonIndex<=heapSize: #向下调整的前提是左儿子不越界
if RightSonIndex>heapSize: #右儿子越界,只有左儿子节点
if array[index]<array[LeftSonIndex]:
swap(array=array,index1=index,index2=LeftSonIndex)
index=LeftSonIndex
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
else: #存在右儿子节点
#获得左右儿子节点的较大值
if array[LeftSonIndex]>=array[RightSonIndex]:
bigIndex=LeftSonIndex
else:
bigIndex=RightSonIndex
if array[index]<array[bigIndex]:
swap(array=array,index1=index,index2=bigIndex)
index=bigIndex
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
假设我们要从小排序到大。在了解堆排序前,首先要明白大根堆的堆顶元素是整个数组里面的最大值,在了解这个后才能了解堆排序。
要在一个原始堆长度为heapsize的大根堆减去一个节点为index的元素,并且让减去元素的大根堆后面还能保持大根堆形式。
堆排序步骤如下:
#交换数组两个下标的数值
def swap(array,index1,index2):
item=array[index1]
array[index1]=array[index2]
array[index2]=item
#建立大根堆,向上调整
def HeapInsert(array,index):
parentIndex=(index-1)//2
while parentIndex>=0 and array[index]>array[parentIndex]:
swap(array=array,index1=index,index2=parentIndex)
index=parentIndex
parentIndex = (index - 1) // 2
#堆调整,向下调整
def HeapIfy(array,index,heapSize):
LeftSonIndex=2*index+1
RightSonIndex=2*index+2
while LeftSonIndex<=heapSize: #向下调整的前提是左儿子不越界
if RightSonIndex>heapSize: #右儿子越界,只有左儿子节点
if array[index]<array[LeftSonIndex]:
swap(array=array,index1=index,index2=LeftSonIndex)
index=LeftSonIndex
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
else: #存在右儿子节点
#获得左右儿子节点的较大值
if array[LeftSonIndex]>=array[RightSonIndex]:
bigIndex=LeftSonIndex
else:
bigIndex=RightSonIndex
if array[index]<array[bigIndex]:
swap(array=array,index1=index,index2=bigIndex)
index=bigIndex
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
#堆调整,从小到大排序,主函数入口
def HeapSort(array):
if array==None or len(array)<2:
return array
heapSize=len(array)-1 #数据堆的长度
#建立大根堆
for i in range(len(array)):
HeapInsert(array=array,index=i)
while heapSize>=0:
swap(array=array,index1=0,index2=heapSize)
heapSize=heapSize-1
HeapIfy(array=array,index=0,heapSize=heapSize)
return array
问题描述: 求一个长度为N的数组数值的中位数
思路一:(一般用来处理静态数据,也就是数据本身不变化)
先对数组进行快速排序,算符复杂度为O(N*log N)
当N为偶数的时候,中位数就排序后数组中间两个数的和再除以2。当N为奇数的时候,中位数就是排序后数组中间那个数值。
思路一的弊端:
每次要查询的时候,都要进行数组排序,不能做到实时查询。
思路二:(一般用来处理动态数据,也就是数据还会不断增加进来)
(a)、数组不断弹出元素来插入到堆里面(array[i for i in range(len(array)-1)] ),第一个元素默认进大根堆。然后在进行插入的时候,若数组的元素小于大根堆的堆顶元素,就进入大根堆,大于大根堆堆顶元素就进入小根堆。
(b)、时刻保持大根堆的元素个数和小根堆的元素个数差值不得大于2,如果大根堆元素个数多,就弹出大根堆的堆顶元素到小根堆的堆顶,然后对这两个堆都进行堆调整(即heapify操作)。
思路一代码
#交换数组两个下标的数值
def swap(array,index1,index2):
item=array[index1]
array[index1]=array[index2]
array[index2]=item
#冒泡排序,从小到大排序
def bubble_sort(array):
if array==None or len(array)<2:
return array
for i in range(len(array)):
for j in range(len(array)-i-1):
if array[j]>array[j+1]:
swap(array=array, index1=j, index2=j + 1)
return array
#求数组的中位数 测试方法
def GetMidNum(array):
if array==None or len(array)<2:
return array
array=bubble_sort(array)
return array[(len(array)-1)//2]
思路二代码
#求数组的中位数,对数器方法
def GetMidNum_right(array):
if array==None or len(array)<2:
return array
array=sorted(array)
return array[(len(array)-1)//2]
大根堆的建立以及堆调整过程代码
###########大根堆的建立以及堆调整过程代码######################
def heapInsert_BigRootPile(array,index):
parentIndex=(index-1)//2
while parentIndex>=0 and array[index]>array[parentIndex]:
swap(array=array,index1=index,index2=parentIndex)
index=parentIndex
parentIndex = (index - 1) // 2
def heapIfy_BigRootPile(array,index,heapSize):
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
while LeftSonIndex <= heapSize: # 向下调整的前提是左儿子不越界
if RightSonIndex > heapSize: # 右儿子越界,只有左儿子节点
if array[index] < array[LeftSonIndex]:
swap(array=array, index1=index, index2=LeftSonIndex)
index = LeftSonIndex
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
else: # 存在右儿子节点
# 获得左右儿子节点的较大值
if array[LeftSonIndex] >= array[RightSonIndex]:
bigIndex = LeftSonIndex
else:
bigIndex = RightSonIndex
if array[index] < array[bigIndex]:
swap(array=array, index1=index, index2=bigIndex)
index = bigIndex
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
小根堆的建立以及堆调整过程代码
###########小根堆的建立以及堆调整过程代码######################
def heapInsert_SmallRootPile(array,index):
parentIndex=(index-1)//2
while parentIndex>=0 and array[index]<array[parentIndex]:
swap(array=array,index1=index,index2=parentIndex)
index=parentIndex
parentIndex = (index - 1) // 2
def heapIfy_SmallRootPile(array,index,heapSize):
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
while LeftSonIndex <= heapSize: # 向下调整的前提是左儿子不越界
if RightSonIndex > heapSize: # 右儿子越界,只有左儿子节点
if array[LeftSonIndex]<array[index]:
swap(array=array, index1=index, index2=LeftSonIndex)
index = LeftSonIndex
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
else: # 存在右儿子节点
# 获得左右儿子节点的较大值
if array[LeftSonIndex] <= array[RightSonIndex]:
smallIndex = LeftSonIndex
else:
smallIndex = RightSonIndex
if array[index] > array[smallIndex]:
swap(array=array, index1=index, index2=smallIndex)
index = smallIndex
LeftSonIndex = 2 * index + 1
RightSonIndex = 2 * index + 2
用堆方法求解数组中位数,主函数
#堆方法求解列表中位数,主函数入口
def GetMidNumHeap(array):
BigRootPile=[] #建立所需的大根堆和小根堆
SmallRootPile=[]
for i in range(len(array)):
#先将数据压到对应的数据堆
if len(BigRootPile)==0: # 一开始的时候,将元素压到大根堆
BigRootPile.append(array[i])
else:
#当数据小于大根堆堆顶元素的时候,压入大根堆
#当数据大于小根堆堆顶元素的时候,压入小根堆
if len(BigRootPile)!=0 and array[i]<=BigRootPile[0]:
BigRootPile.append(array[i])
heapInsert_BigRootPile(array=BigRootPile, index=len(BigRootPile)-1)
else:
# elif len(SmallRootPile)!=0 and array[i]>SmallRootPile[0]:
SmallRootPile.append(array[i])
heapInsert_SmallRootPile(array=SmallRootPile, index=len(SmallRootPile) - 1)
#进行堆调整
# 当len(BigRootPile)-len(SmallRootPile)>=2时,弹出大根堆的堆顶,放到小根堆的堆顶
#同理可推当当len(SmallRootPile)-len(BigRootPile)>=2
if len(BigRootPile)-len(SmallRootPile)>=2:
swap(array=BigRootPile,index1=0,index2=len(BigRootPile)-1)
item=BigRootPile.pop()
heapIfy_BigRootPile(array=BigRootPile,index=0,heapSize=len(BigRootPile)-1)
SmallRootPile.append(item)
heapInsert_SmallRootPile(array=SmallRootPile, index=len(SmallRootPile)-1)
elif len(SmallRootPile)-len(BigRootPile)>2:
swap(array=SmallRootPile, index1=0, index2=len(SmallRootPile) - 1)
item = SmallRootPile.pop()
heapIfy_SmallRootPile(array=SmallRootPile, index=0, heapSize=len(SmallRootPile) - 1)
BigRootPile.append(item)
heapInsert_BigRootPile(array=BigRootPile, index=len(BigRootPile) - 1)
#返回数组的中位数
if len(BigRootPile)-1 ==(len(array)-1)//2:
return BigRootPile[0]
else:
return SmallRootPile[0]