美文网首页
选择排序

选择排序

作者: 欧阳_z | 来源:发表于2019-03-24 21:15 被阅读0次

1、直接选择排序(Straight Select Sorting)

(1)描述

从无序区选出一个最小的数,与第1个数交换;
再从剩下无序数据中找出最小的数,与第2个数交换,总共选择n-1次。

(2)实现

两层循环,外层循环控制当前轮最小值应该存放的数组下标i
内层循环从i到最后选出最小值的下标min
如果imin不是同一个值,则交换。

void straight_select(int data[], int length)
{
    int i, k;
    int min;
    for (i = 0; i < length-1; ++i)
    {
        min = i;
        for (k = i+1; k < length; ++k)
            if (data[k] < data[min])
                min = k;
        if (i != min)
            SWAP_INT(data[i], data[min])
    }
}

代码链接
链表版代码链接

(3) 时间复杂度

因为每次只能选出1个元素,每次选择需要比较无序区的所有元素,所以
时间复杂度固定为 O(n2) 。
当然也可以优化,比如每次选出2个元素,可以是1max1min,也可以是mintop2,但也只是把外层循环由n-1变成了n/2,最终只是无关紧要的项有变化,复杂度仍然是 O(n2) 。

(4)空间复杂度 O(1)
(5)稳定性:不稳定

比如 2, 2, 1,第1轮选择把第1个2交换到了最后。


(2)堆排序(Heapsort)

(1)描述

这里所说的堆并不是内存的堆,而是数据结构的二叉堆。
二叉堆是一棵完全二叉树,分为两种:最大堆和最小堆。
最大堆的父结点的值总是大于或等于它的子节点,
最小堆的父结点的值总是小于或等于它的子节点,
左右子节点之间并无固定的大小关系。

满二叉树的第1层有1个节点,第2层有2个,第3层有4个,
第 h 层有 2h-1 个节点,是一个公比为 2 的等比数列。
根据等比数列求和公式: s_{n} = a1 * \frac{1-q^n}{1-q} (q \neq 1)

可以求出 S_h =2^h -1和 S_{h-1} = 2^{h-1} -1,堆的前h层节点数>= S_{h-1} +1,<=S_h 。

所以如果已知堆的总节点数 n,则高度 h 的取值范围是 [ log_{2}{n+1}, log_2{2n} ]

如果从堆顶往下只遍历某一个子树就可以选择出最值,就可以把直接选择排序的选择最值部分的时间复杂度从 O(n) 降到了 O(logn) 。

2、实现

1.构建一个二叉堆。
2.交换堆顶(第1个数据)与最后一个数据。
3.利用堆的便利,轻松将前n-1个数据重新构建堆。重复2和3步骤,一共选择n-1次。

void build_heap(int data[], int father, int length)
{
    int child;
    for(child = 2 * father + 1; child < length; child = 2 * father + 1)
    {
        if (child+1 < length && data[child+1] > data[child])
            ++child;
        if (data[child] > data[father])
        {
            SWAP_INT(data[child], data[father])
            father = child;
            continue;
        }
        break;
    }
}

void heap_sort(int data[], int length)
{
    for(int i = length/2 -1; i >=0; --i)
    {
        build_heap(data, i, length);
    }
    for(int i = 1; i < length; ++i)
    {
        SWAP_INT(data[0], data[length-i])
        build_heap(data, 0, length-i);
    }
}

代码链接

用数组实现是很方便的,因为数组可以随机访问,根据父节点,访问子节点的复杂度是O(1)。
如果用链表实现,则需要把每个节点的父节点和左右子节点记录下来,需要自定义结构体,如果用STL中的list<int>这样的链表,父子节点访问对方的复杂度是O(n),这样一来整个排序的时间复杂度上升了,与直接选择排序相比也丧失了优势。
STL的algorithm有提供堆排序的算法,只需要提供一个比较元素大小的函数即可。代码链接

(3) 时间复杂度

代码大致分为2个部分:1是初始构建堆,2是选出最值+构建堆;

1)初始构建堆
设节点总数为 n , 设堆的高度为 h ,
设最大的非叶子节点所在的层为第k层,则k = h - 1

初始构建堆时,
h 层,因为全是叶子节点,不需要构建堆,即构建0次。
h-1 层,构建1次堆。
所以第 i 层需要构建 h-i 次堆。
且第i层最多有 2(i-1) 个节点。

所以第 i 层一共需要构建堆的次数为 a_i = 2^{i-1} (h-i)

i 层一共是 S_i = 2^{i-1}(h-i) + 2^{i-2}(h-(i-1)) + 2^{i-3}(h-(i-2)) + ... + 2^{1}(h-2) + 2^{0}(h-1)

初始构建堆的过程是前 k 层,一共最多需要构建堆多少次,接下来就是化简 Si ,再把 i = k 代入计算即可。

等式两边同时乘以 2 得:
2S_i = 2^{i}(h-i) + 2^{i-1}(h-(i-1)) + 2^{i-2}(h-(i-2)) + ... + 2^{1}(h-1)

两式相减(幂相同的项相减)得:
S_i = 2^{i}(h-i) + 2^{i-1} + ... + 2^{1} - 2^{0}(h-1)

除了首尾的两个项,中间是等比数列可以用等比数列求和公式计算:
S_i = 2^{i}(h-i) + (2^{i} -2) - (h -1)

所以 S_k = 2^{k}(h-k) + (2^{k} -2) - (h -1)
因为 k = h - 1 ,所以:
S_k = 2^{h} - h - 1

根据上面算过的 h 的取值范围:
取 h = log_{2}{(n+1)} , 则 S_k = n + 1 - log_{2}{(n+1)} - 1
取 h = log_{2}{(2n)} , 则 S_k = 2n - log_{2}{(2n)} - 1

去掉低阶项,初始构建堆的时间复杂度是 O(n) 。

2)从堆顶往下构建堆的时间复杂度是O(logn),一共选择(n-1)次,时间复杂度是O(nlogn) 。

两项相加,初始构建堆的时间复杂度是低阶项可以忽略不计,所以:
时间复杂度 O(nlogn) 。

(4)空间复杂度 O(1)
(5)稳定性:不稳定

相关文章

  • 算法-选择排序

    算 法:选择排序算法时间复杂度: 选择排序算法概述 选择排序伪代码 选择排序实现 选择排序算法概述 排序算法有许...

  • 常见排序算法

    这里介绍四种排序算法,选择排序、快速排序、归并排序、计数排序 选择排序(使用递归) 选择排序(使用循环) 快速排序...

  • 常用的两种排序-冒泡、选择

    Swift版 冒泡排序 选择排序 OC版 冒泡排序 选择排序

  • 数据结构之排序

    选择排序1.直接选择排序 原理直接选择排序过程直接选择排序过程 实现: DataWrap.java来模拟待排序的数...

  • 记录几个常见的排序算法

    常见的排序有:快速排序、冒泡排序、希尔排序、选择排序、插入排序、归并排序 冒泡排序: 插入排序: 选择排序: 希尔...

  • PHP常用算法

    基于选择的排序算法 常见的基于选择的排序算法有:冒泡排序、插入排序、选择排序、归并排序和快速排序,我们在选在排序算...

  • 排序 -- 选择/插入

    聊聊排序吧 冒泡排序 选择排序 插入排序 快速排序 归并排序 计数排序 桶排序 堆排序 本篇 选择排序与插入排序 ...

  • java快速学习排序---选择排序

    1.java实现选择排序 (1)、图解选择排序 (2)、选择排序的思想 选择排序首先在未排序序列中找到最小(大)元...

  • IOS 常用算法

    一:排序算法 排序方式有插入排序,选择排序和交换排序三种。插入排序有直接插入排序和希尔排序。选择排序有简单选择排序...

  • 给自己备份的排序代码

    交换排序 冒泡排序 快速排序 插入排序 直接插入排序 希尔排序 选择排序 简单选择排序 堆排序

网友评论

      本文标题:选择排序

      本文链接:https://www.haomeiwen.com/subject/uyepvqtx.html