目录
虽然在python和C++里面有自带的排序函数,但是我们还是需要知道十大排序算法的基理,以备面试的提问
分类
选泡插:选择、冒泡、插入 时间复杂度 O(n**2)
快归希堆:快速、归并、希尔、堆 低于 O(n**2)
桶计基:桶、计数、基数 特殊情况下可以达到线性 O(n)
在我们平时的调用的sort函数一般是上面的合并,根据数据的情况进行一次次的优化
冒泡排序
一边比较一边交换,将最值冒泡到极端位置
优化一:我们可以设一个flag来判断当前轮次是否进行了交换,如果没有那么就表示有序了,直接break就好
进一步优化:我们再加一个flag2记录上一次发生交换的位置,下一次排序到这个位置就停止
nums=[1000,9,200,5,300,6]#目标数组
l=len(nums)
flag2=l-1#记录上一次最后交换位置
for i in range(l-1):#极端位置(也就是稳定位置)的循环
#因为我们是和下一个位置交换,所以到l-1就好
flag1=0#是否交换
flagnow=0#当前轮的最后交换位置
for j in range(0,flag2):#只需要遍历到上一轮最后交换的位置
if nums[j]>nums[j+1]:
flag1=1
flagnow=j
nums[j],nums[j+1]=nums[j+1],nums[j]
if flag1==0:
break
flag2=flagnow#用当前轮最后交换位置更新上一轮最后交换位置
print(nums)
交换元素,py里面直接
a,b=b,a
我们还可以
a=a+b
b=a-b
a=a-b
我们还可以(但是异或交换要求两数不相同)
a=a^b
b=a^b
a=a^b
选择排序
在每一轮循环中找还未排序的最小元素和当前极端位置进行交换
那么选择排序和冒泡排序有什么区别呢?
冒泡排序是稳定的,选择排序是不稳定的
什么是排序算法的稳定性?
冒泡排序不会改变相等元素的相对顺序,而选择排序会改变相等元素
什么时候会影响?当排序的内容是一个对象的多个属性的时候,且其原本的顺序存在意义的时候,那么我们不应该改变相同的元素的相对顺序(py里面可以用sorted里面加lambda表达式实现多维属性的联系)
比如问什么算法可能导致了两个属性的元素排序后提取出了错误的元素?那么就是不稳定排序,不稳定排序有:选择排序、希尔排序、快速排序、堆排序
但是我们也是可以把选择排序变得稳定的,比如通过将结果放到新数组里面,在放入的过程中加入一些判断
优化?:我们在找最小值的过程中也把最大值找出来,然后把他们放到两端的极端位置,那么我们最外层的循环次数可以减半,但是我们在循环里面要做的事情也翻倍了,感兴趣的可以用time模块记录一下优化前后的时间对比(结论:并不会优化时间复杂度)
插入排序
不断将无序元素加入到有序数组里面(有点像Dijkstra的处理思想)
有两种写法:
交换法:在新数字插入时不断和前面的数字交换,直到找到自己适合的位置
移动法:在新数字插入时,与前面数字不断比较,前面的数字不断向后挪出位置,当新数字找到自己位置的时候插入一次即可
下面用的是交换法,这个交换和冒泡排序的区别是冒泡一次可以确定一个极端值,而插入排序确定的值不是极端值,而是 i 循环的这个值
nums=[900,1,1000,200,3,400]
l=len(nums)
for i in range(0,l):
for j in range(i,0,-1):
if nums[j]<nums[j-1]:#交换法:在插入时不断和前面的数字交换
nums[j],nums[j-1]=nums[j-1],nums[j]
print(nums)
插入排序也是稳定的排序算法
LC147.对链表进行插入排序
注意:平台在输入一个数组后,会自动调用__init__来初始化链表
def build_linked_list(arr):
dummy = ListNode(0)
current = dummy
for val in arr:
current.next = ListNode(val)
current = current.next
return dummy.next # 返回真正的头节点
而且本题遍历的时候需要三个指针,从而实现链表指向的更替
1.上一个有序的最后一个节点 qufen
2.当前排序的元素节点 cur
3.遍历有序区的指针 nowzhi
而且这道题我们得在前面加一个盲节点dummyhead从而方便更改头节点不会对链表造成影响
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def insertionSortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
#特判
if not head:
return head
#链表的插入排序必须在原链表上区分已排区和未排区
dummyhead=ListNode(0,None)
dummyhead.next=head
qufen=head #排序区和未排区之间的分割,由于要更改下一个元素的next,所以是已排的最后一个元素
cur=qufen.next
while cur!=None:
if qufen.val<=cur.val:
qufen=qufen.next #上一个有序的比当前节点小,符合生序的要求,直接下一个
else:
now=cur.val
nowzhi=dummyhead#从头!找插入位置的遍历的指针
while now>=nowzhi.next.val:
nowzhi=nowzhi.next
'''
temp=nowzhi.next#cur的下一个节点
temp2=nowzhi.next
nowzhi.next=cur#
cur.next=temp2
qufen.next=cur.next#qufen得连接未排区
'''
qufen.next=cur.next #断开当前节点
cur.next=nowzhi.next
nowzhi.next=cur
cur=qufen.next
return dummyhead.next #返回新链表的头节点,也就是盲节点的next
'''
def build_linked_list(arr):
dummy = ListNode(0)
current = dummy
for val in arr:
current.next = ListNode(val)
current = current.next
return dummy.next # 返回真正的头节点
lis=[]
h=head #通过上面的函数,head是已经转为链表了的头节点
lis.append(h.val)
while h.next!=None:
h=h.next
lis.append(h.val)
lis.sort()
lis2=ListNode(lis)
return lis2
'''
'''
lis=ListNode()
lis.next=head#未排区
lis2=ListNode(head)
lis=lis.next
while 1:
now=lis.val
while now>lis2.val:
lis2=lis2.next
temp=lis2.next
lis2.next=now
if lis.next==None:
break
else:
lis=lis.next
'''
小结1
上面的三种排序都是两层循环嵌套(O(n**2)),空间复杂度都是O(1)
选择排序是交换次数最少的(每次只交换极端值)
但是选择排序是不稳定的,冒泡和插入是稳定的
在数组几乎有序的情况下,插入排序的时间复杂度接近线性级别
下一章我会介绍将时间复杂度降到 O(n**2) 下的排序算法