如果依照对于线性顺序表(python中的列表,C++中的数组)的循秩访问的方式,对单向/双向链表进行循秩访问,假设当前节点的秩=r,则需要O(r)的时间才能访问到当前的节点。从概率的角度而言,访问一个包含n个元素的双向链表中的每个元素的概率相等,都是1/n,访问所有n个元素所需要的时间复杂度为:1+2+……+n=O(n**2),则从期望的角度而言,对双向链表中的元素进行循秩访问所需要的平均时间复杂度为O(n**2/n)=O(n)。
可以考虑通过如下方式加快循秩访问的速度:如果r>n/2,则我们可以从尾部哨兵开始不断访问pred(),最终从后向前地找到秩为r的节点。从期望的角度看,r在[0,n)中是等概率分布的话,那么在循秩访问的过程中,对列表元素的访问次数可以节约一半。
当r>n/2时,访问所需要的时间复杂度是O(n-r),访问此范围内节点的概率是1/2
当r<n/2时,访问所需要的时间复杂度是O(r),访问此范围内节点的概率是1/2
故而总的时间复杂度是1/2*O(n-r)+1/2*O(r)=1/2*O(n)
原有方法访问最慢的情形大致出现在r≈n时,而改进后的方法访问最慢的情形大致出现在r≈n/2时
当对于列表的访问集中在列表尾部时,这种优化策略的效果最明显。
通过这样的优化,循秩访问时间复杂度为O(n/2),无法优于O(n)。
插入操作完成后,有序前缀的长度增加1,无序后缀的长度减少1.
#coding=gbk
'''
如果依照对于线性顺序表(python中的列表,C++中的数组)的循秩访问的方式,对单向/双向链表进行循秩访问,假设当前节点的秩=r,则需要O(r)的时间才能访问到当前的节点。从概率的角度而言,访问一个包含n个元素的双向链表中的每个元素的概率相等,都是1/n,访问所有n个元素所需要的时间复杂度为:1+2+……+n=O(n**2),则从期望的角度而言,对双向链表中的元素进行循秩访问所需要的平均时间复杂度为O(n**2/n)=O(n)。
可以考虑通过如下方式加快循秩访问的速度:如果r>n/2,则我们可以从尾部哨兵开始不断访问pred(),最终从后向前地找到秩为r的节点。从期望的角度看,r在[0,n)中是等概率分布的话,那么在循秩访问的过程中,对列表元素的访问次数可以节约一半。
当r>n/2时,访问所需要的时间复杂度是O(n-r),访问此范围内节点的概率是1/2
当r<n/2时,访问所需要的时间复杂度是O(r),访问此范围内节点的概率是1/2
故而总的时间复杂度是1/2*O(n-r)+1/2*O(r)=1/2*O(n)
原有方法访问最慢的情形大致出现在r≈n时,而改进后的方法访问最慢的情形大致出现在r≈n/2时
当对于列表的访问集中在列表尾部时,这种优化策略的效果最明显。
通过这样的优化,循秩访问时间复杂度为O(n/2),无法优于O(n)。
用python实现无序链表以及其中的基本操作
'''
class ListNode(object):
def __init__(self,data):
self.data=data# 当前节点数据域中的数值
self.prev=None# 当前节点的前驱
self.next=None# 当前节点的后继
# 初始化ListNode类,表示双向链表中的一个节点
class LinkList(object):
def __init__(self):
self.header=ListNode(0)
# self.header.prev=None
self.tailer = ListNode(0)
# self.tailer.next = None
self.header.next=self.tailer
self.tailer.prev=self.header
self.size=0
# 初始化只包含头节点和尾节点的空链表
# 对于包含n个节点的双向链表,
def is_empty(self):
return self.size==0
def length(self):
return self.size
def find(self,value):
'''
类似于无序向量的顺序查找,最快情况下时间复杂度O(n)
:param value:
:return:
'''
if self.size==0:
return None
count=self.size
first=self.header.next# 找到链表的首节点(排除哨兵节点后,第一个真实存在的节点)
while(count):
if first.data==value:
return first
else:
first=first.next
count-=1
return None
def find_node(self,value,r,p):
'''
在节点p的r个真前驱中,查找与节点p(value=p.data)数值相同的第一个节点,查找范围不包含节点p
:param value:
:param r:
:param p:
:return:
'''
node=p.prev
while(node is not None and r>0):
if node.data==value:
return node
else:
node=node.prev
r-=1
return None
def insertAsFirst(self,value):
# 将当前节点作为第一个节点first插入
# 相当于在self.header节点后面插入
self.insertAsAfter(self.header,value)
def insertAsLast(self,value):
# 将当前节点作为最后一个节点插入
# 相当于在self.tailer节点前面插入
self.insertAsBefore(self.tailer,value)
def insertAsBefore(self,node,value):
'''
:param node:
:param value:
:return:
在当前节点node之前插入数值
node可能是实际存在的visible节点或者self.tailer,而不能是self.header,故而node节点必然有前驱,
可能存在包含数据域的后继,或者后继为None
step1:将新节点的后继设置成node节点
step2:将新节点的前驱设置成node节点的前驱
step3:将node节点的前驱的后继设置成新节点
step4:将node节点的前驱设置成新节点
'''
new_node=ListNode(value)
new_node.next=node
new_node.prev=node.prev
# print('new_node.prev',new_node.prev.data)
# print(self.travel())
# print(node.data,'tailer.data')
# print(node.prev.data,'header.data')
node.prev.next=new_node
node.prev=new_node
self.size += 1
# print(self.travel(),'here')
def insertAsAfter(self,node,value):
'''
:param node:
:param value:
:return:
在node节点之后添加新节点,node可以是self.header或者是真实存在的visible中的任何节点,而不能是self.tailer,
故而node节点必然存在后继
'''
new_node=ListNode(value)
new_node.prev=node
new_node.next=node.next
node.next.prev=new_node
node.next=new_node
self.size+=1
# 对于在当前节点node之前或者之后插入新节点,假设当前节点node已经定位,则插入操作仅仅涉及原始链表中的
# 两个节点和新节点,可以在常数时间内完成
def insertInPos(self,pos,value):
'''
:param pos:
:param value:
:return:
在指定的位置插入元素,秩从0到self.size-1
'''
if pos<0 and pos>self.size-1:
print('the pos parameter should range from %d to %d'%(0,self.size-1))
else:
node=self.header.next
while(pos):
node=node.next
pos-=1
self.insertAsBefore(node,value)
def remove(self,value,node=None):
'''
:param value:
:return:
删除双向链表中从前向后的第一个等于value的节点
'''
if node==None:
node=self.header.next
count=self.size
while(count):
if node.data==value:
break
else:
node=node.next
count-=1
# node即为所需要删除的节点
# node必然是真实存在于链表中的节点,而不可能是self.header或者self.tailer两个哨兵节点
node.prev.next=node.next
# print('node',node.data,node.next.data)
node.next.prev=node.prev
node.next=None
node.prev=None
self.size-=1
return node
def remove_all(self):
# 删除整个双向链表
# 通过反复调用self.remove方法不断删除self.header.next
# 最后将哨兵节点self.header删除
while(self.size):
self.remove(0,self.header.next)
self.size-=1
del self.header
def travel(self):
# 遍历链表
output=[]
node=self.header.next
count=self.size
while(count):
output.append(node.data)
node=node.next
count-=1
return output
def reverse(self):
# 将链表中的所有元素反转
# 实际上就是交换哨兵节点self.header和self.tailer两个对象
temp=self.header
self.header=self.tailer
self.tailer=temp
def deduplicate(self):
'''
实现对双向链表的唯一化操作,将链表中重复的数值去掉
:return:
算法:
当前节点p将当前链表分为三个部分:
part1:当前已经整理好的不包含任何重复元素的一部分双向链表,起始点为self.header
part2:当前节点p
part3:p后面的部分(带去重的链表),终止点为self.tailer
step1:如果当前的链表是平凡的(self.size==1),则它自然不包含重复的元素,直接返回
初始化 part1=self.header,p=self.header.next,part2=p.next
step2:r=1,在节点p的前面r个前驱中(即part1)找到是否有元素和当前的p数据域数值相同
如果相同:则删除该节点,r-=1
如果不同,则将p添加到part1部分,p=p.next,r+=1
'''
if self.size==1:
return
# part1=self.header
p=self.header.next
# part2=p.next
r=0
while(p!=self.tailer):
node=self.find_node(p.data,r,p)
if node is None:
# part1=part1.next
p=p.next
# part2=part2.next
r+=1
else:
self.remove(0,node)
p=p.next
def deduplicate_for_ordered(self):
'''
假设当前的双向链表已经是有序排列的,其中的元素从左到右不减小地排列着
对于有序列表(即python中的链表)
有序列表的唯一化比无序列表的唯一化(去重操作)能够更为高效率地完成
在任何一个有序的序列中,如果存在重复的元素,则它们必然是紧邻地形成一个小区间
且区间中的所有元素的数值都相等,则有序序列可以看成是多个这样的小区间的顺序排列
排列成一个又一个彼此紧邻的分组,每个分组内部的所有元素数值相等,分组之间是严格的升序排列。
则唯一化可以理解成是从每个分组中挑选出一个代表,将每个分组中除了分组代表以外的所有元素都删除掉
将每个分组的代表设置成为当前分组中的第一个元素
用curr表示当前节点(从self.header开始),next=curr,next
如果next.data==curr.data,则调用链表的标准接口函数self.remove,将节点next删除
next=next.next
如果next.data!=curr.data,说明next为新区间的第一个元素
如果next.next没有到达链表尾部
则 curr=next
next=next.next
'''
if self.size<=1:
return
n=self.size
p=self.header.next
q=p.next
while(q.next is not None):# 当节点q不是尾部的哨兵节点
if q.data==p.data:
next=q.next
self.remove(0,q)
q=next
else:
p=q
q=q.next
def selectMax(self,node,n):
'''
函数功能:对于起始于节点node的n个节点(包括节点node),返回具有最大值的节点
:param node: 必须是真实存在于链表中的节点,不能是哨兵节点
:param n:
:return:
'''
max_value=-float('inf')
output=None
while(n):
# print('node.data,node.next',node.data,node.next)
if node.data>max_value:
max_value=node.data
output=node
node=node.next
n-=1
return output
def search_first(self,node,n,value):
'''
寻找从node节点开始的n个元素(包含节点node)中,第一个比value数值大的节点并返回
如果找不到,则返回None
:param node:
:param value:
:return:
'''
while(n):
if node.data>value:
return node
n-=1
node=node.next
return None
def sort(self,mode):
'''
对当前的双向链表实现排序,mode:选择排序或者插入排序算法
:return:
选择排序算法:
与冒泡排序算法类似(bubble sort的时间复杂度是O(N**2),包含一系列的比较和交换操作)
冒泡排序中的比较仅仅存在于相邻两个元素之间的数值比较
冒泡排序算法同样是初始化了无序前缀(整个链表)和有序后缀(空)
而将某个元素移动到合适的位置是通过一系列相邻元素的比较和交换位置的操作实现的
(通过相邻元素之间的移动将当前无序前缀子序列中最大的元素移动到合适的位置)
selection sort是对于冒泡排序算法的改进,它希望能够一次性将无序前缀中的最大元素移动到合适的位置
具体步骤如下:
(1)初始化:无序前缀子序列为整个链表,有序后缀子序列为空
(2)for i in range(len(list))
从无序前缀中找到当前无序前缀子序列中的最大值,将最大值取出,放到有序后缀子序列的表头位置
找寻最大值节点的过程需要用到selectMax接口函数,而后者则需要使用insert和remove操作
无序前缀长度-1 有序后缀长度+1
(3)结束
选择排序算法的时间复杂度依然为O(n**2)
因为共需要迭代n次,每次迭代都需要找出无序前缀子序列中最大元素,这个selectMax的操作时间复杂度也需要O(n)
'''
if mode=='selection':# 执行选择排序算法
if self.size<=1:# 如果当前链表平凡,则直接返回
return
n=self.size
q=self.tailer# q始终指向有序后缀子序列的最后一个节点(哨兵节点,并不真实存在)
while(n):
# print(n)
p = self.header.next # p 始终指向无序前缀子序列的首部(第一个真正存在的节点)
temp_max_node=self.selectMax(p,n)
# print('temp_max_node',temp_max_node.data,n)
node=self.remove(0,temp_max_node)
self.insertAsBefore(q,node.data)
# print('self.travel()',self.travel())
del node
q=q.prev
n-=1
elif mode=='insert':# 执行插入排序算法
'''
插入排序算法
同样分为有序子序列和无序子序列,
对于无序子序列中的每个元素,寻找它将在有序子序列中的合适位置index,然后将
有序子序列中index之前的所有元素顺次向前移动一个单位,将当前元素插入至合适位置
插入排序每次迭代过程中关注的焦点是无序后缀子序列的首元素
插入排序和选择排序的重要区别:
(1)选择排序:界定了当前的有序后缀序列必须是升序排列的,然后当前的无序前缀子序列中的所有元素
分布任意,但必须满足:无序前缀子序列中的所有元素都将小于有序后缀子序列中的最小值
(2)插入排序:当前的有序前缀子序列升序排列,但是无序后缀子序列中的所有元素数值任意,并没有做上述的限定
这意味着,无无序后缀子序列中之后遇到的元素数值没有任何限定
对于插入过程排序中的已排序子序列(设其长度为k),其中的元素是原序列中位于前方的k个元素
插入排序算法应用于双向链表中则属于就地算法,in-place algorithm,在每次迭代过程中
'''
n=self.size
curr=self.header.next.next
if n<=1:
return
else:
len_sorted=1
# print('node.prev.data',node.prev.data)
while(curr.next is not None):
# 对于当前链表中的每个元素
node = self.header.next
# print(len_sorted,curr.data,node.data)
pos_node=self.search_first(node,len_sorted,curr.data)
# 查找操作:在从self.header开始的前len_sorted个元素中(即有序前缀子序列)
# 找到第一个比当前所关注的节点大的节点,并返回该节点
if pos_node is not None:
next = curr.next
self.insertAsBefore(pos_node, curr.data)
# print(self.travel(),'after insert')
self.remove(0,curr)
# print('pos_node.data',pos_node.data)
# print('node',node.prev.data)
#否则,说明当前的节点比有序前缀子序列中的所有元素数值都大
curr=next
else:
curr = curr.next
len_sorted+=1
# print('here',self.travel())
if __name__=='__main__':
linklist=LinkList()
print(linklist.is_empty())
print(linklist.length())
# True
# 0
linklist.insertAsLast(5)
linklist.insertAsLast(8)
linklist.insertAsLast(3)
linklist.insertAsLast(1)
linklist.insertAsLast(4)
print(linklist.is_empty())
print(linklist.length())
print(linklist.travel())
# False
# 5
# [5, 8, 3, 1, 4]
linklist.insertInPos(2,-5)
print(linklist.length())
print(linklist.travel())
# 6
# [5, 8, -5, 3, 1, 4]
linklist.insertInPos(5, -5)
print(linklist.length())
print(linklist.travel())
linklist.remove(4)
linklist.remove(-5)
print(linklist.length())
print(linklist.travel())
# 5
# [5, 8, 3, 1, -5]
linklist.insertInPos(1,-5)
linklist.insertInPos(5,3)
linklist.insertInPos(1,3)
print(linklist.length())
print(linklist.travel())
# 8
# [5, 3, -5, 8, 3, 1, 3, -5]
# linklist.deduplicate()
# print(linklist.length())
# print(linklist.travel())
# 5
# [5, 8, 1, 3, -5]
linklist.sort(mode='insert')
print(linklist.length())
print(linklist.travel())
linklist.deduplicate_for_ordered()
print(linklist.length())
print(linklist.travel())
# [-5, -5, 1, 3, 3, 3, 5, 8]
# 5
# [-5, 1, 3, 5, 8]
# 5
# [-5, 1, 3, 5, 8]