算法基础: 堆排序

堆排序

概念

堆是一种叫做完全二叉树的数据结构,可以分为大根堆,小根堆,而堆排序就是基于这种结构而产生的一种程序算法。

思路

1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换(相当于把最大的一个元素移除待排序范围),然后将除最后一个节点之外的剩下的节点重新构造成一个大顶堆;

3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。

代码

#include <iostream>
#include <algorithm>
using namespace std;
  
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;
        }
    }
}
  
void heap_sort(int arr[], int len) 
{
    //初始化,i从最後一个父节点开始调整
    for (int i = len / 2 - 1; i >= 0; i--)
        max_heapify(arr, i, len - 1);
    //先将第一个元素和已经排好的元素前一位做交换,再从新调整(刚调整的元素之前的元素),直到排序完毕
    for (int i = len - 1; i > 0; i--) 
    {
        swap(arr[0], arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}
int main() 
{
    int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    heap_sort(arr, len);
    for (int i = 0; i < len; i++)
        cout << arr[i] << ' ';
    cout << endl;
    system("pause");
}

算法考题

TOPK

题目描述

输入一个长度为n的整数数列,从小到大输出前m小的数。

输入格式

第一行包含整数n和m。

第二行包含n个整数,表示整数数列。

输出格式

共一行,包含m个整数,表示整数数列中前m小的数。

思路分析

小顶堆的经典场景

C++ 中可以解除 priority_queue 去实现

代码

#include <iostream>
#include <queue>
using namespace std;
int n,m,a;
priority_queue<int, vector<int>, greater<int> > h; 
int main(){
    scanf("%d%d",&n,&m);
    while(n--) scanf("%d",&a), h.push(a);
    while(m--) printf("%d ",h.top()) , h.pop();
    return 0;
}

最大线段重合问题

题目描述

给定很多线段,每条线段都有两个数组 [start, end],表示线段的开始位置和结束位置,左右都是闭区间。规定:

线段开始和结束位置一定都是整数值;

线段重合区域的长度必须 >=1 (比如(1,3) 和 (3,5) 不算重合,因为只有3这个点重合)

返回线段最多重合区域中,包含了几条线段。

思路分析

1、先根据每条线段的 start 进行从小到大的排序,然后准备一个小根堆,用于存放每条线段的 end。 【重合线段一定是以某条线段的 start 为左边界的】

2、流程

(1)假设第一条线段是 [1,7],先先问小根堆,弹出所有 <=1 的数,由于这是第一条线段,没有 <=1 的数,所以直接将 end = 7 放入小根堆中,此时小根堆中只有 7 这个数,所以 [1,7] 这条线段对应的答案是1;【解释:答案1的含义是如何重合区域必须以 [1,7] 的 1 为左边界的话,有多少条线段会越过这个 1 往右延伸】

(2)下一条线段 [2,3],start = 2,将小根堆中 <=2 的数都弹出,而当前小根堆中的数是 7,没有符合条件的数,所以将 end = 3放入小根堆中,此时小根堆中的数为 (3,7),所以 [2,3] 这条线段对应的答案是 2;【解释:之所以要将 <=2 的数弹出,也就是 end <= 2 的数弹出,因为这样的线段是无法越过2的,答案为2的意思是以[2,3] 的 2 为左边界,有多少条线段可以穿过来】

(3)下一条线段 [4,6],start = 4,小根堆(3,7),弹出 <= 4 的 3,然后将 end = 6 放入小根堆中,此时小根堆(6,7),size = 2,所以 [4,6] 这条线段对应的答案是 2;【解释:小根堆中的3被弹出,因为这条end = 3的线段无法穿过[4,6] 中的 4,所以要将它剔除,答案为2表示重合边界以 [4,6] 的 4 为左边界,有2条线段可以穿过来】

(4)下一条线段 [4,5], start = 4,小根堆(6,7),没有 <= 4 的数,直接将 end = 5 放入小根堆中,此时小根堆(5,6,7),size = 3,所以 [4,5] 这条线段对应的答案是 3;

3、总结:上述流程总结来说就是将所有线段以 start 从小到大进行排序后,依次考察每条线段,将小根堆中 <= start 的数弹出,然后将此时的 end 加入到小根堆中,此时小根堆的size 就是 当前这条线段与多少条线段重合的答案,所有答案中的最大值就是要求的结果。这就是在求以每条线段的 start 为重合区域的开始位置的情况下的所有答案。

代码

#include <iostream>
#include <ctime>
#include <cmath>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;


struct Line {
public:
    int start;
    int end;

    Line() {}

    Line(int s, int e) : start(s), end(e) {  }


    bool operator<(const Line &rhs) const {
        return start < rhs.start;
    }

};


struct CMP {
    bool operator()(const Line &l1, const Line &l2) const {
        return l1.start > l2.start;
    }
};


int maxCover(vector<vector<int> > &arr) {

    vector<Line> lines(arr.size());

    for (int i = 0; i < arr.size(); i++) {
        lines[i] = Line(arr[i][0], arr[i][1]);
    }

    sort(lines.begin(), lines.end()); //start从小到大排序

    priority_queue<int, vector<int>, greater<int> > heap; //小根堆

    int ans = 0;

    for (int i = 0; i < lines.size(); i++) {
        while (!heap.empty() && heap.top() <= lines[i].start) {
            heap.pop();
        }
        heap.push(lines[i].end);
        ans = max(ans, (int)heap.size());
    }

    return ans;
}

//不使用辅助数组
int maxCover2(vector<vector<int> > &arr) {
	//二维数组使用sort + lambda表达式进行排序,按照线段的start从小到大进行排序
    sort(arr.begin(), arr.end(), [](const vector<int>& e1, const vector<int>& e2) {return e1[0] < e2[0]; } );
    
    priority_queue<int, vector<int>, greater<int> > heap; 

    int ans = 0;

    for (vector<int> line : arr) {
        while (!heap.empty() && heap.top() <= line[0]) {
            heap.pop();
        }
        heap.push(line[1]);
        ans = max(ans, (int)heap.size());
    }
        
    return ans;
}


//暴力解法
int maxCover3(vector<vector<int> > &lines) {
    int _min = INT_MAX;
    int _max = INT_MIN;

    for (int i = 0; i < lines.size(); i++) {
        _min = min(_min, lines[i][0]);
        _max = max(_max, lines[i][1]);
    }

    int cover = 0;
    for (double p = _min + 0.5; p < _max; p += 1) {
        int cur = 0;
        for (int i = 0; i < lines.size(); i++) {
            if (lines[i][0] < p && lines[i][1] > p) {
                cur++;
            }
        }
        cover = max(cover, cur);
    }
    return cover;
}

//生成随机数组
vector<vector<int> > generateLines(int n, int l, int r) {
    int size = (rand() % n) + 1;
    vector<vector<int> > ans(size, vector<int>(2));

    for (int i = 0; i < size; i++) {
        int a = l + ((rand() % (r - l + 1)));
        int b = l + (rand() % (r- l + 1));
        if (a == b) b = a + 1;

        ans[i][0] = min(a, b);
        ans[i][1] = max(a, b);
    }
    return ans;
}    


int main() {
    srand(time(0));

    Line l1(4, 9);
    Line l2(1, 4);
    Line l3(7, 15);
    Line l4(2, 4);
    Line l5(4, 6);
    Line l6(3, 7);

    priority_queue<Line, vector<Line>, CMP> heap;
    heap.push(l1);
    heap.push(l2);
    heap.push(l3);
    heap.push(l4);
    heap.push(l5);
    heap.push(l6);

    while (!heap.empty()) {
        Line cur = heap.top(); 
        heap.pop();
        cout << cur.start << ", " << cur.end << endl;
    }


    cout << "test begin" << endl;
    int n = 100;
    int l = 0;
    int r = 200;
    int testTimes = 200000;


    for (int i = 0; i < testTimes + 1; i++) {
        vector<vector<int> > lines = generateLines(n, l, r);
        int ans1 = maxCover(lines);
        int ans2 = maxCover2(lines);
        int ans3 = maxCover3(lines);

        if (ans1 != ans3 || ans2 != ans3) {
            cout << "Oops!!" << endl;
            break;
        }
        
        if (i && i % 1000 == 0) cout << i << " cases passed!" << endl;
    }

    cout << "test end" << endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ym影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值