排序算法整理(冒泡排序、选择排序、插入排序、希尔排序、快速排序、堆排序、计数排序、桶排序、基数排序)
排序算法是计算机科学中用于将数据元素按照特定顺序进行排列的算法,常见的排序算法有以下几类:
比较排序
- 冒泡排序:通过重复地走访要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
- 选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
- 插入排序:对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
- 希尔排序:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
- 归并排序:采用分治法,将一个数组分成两个子数组,对每个子数组进行排序,然后将排序好的子数组合并成一个最终的排序数组。
- 快速排序:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
- 堆排序:利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
非比较排序
- 计数排序:通过统计每个元素在数组中出现的次数,然后根据统计结果将元素按照顺序放入新的数组中,从而实现排序。
- 桶排序:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
- 基数排序:按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
冒泡排序
基本原理
冒泡排序的基本思想是通过相邻元素之间的比较和交换,将最大(或最小)的元素逐步 “浮” 到数组的末尾(或开头),就像气泡在水中上升一样,每一轮比较都会将当前未排序部分的最大(或最小)元素移动到正确的位置。具体过程如下:
- 从数组的第一个元素开始,依次比较相邻的两个元素。
- 如果前一个元素大于后一个元素(假设是升序排序),则交换它们的位置。
- 对数组中的每一对相邻元素都进行这样的比较和交换操作,直到遍历完整个数组。
- 经过第一轮比较和交换后,数组中的最大元素就会被移动到数组的末尾。
- 然后对除了最后一个元素之外的其他元素重复上述过程,每一轮都会将当前未排序部分的最大元素移动到正确的位置,直到整个数组都被排序。
示例代码
def bubble_sort(arr):n = len(arr)for i in range(n):# 最后i个元素已经排好序,无需再比较for j in range(0, n - i - 1):if arr[j] > arr[j + 1]:arr[j], arr[j + 1] = arr[j + 1], arr[j]return arr# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(bubble_sort(arr))
算法复杂度
- 时间复杂度
- 最好情况:数组已经是有序的,只需要进行一次遍历,比较次数为 n − 1 n-1 n−1次,没有交换操作,时间复杂度为 O ( n ) O(n) O(n)。
- 最坏情况:数组是逆序的,需要进行 n − 1 n-1 n−1轮比较,每轮比较需要进行 n − i n-i n−i次比较和交换操作,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 平均情况:时间复杂度也为 O ( n 2 ) O(n^2) O(n2)。
- 空间复杂度:冒泡排序是原地排序算法,只需要常数级别的额外空间来进行元素的交换,空间复杂度为 O ( 1 ) O(1) O(1)
算法稳定性
冒泡排序是一种稳定的排序算法。在比较和交换元素时,如果两个相等的元素相邻,且前一个元素在后面元素之前,只有当前一个元素大于后面元素时才会交换位置,相等时不会交换,所以相等元素的相对顺序在排序前后不会改变。
选择排序
基本原理
选择排序(Selection Sort)的核心思想是每一轮从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置(对于升序排序是起始位置,降序排序则是末尾位置),直到全部待排序的数据元素排完。具体步骤如下:
- 在未排序序列中找到最小(大)元素,记录其索引。
- 将最小(大)元素与未排序序列的第一个元素交换位置。
- 此时,已排序序列长度加 1,未排序序列长度减 1。
- 重复上述步骤,直到未排序序列为空。
算法示例
以下是使用 Python 实现选择排序的代码:
def selection_sort(arr):n = len(arr)for i in range(n):# 假设当前未排序部分的第一个元素是最小值min_index = ifor j in range(i + 1, n):if arr[j] < arr[min_index]:min_index = j# 将找到的最小值与当前未排序部分的第一个元素交换位置arr[i], arr[min_index] = arr[min_index], arr[i]return arr# 测试
arr = [663636]
print(selection_sort(arr))
算法复杂度
- 时间复杂度:无论数组初始状态如何,选择排序都需要进行 n − 1 n - 1 n−1 轮选择和交换操作。每一轮都要遍历未排序部分的所有元素,所以比较次数为 n ( n − 1 ) 2 \frac{n(n - 1)}{2} 2n(n−1) 次。因此,选择排序的时间复杂度始终为 O ( n 2 ) O(n^2) O(n2),其中 n n n 是数组的长度。
- 空间复杂度:选择排序是原地排序算法,只需要常数级别的额外空间用于交换元素,空间复杂度为 O ( 1 ) O(1) O(1)。
算法稳定性
选择排序是一种不稳定的排序算法。例如,对于数组 [5, 8, 5, 2],在第一轮选择中,会将最小元素 2 与第一个 5 交换位置,这样两个 5 的相对顺序就发生了改变,所以选择排序不能保证相等元素的相对顺序不变。
插入排序
基本原理
插入排序(Insertion Sort)的工作原理就像我们整理扑克牌一样。它将数组分为已排序和未排序两部分,初始时已排序部分只有一个元素(通常是数组的第一个元素),然后依次从未排序部分取出元素,将其插入到已排序部分的合适位置,使得已排序部分始终保持有序,直到未排序部分为空。具体步骤如下:
- 从数组的第二个元素开始,将其视为待插入的元素。
- 把这个待插入元素与已排序部分的元素从后往前依次比较。
- 如果待插入元素小于比较的元素,则将比较元素后移一位。
- 重复步骤 3,直到找到一个不大于待插入元素的位置,将待插入元素插入到该位置之后。
- 重复步骤 1 - 4,直到整个数组都被排序。
算法示例
以下是使用 Python 实现插入排序的代码:
def insertion_sort(arr):n = len(arr)for i in range(1, n):# 取出当前待插入的元素key = arr[i]j = i - 1# 将比 key 大的元素后移while j >= 0 and key < arr[j]:arr[j + 1] = arr[j]j = j - 1# 插入 keyarr[j + 1] = keyreturn arr# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(insertion_sort(arr))
算法复杂度
- 时间复杂度:
- 最好情况:数组已经是有序的,此时每一个待插入元素只需要与已排序部分的最后一个元素比较一次,不需要移动元素,所以时间复杂度为 O ( n ) O(n) O(n),其中 n n n 是数组的长度。
- 最坏情况:数组是逆序的,每个待插入元素都要与已排序部分的所有元素进行比较,并将它们依次后移,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 平均情况:平均时间复杂度也是 O ( n 2 ) O(n^2) O(n2)。
- 空间复杂度:插入排序是原地排序算法,只需要常数级别的额外空间来存储待插入元素,空间复杂度为 O ( 1 ) O(1) O(1)。
算法稳定性
插入排序是一种稳定的排序算法。在插入元素时,如果遇到相等的元素,不会将待插入元素插入到相等元素的前面,而是保持它们原来的相对顺序,所以相等元素在排序前后的相对位置不会改变。
希尔排序
基本原理
希尔排序(Shell Sort)是对插入排序的一种改进,也称为缩小增量排序。插入排序在处理基本有序的数据时效率较高,但在处理大规模无序数据时,每次只能将元素移动一位,效率较低。希尔排序通过引入一个“增量”的概念,将原始数据分成多个子序列,对每个子序列分别进行插入排序,随着增量逐渐减小,子序列的长度逐渐增加,整个序列会变得越来越接近有序,最后当增量减至 1 时,整个序列就相当于进行一次普通的插入排序,但此时由于数据已经基本有序,插入排序的效率会大大提高。
具体步骤如下:
- 选择一个增量序列 t 1 , t 2 , ⋯ , t k t_1, t_2, \cdots, t_k t1,t2,⋯,tk,其中 t 1 > t 2 > ⋯ > t k = 1 t_1 > t_2 > \cdots > t_k = 1 t1>t2>⋯>tk=1。
- 按增量 t i t_i ti 对数组进行分组,每个相隔 t i t_i ti 的元素为一组。
- 对每组元素分别进行插入排序。
- 重复步骤 2 和 3,直到增量 t k = 1 t_k = 1 tk=1,此时进行最后一次插入排序,整个数组就被排序完成。
算法示例
以下是使用 Python 实现希尔排序的代码:
def shell_sort(arr):n = len(arr)# 初始增量为数组长度的一半gap = n // 2while gap > 0:for i in range(gap, n):temp = arr[i]j = i# 对相隔 gap 的元素进行插入排序while j >= gap and arr[j - gap] > temp:arr[j] = arr[j - gap]j -= gaparr[j] = temp# 缩小增量gap //= 2return arr# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(shell_sort(arr))
算法复杂度
- 时间复杂度:希尔排序的时间复杂度与增量序列的选择有关,不同的增量序列会导致不同的时间复杂度。最坏情况下,时间复杂度为 O ( n 2 ) O(n^2) O(n2),但在实际应用中,通过合理选择增量序列,希尔排序的平均时间复杂度可以接近 O ( n log n ) O(n \log n) O(nlogn)。
- 空间复杂度:希尔排序是原地排序算法,只需要常数级别的额外空间来进行元素的交换和临时存储,空间复杂度为 O ( 1 ) O(1) O(1)。
算法稳定性
希尔排序是一种不稳定的排序算法。由于在不同的子序列中进行插入排序时,相等元素可能会被交换位置,所以不能保证相等元素的相对顺序在排序前后不变。例如,对于数组 [3, 5, 3, 1],在某个增量下进行排序时,可能会改变两个 3 的相对顺序。
归并排序
基本原理
归并排序(Merge Sort)是采用分治法(Divide and Conquer)的一个非常典型的应用。分治法的核心思想是将一个大问题分解为多个小问题,分别解决这些小问题,然后将小问题的解合并起来得到大问题的解。归并排序具体分为两个步骤:
- 分解(Divide):将待排序的数组从中间分成两个子数组,递归地对这两个子数组分别进行分解,直到每个子数组只有一个元素或者为空。
- 合并(Merge):将两个已排序的子数组合并成一个新的有序数组。合并过程是比较两个子数组的元素,依次取出较小(升序排序)的元素放入新数组,直到两个子数组的元素都被取完。
算法示例
以下是使用 Python 实现归并排序的代码:
def merge_sort(arr):if len(arr) <= 1:return arr# 分解数组mid = len(arr) // 2left_half = arr[:mid]right_half = arr[mid:]# 递归排序左右子数组left_half = merge_sort(left_half)right_half = merge_sort(right_half)# 合并已排序的子数组return merge(left_half, right_half)def merge(left, right):result = []i = j = 0# 比较左右子数组元素,依次添加到结果数组while i < len(left) and j < len(right):if left[i] < right[j]:result.append(left[i])i += 1else:result.append(right[j])j += 1# 添加左子数组剩余元素result.extend(left[i:])# 添加右子数组剩余元素result.extend(right[j:])return result# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(merge_sort(arr))
算法复杂度
- 时间复杂度:归并排序每次将数组分成两部分,递归深度为 log n \log n logn( n n n 是数组长度),每一层合并操作的时间复杂度是 O ( n ) O(n) O(n),所以归并排序的时间复杂度稳定为 O ( n log n ) O(n \log n) O(nlogn),无论是最好情况、最坏情况还是平均情况。
- 空间复杂度:归并排序需要额外的空间来存储合并过程中的临时数组,空间复杂度为 O ( n ) O(n) O(n)。
算法稳定性
归并排序是一种稳定的排序算法。在合并两个子数组时,如果遇到相等的元素,会先将左边子数组的元素放入结果数组,这样就保证了相等元素的相对顺序不变。
快速排序
基本原理
快速排序(Quick Sort)同样采用分治法的思想,它的核心在于选择一个基准元素(pivot),通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比基准元素小,另一部分记录的关键字均比基准元素大,然后分别对这两部分继续进行排序,以达到整个序列有序。具体步骤如下:
- 选择基准元素:从数组中选择一个元素作为基准元素,常见的选择方法有选择数组的第一个元素、最后一个元素或者中间元素等。
- 分区操作:将数组中的元素重新排列,使得所有小于基准元素的元素都位于基准元素的左边,所有大于基准元素的元素都位于基准元素的右边。这个过程称为分区(partition),分区完成后,基准元素就处于它最终排序后的正确位置。
- 递归排序:对基准元素左边和右边的子数组分别递归地应用上述步骤,直到子数组的长度为 0 或 1,此时数组就已经有序。
算法示例
以下是使用 Python 实现快速排序的代码:
def quick_sort(arr):if len(arr) <= 1:return arrelse:# 选择第一个元素作为基准元素pivot = arr[0]# 小于基准元素的元素组成的子数组left = [x for x in arr[1:] if x <= pivot]# 大于基准元素的元素组成的子数组right = [x for x in arr[1:] if x > pivot]# 递归排序左右子数组并合并结果return quick_sort(left) + [pivot] + quick_sort(right)# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(quick_sort(arr))
算法复杂度
- 时间复杂度:
- 最好情况:每次选择的基准元素都能将数组均匀地分成两部分,此时递归树的深度为 log n \log n logn,每层的分区操作时间复杂度为 O ( n ) O(n) O(n),所以最好情况下的时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。
- 最坏情况:如果数组已经是有序的(升序或降序),并且每次都选择第一个或最后一个元素作为基准元素,那么每次分区只能将数组分成一个长度为 0 的子数组和一个长度为 n − 1 n - 1 n−1 的子数组,递归树退化为单链表,此时时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 平均情况:在平均情况下,快速排序的时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。
- 空间复杂度:快速排序的空间复杂度主要取决于递归调用的栈深度。在最好和平均情况下,递归树的深度为 log n \log n logn,所以空间复杂度为 O ( log n ) O(\log n) O(logn);在最坏情况下,递归树退化为单链表,栈深度为 n n n,空间复杂度为 O ( n ) O(n) O(n)。
算法稳定性
快速排序是一种不稳定的排序算法。在分区过程中,相等元素的相对顺序可能会发生改变。例如,在分区时,相等元素可能会被交换到不同的子数组中,从而导致它们的相对顺序发生变化。
堆排序
基本原理
堆排序(Heap Sort)是利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并且满足堆积的性质:每个节点的值都大于或等于其子节点的值(大顶堆),或者每个节点的值都小于或等于其子节点的值(小顶堆)。堆排序的基本思想是先将待排序的数组构建成一个堆,然后不断地从堆中取出最大(大顶堆)或最小(小顶堆)的元素,将其放到已排序序列的末尾,同时调整剩余元素,使其仍然满足堆的性质,重复这个过程直到整个数组有序。具体步骤如下:
- 构建堆:将待排序的数组构建成一个大顶堆(如果是升序排序)或小顶堆(如果是降序排序)。通常从最后一个非叶子节点开始,依次向上调整每个节点,使其满足堆的性质。
- 交换堆顶元素和末尾元素:将堆顶元素(即最大或最小元素)与堆的最后一个元素交换位置,此时堆的最后一个元素就是已排序的元素。
- 调整堆:交换后,堆的结构可能被破坏,需要对剩余的元素重新调整,使其再次满足堆的性质。
- 重复步骤 2 和 3:不断重复交换和调整堆的操作,直到堆中只剩下一个元素,此时整个数组就已经有序。
算法示例
以下是使用 Python 实现堆排序(升序,使用大顶堆)的代码:
def heapify(arr, n, i):# 初始化根节点为最大值largest = ileft = 2 * i + 1right = 2 * i + 2# 如果左子节点大于根节点if left < n and arr[left] > arr[largest]:largest = left# 如果右子节点大于当前最大值if right < n and arr[right] > arr[largest]:largest = right# 如果最大值不是根节点if largest != i:arr[i], arr[largest] = arr[largest], arr[i]# 递归调整受影响的子树heapify(arr, n, largest)def heap_sort(arr):n = len(arr)# 构建大顶堆for i in range(n // 2 - 1, -1, -1):heapify(arr, n, i)# 一个个交换元素for i in range(n - 1, 0, -1):arr[0], arr[i] = arr[i], arr[0] # 交换堆顶和当前末尾元素heapify(arr, i, 0) # 调整剩余元素为大顶堆return arr# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
print(heap_sort(arr))
算法复杂度
- 时间复杂度:
- 构建堆:构建堆的时间复杂度为 O ( n ) O(n) O(n)。因为从最后一个非叶子节点开始调整,每个节点最多需要调整的次数与树的高度有关,而树的高度为 O ( log n ) O(\log n) O(logn),节点总数为 n n n,综合起来构建堆的时间复杂度为 O ( n ) O(n) O(n)。
- 排序过程:每次交换堆顶元素和末尾元素后,需要对剩余元素进行调整,调整的时间复杂度为 O ( log n ) O(\log n) O(logn),总共需要进行 n − 1 n - 1 n−1 次交换和调整操作,所以排序过程的时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。
- 总体:堆排序的时间复杂度始终为 O ( n log n ) O(n \log n) O(nlogn),无论是最好情况、最坏情况还是平均情况。
- 空间复杂度:堆排序是原地排序算法,只需要常数级别的额外空间来进行元素的交换,空间复杂度为 O ( 1 ) O(1) O(1)。
算法稳定性
堆排序是一种不稳定的排序算法。在交换堆顶元素和末尾元素的过程中,可能会改变相等元素的相对顺序。例如,在调整堆的过程中,相等元素可能会被交换到不同的位置,导致它们的相对顺序发生变化。
计数排序
基本原理
计数排序(Counting Sort)是一种非比较排序算法,其核心思想是通过统计数组中每个元素出现的次数,然后根据统计结果将元素按照顺序输出,从而实现排序。该算法适用于待排序元素的取值范围较小且为整数的情况。具体步骤如下:
- 找出待排序数组中的最大值和最小值:确定计数数组的长度,计数数组的长度为最大值减去最小值加 1。
- 统计每个元素出现的次数:遍历待排序数组,以元素的值作为计数数组的索引,统计每个元素出现的次数。
- 计算累积次数:对计数数组进行累加操作,使得计数数组中的每个元素表示小于等于该索引所对应元素的元素个数。
- 输出排序结果:根据计数数组中的累积次数,将待排序数组中的元素依次放入新的数组中,从而得到排序好的数组。
算法示例
以下是使用 Python 实现计数排序的代码:
def counting_sort(arr):if not arr:return arr# 找出最大值和最小值min_val = min(arr)max_val = max(arr)# 计算计数数组的长度range_size = max_val - min_val + 1# 初始化计数数组count = [0] * range_size# 统计每个元素出现的次数for num in arr:count[num - min_val] += 1# 计算累积次数for i in range(1, len(count)):count[i] += count[i - 1]# 初始化结果数组result = [0] * len(arr)# 输出排序结果for num in reversed(arr):index = count[num - min_val] - 1result[index] = numcount[num - min_val] -= 1return result# 测试
arr = [4, 2, 2, 8, 3, 3, 1]
print(counting_sort(arr))
算法复杂度
- 时间复杂度:计数排序的时间复杂度为 O ( n + k ) O(n + k) O(n+k),其中 n n n 是待排序数组的长度, k k k 是待排序元素的取值范围(即最大值减去最小值加 1)。因为需要遍历待排序数组一次来统计元素出现的次数(时间复杂度为 O ( n ) O(n) O(n)),还需要遍历计数数组一次来计算累积次数(时间复杂度为 O ( k ) O(k) O(k)),最后再遍历待排序数组一次来输出排序结果(时间复杂度为 O ( n ) O(n) O(n)),综合起来时间复杂度为 O ( n + k ) O(n + k) O(n+k)。
- 空间复杂度:计数排序需要额外的计数数组和结果数组,空间复杂度为 O ( n + k ) O(n + k) O(n+k)。其中计数数组的长度为 k k k,结果数组的长度为 n n n。
算法稳定性
计数排序是一种稳定的排序算法。在输出排序结果时,通过从后往前遍历待排序数组,保证了相等元素的相对顺序不变。例如,对于数组 [2, 2],在排序过程中,后面的 2 会在前面的 2 之后被放入结果数组,从而保持了它们原来的相对顺序。
桶排序
基本原理
桶排序(Bucket Sort)是一种分布式排序算法,它的核心思想是将待排序的数据分到有限数量的桶里,每个桶再分别进行排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后将各个桶中的数据依次取出,合并成一个有序序列。具体步骤如下:
- 确定桶的数量和范围:根据待排序数据的取值范围和数据规模,确定合适的桶数量,并将数据的取值范围划分为若干个区间,每个区间对应一个桶。
- 将数据分配到桶中:遍历待排序数组,根据每个数据的值将其放入对应的桶中。
- 对每个桶内的数据进行排序:可以使用其他排序算法(如插入排序、快速排序等)对每个桶内的数据进行排序。
- 合并各个桶中的数据:按照桶的顺序,依次将各个桶中的数据取出,合并成一个有序的数组。
算法示例
以下是使用 Python 实现桶排序的代码:
def bucket_sort(arr):if not arr:return arr# 确定桶的数量num_buckets = 10# 找出最大值和最小值min_val = min(arr)max_val = max(arr)# 计算每个桶的范围bucket_range = (max_val - min_val) / num_buckets# 初始化桶buckets = [[] for _ in range(num_buckets)]# 将数据分配到桶中for num in arr:index = int((num - min_val) // bucket_range)if index == num_buckets:index = num_buckets - 1buckets[index].append(num)# 对每个桶内的数据进行排序for bucket in buckets:bucket.sort()# 合并各个桶中的数据result = []for bucket in buckets:result.extend(bucket)return result# 测试
arr = [0.897, 0.565, 0.656, 0.1234, 0.665, 0.3434]
print(bucket_sort(arr))
算法复杂度
- 时间复杂度:
- 平均情况:在平均情况下,如果数据均匀分布在各个桶中,每个桶中的元素数量大致相同,那么对每个桶进行排序的时间复杂度为 O ( n k log n k ) O(\frac{n}{k} \log \frac{n}{k}) O(knlogkn)(其中 n n n 是数据的数量, k k k 是桶的数量),分配数据到桶中的时间复杂度为 O ( n ) O(n) O(n),合并桶的时间复杂度也为 O ( n ) O(n) O(n),综合起来平均时间复杂度为 O ( n + k + n log n k ) O(n + k + n \log \frac{n}{k}) O(n+k+nlogkn)。当 k k k 接近 n n n 时,平均时间复杂度可以接近 O ( n ) O(n) O(n)。
- 最坏情况:如果所有数据都集中在一个桶中,那么就相当于对整个数据使用了另一种排序算法(如插入排序的最坏情况为 O ( n 2 ) O(n^2) O(n2)),此时桶排序的时间复杂度退化为 O ( n 2 ) O(n^2) O(n2)。
- 空间复杂度:桶排序需要额外的空间来存储桶和桶内的数据,空间复杂度为 O ( n + k ) O(n + k) O(n+k),其中 n n n 是数据的数量, k k k 是桶的数量。
算法稳定性
桶排序的稳定性取决于桶内排序算法的稳定性。如果使用的桶内排序算法是稳定的(如插入排序),那么桶排序就是稳定的;如果使用的桶内排序算法是不稳定的(如快速排序),那么桶排序就是不稳定的。
基数排序
基本原理
基数排序(Radix Sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。它是通过多轮的排序来完成整个排序过程,每一轮只关注数据的某一位数字。具体步骤如下:
- 确定最大位数:找出待排序数组中的最大数,确定其位数,这个位数决定了需要进行几轮排序。
- 按位排序:从最低位(个位)开始,依次对每一位进行排序。通常使用稳定的排序算法(如计数排序)对当前位进行排序,将数组元素按照当前位的数字大小重新排列。
- 重复步骤 2:不断增加位数,重复进行排序操作,直到最高位排序完成,此时整个数组就有序了。
算法示例
以下是使用 Python 实现基数排序的代码,这里使用计数排序作为每一轮的稳定排序算法:
def counting_sort_for_radix(arr, exp):n = len(arr)output = [0] * ncount = [0] * 10# 统计当前位上每个数字出现的次数for i in range(n):index = arr[i] // expcount[index % 10] += 1# 计算累积次数for i in range(1, 10):count[i] += count[i - 1]# 构建输出数组i = n - 1while i >= 0:index = arr[i] // expoutput[count[index % 10] - 1] = arr[i]count[index % 10] -= 1i -= 1# 将输出数组复制回原数组for i in range(n):arr[i] = output[i]def radix_sort(arr):if not arr:return arr# 找到最大数,确定最大位数max_num = max(arr)exp = 1while max_num // exp > 0:# 按当前位进行计数排序counting_sort_for_radix(arr, exp)exp *= 10return arr# 测试
arr = [170, 45, 75, 90, 802, 24, 2, 66]
print(radix_sort(arr))
算法复杂度
- 时间复杂度:基数排序的时间复杂度为 O ( d ( n + k ) ) O(d(n + k)) O(d(n+k)),其中 n n n 是待排序数组的长度, d d d 是最大数的位数, k k k 是每一位的取值范围(通常对于十进制数, k = 10 k = 10 k=10)。因为需要对每一位进行一次计数排序,每次计数排序的时间复杂度为 O ( n + k ) O(n + k) O(n+k),总共需要进行 d d d 轮。
- 空间复杂度:主要的额外空间用于计数数组和输出数组,空间复杂度为 O ( n + k ) O(n + k) O(n+k),其中 n n n 是数组长度, k k k 是每一位的取值范围。
算法稳定性
基数排序是一种稳定的排序算法。这是因为每一轮使用的计数排序是稳定的,在排序过程中,相等元素的相对顺序不会改变,所以经过多轮排序后,整个排序过程也是稳定的。
相关文章:
排序算法整理(冒泡排序、选择排序、插入排序、希尔排序、快速排序、堆排序、计数排序、桶排序、基数排序)
排序算法是计算机科学中用于将数据元素按照特定顺序进行排列的算法,常见的排序算法有以下几类: 比较排序 冒泡排序:通过重复地走访要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作…...
Kimi实战1/100 - 读接口文档,编写接口
文章目录 Kimi实战1/100 - 读接口文档,编写接口接口调用requests 调用代码说明注意事项 接口提供FastAPI 接口代码代码说明测试方法 Kimi实战1/100 - 读接口文档,编写接口 接口调用 User: 根据 接口文档 https://www.eiisys.com/home/apiDetails?id00…...
Spring Cache @Cacheable:提升应用性能的利器
在构建企业级应用时,性能优化至关重要。Spring Cache 提供了一种简便而强大的方式来缓存方法调用的结果,从而减少数据库访问、提高响应速度。其中,Cacheable 注解是 Spring Cache 的核心,本文将深入剖析 Cacheable 注解࿰…...
css块级元素和行内元素区别
在CSS中,元素可以分为两大类:块级元素(Block-level elements)和行内元素(Inline elements)。这两种元素在网页布局中起着不同的作用,主要体现在它们的显示方式、尺寸控制、以及与其他元素的交互…...
AWTK fscript 中的 TCP/UDP 客户端扩展函数
fscript 是 AWTK 内置的脚本引擎,开发者可以在 UI XML 文件中直接嵌入 fscript 脚本,提高开发效率。本文介绍一下 fscript 中的 TCP/UDP 客户端扩展函数。 1.iostream_tcp_create 创建 TCP 客户端输入输出流对象。 原型 iostream_tcp_create(host, por…...
[免费]Springboot+Vue医疗(医院)挂号管理系统【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的SpringbootVue医疗(医院)挂号管理系统,分享下哈。 项目视频演示 【免费】SpringBootVue医疗(医院)挂号管理系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 在如今社会上,关于信息上…...
计算机毕业设计PySpark+hive招聘推荐系统 职位用户画像推荐系统 招聘数据分析 招聘爬虫 数据仓库 Django Vue.js Hadoop
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
Jmeter+Influxdb+Grafana平台监控性能测试过程
一、Jmeter自带插件监控 下载地址:https://jmeter-plugins.org/install/Install/ 安装:下载后文件为jmeter-plugins-manager-1.3.jar,将其放入jmeter安装目录下的lib/ext目录,然后重启jmeter,即可。 启动Jmeter&…...
fatal: unable to access ‘https://github.com/xxx/‘: SSL peer certificat
从github上clone代码时报错 F:\Projects>git clone https://github.com/xxx into xxx... fatal: unable to access https://github.com/xxx/: SSL peer certificate or SSH remote key was not OK **可能的原因****解决方法****1. 检查系统时间****2. 禁用 SSL 验证…...
Prompt通用技巧
Prompt 的典型构成 角色:给 AI定义一个最匹配任务的角色,比如:「你是一位软件工程师」「你是一位小学老师」指示:对任务进行描述上下文: 给出与任务相关的其它背景信息(尤其在多轮交互中)。例子 : 必要时给出举例,学术中称为 one-shot learning,few-sho…...
ROACH
End-to-End Urban Driving by Imitating a Reinforcement Learning Coach CARLA-Roach ICCV‘21论文:模仿一个强化学习教练的端到端城市驾驶 文章目录 Roach输入BEV语义分割图像测量向量 Roach输出训练策略网络价值网络 具体实现由 Roach 监督的模仿学习(…...
机械臂运动学笔记(一):正向运动学
正向运动学指的是通过相邻关节间的转动和移动坐标,将末端的坐标计算出来。 反向运动学指的是已知机械臂末端的坐标,反算每个关节可能的转动和移动参数。 参考资料:4.机械臂几何法与DH表示法_哔哩哔哩_bilibili 一.任意连杆连接的变量定义&a…...
【DuodooBMS】给PDF附件加“受控”水印的完整Python实现
给PDF附件加“受控”水印的完整Python实现 功能需求 在实际工作中,许多文件需要添加水印以标识其状态,例如“受控”“机密”等。对于PDF文件,添加水印不仅可以增强文件的可识别性,还可以防止未经授权的使用。本代码的功能需求是…...
GitCode 助力 Dora SSR:开启游戏开发新征程
项目仓库(点击阅读原文链接可直达) https://gitcode.com/ippclub/Dora-SSR 跨越技术藩篱,构建游戏开发乐园 Dora SSR 是一款致力于打破游戏开发技术壁垒的开源游戏引擎。其诞生源于开发者对简化跨平台游戏开发环境搭建的强烈渴望࿰…...
Mediamtx+Python读取webrtc流
一、功能思路: 1、我采用ffmpeg -re -stream_loop -1 -i xcc.mp4 -c:v libx264 -profile:v baseline -x264opts "bframes0:repeat_headers1" -b:v 1500k -preset fast -f flv rtmp://127.0.0.1:1835/stream/111推流到mediamtx的rtmp上 2、通过mediamtx自…...
每日一题——矩阵最长递增路径
矩阵最长递增路径问题 题目描述数据范围:进阶要求:示例示例 1示例 2 题解思路算法步骤:代码实现代码解释复杂度分析总结 题目描述 给定一个 n 行 m 列的矩阵 matrix,矩阵内所有数均为非负整数。你需要在矩阵中找到一条最长路径&a…...
【CLIP系列】4:目标检测(ViLD、GLIP)
目录 1 ViLD2 GLIP2.1 前言2.2 损失计算2.3 模型框架 1 ViLD OPEN-VOCABULARY OBJECT DETECTION VIA VISION AND LANGUAGE KNOWLEDGE DISTILLATION 从标题就能看出来,作者是把CLIP模型当成一个Teacher,去蒸馏他自己的网络,从而能Zero Shot去…...
Cesium for Unity Linux版本
Cesium for Unity 直装不支持Linux 参照官方开发流程一些操作命令issues 宝藏最后运行图 参照官方开发流程 https://github.com/CesiumGS/cesium-unity/blob/main/Documentation~/developer-setup.md 系统已经安装过dotnet和cmake xuefeixuefei:~$ dotnet --version 9.0.102 …...
Spring Boot过滤器链:从入门到精通
文章目录 一、过滤器链是什么?二、为什么需要过滤器链?三、Spring Boot中的过滤器链是如何工作的?(一)过滤器的生命周期(二)过滤器链的执行流程 四、如何在Spring Boot中定义自己的过滤器&#…...
关于 IoT DC3 中驱动(Driver)的理解
在开源IoT DC3物联网系统中,驱动(Driver)扮演着至关重要的角色,它充当了软件系统与物理设备之间的桥梁。驱动的主要功能是依据特定的通信协议连接到设备,并根据设备模板中配置的位号信息进行数据采集和指令控制。不同的…...
微信小程序地图标记点,安卓手机一次性渲染不出来的问题
问题描述: 如果微信小程序端,渲染的标记物太多,安卓手机存在标记物不显示的问题,原因初步判断是地图还没有渲染完,标记物数据已经加载完了,导致没有在地图上显示。 解决办法: 使用map组件的b…...
一维差分与二维差分
差分(Difference)是一种与前缀和密切相关的技术,主要用于高效处理区间更新操作。差分数组的核心思想是通过记录相邻元素的差值来表示原数组的变化,从而将区间更新操作的时间复杂度从 O(n) 优化到 O(1)。下面详细讲解一维差分和二维…...
EasyRTC嵌入式WebRTC视频通话SDK支持Web浏览器、Linux、ARM、Android、iOS
随着互联网技术的飞速发展,实时通信(RTC)已经成为现代应用中不可或缺的一部分。无论是视频会议、在线教育、远程医疗,还是社交娱乐,实时通信技术都在其中扮演着重要角色。 然而,WebRTC技术在PC和移动端的支…...
数据库脚本MySQL8转MySQL5
由于生产服务器版本上部署的是MySQL5,而开发手里的脚本代码是MySQL8。所以只能降版本了… 升级版本与降级版本脚本转换逻辑一样 MySQL5与MySQL8版本SQL脚本区别 大多数无需调整、主要是字符集与排序规则 MySQL5与MySQL8版本SQL字符集与排序规则 主要操作&…...
【PGCCC】commit_delay 对性能的提升:PostgreSQL 基准测试
通过禁用参数可以来调整事务工作负载synchronous_commit。该措施有惊人效果。但在操作系统崩溃期间丢失已提交事务的可能性使其成为许多应用程序无法启动的因素。因此我决定写下来。 WAL 刷新是事务数据库工作负载的瓶颈 为了确保已提交的事务不会丢失,PostgreSQL…...
AI大模型随机初始化权重并打印网络结构方法(以Deepseekv3为例,单机可跑)
背景 当前大模型的权重加载和调用,主要是通过在HuggingFace官网下载并使用transformer的库来加以实现;其中大模型的权重文件较大(部分>100GB),若只是快速研究网络结构和数据流变化,则无需下载权重。本文…...
字符串解码——巧妙使用递归解题
题目描述 给定一个经过编码的字符串,返回它解码后的字符串。编码规则为 k[encoded_string],表示方括号内部的 encoded_string 重复 k 次。其中 k 是正整数,输入字符串确保符合格式要求且无额外空格。 示例: 复制 示例 1&#…...
Flask Web开发的重要概念和示例
一口气列举Flask Web应用的所有概念和示例 Flask Web 应用基本框架 路由(Routing) 模版(Template) request 对象 JSON 数据处理 redirect 示例 文件上传示例 文件下载示例 Session 示例 Cookie操作 Flask Web 应用基本框架 这是一个 最基础的 Flask Web 应用,…...
51-ArrayList
51-ArrayList Collection 类型介绍 仓颉中常用的几种基础 Collection 类型,包含 Array、ArrayList、HashSet、HashMap。 可以在不同的场景中选择适合对应业务的类型: Array:如果不需要增加和删除元素,但需要修改元素ÿ…...
Ollama+WebUI+DeepSeek部署自己的本地大模型
前言 使用AI几乎成为互联网工作者必备技能了,DeepSeek的出现把AI再次推向高潮,在本文中,我们将带领大家借助 Ollama、WebUI 和 deepseek 这三个工具,成功搭建属于自己的本地大模型环境。Ollama 作为一款轻量级的大模型运行工具&a…...
