- 一、合并两个已经排好序的链表
- 二、找到两个长度不一的链表中的共同使用的开始节点
- 三、链表的插入排序
- 四、链表的排序
- 五、反转链表
- 六、分治链表
一、合并两个已经排好序的链表
有两个链表,已经排好顺序,在不使用额外空间的前提下,也就是在两个链表本身去合并,应该如何做?
首先,准备两个pointer,一个是L1一个是L2,两者的value进行一个比较,如果L1<L2,那么就L1.next指向L2,这样就完成了吗?这样就错了,当我们把L1.next不加以处理就直接指向L2的话,L1的链表后面就内容我们就找不到了,好,既然这样,能不能先将L1 = L1.next呢?那第一个节点不就找不到了吗?
说来说去,这也不行那也不行,那怎么办呢?
准备一个pre用来存储L1,pre = L1,然后用L1 = pre.next向后遍历,如果L1<L2 ,pre.next = L2,这不就搞定了吗,
思路有了之后,上代码,链表的创建是用到了之前手动创建链表的文章当中的链表类,创建出一些符合要求的链表,再此基础上进行合并
from create_a_LinkedList import LinkedList
from create_a_LinkedList import Node
# 时间复杂度 O(m + n)
def merge_two_sortedLinkedList(l1, l2):
dummy = cur = Node(0)
while l1 and l2:
if l1.value < l2.value:
cur.next = l1
l1 = l1.next
else:
cur.next = l2
l2 = l2.next
# 指向了链表当中的节点之后,cur也得向后
cur = cur.next
# 当l1或者l2有一方结束了
cur.next = l1 or l2
return dummy.next
# 创建L1链表
n11 = Node(1)
n12 = Node(3)
n13 = Node(5)
n14 = Node(9)
n11.next, n12.next, n13.next = n12, n13, n14
# 创建L2链表
n21 = Node(2)
n22 = Node(4)
n23 = Node(6)
n24 = Node(10)
n25 = Node(15)
n21.next, n22.next, n23.next, n24.next = n22, n23, n24, n25
res = merge_two_sortedLinkedList(n11, n21)
L = LinkedList()
L.head.next = res
L.print_linkedList()
通过创建额外的节点进行链表节点的存储,并实时向后遍历,不断更新next的指向,这样就完成了链表的合并了。
有没有更好的思路呢?
我们可以用递归去完成,递归的思路在之前的文章当中有提到过,在做递归的时候一定要有一个base条件,来作为最后的收尾,那base条件也很好去设置,我们设置的链表的长度不一定都是一样的,所以当两个链表中有一个为空了,那就要把剩下的节点接在后面
遍历的时候判断两个链表的value,如果L1.value < L2.value 那么就把当前的节点返回,next指向剩下的合并的结果
# 使用递归合并两个链表
# 时间复杂度O(m + n)
def recursive_merge_LinkedList(l1, l2):
# base 条件, 当有一个链表走完之后,另一个链表不为空,那就把剩下的节点直接统一接过去
if not l1 or not l2:
return l1 or l2
if l1.value < l2.value:
l1.next = recursive_merge_LinkedList(l1.next , l2)
return l1
else :
l2.next = recursive_merge_LinkedList(l1, l2.next)
return l2
# 创建L1链表
n11 = Node(1)
n12 = Node(3)
n13 = Node(5)
n14 = Node(7)
n11.next, n12.next, n13.next = n12, n13, n14
# 创建L2链表
n21 = Node(2)
n22 = Node(4)
n23 = Node(6)
n24 = Node(10)
n25 = Node(15)
n21.next, n22.next, n23.next, n24.next = n22, n23, n24, n25
res = recursive_merge_LinkedList(n11, n21)
L = LinkedList()
L.head.next = res
L.print_linkedList()
二、找到两个长度不一的链表中的共同使用的开始节点
类似于下图所示的内容
第一种思路是利用链表的长度:首先链表A,B的长度不一样,但是他们共同走过的节点数是一样的。以上图的题目为例,A链表的长度是5,B链表的长度为4,A比B多一个,那么准备两个指针,一个是a_pointer,一个是b_pointer,让a_pointer先走一步之后b_pointer在走,两个指针的速度是一样的,那么他们相遇的时候就找到了共同的开始节点
上代码
# 找到两个长度不一的链表中的共同使用的开始节点
def find_the_begin(head1, head2):
l1, l2 = head1, head2
len_l1 = 0
len_l2 = 0
while l1 is not None:
len_l1 += 1
l1 = l1.next
while l2 is not None:
len_l2 += 1
l2 = l2.next
# 链表遍历完成之后将指针再挪到各自链表开头
l1, l2 = head1, head2
if len_l2 > len_l1:
for i in range(len_l2 - len_l1):
l2 = l2.next
elif len_l1 > len_l2:
for i in range(len_l1 - len_l2):
l1 = l1.next
while l1 != l2:
l1 = l1.next
l2 = l2.next
print('a: %s\nb: %s'%(len_l1, len_l2))
return l2.value
# 创建a链表
a1 = Node(1)
a2 = Node(4)
a3 = Node(10)
# 创建b链表
b1 = Node(8)
b2 = Node(7)
# 创建c链表
c1 = Node(20)
c2 = Node(40)
a1.next, a2.next, a3.next, c1.next = a2, a3, c1, c2
b1.next, b2.next, c1.next = b2, c1, c2
res = find_the_begin(a1, b1)
print(res)
解决之后问题又来了,如果不能使用长度呢?
那么就直接遍历吧,
首先,在A链表中a指针从头走到尾,然后让a指针从B链表开始走到尾,B链表的b指针也是同样的操作,第一个走完之后,b指针挪到A链表开始移动,二者相遇之后,使用的共同开始节点就找到了,第一趟的时候是不会相遇的,在第一趟完成之后,第二趟完成之前,相遇。节点找到 了
上代码:
# 不用长度找到共同使用的开始节点
def find_begin(l1, l2):
if l1 and l2:
a, b = l1, l2
while a != b :
# 指针不断向后,走完之后将指针移动到两个链表开头
a = a.next if a else l2
b = b.next if b else l1
return a.value
# 创建a链表
a1 = Node(1)
a2 = Node(4)
a3 = Node(10)
# 创建b链表
b1 = Node(8)
b2 = Node(7)
# 创建c链表
c1 = Node(20)
c2 = Node(40)
a1.next, a2.next, a3.next, c1.next = a2, a3, c1, c2
b1.next, b2.next, c1.next = b2, c1, c2
res = find_begin(a1, b1)
print('共同开始的节点:%s'%res)
三、链表的插入排序
给链表进行插入排序:
列表的插入排序是比较简单的,把要插入的值和之前的值一一进行比较,如果要插入的值小,那就依次向前交换即可。
但是链表的交换要复杂:
首先,我们要准备一个dummyNode,preNode,currentNode和temp用来为交换做准备,dummyNode用来返回最后排序好的链表的所有结果,preNode是用来指向链表的头部的,因为每次进行插入排序的时候,之前的链表节点是已经排序好了的,当currentNode是要小于preNode时,将currentNode的next指向pre的next,pre的next要指向currentNode,那如果currentNode>preNode的时候preNode会后遍历,然后与currentNode再次作比较,直到比较出结果,交换完毕之后,currentNode向后遍历,为了保证节点不会丢失,交换之后,要准备一个temp用来临时存储节点,也就是将currentNode.next给到temp,最后交换完毕,currentNode的内容为temp.
最后全部做完之后,返回dummy的next即可
上代码
# 时间复杂度——O(n^2)
def insertionSort_of_LinkedList(head):
# 创建哨兵节点
dummyNode = Node(0)
currentNode = head
while currentNode is not None:
preNode = dummyNode # 创建preNode节点
# 如果currentNode的值 > preNode.next的值,说明不发生交换,preNode向下一个遍历
while preNode.next is not None and preNode.next.value < currentNode.value:
preNode = preNode.next
# 开始做交换
temp = currentNode.next
currentNode.next = preNode.next
preNode.next = currentNode
currentNode = temp
return dummyNode.next
n1 = Node(5)
n2 = Node(8)
n3 = Node(4)
n4 = Node(1)
n5 = Node(7)
n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5
L1 = LinkedList()
L1.head.next = n1
print('插入排序之前:', end = ' ')
L1.print_linkedList()
res = insertionSort_of_LinkedList(n1)
L1.head.next = res
print('插入排序之后:', end = ' ')
L1.print_linkedList()
四、链表的排序
上一题当中的时间复杂度是在O(n^2)的,那么现在我们想要降低时间复杂度,写出一个O(nlogn)的,那么有什么思路呢?可以使用分治法,然后再将链表合并
分治法将链表分开之后进行排序,排序完成之后进行合并
def Sort_LinkedList(node):
if node is None or node.next is None:
return node
middle = get_middle_pos(node)
l_list = node # 左侧链表
r_list = middle.next # 右侧链表
middle.next = None # 左侧链表末尾节点指向None
return merge(Sort_LinkedList(l_list), Sort_LinkedList(r_list))
def merge(l_list, r_list):
dummyNode = dummyHead = Node(0)
while l_list and r_list:
if l_list.value < r_list.value:
dummyHead.next = l_list
l_list = l_list.next # 指向左侧链表节点下一个
else:
dummyHead.next = r_list
r_list = r_list.next # 指向右侧链表节点下一个
dummyHead = dummyHead.next
if l_list:
dummyHead.next = l_list
elif r_list:
dummyHead.next = r_list
return dummyNode.next
def get_middle_pos(node):
if node is None:
return node
fast = node
slow = node
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
return slow
n1 = Node(5)
n2 = Node(8)
n3 = Node(4)
n4 = Node(1)
n5 = Node(7)
n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5
l1 = LinkedList()
l1.head.next = n1
print('排序之前:', end = ' ')
l1.print_linkedList()
res = Sort_LinkedList(n1)
L1 = LinkedList()
L1.head.next = res
print('排序之后:', end = ' ')
L1.print_linkedList()
五、反转链表
一个列表进行逆序输出是非常简单的,如下代码
num = [3, 5, 6, 4, 1, 2]
print(num[::-1])
那如果给一个链表呢?由情况1,转换成情况2
可以准备三个指针,pre_pointer,current_pointer,next_pointer
pre_pointer可以作为一个哨兵节点,current_pointer是指向的当前节点,next_pointer是指向的current_pointer的next节点,当节点创建完毕时,开始反转。
current_pointer的next指向pre_pointer,pre_pointer更新成为current_pointer,next_pointer更新成为current_pointer的next,依次向后遍历,直到current_pointer为空时,pre_pointer正好指向链表当中最后一个节点,然后输出pre_pointer即可
上代码:
from create_a_LinkedList import LinkedList, Node
def reverse_LinkedList(node):
preNode = None
currentNode = node # 当前节点
nextNode = None
while currentNode != None:
nextNode = currentNode.next # nextNode,向后
currentNode.next = preNode # currentNode指向preNode
preNode = currentNode
currentNode = nextNode
return preNode
n1 = Node(1)
n2 = Node(3)
n3 = Node(5)
n4 = Node(7)
n1.next, n2.next, n3.next = n2, n3, n4
res = reverse_LinkedList(n1)
L1 = LinkedList()
L1.head.next = res
L1.print_linkedList()
六、分治链表
将快排的对象从列表改成链表
给你一个节点的值,该节点左侧都是小于该节点的值,右侧都是大于该节点的值
在做列表的快排的时候我们是准备一个pivot,列表头准备一个指针i,列表尾部准备一个指针j,i指针从左向右去遍历,如果遇到比pivot的值要大的元素,i指针停止,j指针从右向左去遍历,如果遇到了比pivot要小的元素,j指针则停止,此时交换i,j指针对应的元素,如果i,j的指针指向了同一个元素, 则将这个元素和pivot进行交换
当然了,列表的遍历是很方便的,如果链表是双向链表的话也会好一些,但是目前我们要做的是单链表,那就不太方便了,我们需要转换思路了
准备两个节点,LNode和RNode,当遍历到的节点比设置的节点值要小,那LNode.next直接指向该节点,如果比设置的节点的值要大,RNode.next指向该节点,最后整合的时候RNode节点指向None,LNode.next指向RNode.next连起来就可以了
def partition_LinkedList(node, x):
# 先创建左右单独节点
LNode = Node(0)
RNode = Node(0)
left = LNode
right = RNode
while node:
if node.value < x:
left.next = node
left = left.next
else:
right.next = node
right = right.next
# 指向结束之后node接着向后
node = node.next
# 排序完成之后
right.next = Node # 设置右侧链表末尾指向None
left.next = RNode.next
return LNode.next
n1 = Node(3)
n2 = Node(5)
n3 = Node(1)
n4 = Node(7)
n5 = Node(4)
n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5
L1 = LinkedList()
res = partition_LinkedList(n1, 3)
L1.head.next = res
L1.print_linkedList()