堆排序

作者: _Cappuccino_ | 来源:发表于2019-08-23 16:51 被阅读0次

    综述

    堆排序是指利用堆这种数据结构所设计的一种排序算法.
    堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点.

    算法描述

    1.将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
    2.将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
    3.由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn).
    4.不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成.

    示意图

    堆排序动态示意图

    详解示意图(感谢https://blog.csdn.net/l577217/article/details/80516654)

    性质

    排序类别:非交换排序
    是否是稳定排序:稳定
    是否是原地排序:是
    时间复杂度:O(n*log N)
    空间复杂度:O(n)

    ##############################################################

    说明

    完全二叉树: 除了最后一层之外的其他每一层都被完全填充,并且所有结点都保持向左对齐.
    满二叉树:除了叶子结点之外的每一个结点都有两个孩子,每一层(当然包含最后一层)都被完全填充.
    完满二叉树:除了叶子结点之外的每一个结点都有两个孩子结点.

    分类

    每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;
    每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆.

    堆排序是将数据看成是完全二叉树、根据完全二叉树的特性来进行排序的一种算法
    最大堆要求节点的元素都要不小于其孩子,最小堆要求节点元素都不大于其左右孩子
    那么处于最大堆的根节点的元素一定是这个堆中的最大值

    堆排序可以说是一种利用堆的概念来排序的选择排序.
    分为两种方法:
    大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
    小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

    Python实现

    # 大顶堆进行升序排序
    def max_heapify(heap, heapSize, root):  # 调整列表中的元素并保证以root为根的堆是一个大根堆
        left = 2 * root + 1
        right = left + 1
        larger = root
        print('left:', left, ', right:', right, ', root:', larger)
        # 交换的只是下标,完成之后进行数值交换
        if left < heapSize and heap[larger] < heap[left]:
            larger = left
        if right < heapSize and heap[larger] < heap[right]:
            larger = right
        if larger != root:  # 如果做了堆调整则larger的值等于左节点或者右节点的值,这个时候做堆调整操作
            heap[larger], heap[root] = heap[root], heap[larger]
            # 递归的对子树做调整
            max_heapify(heap, heapSize, larger)
        print(heap)
    
    
    def build_max_heap(heap):  # 构造一个堆,将堆中所有数据重新排序
        heapSize = len(heap)
        # 构建堆由下往上构建所以用-1
        for i in range((heapSize - 2) // 2, -1, -1):  # 自底向上建堆
            max_heapify(heap, heapSize, i)
    
    
    def heap_sort1(heap):
        # 将根节点取出与最后一位做对调,对前面len-1个节点继续进行堆调整过程。
        build_max_heap(heap)
        print('第一次构建堆完成...')
        # 调整后列表的第一个元素就是这个列表中最大的元素,将其与最后一个元素交换,然后将剩余的列表再递归的调整为最大堆
        for i in range(len(heap) - 1, -1, -1):
            heap[0], heap[i] = heap[i], heap[0]
            max_heapify(heap, i, 0)
    
    
    dest1 = [1, 2, 3, 4, 5, 6, 7]
    dest2 = [1, 2, 3, 4, 5, 6, 7, 8]
    dest3 = [7, 6, 5, 4, 3, 2, 1]
    dest4 = [8, 7, 6, 5, 4, 3, 2, 1]
    result = heap_sort1(dest1)
    print('最后的结果是:', dest1)
    print('*' * 50)
    result = heap_sort1(dest2)
    print('最后的结果是:', dest2)
    print('*' * 50)
    result = heap_sort1(dest3)
    print('最后的结果是:', dest3)
    print('*' * 50)
    result = heap_sort1(dest4)
    print('最后的结果是:', dest4)
    print('*' * 50)
    
    '''
    最后的结果是: [1, 2, 3, 4, 5, 6, 7]
    **************************************************
    最后的结果是: [1, 2, 3, 4, 5, 6, 7, 8]
    **************************************************
    最后的结果是: [1, 2, 3, 4, 5, 6, 7]
    **************************************************
    最后的结果是: [1, 2, 3, 4, 5, 6, 7, 8]
    **************************************************
    '''
    
    dest = [5, 2, 7, 4, 8, 1, 6, 3]
    result = heap_sort1(dest)
    print('最后的结果是:', dest)
    
    '''
    left: 7 , right: 8 , root: 3
    [5, 2, 7, 4, 8, 1, 6, 3]
    left: 5 , right: 6 , root: 2
    [5, 2, 7, 4, 8, 1, 6, 3]
    left: 3 , right: 4 , root: 1
    left: 9 , right: 10 , root: 4
    [5, 8, 7, 4, 2, 1, 6, 3]
    [5, 8, 7, 4, 2, 1, 6, 3]
    left: 1 , right: 2 , root: 0
    left: 3 , right: 4 , root: 1
    [8, 5, 7, 4, 2, 1, 6, 3]
    [8, 5, 7, 4, 2, 1, 6, 3]
    第一次构建堆完成...
    left: 1 , right: 2 , root: 0
    left: 5 , right: 6 , root: 2
    left: 13 , right: 14 , root: 6
    [7, 5, 6, 4, 2, 1, 3, 8]
    [7, 5, 6, 4, 2, 1, 3, 8]
    [7, 5, 6, 4, 2, 1, 3, 8]
    left: 1 , right: 2 , root: 0
    left: 5 , right: 6 , root: 2
    [6, 5, 3, 4, 2, 1, 7, 8]
    [6, 5, 3, 4, 2, 1, 7, 8]
    left: 1 , right: 2 , root: 0
    left: 3 , right: 4 , root: 1
    left: 7 , right: 8 , root: 3
    [5, 4, 3, 1, 2, 6, 7, 8]
    [5, 4, 3, 1, 2, 6, 7, 8]
    [5, 4, 3, 1, 2, 6, 7, 8]
    left: 1 , right: 2 , root: 0
    left: 3 , right: 4 , root: 1
    [4, 2, 3, 1, 5, 6, 7, 8]
    [4, 2, 3, 1, 5, 6, 7, 8]
    left: 1 , right: 2 , root: 0
    left: 5 , right: 6 , root: 2
    [3, 2, 1, 4, 5, 6, 7, 8]
    [3, 2, 1, 4, 5, 6, 7, 8]
    left: 1 , right: 2 , root: 0
    left: 3 , right: 4 , root: 1
    [2, 1, 3, 4, 5, 6, 7, 8]
    [2, 1, 3, 4, 5, 6, 7, 8]
    left: 1 , right: 2 , root: 0
    [1, 2, 3, 4, 5, 6, 7, 8]
    left: 1 , right: 2 , root: 0
    [1, 2, 3, 4, 5, 6, 7, 8]
    最后的结果是: [1, 2, 3, 4, 5, 6, 7, 8]
    '''
    
    
    # 小顶堆实现降序排序
    def small_heapify(heap, heapSize, root):  # 调整列表中的元素并保证以root为根的堆是一个大根堆
        left = 2 * root + 1
        right = left + 1
        small = root
        print('left:', left, ', right:', right, ', root:', small)
        # 交换的只是下标,完成之后进行数值交换
        if left < heapSize and heap[small] > heap[left]:
            small = left
        if right < heapSize and heap[small] > heap[right]:
            small = right
        if small != root:  # 如果做了堆调整则larger的值等于左节点或者右节点的值,这个时候做堆调整操作
            heap[small], heap[root] = heap[root], heap[small]
            # 递归的对子树做调整
            small_heapify(heap, heapSize, small)
        print(heap)
    
    
    def build_small_heap(heap):  # 构造一个堆,将堆中所有数据重新排序
        heapSize = len(heap)
        # 构建堆由下往上构建所以用-1
        for i in range((heapSize - 2) // 2, -1, -1):  # 自底向上建堆
            small_heapify(heap, heapSize, i)
    
    
    def heap_sort2(heap):
        # 将根节点取出与最后一位做对调,对前面len-1个节点继续进行堆调整过程。
        build_small_heap(heap)
        print('第一次构建堆完成...')
        # 调整后列表的第一个元素就是这个列表中最大的元素,将其与最后一个元素交换,然后将剩余的列表再递归的调整为最大堆
        for i in range(len(heap) - 1, -1, -1):
            heap[0], heap[i] = heap[i], heap[0]
            small_heapify(heap, i, 0)
    
    
    dest = [5, 2, 7, 4, 8, 1, 6, 3]
    result = heap_sort2(dest)
    print('最后的结果是:', dest)
    
    '''
    left: 7 , right: 8 , root: 3
    left: 15 , right: 16 , root: 7
    [5, 2, 7, 3, 8, 1, 6, 4]
    [5, 2, 7, 3, 8, 1, 6, 4]
    left: 5 , right: 6 , root: 2
    left: 11 , right: 12 , root: 5
    [5, 2, 1, 3, 8, 7, 6, 4]
    [5, 2, 1, 3, 8, 7, 6, 4]
    left: 3 , right: 4 , root: 1
    [5, 2, 1, 3, 8, 7, 6, 4]
    left: 1 , right: 2 , root: 0
    left: 5 , right: 6 , root: 2
    [1, 2, 5, 3, 8, 7, 6, 4]
    [1, 2, 5, 3, 8, 7, 6, 4]
    第一次构建堆完成...
    left: 1 , right: 2 , root: 0
    left: 3 , right: 4 , root: 1
    left: 7 , right: 8 , root: 3
    [2, 3, 5, 4, 8, 7, 6, 1]
    [2, 3, 5, 4, 8, 7, 6, 1]
    [2, 3, 5, 4, 8, 7, 6, 1]
    left: 1 , right: 2 , root: 0
    left: 3 , right: 4 , root: 1
    left: 7 , right: 8 , root: 3
    [3, 4, 5, 6, 8, 7, 2, 1]
    [3, 4, 5, 6, 8, 7, 2, 1]
    [3, 4, 5, 6, 8, 7, 2, 1]
    left: 1 , right: 2 , root: 0
    left: 3 , right: 4 , root: 1
    left: 7 , right: 8 , root: 3
    [4, 6, 5, 7, 8, 3, 2, 1]
    [4, 6, 5, 7, 8, 3, 2, 1]
    [4, 6, 5, 7, 8, 3, 2, 1]
    left: 1 , right: 2 , root: 0
    left: 5 , right: 6 , root: 2
    [5, 6, 8, 7, 4, 3, 2, 1]
    [5, 6, 8, 7, 4, 3, 2, 1]
    left: 1 , right: 2 , root: 0
    left: 3 , right: 4 , root: 1
    [6, 7, 8, 5, 4, 3, 2, 1]
    [6, 7, 8, 5, 4, 3, 2, 1]
    left: 1 , right: 2 , root: 0
    left: 3 , right: 4 , root: 1
    [7, 8, 6, 5, 4, 3, 2, 1]
    [7, 8, 6, 5, 4, 3, 2, 1]
    left: 1 , right: 2 , root: 0
    [8, 7, 6, 5, 4, 3, 2, 1]
    left: 1 , right: 2 , root: 0
    [8, 7, 6, 5, 4, 3, 2, 1]
    最后的结果是: [8, 7, 6, 5, 4, 3, 2, 1]
    '''
    
    
    # 非递归实现堆排序
    def heapAdjust(arr, start, end):
        i = start
        temp = arr[start]
        j = 2 * i
        while j <= end:
            if j < end and arr[j] < arr[j + 1]:
                j += 1
            if arr[i] < arr[j]:
                arr[i] = arr[j]
                i = j
                j = 2 * i
            else:
                break
        arr[i] = temp
        print(arr)
    
    
    def heap_sort3(numList):
        # 由于python数组的下标从0开始,因此需要加入一个辅助元素,是所有的元素的下标都往后移动一位。
        numList.insert(0, 0)
        length = len(numList) - 1
        firstAdjustRoot = length // 2
        for i in range(firstAdjustRoot):
            # 在构造最大堆的时候,不会对最左侧的0进行处理,因为i不会取到firstAdjustRoot。
            heapAdjust(numList, firstAdjustRoot - i, length)
        print('第一次构建大顶堆完成...')
        for i in range(length - 1):
            numList[1], numList[length - i] = numList[length - i], numList[1]
            # 由于大顶堆的堆顶元素是最大的,把它和数组最后的元素(堆的最下层最右元素)
            # 进行互换,就相当于把最大值放在了最后,下一次,把最后一个元素撇出来,对剩下来的在排序
            heapAdjust(numList, 1, length - i - 1)
            # 由于交换之前,已经都调整为最大堆了,而交换之后,大部分都符合堆的规则,
            # 只从堆顶元素(下标为1)开始,只进行局部的调整就好,
            # 这时候不用再像刚开始构建最大堆一样从下往上,从右往左调整交换了。
        return [numList[i] for i in range(1, len(numList))]
    
    
    dest = [5, 2, 7, 4, 8, 1, 6, 3]
    result = heap_sort3(dest)
    print('最后的结果是:', dest[1:])
    
    '''
    [0, 5, 2, 7, 4, 8, 1, 6, 3]
    [0, 5, 2, 7, 4, 8, 1, 6, 3]
    [0, 5, 8, 7, 4, 2, 1, 6, 3]
    [0, 8, 5, 7, 4, 2, 1, 6, 3]
    第一次构建大顶堆完成...
    [0, 7, 5, 3, 4, 2, 1, 6, 8]
    [0, 6, 5, 3, 4, 2, 1, 7, 8]
    [0, 5, 1, 3, 4, 2, 6, 7, 8]
    [0, 3, 1, 2, 4, 5, 6, 7, 8]
    [0, 4, 1, 2, 3, 5, 6, 7, 8]
    [0, 2, 1, 4, 3, 5, 6, 7, 8]
    [0, 1, 2, 4, 3, 5, 6, 7, 8]
    最后的结果是: [0, 1, 2, 4, 3, 5, 6, 7, 8]
    '''
    

    C语言实现及优化

    #include <stdio.h>
    #include <stdlib.h>
    
    void swap(int* a, int* b)
    {
        int temp = *b;
        *b = *a;
        *a = temp;
        return;
    }
    
    void max_heapify(int arr[], int start, int end)
    {
        //建立父节点指标和子节点指标
        int dad = start;
        int son = dad * 2 + 1;
        while (son <= end)  //若子节点指标在范围内才做比较
        {
            if (son + 1 <= end && arr[son] < arr[son + 1])
                //先比较两个子节点大小,选择最大的
                son++;
            if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数
                return;
            else  //否则交换父子内容再继续子节点和孙节点比较
            {
                swap(&arr[dad], &arr[son]);
                dad = son;
                son = dad * 2 + 1;
            }
        }
        return;
    }
    
    void heap_sort(int arr[], int len)
    {
        int i;
        //初始化,i从最後一个父节点开始调整
        for (i = len / 2 - 1; i >= 0; i--)
            max_heapify(arr, i, len - 1);
        //先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完毕
        for (i = len - 1; i > 0; i--)
        {
            swap(&arr[0], &arr[i]);
            max_heapify(arr, 0, i - 1);
        }
        return;
    }
    
    int main() {
        int arr[] = {5, 2, 7, 4, 8, 1, 6, 3};
        int len = (int) sizeof(arr) / sizeof(*arr);
        heap_sort(arr, len);
        int i;
        for (i = 0; i < len; i++)
            printf("%d ", arr[i]);
        printf("\n");
        return 0;
    }
    
    
    ///////////////////////////////////////////////////////////
    
    //优化的堆排序体现在不需要重新生成一个数组,而是直接原地进行所谓的堆排序。
    
    
    void swap(int* a, int* b)
    {
        int temp = *b;
        *b = *a;
        *a = temp;
        return;
    }
    
    
    void __shiftdown(int arr[],int n,int k)//下沉操作,传入数组以及需要排序的个数和下沉的索引
    {
        int e=arr[k];//记录首个下沉的节点的值
        while(2*k+1<=n-1)//保证需要进行操作的节点至少有左孩子
        {
            int maxindex=2*k+1;//假设最大的子节点为左孩子
            if(2*k+2<=n-1&&arr[2*k+2]>arr[maxindex])//如果存在右孩子并且右孩子比左孩子大
            {
                maxindex=2*k+2;//最大的子节点指向右孩子
            }
            if(e<arr[maxindex])//下沉节点的值要小于他的孩子节点
            {
                arr[k]=arr[maxindex];//下沉节点的值被大孩子的值替换
                k=maxindex;//更新需要下沉节点的索引
            }
            else break;
        }
        arr[k]=e;//最后不能下沉的节点赋值为首下沉节点的值
    }
    
    void heap_sort2(int arr[],int n)//传入数组以及数组的大小
    {
       for(int i=(n-2)/2;i>=0;i--)//对数组进行heapfiy产生最大值arr[0]
           __shiftdown(arr,n,i);//shiftdown所有可以下沉的父节点
    
        for(int j=n-1;j>0;j--)
        {
            swap(&arr[0], &arr[j]);//把每次产生的最大值调到数组的后面去
            __shiftdown(arr,j,0);
    
        }
    }
    
    int main() {
        int arr[] = {5, 2, 7, 4, 8, 1, 6, 3};
        int len = (int) sizeof(arr) / sizeof(*arr);
        heap_sort2(arr, len);
        int i;
        for (i = 0; i < len; i++)
            printf("%d ", arr[i]);
        printf("\n");
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:堆排序

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