排序算法(二)

希尔、归并、堆

Posted by 新宇 on January 16, 2020

一、希尔排序

1. 定义

  1. 希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。希尔排序又称缩小增量排序,因 DL.Shell 于 1959 年提出而得名。
  2. 它通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。

2. 动画演示

3. 代码实现

def shell_sort(l):
    n = len(l)
    # 定义gap为排序间隔

    gap = int(n/2)
    # 最外层循环到gap=1为止

    while gap > 0:
        # 这层为循环的轮数

        for i in range(0, gap):
            # 插入排序
            # j j+gap j+2*gap ....

            for j in range(i+gap, n, gap):
                for k in range(j, 0, -gap):
                    if l[k] < l[k-gap]:
                        l[k], l[k-gap] = l[k-gap], l[k]
                    else:
                        break

        gap = int(gap/2)

l = [7,8,9,1,2,3,4,5,6]
shell_sort(l)
print(l)

4. 效率分析

  1. 时间复杂度:O(nlognlogn)
  2. 稳定性:不稳定
  3. 空间复杂度:O(1) 原地排序

二、归并排序

1. 定义

  1. 归并排序(Merge sort)是建立在归并操作上的一种有效、稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
  2. 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

2. 动画演示

3. 代码实现

def merge_sort(l, start, end):
    if start + 1 >= end:
        return
    length = end-start+1
    mid = start + int(length/2)
    # 分
    
    merge_sort(l, start, mid)
    merge_sort(l, mid, end)
    # 治
    
    new = []
    k = start
    i = start
    j = mid
    while k < end:
        while i < mid and (j >= end or l[i] <= l[j]):
            new.append(l[i])
            i += 1
            k += 1
        while j < end and (i >= mid or l[i] > l[j]):
            new.append(l[j])
            j += 1
            k += 1
    m = start
    for item in new:
        l[m] = item
        m += 1

l = [8,7,6,6,5,4,3,3,1,2]
merge_sort(l, 0, len(l))
print(l)

4. 效率分析

  1. 时间复杂度:O(nlogn)
  2. 稳定性:稳定
  3. 空间复杂度:O(n) 每一层都需要申请和原数组一样大的空间

三、堆排序

1. 定义

1. 堆的定义

  1. 堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。堆满足下列性质:
    • 堆中某个节点的值总是不大于或不小于其父节点的值。(大根堆/小根堆)
    • 堆总是一棵完全二叉树。
  2. 注:二叉堆是一颗完全二叉树,且堆中某个节点的值总是不大于其父节点的值,该完全二叉树的深度为 k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边。

  3. 图示:

2. 添加/删除元素

  1. shift up参考
  2. shift down参考

3. 堆排序

先通过shift up构造小根堆/大根堆,在通过shift down取出数据

2. 动画演示

3. 代码实现

# 小根堆

class min_heap():
    def __init__(self):
        # 使用线性表存储堆数据,第一个位置不使用,用0填充

        self.__data = [0]

    # 往堆中插入数据

    def shift_up(self, l):
        i = 1
        while i <= len(l):
            self.__data.append(l[i-1])
            cur = i
            # 依次与父节点比较

            while cur > 1 and self.__data[cur] < self.__data[int(cur/2)]:
                self.__data[cur], self.__data[int(cur/2)] = self.__data[int(cur/2)], self.__data[cur]
                cur = int(cur/2)
            i += 1
        print(self.__data)

    # 从堆中取出当前最小元素

    def shift_down(self):
        result = []
        length = len(self.__data)
        i = length-1
        while i > 1:
            result.append(self.__data[1])
            self.__data[1] = self.__data[i]
            self.__data.pop()
            i -= 1
            cur = 1
            # 依次与左右子节点中较小的值比较

            while cur <= i:
                if 2*cur + 1 <= i:
                    # 左子节点 <= 右子节点

                    if self.__data[2*cur] <= self.__data[2*cur + 1]:
                        # cur > 左子节点时则交换,否则 break

                        if self.__data[cur] > self.__data[2 * cur]:
                            self.__data[cur], self.__data[2 * cur] = self.__data[2 * cur], self.__data[cur]
                            cur = 2 * cur
                        else:
                            break
                    # 左子节点 > 右子节点

                    else:
                        # cur > 右子节点时则交换,否则 break

                        if self.__data[cur] > self.__data[2 * cur + 1]:
                            self.__data[cur], self.__data[2 * cur + 1] = self.__data[2 * cur + 1], self.__data[cur]
                            cur = 2 * cur + 1
                        else:
                            break
                elif 2*cur <= i:
                    # cur > 左子节点时则交换,否则 break

                    if self.__data[cur] > self.__data[2 * cur]:
                        self.__data[cur], self.__data[2 * cur] = self.__data[2 * cur], self.__data[cur]
                        cur = 2 * cur
                    else:
                        break
                else:
                    break
        return result


if __name__ == '__main__':
    h = min_heap()
    h.shift_up([3,2,4,5,8,7,3,6,1,3,4,6,4,3,2,1,0,9,8,5,6,7,8,9])
    print(h.shift_down())

4. 效率分析

  1. 时间复杂度:
    • shift up: O(logn)
    • shift down: O(logn)
  2. 稳定性:不稳定
  3. 空间复杂度:O(n)