冒泡排序
冒泡排序的思想是:每次比较相邻的两个元素,如果前面的元素大于后面的元素,则将它们交换位置。这样,重复进行多次比较和交换,就能将数据从小到大排序。
换句话说,冒泡排序就是将最大的元素“冒”到数组的末尾。
fun bubbleSort(array: Array<Int>) {
for (i in array.indices) {
for (j in 0 until array.size - i - 1) {
if (array[j] > array[j + 1]) {
val temp = array[j]
array[j] = array[j + 1]
array[j + 1] = temp
}
}
}
}
- 从数组的第一个元素开始,比较相邻的两个元素。
- 如果第一个元素比第二个元素大,则交换它们的位置。
- 重复上述步骤
- 优化:可以使用一个标志来表示是否有元素交换过位置。如果没有元素交换过位置,则说明数组已经排序好了,可以提前退出循环。
选择排序
选择排序的思想是:每次从未排序的元素中选择最小(或最大)的元素,并将其放到已排序序列的末尾。这样,重复进行多次比较和交换,就能将数据从小到大排序。
换句话说,选择排序就是每次将最小的元素“选”到数组的起始位置。
fun selectionSort(array: Array<Int>) {
for (i in 0 until array.size - 1) {
// 找到未排序元素中最小的元素
var minIndex = i
for (j in i + 1 until array.size) {
if (array[j] < array[minIndex]) {
minIndex = j
}
}
// 将最小的元素交换到已排序的部分的最前面
if (minIndex != i) {
val temp = array[i]
array[i] = array[minIndex]
array[minIndex] = temp
}
}
}
- 从数组的第一个元素开始,找到未排序元素中最小的元素。
- 将最小的元素交换到已排序的部分的最前面。
- 重复上述步骤
插入排序
插入排序算法的思想是:将待排序数列看成已经排好序的序列,每次从未排序的数列中取出一个元素,在已排序的序列中找到合适的位置插入,使其仍然有序。
换句话说就是:将未排序元素插入到已排序序列中。
fun insertionSort(array: Array<Int>): Array<Int> {
// 从数组的第二个元素开始,逐个将元素插入到已排序的序列中
for (i in 1 until array.size) {
// 将当前元素与已排序序列中的元素进行比较
var j = i - 1
val key = array[i]
while (j >= 0 && array[j] > key) {
// 将已排序序列中的元素向后移动一位
array[j + 1] = array[j]
j--
}
// 将当前元素插入到正确的位置
array[j + 1] = key
}
return array
}
- 初始时,已排序序列只有一个元素,即数组的第一个元素。
- 从数组的第二个元素开始,逐个将元素插入到已排序序列中。
- 每次插入时,将当前元素与已排序序列中的元素进行比较。
- 如果当前元素小于或等于已排序序列中的元素,则将其插入到已排序序列中的正确位置。
- 如果当前元素大于已排序序列中的元素,则将已排序序列中的元素向后移动一位,为当前元素腾出空间。
快速排序
快速排序的思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程递归进行,以此达到整个数据变成有序序列。
换句话说就是:将数据分割为两部分,然后分别对两部分进行排序。
快速排序算法通常选择待排序数组的第一个元素作为基准元素,然后将数组划分为两个部分:小于基准元素的部分和大于基准元素的部分。然后,再分别对这两个部分进行快速排序。
fun quickSort(array: Array<Int>, left: Int, right: Int): Array<Int> {
// 递归终止条件
if (left >= right) {
return array
}
// 选择基准值
val pivot = array[left]
// 将基准值放到正确的位置
var i = left
var j = right
while (i < j) {
while (array[j] > pivot) {
j--
}
while (array[i] < pivot) {
i++
}
if (i < j) {
// 交换 array[i] 和 array[j] 的值
val temp = array[i]
array[i] = array[j]
array[j] = temp
}
}
// 递归排序基准值左边的子数组
quickSort(array, left, i - 1)
// 递归排序基准值右边的子数组
quickSort(array, i + 1, right)
return array
}
- 递归终止条件:如果左边索引大于等于右边索引,则说明子数组中只有一个元素,直接返回。
- 选择基准值:通常选择数组中的第一个元素作为基准值。
- 将基准值放到正确的位置:使用双指针来实现,左指针从左边开始,右指针从右边开始,每次比较左指针和右指针指向的元素,如果左指针指向的元素小于基准值,则将左指针向右移动一位;如果右指针指向的元素大于基准值,则将右指针向左移动一位。如果左指针和右指针相遇,则将基准值放到左指针的位置。
- 递归排序基准值左边和右边的子数组:分别将基准值左边和右边的子数组作为新的子数组,递归调用快速排序算法进行排序。
希尔排序
希尔排序算法思想是:通过增量序列,将待排序数组分为若干子序列,然后分别对各子序列进行插入排序。随着增量逐渐缩小,各子序列中的元素越来越接近有序,最后,当增量为 1 时,整个数组已经基本有序,再进行一次插入排序,即可得到最终结果。
具体来说,希尔排序算法首先计算出一个增量序列,该序列通常是递减的,例如 {1, 4, 13, 40, …}。然后,算法从增量序列的第一个元素开始,对各子序列进行插入排序。在每个子序列中,算法从增量处开始,对相邻元素进行比较和交换,直到增量为 1。
希尔排序算法是一种改进的插入排序算法,比插入排序算法更快。这是因为希尔排序算法通过增量序列,将待排序数组分为若干子序列,然后分别对各子序列进行插入排序。这样,可以使待排序数组中的元素相对接近有序,从而减少插入排序算法中比较和交换元素的次数。
希尔排序算法的时间复杂度为 O(n log n),空间复杂度为 O(1)。它是一种稳定的排序算法。
fun shellSort(array: IntArray) {
// 计算增量序列
val increments = generateIncrements(array.size)
// 对每个增量进行排序
for (increment in increments) {
// 从增量处开始,对相邻元素进行比较和交换
for (i in 0 until array.size - increment) {
var j = i + increment
while (j >= 0 && array[j] < array[j - increment]) {
// 交换元素
val temp = array[j]
array[j] = array[j - increment]
array[j - increment] = temp
j -= increment
}
}
}
}
// 生成增量序列
fun generateIncrements(n: Int): List<Int> {
val increments = mutableListOf(1)
while (increments.last() < n / 3) {
increments.add(increments.last() * 3 + 1)
}
return increments
}
该算法首先计算增量序列,然后对每个增量进行排序。在每个增量中,算法从增量处开始,对相邻元素进行比较和交换,直到增量为 1。
堆排序
堆排序算法的思想可以概括为:
- 将待排序数组构建为一个大顶堆。
- 将大顶堆的堆顶元素与最后一个元素交换,并将堆的大小减 1。
- 重复步骤 2,直到堆中只剩下一个元素。
fun heapSort(array: IntArray) {
// 构建大顶堆
for (i in array.size / 2 - 1 downTo 0) {
heapify(array, array.size, i)
}
// 将最大元素与最后一个元素交换,并将堆的大小减 1
for (i in array.size - 1 downTo 1) {
val temp = array[0]
array[0] = array[i]
array[i] = temp
// 将剩下的元素重新调整为最大堆
heapify(array, i, 0)
}
}
// 将堆的某个元素调整为大顶堆
fun heapify(array: IntArray, n: Int, i: Int) {
// 子节点索引
val leftChildIndex = 2 * i + 1
val rightChildIndex = 2 * i + 2
// 找到最大元素的索引
var maxIndex = i
if (leftChildIndex < n && array[leftChildIndex] > array[maxIndex]) {
maxIndex = leftChildIndex
}
if (rightChildIndex < n && array[rightChildIndex] > array[maxIndex]) {
maxIndex = rightChildIndex
}
// 如果最大元素不是当前元素,则交换两者的位置
if (maxIndex != i) {
val temp = array[i]
array[i] = array[maxIndex]
array[maxIndex] = temp
// 递归调整大顶堆
heapify(array, n, maxIndex)
}
}
首先构建一个大顶堆,然后将最大元素与最后一个元素交换,并将堆的大小减 1。然后,再将剩下的元素重新调整为最大堆,直到堆中只剩下一个元素。
归并排序
归并排序算法的思想是:利用归并操作进行排序。归并操作是将两个已经排好序的序列合并成一个新的排好序的序列。
归并排序算法的工作原理是:
- 将待排序数组分为两半。
- 递归地对两半数组进行排序。
- 将两半数组合并成一个排好序的数组。
归并排序算法是一种稳定的排序算法,这意味着在排序完成后,具有相同值的元素将保留其原来的相对顺序。
fun mergeSort(array: IntArray) {
// 递归基
if (array.size <= 1) {
return
}
// 将数组分为两半
val mid = array.size / 2
val leftArray = array.copyOfRange(0, mid)
val rightArray = array.copyOfRange(mid, array.size)
// 递归排序两半数组
mergeSort(leftArray)
mergeSort(rightArray)
// 合并两半数组
merge(array, leftArray, rightArray)
}
fun merge(array: IntArray, leftArray: IntArray, rightArray: IntArray) {
var i = 0
var j = 0
var k = 0
while (i < leftArray.size && j < rightArray.size) {
if (leftArray[i] <= rightArray[j]) {
array[k] = leftArray[i]
i++
} else {
array[k] = rightArray[j]
j++
}
k++
}
// 将剩余元素复制到数组中
while (i < leftArray.size) {
array[k] = leftArray[i]
i++
k++
}
while (j < rightArray.size) {
array[k] = rightArray[j]
j++
k++
}
}
该算法首先将数组分为两半,然后递归地对两半数组进行排序。最后,再将两半数组合并成一个排好序的数组。
对比总结:
不同的排序算法适用于不同的实际场景
冒泡排序(Bubble Sort):冒泡排序是一种简单但效率较低的排序算法,它适用于小型数据集或已经接近排序的数据集。由于其简单性,冒泡排序通常用于教学和理解排序算法的基本概念。
选择排序(Selection Sort):选择排序是一种简单且稳定的排序算法,适用于中小规模的数据集。它的原理是每次选择最小的元素放置到已排序部分的末尾。选择排序不适合大规模数据集,因为其时间复杂度为O(n^2),效率较低。
插入排序(Insertion Sort):插入排序是一种简单且稳定的排序算法,适用于小规模或基本有序的数据集。插入排序的原理是将元素逐个插入到已排序部分的正确位置。由于其简单性和稳定性,插入排序在小型数据集或部分有序的数据集上表现良好。
快速排序(Quick Sort):快速排序是一种高效的排序算法,适用于大规模数据集。它采用分治法的思想,通过选择一个基准元素将数据集分割成较小和较大的两个子集,并对子集进行递归排序。快速排序通常是最常用的排序算法之一,广泛应用于各种场景。
希尔排序(Shell Sort):希尔排序是一种改进的插入排序算法,适用于中等大小的数据集。它通过将数据集按一定间隔分组,对每个分组进行插入排序,然后逐渐减小间隔,最终完成排序。希尔排序在大型数据集上的性能相对较好。
堆排序(Heap Sort):堆排序是一种高效的排序算法,适用于大规模数据集。它利用二叉堆的性质进行排序,构建最大堆或最小堆,并逐步将最大或最小元素放置在已排序部分的末尾。堆排序对于需要快速获取最大或最小元素的场景非常有用。
归并排序(Merge Sort):归并排序是一种稳定的排序算法,适用于大规模数据集。它采用分治法的思想,将数据集分割成较小的子集,对每个子集进行排序,然后将子集合并以获得最终排序结果。归并排序的性能稳定且较好,常被用于外部排序和大型数据集的排序。