链表排序python快排_python链表实例

链表排序python快排_python链表实例此文章是跟DataWhale开源组织刷leetcode算法题时所写,主要内容借鉴算法通关手册1.链表排序简介在数组排序中,常见的排序算法有:冒泡,选择,插入,希尔,归并,快速,堆,计数,桶,基数排序等而对于链表排序而言,因为链表不支持随机访问,访问链表后面的节点只能依靠next指针从头部顺序遍历,所以相对于数组排序问题来说,链表排序问题会更加复杂一点。下面来总结一下适合链表排序与不适合链表排序的算法:适合链表的排序算法:冒泡,选择,插入,归并,快速,计数,桶,基数排序不适合链表的排序算法:希尔


Jetbrains全系列IDE稳定放心使用

此文章是跟DataWhale开源组织刷leetcode算法题时所写,主要内容借鉴算法通关手册

1.链表排序简介

在数组排序中,常见的排序算法有:冒泡,选择,插入,希尔,归并,快速,堆,计数,桶,基数排序等
而对于链表排序而言,因为链表不支持随机访问,访问链表后面的节点只能依靠next指针从头部顺序遍历,所以相对于数组排序问题来说,链表排序问题会更加复杂一点。

下面来总结一下适合链表排序与不适合链表排序的算法:

  • 适合链表的排序算法:冒泡,选择,插入,归并,快速,计数,桶,基数排序
  • 不适合链表的排序算法:希尔排序
  • 可以用于链表排序但不建议使用的排序算法:堆排序

希尔排序为什么不适合链表排序?

(什么是希尔排序?)
在这里插入图片描述

希尔排序:希尔排序中经常涉及到对序列中第i + gap的元素进行操作,其中gap是希尔排序中当前的步长。而链表不支持随机访问的特性,导致这种操作不适合链表,因而希尔排序算法不适合进行链表排序

为什么不建议使用堆排序?

堆排序:堆排序所使用的最大堆/最小堆结构本质上是一颗完全二叉树。而完全二叉树适合采用顺序存储结构(数组)。因为数组存储的完全二叉树可以很方便的通过下标序号来确定父亲节点和孩子节点,并且可以极大限度的节省存储空间。

而链表用在存储完全二叉树的时候,因为不支持随机访问的特性,导致其寻找节点和父亲节点会比较耗时,如果增加值向父亲节点的变量,又会浪费大量存储空间。所以堆排序算法不适合进行链表排序。

如果一定要对链表进行堆排序,则可以使用额外的数组空间表示堆结构。然后将链表中各节点的值依次添加入堆结构中,对数组进行堆排序。排序后,再按照堆中元素顺序,依次建立链表节点,构建新的链表并返回新链表头节点。

需要用到额外的辅助空间进行排序的算法
刚才我们说到如果一定要对链表进行堆排序,则需要使用额外的数组空间。除此之外,计数排序,桶排序,基数排序都需要用到额外的数组空间。
接下来,我们将对适合链表排序的8种算法进行一一讲解。当然,这些排序算法不用完全掌握,重点掌握【链表插入排序】,【链表归并排序】这两种排序算法。

2.链表冒泡排序

2.1 链表冒泡排序算法描述

  1. 使用node_i、node_j和tail。其中node_i用于控制外循环次数,循环次数为链节点个数(链表长度)。node_j和tail用于控制内循环次数和循环结束位置。
  2. 排序开始前,将node_i、node_j置于头节点位置。tail指向链表末尾,即None。
  3. 比较链表中相邻两个元素node_j.val与node_j.next.val的值大小,如果node_j.val > node_j.next.val,则值相互交换。否则不发生交换。然后向右移动node_j指针,直到node_j.next == tail时停止。
  4. 一次循环之后,将tail移动到node_j所在位置。相对于tail向左移动了一位。此时tail节点右侧为链表中最大的链节点。
  5. 然后移动node_i节点,并将node_j置于头节点位置。然后重复第3,4步操作。
  6. 直到node_i节点移动到链表末尾停止,排序结束。
  7. 返回链表的头节点head。

2.2 链表冒泡排序算法实现代码

def bubbleSort(self, head: ListNode):
	node_i = head
	tail = None
	# 外层循环次数为 链表节点个数
	while node_i:
		node_j = head
		while node_j and node_j.next != tail:
			if node_j.val > node_j.next.val:
				# 交换两个节点的值
				node_j.val, node_j.next.val = node_j.next.val, node_j.val
				node_j = node_j.next
		# 尾指针向前移动1位,此时尾指针右侧为排好序的链表
		tail = node_j
		node_i = node_i.next
	
	return head

3.链表选择排序

3.1 链表选择排序算法描述

  • 使用两个指针node_i、node_j。node_i既可以用于控制外循环次数,又可以作为当前未排序链表的第一个链节点位置。
  • 使用min_node记录当前未排序链表中值最小的链节点。
  • 每一趟排序开始时,先令min_node = node_i(即暂时假设链表中node_i节点为值最小的节点,经过比较后再确定最小值节点位置)。
  • 然后依次比较未排序链表中node_j.val与min_node.val的值大小。如果node_j.val < min_node.val,则更新min_node为node_j。
  • 这一趟排序结束时,未排序链表中最小值节点为min_node,如果node_i != min_node,则不用交换。
  • 排序结束后,继续向右移动node_i,重复上述步骤,在剩余未排序链表中寻找最小的链节点,并与node_i进行比较和交换,直到node_i == None或者node_i.next == None时,停止排序。
  • 返回链表的头结点head。

3.2 链表选择排序实现代码

def sectionSort(self, head: ListNode):
	node_i = head
	# node_i 为当前未排序链表的第一个链表结点
	while node_i and node_i.next:
		# min_node 为未排序链表中的值最小节点
		min_node = node_i
		node_j = node_i.next
		while node_j:
			if node_j.val < min_node.val:
				min_node = node_j
			node_j = node_j.next
		# 交换值最小节点与未排序链表中第一个节点的值
		if node_i != min_node:
			node_i.val, min_node.val = min_node.val, node_i.val
		node_i = node_i.next
	
	return head

4.链表插入排序

4.1 链表插入排序算法描述

  1. 先使用哑节点dummy_head构造一个值向head的指针,使得可以从head开始遍历。
  2. 维护sorted_list为链表的已排序部分的最后一个节点,初始时,sorted_list = head。
  3. 维护prev为插入元素位置的前一个节点,维护cur为待插入元素。初始时,prev = head,cur = head.next。
  4. 比较sorted_list和cur的节点值。
    • 如果sorted_list.val <= cur.val,说明cur应该插入到sorted_list之后,则将sorted_list后移一位。
    • 如果sorted_list.val > cur.val,说明cur应该插入到head与sorted_list之间。则使用prev从head开始遍历,直到找到插入cur的位置的前一个节点位置,然后将cur插入。
  5. 令cur = sorted_list.next,此时cur为下一个待插元素。
  6. 重复4、5步骤,直到cur遍历结束为空。返回dummy_head的下一个节点。

4.2 链表插入排序实现代码

def insertionSort(self, head: ListNode):
	if not head and not head.next:
		return head
	
	dummy_head = ListNode(-1)
	dummy_head.next = head
	sorted_list = head
	cur = head.next

	while cur:
		if sorted_list.val <= cur.val:
			# 将 cur 插入到 sorted_list之后
			sorted_list = sorted_list.next
		else:
			prev = dummy_head
			while prev.next.val <= cur.val:
				prev = prev.next
			# 将 cur 到链表中间
			sorted_list.next = cur.next
			cur.next = prev.next
			prev.next = cur
		cur = sorted_list.next

	return dummy_head.next

5.链表归并排序

5.1 链表归并排序算法描述

  • 分割环节:找到链表中心链节点,从中心节点将链表断开,并递归进行分割。

    • 使用快慢指针fast = head.next、slow = head, 让fast每次移动2步,slow移动1步,移动到链表末尾,从而找到链表中心链节点,即slow。
    • 从中心位置将链表从中心位置分为左右链表left_head和right_head,并从中心位置将其断开,即slow.next = None。
    • 对左右两个链表分别进行递归分割,直到每个链表中包含一个链节点。
  • 归并环节:将递归后的链表进行两两归并,完成一遍后每个子链表长度加倍。重新进行归并操作,直到得到完整的链表。

    • 使用哑节点dummy_head构造一个头节点,并使用cur值向dummy_head用于遍历。
    • 比较两个链表头节点left和right的值大小。将较小的头节点加入到合并的链表中。并向后移动该链表的头节点指针。
    • 然后重复上一步操作,直到两个链表中出现链表为空的情况。
    • 将剩余链表插入到合并中的链表中。
    • 将哑节点dummy_dead的下一个链节点dummy_head.next作为合并后的头节点返回。

5.2 链表归并排序实现代码

def merge(self, left, right):
	# 归并环节
	dummy_head = ListNode(-1)
	cur = dummy_head
	while left and right:
	if left.val <= right.val:
		cur.next = left
		left = left.next
	else:
		cur.next = right
		right = right.next
	cur = cur.next

	if left:
		cur.next = left
	elif right:
		cur.next = right

	return dummy_head.next

def mergeSort(self, head: ListNode):
	# 分割环节
	if not head or not head.next:
		return head
	
	# 快慢指针找到中心链节点
	slow, fast = head, head.next
	while fast and fast.next:
		slow = slow.next
		fast = fast.next.next

	# 断开左右链节点
	left_head, right_head = head, slow.next
	slow.next = None

	# 归并操作
	return self.merge(self.mergeSort(left_head), self.mergeSort(right_head))

6.链表快速排序

6.1 链表快速排序算法描述

  • 从链表中找到一个基准值pivot,这里以头节点为基准值。
  • 然后通过快慢指针node_j、node_j在链表中移动,使得node_i之前的节点值都小于基准值,node_i之后的节点值都大于基准值。从而把数组拆分为左右两个部分。
  • 再对左右两个部分分别重复第二步,直到各个部分只有一个节点,则排序结束。

6.2 链表快速排序实现代码

def partition(self, left: ListNode, right: ListNode):
	# 左闭右开,区间没有元素或者只有一个元素,直接返回第一个节点
	if left == right or left.next == right:
		return left
	# 选择头节点为基准节点
	pivot = left.val
	# 使用node_i,node_j双指针,保证node_i之前的节点都小于基准节点值,node_i与node_j之间的节点值都大于等于基准节点值
	node_i, node_j = left, left.next

	while node_j != right:
		# 发现一个小于基准值的元素
		if node_j.val < pivot:
			# 因为 node_i 之前节点都小于基准值,所以先将node_i向右移动一位(此时node_i节点大于等于基准节点值)
			node_i = node_i.next
			# 将小于基准值的元素 node_j 与当前node_i换位,换位后可以保证node_i之前的节点都小于基准节点值
			node_i.val, node_j.val = node_j.val, node_i.val
		node_j = node_j.next
	# 将基准节点放到正确位置上
	node_i.val, left.val = left.val, node_i.val
	return node_i

def quickSort(self, left:ListNode, right: ListNode):
	if left == right or left.next == right:
		return left
	pi = self.partition(left, right)
	self.quickSort(left, pi)
	self.quickSort(pi.next, right)
	return left

7.链表计数排序

7.1 链表计数算法描述

  • 使用cur指针遍历一遍链表。找出链表中最大值list_max和最小值list_min。
  • 使用数组counts存储节点出现次数。
  • 再次使用cur指针遍历一遍链表。将链表中每个值为cur.val的节点出现次数,存入数组对应第cur.val - list_min项中。
  • 反向填充目标链表:
    • 建立一个哑节点dummy_head,作为链表的头节点,使用cur指针值向dummy_head。
    • 从小到大遍历一遍数组counts。对于每个counts[i] != 0的元素建立一个链节点,值为i + list_min,将其插入到cur.next上。并向右移动cur。同时counts[i] -= 1。直到counts[i] == 0后继续向后遍历数组counts。
    • 将哑节点dummy_dead的下一个链节点dummy_head.next作为新链表的头节点返回。

7.2 链表计数排序代码实现

def countingSort(self, head: ListNode):
	if not head:
		return head
	
	# 找出链表中最大值list_max和最小值list_min
	list_min, list_max = float('inf'), float('-inf')
	cur = head
	while cur:
		if cur.val < list_min:
			list_min = cur.val
		if cur.val > list_max:
			list_max = cur.val
		cur = cur.next
	
	size = list_max - list_min + 1
	counts = [0 for _ in range(size)]

	cur = head
	while cur:
		counts[cur.val - list_min] += 1
		cur = cur.next
	
	dummy_head = ListNode(-1)
	cur = dummy_head
	for i in range(size):
		while counts[i]:
			cur.next = ListNode(i + list_min)
			counts[i] -= 1
			cur = cur.next
	return dummy_head.next

8.链表桶排序

8.1 链表桶排序算法描述

  • 使用cur指针遍历一遍链表。找出链表中最大值list_max和最小值list_min。
  • 通过(最大值 - 最小值) / 每个桶的大小计算出桶的个数,即bucket_count = (list_max - list_min) // bucket_size + 1个桶。
  • 定义二维数组buckets为桶,外层数组大小为bucket_count个。
  • 使用cur指针再次遍历一遍链表,将每个元素装入对应的桶中。
  • 对每个桶内元素单独排序(使用插入、归并、快排等算法)。
  • 最后按照顺序将桶内的元素拼成新的链表,并返回。

8.2 链表桶排序代码实现

def insertionSort(self, arr):
	for i in range(1, len(arr)):
		temp = arr[i]
		j = i
		while j > 0 and arr[j - 1] > temp:
			arr[j] = arr[j - 1]
			j -= 1
		arr[j] = temp

	return arr

def bucketSort(self, head: ListNode, bucket_size = 5):
	if not head:
		return head

	# 找出链表中最大值list_max 和最小值 list_min
	list_min, list_max = float('inf'), float('-inf')
	cur = head
	while cur:
		if cur.val < list_min:
			list_min = cur.val
		if cur.val > list_max:
			list_max = cur.val
		
		cur = cur.next
	
	# 计算桶的个数,并定义桶
	bucket_count = (list_max - list_min) // bucket_size + 1
	buckets = [[] for _ in range(bucket_count)]

	# 将链表节点依次添加到对应桶中
	cur = head
	while cur:
		buckets[(cur.val - list_min) // bucket_size].append(cur.val)
		cur = cur.next

	dummy_head = ListNode(-1)
	cur = dummy_head
	for bucket in buckets:
		# 对桶中元素单独排序
		self.sortLinkedList(bucket)
		for num in bucket:
			cur.next = ListNode(num)
			cur = cur.next

	return dummy_head.next

9. 链表基数排序

9.1 链表基数排序算法描述

  • 使用cur指针遍历,获取节点位数最长的位数size。
  • 从个位到高位遍历位数。因为0–9共有10位数字,使用建立10个桶。
  • 以每个节点对应数上的数字为索引,将节点值放入到对应桶中。
  • 建立一个哑节点dummy_head,作为链表的头节点。使用cur指针指向dummy_head。
  • 将桶中元素依次取出,并根据元素值建立链表节点,并插入到新的链表后面。从而生成新的链表。
  • 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,放入到对应桶中,并生成新的链表,最终完成排序。
  • 将哑节点dummy_dead的下一个链节点dummy_head.next作为新链表的头节点返回。

9.2 链表基数排序代码实现

def radixSort(self, head: ListNode):
	# 计算位数最长的位数
	size = 0
	cur = head
	while cur:
		val_len = len(str(cur.val))
		if val_len > size:
			size = val_len
		cur = cur.next

	# 从个位到高位遍历位数
	for i in range(size):
		buckets = [[] for _ in range(10)]
		cur = head
		while cur:
			# 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中
			buckets[cur.val // (10 ** i) % 10].append(cur.val)
			cur = cur.next

	# 生成新的链表
	dummy_head = ListNode(-1)
	cur = dummy_head
	for bucket in buckets:
		for num in bucket:
			cur.next = ListNode(num)
			cur = cur.next
	head = dummy_head.next

return head
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/183104.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • 怎么用sql脚本创建数据库_mysql数据库导入

    怎么用sql脚本创建数据库_mysql数据库导入使用sql脚本建立数据库,可以方便各用户,各数据库之间的复制使用,下面将在cmd中完成上述操作:cmd中mysql基本操作:1.连结mysql:C:\Users\WJ>mysql-h127.0.0.1-uroot-p123456其中-h表示host127.0.0.1表示地址,这里你如果是远程访问的话,直接写上远程地址即可,-u-p分别为用户名及密码;2.查看所有数据库:showdatabases;3.操作某一数据库:useschool_2;4.查看该数据库下的表:s

    2022年9月24日
  • 什么是多层感知机(什么是多层感知机)

    1.感知机与多层感知机1.1门与门:实现逻辑“乘”运算y=AB与门真值表ABy000010100111非门:实现逻辑非,一对一输出非门真值表Ay0110或门:实现逻辑“和”运算y=A+B或门真值表ABy00010101111…

    2022年4月11日
  • try catch 对性能影响

    try catch 对性能影响引言之前一直没有去研究trycatch的内部机制,只是一直停留在了感觉上,正好这周五开会交流学习的时候,有人提出了相关的问题。借着周末,正好研究一番。讨论的问题当时讨论的是这样的问题:比较下面两种trycatch写法,哪一种性能更好。for(inti=0;i<1000000;i++){try{Ma

    2022年6月16日
  • 圆柱体积立方计算公式_圆的体积公式怎么算

    圆柱体积立方计算公式_圆的体积公式怎么算圆柱的体积=底面积x高,即V=S底面积×h=(π×r×r)h。假设圆柱的底面半径为2米,高为2米,则此圆柱的体积为:8π立方米。再根据1立方米=1000立方分米,转换成立方分米。其他转换类似。圆柱体是由两个底面和一个侧面组成的。在同一个平面内有一条定直线和一条动线,当这个平面绕着这条定直线旋转一周时,这条动线所成的面叫做旋转面,这条定直线叫做旋转面的轴,这条动线叫做旋转面的母线。扩展资料:圆柱的性…

    2022年9月20日
  • oracle+mybatis分页查询

    oracle+mybatis分页查询当使用oracle进行分页查询时使用以下方式:SELECT* FROM(SELECTA.*,ROWNUMRN     FROM(selectt.name   asname,           t.formula asformula,           t.data_fromasdataFro

    2022年5月6日
  • 【菠萝狗四足机器人】二次开发教程–第三章 【腿部校准和步态效果调试】

    【菠萝狗四足机器人】二次开发教程–第三章 【腿部校准和步态效果调试】腿部校准和步态效果调试1前言2腿部调中(0位校准)3步态效果调试3.1重心标定3.2自动重心补偿量调节4结束1前言承接上次的教程,本次文章,将教你怎么对Py-appleDynamics6.5及6.5以后版本的软件进行腿部校准(调中)和步态效果调试。Py-appleDynamics开源四足控制软件Github调中顾名思义为“机器狗腿部中位调整“,既对机器人的腿部进行标定,使他的机械0位和软件0位重合,以实现机械的运动效果和软件对应。从Py-appleDynamic

    2022年6月4日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号