数据结构与算法 —— 堆排序(c++)

含义

大顶堆:完全二叉树,每个根节点值都大于等于其左右孩子的节点值,这样根节点就是最大值
小顶堆:每个根节点值都小于等于左右孩子节点值,根节点为最小值

前置知识

我们对堆中的结点按层进行编号从左到右,对应的数组就是一个大顶堆数组。
由于是完全二叉树,所以有下面结论

  1. 父结点索引:(i-1)/2
  2. 左孩子索引:2*i+1
  3. 右孩子索引:2*i+2

思路

堆排序的过程可以具体分为三步,

  1. 创建堆:创建完全二叉树,首先将待排序的数组构造成一个大根堆,此时整个数组的最大值就是堆结构的顶端。遍历数组,每次将节点从最后一位放入,最后一位是完全二叉树的最后一位(也就是从底向上,从右向左)。我们对比当前节点和父节点。如果比父节点大,就交换。
  2. 调整堆:这时我们找到最大值了,也就是堆顶元素。放到数组最后。也就是排序好的,然后继续遍历前面没有排序好的。
  3. 重复:已排序的就不需要再参与排序了,将最堆顶元素和最后一个元素交换。将剩余n-1个元素继续上面两步,找到最大值,并放到排序好的后面,反复排序。

复杂度

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
空间复杂度:在原数组进行操作,所以辅助空间为O(1),不需要额外空间

代码

    //1. 新建大顶堆函数,该函数执行完之后,会让数组中的元素按照大顶堆的顺序排序
    void heapify(vector<int> &nums) {
    
    
        //循环遍历,每次插入一个节点
        for(int i = 1; i < nums.size(); i++) {
    
    
            //这里有个概念,因为我们每次插入的数据要构成完全二叉树,那么这个数组中,插入之后,他的父节点索引值为(i-1) / 2
            int parent = (i-1) / 2; // 找到当前节点的父节点的索引肯定是(i-1)/2
            int cur = i;  // 当前遍历节点为子节点
            //如果当前节点值大于父节点,则交换父子节点值,然后将当前节点上移动继续和父节点对比
            while(nums[parent] < nums[cur]) {
    
    
                swap(nums[parent], nums[cur]);//交换值
                //自底向上移动节点
                cur = parent;//上移当前节点游标
                parent = (parent-1)/2;//上移父节点索引
            }
        }
    }

    //2. 调整堆,使用递归,因为当前根节点小,目的是将该根节点放到他指定的位置。从根节点向下遍历,如果根节点小于他最大的子节点,则交换,并继续递归该子树
    //传入当前节点和最后一个节点的索引
    void rebuildHeap(vector<int> & nums, int parent, int last) {
    
    
        
        //因为是完全二叉树,对应数组中左孩子索引:2*i+1   右孩子索引:2*i+2
        //拿到当前节点的左右子节点中较大的,放到根节点,这时根节点就是最大值
        int left = 2*parent+1;                           // 左子节点
        int right = 2*parent+2;                          // 右子节点
        //看是否有左子节点
        if(last < left) return; //终止条件:如果当前节点没有左子节点,也就是当前左子节点索引大于最后一个index,也就是,也就是当前节点是最底层节点,则终止
        //看是否有右子节点,如果不存在右节点则最大索引就是左子节点了
        int maxIndex = left;
        //如果存在右节点,且右子节点值大于左节点则右节点值大
        if(right <= last && nums[right] > nums[left]) maxIndex = right;
        
        //如果当前根节点值小于他的较大子节点值,则交换,因为要把最大的数放到最上面根节点
        if (nums[parent] < nums[maxIndex]) {
    
    // 和最大子节点比较
            swap(nums[parent], nums[maxIndex]);            // 互换到最大子节点
            //进行递归,继续向下调整子树,一直递归到他是子节点,或者他下面的值都比他小则结束
            rebuildHeap(nums, maxIndex, last);        
        }
    }
    void HeapSort(vector<int>& nums) {
    
    
        //新建一个大顶堆
        heapify(nums);
        
        //3. 从后往前依次排序  ,这时,堆顶的值最大,也就是数组第一个元素最大,我们把最大值放到最后作为排好序的值,然后我们调整未排序的大顶堆中的值                             
        for (int i = nums.size() - 1; i >= 1; i--) {
    
    
            swap(nums[i], nums[0]);                       // 最大值和最后一个数交换
            rebuildHeap(nums, 0,i-1);          // 去掉已排序的数,对未排序的数重建大顶堆
        }
    }

int main()
{
    
    
    //随机产生N个数
    int N = 200000;
    srand(time(NULL));  //生成随机数种子
    //放到vector中
    vector<int> nums(N);
    for (int i = 0; i < N; ++i) {
    
    
        nums[i] = 1 + (rand() % N);
        //cout << nums[i] << ",";
    }
    cout << endl;
    //开始计时
    double start, finish; /* 定义开始的时间和结束的时间 */
    start = (double)clock();

    //20000
    //BubbleSort(nums); //2288ms
    //SelectionSort(nums); //915ms
    //InsertionSort(nums); //528ms

    //200000
    //mergeSort(nums); //93ms
    //quickSort(nums, 0, nums.size()-1);//41ms 
    HeapSort(nums);//78ms

    finish = (double)clock();
    //打印
    // for (auto item : nums)
    // {
    
    
    //     cout << item << ",";
    // }
    
    cout << (finish) - start << " ms" << endl;
}

参考文档

https://blog.csdn.net/u010452388/article/details/81283998

猜你喜欢

转载自blog.csdn.net/chongbin007/article/details/114945404