实现JavaScript中的数组排序功能

一、引言

在JavaScript中,数组是一种常用的数据结构,而排序是处理数组的常见任务。对于JavaScript中的数组排序,我们可以通过多种方式来实现。本篇博客将详细介绍如何使用JavaScript实现数组排序功能,并分享一些感悟。

二、实现过程

  1. 冒泡排序

冒泡排序是一种简单的排序算法,它重复地遍历待排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

以下是一个简单的冒泡排序实现:

function bubbleSort(arr) {  
    let len = arr.length;  
    for (let i = 0; i < len - 1; i++) {  
        for (let j = 0; j < len - 1 - i; j++) {  
            if (arr[j] > arr[j + 1]) {        // 如果前一个元素大于后一个元素,交换它们  
                let temp = arr[j];  
                arr[j] = arr[j + 1];  
                arr[j + 1] = temp;  
            }  
        }  
    }  
    return arr;  
}
  1. 快速排序

快速排序是一种分而治之的排序算法。它首先选择一个“基准”元素,然后将所有小于基准的元素移动到其左侧,将所有大于基准的元素移动到其右侧。然后对左侧和右侧的元素再次进行快速排序。

以下是一个简单的快速排序实现:

function quickSort(arr) {  
    if (arr.length < 2) { return arr; }  // 基本结束条件:如果数组只有一个元素,则已经排好序了  
    let pivot = arr[0];  // 选择第一个元素作为基准元素  
    let less = [];  // 存放所有小于基准的元素  
    let greater = [];  // 存放所有大于基准的元素  
    for (let i = 1; i < arr.length; i++) {  // 将其他元素与基准进行比较,并放入相应的数组中  
        if (arr[i] < pivot) {  
            less.push(arr[i]);  
        } else {  
            greater.push(arr[i]);  
        }  
    }  
    return quickSort(less).concat(pivot, quickSort(greater));  // 对左侧和右侧的元素递归进行快速排序,并将结果连接起来形成最终的排序数组  
}

 

插入排序:

  插入排序的基本思想是将数组分为已排序和未排序两部分,初始时已排序部分包含一个元素,之后从未排序部分取出元素,并在已排序部分找到合适的插入位置插入,并保持已排序部分一直有序,重复此过程,直到未排序部分元素为空。

function insertionSort(arr) {  
    for (let i = 1; i < arr.length; i++) {  
        let key = arr[i];  
        let j = i - 1;  
        while (j >= 0 && arr[j] > key) {  
            arr[j + 1] = arr[j];  
            j = j - 1;  
        }  
        arr[j + 1] = key;  
    }  
    return arr;  
}
  1. 选择排序

  2. 选择排序的基本思想是每一次从未排序部分选择最小的元素,将其放到已排序部分的末尾,重复此过程,直到未排序部分元素为空。

function selectionSort(arr) {  
    for (let i = 0; i < arr.length - 1; i++) {  
        let minIndex = i;  
        for (let j = i + 1; j < arr.length; j++) {  
            if (arr[j] < arr[minIndex]) {  
                minIndex = j;  
            }  
        }  
        let temp = arr[minIndex];  
        arr[minIndex] = arr[i];  
        arr[i] = temp;  
    }  
    return arr;  
}

 

  1. 归并排序:

  2. 归并排序是一种分治策略的排序算法,将数组分成两部分,分别对两部分进行排序,然后将有序的部分合并起来。这个过程递归进行,直到得到最终的有序数组。

function mergeSort(arr) {  
    if (arr.length < 2) { return arr; }  // 基本结束条件:如果数组只有一个元素,则已经排好序了  
    let mid = Math.floor(arr.length / 2);  // 找到中间点  
    let left = arr.slice(0, mid);  // 分割数组为两部分  
    let right = arr.slice(mid);  // 分割数组为两部分  
    return merge(mergeSort(left), mergeSort(right));  // 递归对左右两部分进行归并排序,并将结果合并起来形成最终的排序数组  
}  
function merge(left, right) {  // 将两个已排序的数组合并成一个已排序的数组  
    let result = [];  // 存放合并后的已排序数组的数组  
    while (left.length && right.length) {  // 当左右两个数组都还有元素时,将较小的元素放入result数组中,并从相应的数组中删除该元素  
        if (left[0] < right[0]) {  
            result.push(left.shift());  // 将左侧元素放入result数组中,并从left数组中删除该元素  
        } else {  
            result.push(right.shift());  // 将右侧元素放入result数组中,并从right数组中删除该元素  
        }  
    }  // 当其中一个数组的元素已经全部取出并放入result数组中时,将另一个数组剩下的元素直接全部放入result数组中(因为这些元素已经是已排序的了)  
    while (left.length) { result.push(left.shift()); }  // 如果左数组还有元素,将其全部放入result数组中  
    while (right.length) { result.push(right.shift()); }  // 如果右数组还有元素,将其全部放入result数组中  
    return result;  // 返回合并后的已排序数组  
}

 堆排序(Heap Sort):

  1. 堆排序是一种利用堆这种数据结构所设计的一种排序算法。它通过将一个无序数组构建成一个大顶堆或小顶堆,然后将堆顶元素(最大值或最小值)与堆尾元素互换,之后将剩余元素重新调整为大顶堆或小顶堆,以此类推,直到整个数组有序。

    当然,下面我将通过一个简单的例子来演示堆排序(Heap Sort)的过程。

    假设我们有一个无序数组:[4, 10, 3, 5, 1]。我们要将这个数组按照从小到大的顺序排列。在这个例子中,我们将使用小顶堆来进行排序。

    步骤 1:构建小顶堆

    首先,我们需要将数组转换成一个小顶堆。对于给定的数组,我们可以从最后一个非叶子节点开始,向上调整堆。

    数组的初始状态(索引从0开始):

    [4, 10, 3, 5, 1]  
      0   1   2   3   4

    从索引 (n-2)/2 开始,这里是 (5-2)/2 = 1,即元素 10。但由于 10 已经是叶子节点,我们不需要调整它。接着我们考虑它的前一个元素,即索引为 0 的元素 4

    调整后的堆(通过比较和交换):

    [3, 10, 4, 5, 1]  // 与子节点10比较,需要交换  
      0   1   2   3   4
    [1, 10, 4, 5, 3]  // 与子节点10和4比较,需要交换两次(先与4交换,再与10交换)  
      0   1   2   3   4

    现在我们有了一个小顶堆。

    步骤 2:交换堆顶元素与末尾元素

    接下来,我们将堆顶元素(最小值)与末尾元素交换位置:

    [1, 10, 4, 5, 3]  // 交换前  
    [3, 10, 4, 5, 1]  // 交换后,将最小值1放到了末尾位置  
      0   1   2   3   4
     

    交换后,我们移除了最小值 1,并且将其放到了正确的位置上。现在剩下的数组部分 [3, 10, 4, 5] 不再是一个小顶堆,我们需要重新调整它。

    步骤 3:重新调整堆

    重新调整剩余部分 [3, 10, 4, 5] 为小顶堆:

    [3, 5, 4, 10]  // 调整后的堆,通过与子节点比较和交换得到  
      0   1   2   3

    注意,我们现在不考虑已经排序好的最后一个元素 1

    重复步骤 2 和 3

    继续这个过程,直到整个数组有序:

    [3, 5, 4, 10]  // 当前堆状态  
    [4, 5, 3, 10]  // 交换堆顶和末尾元素  
    [4, 3, 5, 10]  // 重新调整剩余部分为小顶堆  
    [3, 4, 5, 10]  // 再次交换堆顶和末尾元素,现在数组已经有序了
     

    最终我们得到了一个有序的数组 [1, 3, 4, 5, 10]。需要注意的是,在实际执行过程中,我们不需要每次都重新构建整个堆,而只需要调整受影响的子堆部分。这里为了简单说明,我展示了整个数组的状态。在实际实现中,我们可以通过维护一个堆的数据结构来更有效地进行这些操作。

 希尔排序(Shell Sort):

希尔排序是一种基于插入排序的算法,通过比较相距一定间隔的元素,使得在较远元素比较时能够克服插入排序的局限性。希尔排序的时间复杂度介于O(n^2)和O(n log n)之间,具体取决于间隔序列的选择。

下面是一个希尔排序的例子:

假设我们有一个无序数组:[69, 56, 12, 136, 3, 55, 46, 99, 88, 25]

步骤 1:选择间隔序列

首先,我们需要选择一个间隔序列。常见的间隔序列有 [n/2, n/4, n/8, ..., 1] 或 [3, 7, 15, ..., n/2] 等。在这里,我们选择 [n/2, n/4, n/8, ..., 1] 作为间隔序列。

步骤 2:进行插入排序

接下来,我们按照间隔序列的顺序,对数组进行插入排序。

首先,取间隔 d=10/2=5,将数组分成两组进行直接插入排序:

[69, 56, 12, 136, 3, 55, 46, 99, 88, 25]  // 第一组  
[25, 3, 12, 55, 56, 88, 99, 12, 136, 69]  // 第二组

对每组分别进行插入排序:

[3, 12, 25, 55, 56, 69]  // 第一组插入排序结果  
[12, 25, 3, 55, 56, 88]  // 第二组插入排序结果

接下来,取 d=d/2=2,将数组分成两组进行直接插入排序:

[3, 12, 25, 55, 56, 69]  // 第一组  
[12, 25, 3, 55, 56, 88]  // 第二组

对每组分别进行插入排序:

[3, 12, 25]  // 第一组插入排序结果  
[3, 12]  // 第二组插入排序结果

最后,取 d=d/2=1,将整个数组作为一个序列进行直接插入排序:

[3, 12, 25]  // 进行一次插入排序后得到最终结果 [25, 12, 3]

最终我们得到了一个有序的数组 [25, 12, 3]。可以看到,通过希尔排序,我们能够更快地将数组排序。

 计数排序(Counting Sort):

计数排序是一种非比较型整数排序算法,适用于正整数数组的排序。它通过统计每个元素出现的次数,将其按照出现次数放入新的数组中。计数排序在处理大数据集时具有较好的性能表现,但不适用于非整数或小数元素的排序。

以下是一个简单的计数排序的例子:

假设我们有以下整数数组:

arr = [4, 2, 7, 1, 5, 2]

首先,我们需要找到数组中的最大值和最小值,以确定计数数组的范围。在这个例子中,最小值为1,最大值为7。

然后,我们创建一个计数数组count,其长度为(最大值 - 最小值 + 1)。在这个例子中,计数数组长度为(7 - 1 + 1) = 7。

count = [0, 0, 0, 0, 0, 0, 0]


接下来,我们遍历原始数组,统计每个元素出现的次数,并在计数数组中相应位置累加。

计数数组count更新后为:

[1, 2, 0, 1, 1, 0, 1]


现在,我们可以根据计数数组还原排序后的数组。我们从计数数组中取出元素,其位置表示原始数组中的值,元素的值表示原始数组中该值出现的次数。然后,我们将这些值按照计数数组的顺序放回原始数组。

最终得到的排序后的数组为:

[1, 2, 2, 4, 5, 7]

这就是计数排序的基本过程。

 基数排序(Radix Sort):

基数排序是一种非比较型整数排序算法,适用于正整数或负整数的排序。它通过将整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序在处理具有相同值元素的数组时具有较好的性能表现。

下面是一个基数排序的例子:

假设我们有一个整数数组:

arr = [170, 45, 75, 90, 802, 24, 2, 66]

首先,我们需要找到数组中最大数的位数,以便确定每个桶的个数。在这个例子中,最大数的位数是3位。

然后,我们创建一个长度为10的桶数组,每个桶用于存储一个位数的数字。桶的索引表示该位数的值,桶中的元素表示原始数组中该位数的数字。

接下来,我们按照从最低位到最高位的顺序遍历整数的每一位数,将每个数字放入相应的桶中。在这个例子中,我们先从个位开始。

个位上的数字有:[0, 2, 4, 6, 8, 9]
十位上的数字有:[1, 2, 4, 5, 7, 9]
百位上的数字有:[1, 2, 6, 8]

最后,我们将每个桶中的元素按照顺序合并起来,得到最终的排序结果:

[2, 24, 45, 66, 75, 90, 170, 802]

这就是基数排序的基本过程。注意,在实际应用中,我们需要根据具体情况选择合适的基数排序实现方式,例如选择从最高位开始排序还是从最低位开始排序,以及如何处理进位等情况。

三、总结和感悟

在实现数组排序功能的过程中,我们分别介绍了冒泡排序和快速排序两种算法。冒泡排序虽然简单易懂,但效率较低,适用于较小的数组排序。而快速排序虽然实现起来稍显复杂,但其平均时间复杂度为O(nlogn),具有较好的性能表现,适用于较大的数组排序。在实际应用中,我们可以根据具体需求选择合适的排序算法。

通过这次实践,我深刻体会到算法在编程中的重要性。掌握多种算法不仅可以提高我们的编程能力,还可以让我们在处理问题时更加得心应手。同时,我也认识到算法的学习是一个不断深入的过程,需要我们不断地实践和总结。在未来的学习和工作中,我将继续努力深入学习算法,提高自己的编程能力。

 

你可能感兴趣的:(javascript,前端,算法)