排序【1.1】选择排序(SelectSort && HeapSort)

1、选择排序——简单选择排序(Simple Selection Sort)

1.1 基本思想:

它的基本思想是:首先在未排序的数列中找到最小(or最大)元素,然后将其存放到数列的起始位置;接着,再从剩余未排序的元素中继续寻找最小(or最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

1.2 排序过程:

以数列{20,40,30,10,60,50}为例,演示它的选择排序过程:
在这里插入图片描述

1.3 排序流程

第1趟:i=0。找出a[1…5]中的最小值a[3]=10,然后将a[0]和a[3]互换。 数列变化:20,40,30,10,60,50 – > 10,40,30,20,60,50
第2趟:i=1。找出a[2…5]中的最小值a[3]=20,然后将a[1]和a[3]互换。 数列变化:10,40,30,20,60,50 – > 10,20,30,40,60,50
第3趟:i=2。找出a[3…5]中的最小值,由于该最小值大于a[2],该趟不做任何处理。
第4趟:i=3。找出a[4…5]中的最小值,由于该最小值大于a[3],该趟不做任何处理。
第5趟:i=4。交换a[4]和a[5]的数据。 数列变化:10,20,30,40,60,50 – > 10,20,30,40,50,60

1.4 时间复杂度:

选择排序的时间复杂度是O(N^2)
假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?N-1!因此,选择排序的时间复杂度是O(N^2)

1.5 稳定性:

选择排序是稳定的算法,它满足稳定算法的定义。


2、选择排序——堆排序(Heap Sort)

2.1 基本思想:

堆分为"最大堆"和"最小堆":最大堆通常被用来进行"升序"排序,而最小堆通常被用来进行"降序"排序。
鉴于最大堆和最小堆是对称关系,理解其中一种即可。

最大堆进行升序排序的基本思想:
① 初始化堆:将数列a[1…n]构造成最大堆。
② 交换数据:将a[1]和a[n]交换,使a[n]是a[1…n]中的最大值;然后将a[1…n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1…n-1]中的最大值;然后将a[1…n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。

下面,通过图文来解析堆排序的实现过程。注意实现中用到了"数组实现的二叉堆的性质"。
在第一个元素的索引为 0 的情形中:
性质一:索引为i的左孩子的索引是 (2i+1);
性质二:索引为i的左孩子的索引是 (2
i+2);
性质三:索引为i的父结点的索引是 floor((i-1)/2);
在这里插入图片描述
例如,对于最大堆{110,100,90,40,80,20,60,10,30,50,70}而言:索引为0的左孩子的所有是1;索引为0的右孩子是2;索引为8的父节点是3。

2.2 排序过程:

演示heap_sort_asc(a, n)对a={20,30,90,40,70,110,60,10,100,50,80}, n=11进行堆排序过程。下面是数组a对应的初始化结构:
在这里插入图片描述

2.2.1 初始化堆
在堆排序算法中,首先要将待排序的数组转化成二叉堆。
下面演示将数组{20,30,90,40,70,110,60,10,100,50,80}转换为最大堆{110,100,90,40,80,20,60,10,30,50,70}的步骤。

(1)i=4
在这里插入图片描述
上面是maxheap_down(a, 4, 9)调整过程。maxheap_down(a, 4, 9)的作用是将a[4…9]进行下调;a[4]的左孩子是a[9],右孩子是a[10]。调整时,选择左右孩子中较大的一个(即a[10])和a[4]交换。

(2)i=3
在这里插入图片描述
上面是maxheap_down(a, 3, 9)调整过程。maxheap_down(a, 3, 9)的作用是将a[3…9]进行下调;a[3]的左孩子是a[7],右孩子是a[8]。调整时,选择左右孩子中较大的一个(即a[8])和a[4]交换。

(3)i=2
在这里插入图片描述
上面是maxheap_down(a, 2, 9)调整过程。maxheap_down(a, 2, 9)的作用是将a[2…9]进行下调;a[2]的左孩子是a[5],右孩子是a[6]。调整时,选择左右孩子中较大的一个(即a[5])和a[2]交换。

(4)i=1
在这里插入图片描述
上面是maxheap_down(a, 1, 9)调整过程。maxheap_down(a, 1, 9)的作用是将a[1…9]进行下调;a[1]的左孩子是a[3],右孩子是a[4]。调整时,选择左右孩子中较大的一个(即a[3])和a[1]交换。交换之后,a[3]为30,它比它的右孩子a[8]要大,接着,再将它们交换。

(5)i=0
在这里插入图片描述
上面是maxheap_down(a, 0, 9)调整过程。maxheap_down(a, 0, 9)的作用是将a[0…9]进行下调;a[0]的左孩子是a[1],右孩子是a[2]。调整时,选择左右孩子中较大的一个(即a[2])和a[0]交换。交换之后,a[2]为20,它比它的左右孩子要大,选择较大的孩子(即左孩子)和a[2]交换。
调整完毕,就得到了最大堆。此时,数组{20,30,90,40,70,110,60,10,100,50,80}也就变成了{110,100,90,40,80,20,60,10,30,50,70}。

2.2.2 交换数据

在将数组转换成最大堆之后,接着要进行交换数据,从而使数组成为一个真正的有序数组。
交换数据部分相对比较简单,下面仅仅给出将最大值放在数组末尾的示意图。
在这里插入图片描述
上面是当n=10时,交换数据的示意图。
当n=10时,首先交换a[0]和a[10],使得a[10]是a[0…10]之间的最大值;然后,调整a[0…9]使它称为最大堆。交换之后:a[10]是有序的!
当n=9时, 首先交换a[0]和a[9],使得a[9]是a[0…9]之间的最大值;然后,调整a[0…8]使它称为最大堆。交换之后:a[9, 10]是有序的!

依此类推,直到a[0…10]是有序的。

2.3 时间复杂度:

堆排序的时间复杂度是O(N*logN)
假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?
堆排序是采用的二叉堆进行排序的,二叉堆就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是log(N+1)。最多是多少呢?由于二叉堆是完全二叉树,因此,它的深度最多也不会超过log(2N)。因此,所有数据遍历一趟的时间复杂度是O(N),而调整堆遍历次数介于log(N+1)log(2N)之间;因此得出它的时间复杂度是O(N*logN)

2.4 稳定性:

堆排序是不稳定的算法,它不满足稳定算法的定义。它在交换数据的时候,是比较父结点和子节点之间的数据,所以,即便是存在两个数值相等的兄弟节点,它们的相对顺序在排序也可能发生变化。


3、算法实现(python3)

"""
直接插入排序:

思想:从未排序的元素中寻找最小(or最大)元素,将其放到已排序序列的末尾。

时间复杂度:O(n^2)
"""

def selectSort(num_list, num_len):
    for i in range(num_len):      # O(n)
        min_index = i
        for j in range(i+1, num_len):      # O(n)
            if num_list[j] < num_list[min_index]:
                min_index = j      # 最小值索引存放在min_index
        if min_index != i:
            num_list[i], num_list[min_index] = num_list[min_index], num_list[i]
    return num_list

"""
堆排序:

思想:
① 初始化堆:将数列a[1...n]构造成最大堆。
② 交换数据:将num_list[0]和num_list[n-1]交换,使num_list[n-1]是num_list[0...n-1]中的最大值;
然后将num_list[0...n-2]重新调整为最大堆。 接着,将num_list[0]和num_list[n-2]交换,使a[n-2]是a[1...n-2]中的最大值;
然后将a[0...n-3]重新调整为最大值,依次类推,直到整个数列都是有序的。

时间复杂度:O(nlog(n))
"""
# 构造最大堆
def maxHeap(num_list, start, end):
    current = start
    left = 2*current + 1
    tmp = num_list[current]
    while left <= end:
        if left < end and num_list[left] < num_list[left + 1]:
            left += 1
        if tmp >= num_list[left]:
            break
        else:
            num_list[current], num_list[left] = num_list[left], num_list[current]
        current = left
        left = 2*left + 1

# 堆排序
def heapSort(num_list, num_len):
    # 初始化堆
    for i in range(num_len//2 - 1, -1, -1):
        maxHeap(num_list, i, num_len - 1)

    # 从最后一个元素开始调整排序:最后元素交换排序,前面元素重新生成最大堆
    for i in range(num_len - 1, -1, -1):      # O(n)
        num_list[0], num_list[i]= num_list[i], num_list[0]
        maxHeap(num_list, 0, i-1)      # O(log(n))
    return num_list

if __name__ == '__main__':
    num_list = [20, 40, 30, 10, 60, 50]
    num_len = len(num_list)
    print("before sort:", num_list)

    num_order = heapSort(num_list, num_len)

    print("after sort:", num_order)

运行结果:

before sort: [20, 40, 30, 10, 60, 50]
after sort: [10, 20, 30, 40, 50, 60]

猜你喜欢

转载自blog.csdn.net/olizxq/article/details/82825159