js数组按中文拼音排序_这才是选择排序正确的打开方式!

点击上方“ 五分钟学算法 ”,选择“星标”公众号

重磅干货,第一时间送达bbc0e3ee4bfb3e9988bde6dcb6384765.png

4ad21e72faa1810afba191390823a037.png

3fa24edb2cb6b8f9236e17fd324bd4a4.png
57f0aa30847bd19a7619fb4429417aaf.png
e42a0859bf6fdc80cc582ccc31c280a0.png

选择排序思想

选择排序(Selection Sort)的基本思想是不断地从数组当中未排序的部分选取关键字最小的记录,并将该记录作为已排序部分的最后一个记录(考虑升序排列的情况)。算法主要就是维护一个给定数组的两个子数组:

  1. 数组已排序的部分;
  2. 数组未排序的部分;

在选择排序的每一次迭代中,从数组中未排序的部分选择出最小元素(升序排列的情况),然后将其移入数组已排序的部分。

7a2f0e2932a98428ddee92ff26c17798.png7fc1f4038b92d8587b31eb4f4fa70d99.png

初始时,给定一个数组,且将该数组当中的所有元素都被划分为无序部分:

6b540a0438e9181cc3cf3ab3ba8f6b68.png

遍历数组 [0,7],找到下标为 5 最小的关键字 13:

71678bd819d4a4303f021f7377182cbf.png

下标为 5 最小的关键字 13 与下标为 0 的关键字 49 进行交换,这样就得到了数组有序部分的第一个关键字 13,而无序部分相应的减少了一个关键:

5223bef0cc62cddbe0eeee70ed6f21c3.png4b516fdc8095023cc36a6a3334ef3ab6.png

之后的所有操作和之前一样,在无序部分找到最小的关键字,然后与无序部分的第一个关键字交换,有序部分加一,无序部分减一:

1269792bc8bb48837f68821d7fe75d48.png15fc2fb1c818025dd40ae65bb41d50a6.png

c629d79321b1ed482fde80d3e2132321.png7317ba98d5883f5e20ddfe9c1f0b99ed.pngbf5dfba05fde6b861356c5fe2d6b08fc.png8fd3623de6f5de6d4043bcbd4de49cc0.pngc9a2dc6964df0393b46d96fe7684dece.png

简而言之,选择排序就两步:

  1. 选择最小(或最大)
  2. 交换

实现代码

C 实现

void swap(int *xp, int *yp) { 
 int temp = *xp; 
 *xp = *yp; 
 *yp = temp; 


void selectionSort(int arr[], int n) { 
 int i, j, min_idx; 

 // i就相当于将数组划分为有序部分和无序部分的边界,不断地让这个边界后移
 for (i = 0; i -1; i++) 
 { 
  //找到数组中无序部分的最小关键字
  min_idx = i; 
  for (j = i+1; j   if (arr[j]    min_idx = j; 

  // 将最小关键字与无序部分的第一给关键字交换
  swap(&arr[min_idx], &arr[i]); 
 } 

Java实现

class SelectionSort { 
 void sort(int arr[]) { 
  int n = arr.length; 

  for (int i = 0; i 1; i++) 
  { 
   int min_idx = i; 
   for (int j = i+1; j                 if (arr[j]      min_idx = j; 
            } 

   int temp = arr[min_idx]; 
   arr[min_idx] = arr[i]; 
   arr[i] = temp; 
  } 
 } 

Python实现

def SelectionSort(A):
    for i in range(len(A)): 
        min_idx = i 
        for j in range(i+1, len(A)): 
            if A[min_idx] > A[j]: 
                min_idx = j 

        A[i], A[min_idx] = A[min_idx], A[i] 
    return A

复杂度分析

时间复杂度

上面的循环中的 if 语句最坏情况下一共计算了多少次?

当 i == 0 的时候,j 的取值范围是从 1 到 n -1,内循环的判断一种执行了 n - 1 次;

当 i == 1 的时候,j 的取值范围是从 2 到 n -1,内循环的判断一种执行了 n - 2 次;

以此类推......

当 i 取最大值 n - 2 时,j 的取值为 n -1,内循环的判断执行了1 次;

所以,整体内循环的判断语句执行次数就是:1 + 2 + 3 + ... + (n - 2) + (n - 1) 。

这种计算一个高斯公式搞定,则 if 执行了就是 次,时间复杂度为 量级。

空间复杂度

选择排序属于原地排序(In-place Sorting),因为选择排序没有使用任何额外的空间,仅使用了数组自身所占用的空间,所以空间复杂度就是 。你也可以认为原地排序算法就是空间复杂度为 的算法。

稳定性分析

关于排序算法的稳定性问题,景禹在之前的一篇文章 排序算法的稳定性 中有分享,这里我们就直接分析选择排序的稳定性问题。

我可以直接告诉你选择排序的默认实现方式是不稳定的,具体为神马,我们接着看一个例子:

1ec9d6e829058dc8c6e139a11e3b6f57.png

给定上面一个数组,我们按照前面的实现方式进行排序。

第一步:在数组中找到最小的关键字 1 ,并与数组中的第一个元素(红色色块 4)交换位置:

2fd9eb792dac88445274b9827493dcfa.png

第二步:在数组中无序部分 [5,3,2,4,4] 找到最小的关键字 2 与无序部分的第一个关键字 5 交换位置:

fe12d59bd06a3b36b06234fd407247af.png

第三步:在数组中无序部分 [3,5,4,4] 中找到最小的关键字 3 和无序部分的第一个关键字 3 交换,和之前一样:

fe12d59bd06a3b36b06234fd407247af.png

第四步:在数组中无序部分 [5,4,4] 中找到最小的关键字 4(注意是蓝色色块的4)  和 5 交换:

f7b2fc0a70a01e346b6e87400079ad79.png

第五步:在数组中无序部分 [5,4] 中找到最小的关键字 4(注意是红色色块的4)  和 5 交换:

b76cb86487177fa691ac0028bc2a8eed.png

此时我们得到一个有序数组,但是与原始的数组相比,两个 4 的相对位置发生了变化。即,本来红色色块的 4 在蓝色色块的 4 的前面,而排序后蓝色的在红色的前面,这就是我们之前所说的不稳定(两个值相同的关键字排序前后的相对位置发生了变化)。也就是说目前的实现方式下的选择排序是不稳定的。

88570aa8199caf4f45409e2709b1363e.pnge3326573a756eee7ef7b9bdff7109790.png

稳定的选择排序

不稳定的选择排序结果:

2d1cdf25ccb72e5d8bbed377209b81b6.png

目标 -- 实现一个稳定的选择排序:

ef7c0bf0a9b54e938cda90cc0ccb2286.png

为了实现现在这一目标,我们分析一下原始的选择排序为什么不稳定?

答案就是交换操作造成了不稳定。选择排序的工作原理就是在未排序的部分找到关键字最小的元素,然后将该元素与未排序部分的第一个元素进行交换位置。也就是这个交换操作导致其不稳定,比如前面分析的时候,第一次交换就是的 4 得相对位置发生了变化。

fdee24a380b49317906aaadc9f003b01.png

因此可以考虑对这里的交换操作进行修改使得选择排序变得稳定。

要想每一次将最小元素放置在其位置而不进行交换,可以通过将每一次选择出的最小关键字前面的无序数组元素都向后移动一个位置,使选择排序稳定。简单来说,就是利用类似于插入排序的技术将最小的元素插入正确的位置。

1ec9d6e829058dc8c6e139a11e3b6f57.png

第一步:找到最小元素是 1 ,此时 不再 是将红色色块 4 和最小元素 1 进行交换,而是将 1 插入到正确的位置,然后将 1 之前的每一个元素都向后移动一个位置:5055171d8714c26670a2c87418c7e806.png

第二步:找到数组无序部分的最小元素 2 ,将 2 之前的 [4,5,3] 的每一个元素向后移动一个位置:

f161d054be7f21a5182769c64af5deb7.png

第三步:找到数组无序部分的最小元素 3 ,将 3 之前的 [4,5] 的每一个元素向后移动一个位置:

d29fa4e6c35d72dd83b6bea0d3bc7edd.png

第四步:找到数组无序部分的最小元素 4(红色色块) ,其前面没有无序元素,什么都不做;

第五步:找到数组无序部分的最小元素 4(蓝色色块) ,将其前面的元素 5 向后移动,我们得到了一个稳定的有序数组:

c205709027cbdf1b1f78aea6d40ce491.png

稳定的选择排序的实现

Java实现

class JingYuSorting { 
 static void stableSelectionSort(int[] a, int n) { 
  // 与默认的实现方式相同
  for (int i = 0; i 1; i++) 
  { 
   // a[i - 1] 之前的元素为数组的有序部分 
   // 从 arr[i] 到 arr[n - 1] 找到最小元素的下标保存到min中
   int min = i; 
   for (int j = i + 1; j     if (a[min] > a[j]) 
     min = j; 

   // 将最小的元素移动到当前的位置 i.
   int key = a[min]; 
            // 将从 i 到 min - 1 的元素都向后移动一个位置
   while (min > i) 
   { 
    a[min] = a[min - 1]; 
    min--; 
   } 
   // 将当前选择的最小元素放到正确的位置
   a[i] = key; 
  } 
 } 

复杂度分析

时间复杂度

时间复杂度为 ,整体依旧是两层 for 循环嵌套。但是稳定的选择排序需要移动元素,每一次选择出一个最小元素,就需要对其前面无序的元素向后移动,最坏的情况是,第一次移动 n-1 次,第二次移动 n - 2 次,......,第 n-1 次移动 1 次,总共移动 ,这并不会影响到整个实现的时间复杂度的量级。

空间复杂度

稳定的选择排序依旧没有使用任何额外的空间,所有操作都是在数组内进行的,所以空间复杂度为 .

实战演练

给定一个字符串数组,使用选择排序对数组进行排序。

输入: paper true soap floppy flower 输出: floppy, flower, paper, soap, true

我们前面所讲的所有例子都是用整数进行说明的,这里要使用选择排序对字符串数组进行排序,我们仅需要对原始的实现中整型的比较操作和拷贝操作转化为字符串的比较和拷贝操作。

java 中字符串的比较操作使用 compareTo() 函数即可;C++/C 的比较操作可以使用 strcmp() 函数进行比较,拷贝可以使用 strcpy() 函数进行拷贝。这里就给大家提供 Java 的参考代码。

题外话:面试中,好多面试官会考察函数 strcmp()strcpy() 的底层实现,对这个点不太熟悉的小禹禹可以了解一下,觉得没时间,希望景禹解析的,评论区留下你的足迹。

// 使用选择排序对字符串数组进行排序
static void selectionSort(String arr[],int n) { 
 // 将有序部分和无序部分的界限 i 不断向后移动
 for(int i = 0; i 1; i++) 
 { 
 
  // 在数组未排序部分找到最小的字符串
  int min_index = i; //保存最小的字符串的下标
  String minStr = arr[i]; //保存最小的字符串
  for(int j = i + 1; j   { 
   if(arr[j].compareTo(minStr) 0) 
   { 
    minStr = arr[j]; 
    min_index = j; 
   } 
  } 
        // 将最小字符串与 位置为 i 的字符串进行交换
        if(min_index != i) 
        { 
            String temp = arr[min_index]; 
            arr[min_index] = arr[i]; 
            arr[i] = temp; 
        } 
 } 
}

07a7704584acecb39bf3512aac436343.png6b19242623ceb42ed6ba10bc00240f49.png

网站推荐

给大家推荐一个可视化网站:https://visualgo.net/zh/sorting

我们以今日的选择排序给大家讲一下使用的方法。

输入网址后,进入如下界面:

34fe3095918222dcbcb616d15ed0c5f7.png

但看着密密麻麻的英文,我们直接按 ESC 键。

紧接着选择 zh ,中文模式:

cfdd17272bd4bb3bad09f602498a83d8.png

导航栏中分别为:冒泡排序、选择排序(SEL)、插入排序(INS)、归并排序(MER)、

快速排序(QUI)、随机快速排序(R-Q)、计数排序(COU)、基数排序(RAD)。

我们点击 SEL,即选择排序:

6297004f8fd1b4dc940365116d4ea4a0.png

默认提供了一组输入数组,你也可以通过下图中几种方式自己生成:

f6a9f2e953bb97935665013f0c657893.png

我们输入我们的示例输入 [4,5,3,2,4,1] :

816c995b141dbc52203dc74c1d4f0040.png

然后就是进行选择排序了,依次点击排序→执行:

21f4ea3c9835dfa05f3675e537605fec.png

紧接着你就可以看到下面的执行动画了:

8e6ec25b56d78f71532898fac3a27657.gif

就到这里了,记得一定要自己去探索一下奥~~

aa11e93b5bf4da852c9568e89060d5cb.png4bcf2e6a33b7d13d8db920cbcae6b9e0.png


推荐阅读

•   吴师兄实名吐槽 LeetCode 上的一道题目。。。•   面试字节跳动时,我竟然遇到了原题……•   Leetcode 惊现马化腾每天刷题 ?为啥大佬都这么努力!•   为什么 MySQL 使用 B+ 树•   一道简简单单的字节跳动算法面试题•   新手使用 GitHub 必备的两个神器•   卧槽!红警代码竟然开源了!!!


欢迎关注我的公众号“五分钟学算法”,如果喜欢,麻烦点一下“在看”~

7f52877cea1d6c12b96136620e8412c5.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值