原理:因为处理的本来就是两个递增有序顺序表,所以只要把两个指针i、j放在两个表的最前边,比较指针的元素大小,然后把小的置入空顺序表C中,大的那个元素的指针不动,留着进行下一次比较。在置入较小的元素之后,较小元素的指针后移一位,与刚刚较大的元素进行第二次比较。依次往复。
如果其中一个表遍历完了另一个表没有遍历完,说明遍历完的那个表的最后一个元素(也就是最大的那个元素)(因为是递增有序顺序表)要小于没遍历完的那个表的头一个元素,而这个头一个元素之后的每一个元素也都是递增有序的,所以只要把剩下的所有元素添加到列表C中就可以了。
代码部分前边是顺序表的一些基本操作,后面是具体的Merge2()函数。
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)
#二路归并:两个按元素值递增有序顺序表,合并到一个递增有序顺序表里。
def Merge2(A,B):#A,B 是任意两个递增有序顺序表
C=SqList()
i=j=0
while i<A.getsize() and j <B.getsize():
if A[i]<B[j]:
C.Add(A[i])
i+=1
else:
C.Add(B[j])
j+=1
while i<A.getsize():
C.Add(A[i])
i+=1
while j<B.getsize():
C.Add(B[j])
j+=1
return C
#设m为A的长度,n为B的长度
#时间复杂度O(m+n),因为三个while循环加起来循环的次数必定的m+n
#空间复杂度O(m+n),因为临时存储的只有递增有序顺序表C,而且A与B里的每一个元素最终都要添加到C中
探讨:时间复杂度取决于循环最内层的语句的执行次数,而值得研究的是第一个while循环中if语句的执行次数。
if语句的执行次数有两种极端情况:最小比较次数和最大比较次数。
最小比较次数就是某一个表里的元素都比另一个大或者都比另一个小。这样只要比较完两个表长度最小的那一个就可以了。比较次数为mim(m,n)
最大比较次数就是比较完两个表里的每一个元素,当然最后一个元素因为必定其中一个比较完了所以不用再比较,比较次数为m+n-1