#规定:一个元素个数为偶数的按元素大小递增的有序顺序表的中位数为中间两个较小(或者说前一个)元素。
按照常规的数学思维来讲,两个按元素有序递增顺序表合并再求中位数分为两大步:
1、合并两表使之成为一个按元素递增的有序顺序表。
2、求合成表的中间位置从而求得中位数。
但是,在合成为一个有序顺序表时,完全可以只合成一半,然后最后一个就是所要求的中位数。
接下来展示两个长度相同的有序顺序表求合成表中位数。如果两个表长度不一致,改一下判定的条件即可。
算法精妙之处分析:引入k,k用来判断是否判定到了中位数。k是合并成一个顺序表时两个表中元素的比较次数(建议看一下二路归并),也就是空表 C的长度,也就是元素个数。当k到达原表长度时,也就找到了中位数。(因为两个有序顺序表长度是相同的)
具体代码如下:(先是顺序表的一些基本操作,然后是具体的函数Middle(A,B))
class SqList:#Sq 是squen的意思,表示顺序;这里建立了一个顺序表类
def __init__(self):
self.incapacity=5
self.capacity=self.incapacity
self.data=[None]*self.capacity#None表示不知道存储元素的类别,也起到占位的作用;这个语句设置了顺序表的空间
self.size=0
def resize(self,newcapacity):
assert newcapacity>=0
olddata=self.data#olddata这个变量用来存储源列表data的内容,相当于把罐子里的水挪出来才能操作原来装水的罐子
self.data=[None]*newcapacity
self.capacity=newcapacity
for i in range(self.size):
self.data[i]=olddata[i]#这样操作过后原列表data的空间会被释放
#时间复杂度O(n),n为olddata的长度
def CreateList(self,a):#其中a是一个列表类型,这个语句是整体将一个列表创建成一个顺序表存储
for i in range(0,len(a)):
if self.size==self.capacity:#以防顺序表容量不够
self.resize(2*self.size)
self.data[self.size]=a[i]#这样是为了顺序表前边压实,不会出现露的位置,每次循环self.size都+1,也是每个位置都随着i的变化逐个填充
self.size+=1
#本算法的时间复杂度为O(n),n表示顺序表中元素的个数
def Add(self,e):#将元素e添加到顺序表的末尾
if self.size==self.capacity:
self.resize(2*self.size)
self.data[self.size]=e
self.size+=1
#一般来讲if语句块大多情况下不会执行,执行的话也执行一次就够。resize()的时间复杂度为O(n),如果
#resize()方法不调用的话时间复杂度为O(1),而调用resize()方法的概率很低,所以算平均时间复杂度仍然为O(1)
def getsize(self):#求顺序表的长度
return self.size
#时间复杂度为O(1)
def __getitem__(self,i): #获取顺序表中序号为i的元素
assert 0<=i<self.size
return self.data[i]
#时间复杂度为O(1)
def __setitem__(self,i,x):
assert 0<=i<self.size #这句话叫参数i的正确性断言
self.data[i]=x
# 时间复杂度为O(1)
def GetNo(self,e):#获取第一个值为e的元素序号,如果不存在则返回-1
i=0
while i<self.size and self.data[i]!=e:#前面的小于判断是为了检测每一个元素,后面的判断语句是检测是不是所要找的元素
i+=1#为了检测下一个
if(i>=self.size):#前面的语句i进行加1之后如果大于则证明检测完但是没有找到等于e的元素值
return -1
else:
return i
#时间复杂度为O(n)
def Insert(self,i,e):#把元素e放到序号为i的位置,原来序号为i及其之后的元素均后移一位
assert 0<=i<=self.size #这句话叫参数i的正确性断言
if self.size == self.capacity:#先要判断要不要扩容
#其实我觉得应该要判断self.size+1是否等于self.capacity,因为要考虑添加完一个元素之后是否顺序表满
self.resize(2*self.size)
for j in range(self.size,i-1,-1):#最后一个移开,倒数第二个才能移动,倒数第二个移动完,倒数第三个才能移动……所以要考虑for range语句的倒用
#步长为负数,是从后向前依次遍历,但是集合的开闭依然是前面闭,后面开,[self.size,i-1),从数轴的角度来看从self.size开始,到i截止
#这里有一点不清晰,要去问老师
self.data[i]=self.data[i-1]#把元素整体右移,但我认为最后一个元素没有考虑到,应该是self.data[j+1]=self.data[j]
self.data[i]=e
self.size+=1
#计算时间复杂度:
#假设表长是n,那么序号就是0到n-1,但是插入的位置有n+1个,因为不仅n个为位置可以插入,最后序号为n的空位置也可以插入(此种情况不要移动)
#假设序号为i的位置要进行插入操作,则后面(n-1)-(i-1)个元素需要移动,即n-i个元素需要移动,要移动n-i次
#因为插入的位置有n+1个,所以假设每个位置被操作的概率是1/n+1
#进行平均时间复杂度的计算,i从0到n, Σ(1/n+1)*(n-i),i从0到n
#数学方法求出来是n/2,所以时间复杂度为O(n)
#调整顺序表内存的长度的语句发生的概率极小,所以不考虑进时间复杂度内
def Delete(self,i):#删除序号为i的元素,删除完后面的元素依次补位
assert 0<=i<=self.size #这句话叫参数i的正确性断言
for j in range(i,self.size):
self.data[i]=self.data[i+1]
self.size-=1
if self.capacity>self.initcapacity and self.size<=self.capacity/4:#为什么有这样的条件?
self.resize(self.capacity//2)
# 计算时间复杂度:
# 假设表长是n,那么序号就是0到n-1,删除的位置有n个
# 假设序号为i的位置要进行删除操作,则后面(n-1)-(i-1)-1个元素需要移动,-1的原因是本身删除了的元素不要进行移动,即n-i-1个元素需要移动,要移动n-i-1次
# 因为删除的位置有n个,所以假设每个位置被操作的概率是1/n
# 进行平均时间复杂度的计算,i从0到n, Σ(1/n+1)*(n-i-1),i从0到n
# 数学方法求出来是n-1/2,所以时间复杂度为O(n)
# 调整顺序表内存的长度的语句发生的概率极小,所以不考虑进时间复杂度内
def display(self):#输出顺序表
for i in range(0,self.size):
print(self.data[i],end='')
print()
#时间复杂度O(n)
接下来是具体的函数Middle()
#求两个有序顺序表合并成一个有序顺序表,最后这个有序顺序表的中位数
#规定:如果一个有序顺序表的长度是偶数,那么中位数取中间两个靠前边的那个
def Middle(A,B):#A与B是两个按元素递增的有序顺序表
i=j=k=0#i代表A的比较时所用到的指针,j代表B的比较时所用到的指针,k代表比较次数,也是新建空表的长度
while i < A.getsize() and j<B.getsize():
k+=1#k代表比较次数,也代表新表的长度
#因为在比较建新表C的时候,不是A[i]<B[j],就是A[i]>=B[j],而中位数规定中间两数取小,所以在第一个判定中把k与A.getsize()比较,
#在第二个判定中把k与B.getsize()比较。
if A[i]<B[j]:
if k == A.getsize():
return A[i]
i+=1#额这个语句好像没啥用
else:
if k==B.getsize():
return B[j]
j+=1#额这个语句好像也没啥用
该方法的时间复杂度O(n),空间复杂度O(1)