数据结构与算法 排序篇(详细、kotlin):冒泡排序,选择排序,快速排序,希尔排序,堆排序等。

冒泡排序

冒泡排序的思想是:每次比较相邻的两个元素,如果前面的元素大于后面的元素,则将它们交换位置。这样,重复进行多次比较和交换,就能将数据从小到大排序。

换句话说,冒泡排序就是将最大的元素“冒”到数组的末尾。

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
      }
    }
  }
}
  1. 从数组的第一个元素开始,比较相邻的两个元素。
  2. 如果第一个元素比第二个元素大,则交换它们的位置。
  3. 重复上述步骤
  4. 优化:可以使用一个标志来表示是否有元素交换过位置。如果没有元素交换过位置,则说明数组已经排序好了,可以提前退出循环。

选择排序

选择排序的思想是:每次从未排序的元素中选择最小(或最大)的元素,并将其放到已排序序列的末尾。这样,重复进行多次比较和交换,就能将数据从小到大排序。

换句话说,选择排序就是每次将最小的元素“选”到数组的起始位置。

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
    }
  }
}

  1. 从数组的第一个元素开始,找到未排序元素中最小的元素。
  2. 将最小的元素交换到已排序的部分的最前面。
  3. 重复上述步骤

插入排序

插入排序算法的思想是:将待排序数列看成已经排好序的序列,每次从未排序的数列中取出一个元素,在已排序的序列中找到合适的位置插入,使其仍然有序。

换句话说就是:将未排序元素插入到已排序序列中。

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
}
  1. 初始时,已排序序列只有一个元素,即数组的第一个元素。
  2. 从数组的第二个元素开始,逐个将元素插入到已排序序列中。
  3. 每次插入时,将当前元素与已排序序列中的元素进行比较。
  4. 如果当前元素小于或等于已排序序列中的元素,则将其插入到已排序序列中的正确位置。
  5. 如果当前元素大于已排序序列中的元素,则将已排序序列中的元素向后移动一位,为当前元素腾出空间。

快速排序

快速排序的思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程递归进行,以此达到整个数据变成有序序列。

换句话说就是:将数据分割为两部分,然后分别对两部分进行排序。

快速排序算法通常选择待排序数组的第一个元素作为基准元素,然后将数组划分为两个部分:小于基准元素的部分和大于基准元素的部分。然后,再分别对这两个部分进行快速排序。

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. 递归终止条件:如果左边索引大于等于右边索引,则说明子数组中只有一个元素,直接返回。
  2. 选择基准值:通常选择数组中的第一个元素作为基准值。
  3. 将基准值放到正确的位置:使用双指针来实现,左指针从左边开始,右指针从右边开始,每次比较左指针和右指针指向的元素,如果左指针指向的元素小于基准值,则将左指针向右移动一位;如果右指针指向的元素大于基准值,则将右指针向左移动一位。如果左指针和右指针相遇,则将基准值放到左指针的位置。
  4. 递归排序基准值左边和右边的子数组:分别将基准值左边和右边的子数组作为新的子数组,递归调用快速排序算法进行排序。

希尔排序

希尔排序算法思想是:通过增量序列,将待排序数组分为若干子序列,然后分别对各子序列进行插入排序。随着增量逐渐缩小,各子序列中的元素越来越接近有序,最后,当增量为 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. 将大顶堆的堆顶元素与最后一个元素交换,并将堆的大小减 1。
  3. 重复步骤 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。然后,再将剩下的元素重新调整为最大堆,直到堆中只剩下一个元素。

归并排序

归并排序算法的思想是:利用归并操作进行排序。归并操作是将两个已经排好序的序列合并成一个新的排好序的序列。

归并排序算法的工作原理是:

  1. 将待排序数组分为两半。
  2. 递归地对两半数组进行排序。
  3. 将两半数组合并成一个排好序的数组。
    归并排序算法是一种稳定的排序算法,这意味着在排序完成后,具有相同值的元素将保留其原来的相对顺序。
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):归并排序是一种稳定的排序算法,适用于大规模数据集。它采用分治法的思想,将数据集分割成较小的子集,对每个子集进行排序,然后将子集合并以获得最终排序结果。归并排序的性能稳定且较好,常被用于外部排序和大型数据集的排序。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiet_h

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值