[排序算法二]选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

算法性能

  • 时间复杂度:O(n^2),总循环次数 n(n-1)/2。数据交换次数 O(n),这点上来说比冒泡排序要好,因为冒泡是把数据一位一位的移上来,而选择排序只需要在子循环结束后移动一次即可。冒泡的极端情况下,交换次数是O(n^2)
  • 稳定性:不稳定

为啥说这货不稳定涅

举个例子来说吧,有这样一个数组 [5, 2, 5*],假设 5* 就是 5,这里加个星号是为了与第一个5区分出来。对这个数组使用选择排序,最后的结果是[2, 5* ,5],破坏了 5 这个元素在原数组中的排序,所以说这货是不稳定的。

算法步骤:

  • 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  • 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  • 重复第二步,直到所有元素均排序完毕。

又盗图了,再次请求谅解...

上面算法步骤中有个词儿叫未排序序列,这里稍微解释下:

假设有一个10个数字的数组需要排序,那初始时这10个数字就是未排序序列,
经过第一次循环,我们会把下标为[0]元素替换成数组中最小的数字,也就是说此元素已经被排序,那剩下的下标为[1-9]的9个数字才是未排序序列。

举个例子吧,数组 [1, 7, 4, 8]

  1. 下标为0的元素1不动,遍历后面的数字7, 4, 8,找到最小数字为4,记录下它的下标为2,因为 1 < 4,所以不做任何操作,数组依然是[1, 7, 4, 8]
  2. 下标为1的元素7不动,遍历后面的数字4, 8,找到最小数字为4,记录下它的下标为2,因为 7 > 4,所以把下标为1和下标为2的元素互换,数组变成[1, 4, 7, 8]
  3. 继续以上步骤...直到把所有元素遍历一遍

这样看下来,过程还是很简单的,就是两个嵌套,代码实现如下:

public void Sort1(int[] numbers)
{
    var count1 = 0;
    var count2 = 0;

    var length = numbers.Length;

    for (var i = 0; i < length; i++)
    {
        var minIndex = i;

        for (var j = i + 1; j < length; j++)
        {
            if (numbers[j] < numbers[minIndex])
            {
                minIndex = j;
            }

            count1++;
        }

        if (minIndex != i)
        {
            var temp = numbers[i];
            numbers[i] = numbers[minIndex];
            numbers[minIndex] = temp;

            count2++;
        }
    }

    Console.WriteLine($"结果:{string.Join(",", numbers)};循环次数:{count1};交换次数:{count2}");
}

算法优化

选择排序的核心思路无外乎就是用未排序序列中的第一个元素和其他元素中最小的那个元素比较,如果第一个元素比较大,那就把两个元素互换。
发散一下,反正是一次遍历,如果同时把最小和最大两个元素找出来,分别跟未排序序列的第一个元素和最后一个元素比较,不是一次性就能对两个元素进行排序吗? 这样一来,遍历的次数也就只需要数组长度的一半即可,想想就让人兴奋。

对给定数组 [4, 7, 3, 8, 6] 进行第一次循环:

  • 找到最小值3,将它与第一个元素互换 [3, 7, 4, 8, 6]
  • 找到最大值8,将它与最后一个元素互换 [3, 7, 4, 6, 8]

这样在一次循环中,我们找到了数组的最小值和最大值,并将它们放置到了数组的相应位置。换句话说,数组首尾两个元素都进行了排序,此时,未排序序列变成了7, 4, 6,接下来我们只需对这三个元素再进行排序即可。

当然,世事不会总那么一番风顺,上面的思路乍一看很OK,但其实还是有点小坑的。

因为要同时操作最大和最小两个元素,所以在一次遍历后我们需要两次互换的操作,一般顺序是先处理最小值,再处理最大值,那问题就来了,
当未排序序列的第一项比其他项都大 的情况下,如:[9, 8, 7, 6] ,我们一般思路是这样的:

  • 第一个元素,下标为0值为9
  • 声明两个变量,用于保存其余元素中的最小值及其下标,初始化都指向第一个元素 var min = 9; var minIndex = 0;
  • 声明两个变量,用于保存其余元素中的最大值及其下标,初始化都指向第一个元素 var max = 9; var maxIndex = 0;
  • 遍历8, 7, 6
  • 找到最小值为6下标为3,所以更新最小值及其索引 min = 6; minIndex = 3;
  • 没有找到比9更大的元素,所以此时最大值及其索引依然是 max = 9; maxIndex = 0;
  • 先处理最小值,发现 9 > 6,需要将两者(下标是[0]的元素和下标是[3]的元素)互换,互换之后数组变成 [6,8,7,9]
  • 接着处理最大值,我们的思路是,把下标是maxIndex的元素与最后一个元素互换,这时最大值的下标是0,最后一个元素的下标是3,如果直接将两者互换会发生什么情况呢?因为刚才第[0]个元素已经与第[3]个元素发生了互换,当前的数组已经是 [6,8,7,9]了,再换一次,又变回了 [9,8,7,6],这显然是不对的,事实上,当上一步结束后,最大值[9]的下标已经变成了minIndex,所以这里要处理一下 maxIndex 的指向,将它指到 [9] 当前的位置上 maxIndex = minIndex

罗里吧嗦一堆,自己看了都头昏,还是直接上代码吧

public void Sort3(int[] arr)
{
    var len = arr.Length;
    var left = 0;
    var right = len - 1;

    var count1 = 0;
    var count2 = 0;

    while (left < right)
    {
        int max = left;//记录无序区最大元素下标
        int min = left;//记录无序区最小元素下标
        int j = 0;
        for (j = left + 1; j <= right; j++)
        {
            //找最大元素下标
            if (arr[j] < arr[min])
            {
                min = j;
            }
            //找最小元素下标
            if (arr[j] > arr[max])
            {
                max = j;
            }

            count1++;
        }

        //最小值如果是第一个则没有必要交换
        if (min != left)
        {
            int tmp = arr[left];
            arr[left] = arr[min];
            arr[min] = tmp;

            count2++;
        }

        // 手动高亮
        // left 是第一个元素的下标,max是最大值的下标,
        // left == max 说明第一个元素的值比剩余元素都大,这时就需要更新索引
        if (max == left)
        {
            max = min;
        }

        //最大值如果是最后一个则没必要交换
        if (max != right)
        {
            int tmp = arr[right];
            arr[right] = arr[max];
            arr[max] = tmp;

            count2++;
        }
        left++;
        right--;
    }

    Console.WriteLine($"结果:{string.Join(",", arr)};循环次数:{count1};交换次数:{count2}");
}

上面的代码是我从其他博客上抄的,因为感觉比我自己实现的要好一些,下面是我自己写的:

public void Sort2(int[] numbers)
{
    var count1 = 0;
    var count2 = 0;

    for (var i = 0; i < numbers.Length / 2; i++)
    {
        var left = i;
        var right = numbers.Length - i - 1;

        var minIndex = left;
        var maxIndex = left;

        for (var j = left + 1; j <= right; j++)
        {
            if (numbers[j] < numbers[minIndex])
            {
                minIndex = j;
            }

            if (numbers[j] > numbers[maxIndex])
            {
                maxIndex = j;
            }

            count1++;
        }

        if (left != minIndex)
        {
            var temp = numbers[minIndex];
            numbers[minIndex] = numbers[left];
            numbers[left] = temp;

            count2++;
        }

        // 如果第一个就是最大的,因为已经把他移动到minIndex上了,所以这里要更新下索引
        if (left == maxIndex)
        {
            maxIndex = minIndex;
        }

        if (right != maxIndex)
        {
            var temp = numbers[right];
            numbers[right] = numbers[maxIndex];
            numbers[maxIndex] = temp;

            count2++;
        }
    }

    Console.WriteLine($"结果:{string.Join(",", numbers)};循环次数:{count1};交换次数:{count2}");
}

后面两个版本其实代码的运行结果是一致的,只是第三个版本看起来不如第二个直观,仅做记录,毕竟也是费了些脑力搞出来的,不拉出来溜溜感觉有点可惜~~手动捂脸

原文地址:https://www.cnblogs.com/diwu0510/p/12334101.html

时间: 2024-08-07 20:59:46

[排序算法二]选择排序的相关文章

排序算法(二)选择排序---堆排序

概念:利用树结构进行排序. 分类:1.大顶堆: 每个小树的根节点都大于子节点   升序排序使用大顶堆 2.小顶堆:每个小树的子节点都大于根节点 降序排序使用小顶堆 1 public class HeapSort { 2 3 public static void main(String[] args){ 4 int[] arr=new int[]{9,6,7,0,1,10,4,2}; 5 System.out.println(Arrays.toString(arr)); 6 heapSort(ar

排序算法之选择排序

一. 算法描述 选择排序:在一个长度为N的无序数组中,在第一趟遍历N个数据,找出其中最小的数值与第一个元素交换,第二趟遍历剩下的N-1个数据,找出其中最小的数值与第二个元素交换......第N-1趟遍历剩下的2个数据,找出其中最小的数值与第N-1个元素交换,至此选择排序完成. 二. 算法分析 平均时间复杂度:O(n2) 空间复杂度:O(1)  (用于交换和记录索引) 稳定性:不稳定 (比如序列[5, 5, 3]第一趟就将第一个[5]与[3]交换,导致第一个5挪动到第二个5后面) 三. 算法实现

数据结构排序算法之选择排序

今天继续介绍一种排序算法:选择排序. 选择排序的基本思想就是从待排序列中选择出最小的,然后将被选出元素和序列的第一个元素互换位置(当前默认是升序排列),则互换完成后第一个元素就是整个序列的最小的元素,则一次选择排序结束.然后我们从剩下的子序列中选择出最小的,然后将该被选出来的元素和该子序列的第一个元素(即整个序列的第二个元素)互换位置,则当前整个序列的第二个元素就是当前序列中的次最小值,第二次选择排序结束.以此类推,直到该待排序列只剩下一个元素后,则整个序列有序. 具体过程如下图所示: 下面就不

【排序算法】选择排序(Selection sort)

0. 说明 选择排序(Selection sort)是一种简单直观的排序算法. 它的工作原理如下. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾.以此类推,直到所有元素均排序完毕. 选择排序的主要优点与数据移动有关.如果某个元素位于正确的最终位置上,则它不会被移动.选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对 n 个元素的表进行排序总共进行至多 n-1 次交换.在所有的完全依

初级排序算法之选择排序

初级排序算法 本质是对要排序的数组进行嵌套循环,内层循环负责局部的排序,外层循环负责剩余的无序元素的递减.所以你只要理解嵌套循环和比较大小就能很快的掌握初级排序算法. 选择排序 一个无序的数组 a = [0, 4, 6, 3, 8, 2, 3, 9], 你也可以把a的元素想象成任何现实中可比较的具体物体.例如,有10根长短不一的木条,我们如何对它们进行排序?一个最直接的思想,先拿出最短的放到最前面,在剩余的木条中再拿出最短的放在第二位...直到最后一根木条.从中我们可以看出,1. 我们需要再一次

【排序算法】选择排序

选择排序算法原理 选择排序算法时间复杂度分析 选择排序算法稳定性分析 选择排序算法C语言代码 #include <stdio.h> //交换两个元素的值 void swap(int* a, int* b) { int temp; temp = *a; *a = *b; *b = temp; } void selectionSort(int arr[], int length) { int i, j, maxIndex; for(i = length; i > 0; i--) { //假设

算法学习之排序算法:选择排序

选择排序:每一趟在n-i+1(i=1,2,...,n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录. 一.简单选择排序 一趟选择排序操作: 通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换之. 对L[1...n]中记录进行简单选择排序的算法为:令i从1至n-1,进行n-1趟选择操作.简单选择排序过程中,所需进行记录移动的操作次数较少,然而,无论记录的初始排列如何,所需关键字间的比较次数相同.因此,总的时间复杂度为O(n^2)

选择排序算法---直接选择排序和堆排序

本文主要是解析选择排序算法:直接选择排序和堆排序. 一.直接选择排序   基本思想:       选择排序(Selection sort)是一种简单直观的排序算法.它的工作原理如下.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾.以此类推,直到所有元素均排序完毕. 选择排序的主要优点与数据移动有关.如果某个元素位于正确的最终位置上,则它不会被移动.选择排序每次交换一对元素,它们当中至少有一个将被移到其最终

经典排序算法--简单选择排序

算法简介 简单选择排序是一种选择排序. 选择排序:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止. 白话理解 依然已排队为例,在排队时,有的老师可能会选择这样排序,先在一列中选出最矮的,放在第一位,然后选出第二矮的,放在第二位.队伍完成排序.而这就是选择排序的思想. 简单排序处理流程 (1)从待排序序列中,找到关键字最小的元素: (2)如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换: (3)从余下的 N - 1 个元素中,找出关键字最小