排序算法
冒泡排序
简介
冒泡排序(Bubble sort)是一种简单的排序算法。由于在算法的执行过程中,较小的元素像是气泡般慢慢「浮」到数列的顶端,故叫做冒泡排序。
它的工作原理是每次检查相邻两个元素,如果前面的元素与后面的元素满足给定的排序条件,就将相邻两个元素交换。当没有相邻的元素需要交换时,排序就完成了。
经过
代码实现
def bubble_sort(a, n): flag = True while flag: flag = False for i in range(1, n): if a[i] > a[i + 1]: flag = True a[i], a[i + 1] = a[i + 1], a[i]
选择排序
简介
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每次找出第 i 小的元素(也就是 A_{i..n} 中最小的元素),然后将这个元素与数组第 i 个位置上的元素交换。
由于 swap(交换两个元素)操作的存在,选择排序是一种不稳定的排序算法。选择排序的最优时间复杂度、平均时间复杂度和最坏时间复杂度均为 O(n^2)。
代码实现
def selection_sort(a, n): for i in range(1, n): ith = i for j in range(i + 1, n + 1): if a[j] < a[ith]: ith = j a[i], a[ith] = a[ith], a[i]
快速排序
简介
快速排序(Quick sort),又称分区交换排序(partition-exchange sort),简称「快排」,是一种被广泛运用的排序算法。
快速排序的工作原理是通过 分治 的方式来将一个数组排序。
快速排序分为三个过程:
-
将数列划分为两部分(要求保证相对大小关系);
-
递归到两个子序列中分别进行快速排序;
-
不用合并,因为此时数列已经完全有序。
和归并排序不同,第一步并不是直接分成前后两个序列,而是在分的过程中要保证相对大小关系。具体来说,第一步要是要把数列分成两个部分,然后保证前一个子数列中的数都小于后一个子数列中的数。为了保证平均时间复杂度,一般是随机选择一个数 m 来当做两个子数列的分界。
之后,维护一前一后两个指针 p 和 q,依次考虑当前的数是否放在了应该放的位置(前还是后)。如果当前的数没放对,比如说如果后面的指针 q 遇到了一个比 m 小的数,那么可以交换 p 和 q 位置上的数,再把 p 向后移一位。当前的数的位置全放对后,再移动指针继续处理,直到两个指针相遇。
其实,快速排序没有指定应如何具体实现第一步,不论是选择 m 的过程还是划分的过程,都有不止一种实现方法。
第三步中的序列已经分别有序且第一个序列中的数都小于第二个数,所以直接拼接起来就好了。
代码实现
【Java实现】
public class QuickSort { public int[] quickSort(int[] nums) { shuffle(nums); quickSort(nums, 0, nums.length - 1); return nums; } private void quickSort(int[] nums, int left, int right) { if (left >= right) { return; } int pivotIndex = partition(nums, left, right); quickSort(nums, left, pivotIndex - 1); quickSort(nums, pivotIndex + 1, right); } private int partition(int[] nums, int left, int right) { int k = left; // 记录交换的起点 // 以nums[right]作为基准值:遍历基准值左侧的所有元素 for (int i = left; i < right; i++) { // 将小于基准值的元素归位 if (nums[i] < nums[right]) { swap(nums, k++, i); } } swap(nums, k, right); // 将基准元素归位 return k; } private void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } private void shuffle(int[] nums) { Random random = new Random(); int n = nums.length; for (int i = 0; i < n; i++) { swap(nums, i, random.nextInt(n - i) + i); } } }
归并排序
简介
归并排序(merge sort)是高效的基于比较的稳定排序算法。归并排序基于分治思想将数组分段排序后合并,时间复杂度在最优、最坏与平均情况下均为
归并排序可以只使用
归并排序最核心的部分是合并(merge)过程:
-
将两个有序的数组
和 合并为一个有序数组-
从左往右枚举
和 ,找出最小的值并放入数组 ; -
重复上述过程直到
和 有一个为空时,将另一个数组剩下的元素放入 。
-
为保证排序的稳定性,前段首元素小于或等于后段首元素时(
代码实现
public class MergeSort { public int[] sort(int[] nums) { sort(nums, 0, nums.length - 1); return nums; } private void sort(int[] nums, int left, int right) { if (left >= right) { return; } int mid = left + (right - left) / 2; sort(nums, left, mid); // 左侧区间:[left, mid] sort(nums, mid + 1, right); // 右侧区间:[mid + 1, right] merge(nums, left, mid, right); } private void merge(int[] nums, int left, int mid, int right) { // 将区间[left, mid]和[mid + 1, right] 合并 int[] temp = new int[nums.length]; for (int i = 0; i < nums.length; i++) { temp[i] = nums[i]; } int i = left, j = mid + 1, k = left; while (i <= mid && j <= right) { if (nums[i] < nums[j]) { nums[k++] = temp[i++]; } else { nums[k++] = temp[j++]; } } while (i <= mid) { nums[k++] = temp[i++]; } while (j <= right) { nums[k++] = temp[j++]; } } }
插入排序
简介
插入排序(Insertion sort)是一种简单直观的排序算法。它的工作原理为将待排列元素划分为「已排序」和「未排序」两部分,每次从「未排序的」元素中选择一个插入到「已排序的」元素中的正确位置。
一个与插入排序相同的操作是打扑克牌时,从牌桌上抓一张牌,按牌面大小插到手牌后,再抓下一张牌。
插入排序是一种稳定的排序算法。
代码实现
def insertion_sort(arr, n): for i in range(1, n): key = arr[i] j = i - 1 while j >= 0 and arr[j] > key: arr[j + 1] = arr[j] j = j - 1 arr[j + 1] = key
折半插入排序
简介
插入排序还可以通过二分算法优化性能,在排序元素数量较多时优化的效果比较明显。
代码实现
void insertion_sort(int arr[], int len) { if (len < 2) return; for (int i = 1; i != len; ++i) { int key = arr[i]; auto index = upper_bound(arr, arr + i, key) - arr; // 使用 memmove 移动元素,比使用 for 循环速度更快,时间复杂度仍为 O(n) memmove(arr + index + 1, arr + index, (i - index) * sizeof(int)); arr[index] = key; } }
堆排序
简介
堆排序(Heap sort)是指利用 二叉堆 这种数据结构所设计的一种排序算法。堆排序的适用数据结构为数组。
过程:
-
首先建立大顶堆,然后将堆顶的元素取出,作为最大值,与数组尾部的元素交换,并维持残余堆的性质;
-
之后将堆顶的元素取出,作为次大值,与数组倒数第二位元素交换,并维持残余堆的性质;
-
以此类推,在第 n-1 次操作后,整个数组就完成了排序。
代码实现
def sift_down(arr, start, end): # 计算父结点和子结点的下标 parent = int(start) child = int(parent * 2 + 1) while child <= end: # 子结点下标在范围内才做比较 # 先比较两个子结点大小,选择最大的 if child + 1 <= end and arr[child] < arr[child + 1]: child += 1 # 如果父结点比子结点大,代表调整完毕,直接跳出函数 if arr[parent] >= arr[child]: return else: # 否则交换父子内容,子结点再和孙结点比较 arr[parent], arr[child] = arr[child], arr[parent] parent = child child = int(parent * 2 + 1) def heap_sort(arr, len): # 从最后一个节点的父节点开始 sift down 以完成堆化 (heapify) i = (len - 1 - 1) / 2 while(i >= 0): sift_down(arr, i, len - 1) i -= 1 # 先将第一个元素和已经排好的元素前一位做交换,再重新调整(刚调整的元素之前的元素),直到排序完毕 i = len - 1 while(i > 0): arr[0], arr[i] = arr[i], arr[0] sift_down(arr, 0, i - 1) i -= 1
桶排序
简介
桶排序(Bucket sort)是排序算法的一种,适用于待排序数据值域较大但分布比较均匀的情况。
过程:
-
设置一个定量的数组当作空桶;
-
遍历序列,并将元素一个个放到对应的桶中;
-
对每个不是空的桶进行排序;
-
从不是空的桶里把元素再放回原来的序列中。
代码实现
N = 100010 w = n = 0 a = [0] * N bucket = [[] for i in range(N)] def insertion_sort(A): for i in range(1, len(A)): key = A[i] j = i - 1 while j >= 0 and A[j] > key: A[j + 1] = A[j] j -= 1 A[j + 1] = key def bucket_sort(): bucket_size = int(w / n + 1) for i in range(0, n): bucket[i].clear() for i in range(1, n + 1): bucket[int(a[i] / bucket_size)].append(a[i]) p = 0 for i in range(0, n): insertion_sort(bucket[i]) for j in range(0, len(bucket[i])): a[p] = bucket[i][j] p += 1
希尔排序
简介
希尔排序(Shell sort),也称为缩小增量排序法,是 插入排序 的一种改进版本。希尔排序以它的发明者希尔(英语:Donald Shell)命名。
过程
-
将待排序序列分为若干子序列(每个子序列的元素在原始数组中间距相同);
-
对这些子序列进行插入排序;
-
减小每个子序列中元素之间的间距,重复上述过程直至间距减少为 1。
代码实现
def shell_sort(array, length): h = 1 while h < length / 3: h = int(3 * h + 1) while h >= 1: for i in range(h, length): j = i while j >= h and array[j] < array[j - h]: array[j], array[j - h] = array[j - h], array[j] j -= h h = int(h / 3)
参考:
本文作者:LARRY1024
本文链接:https://www.cnblogs.com/larry1024/p/17693852.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步