堆排序

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

输入格式

第一行包含整数 n 和 m。

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

输出格式

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

数据范围

1≤m≤n≤1e5,
1≤数列中元素≤1e9

输入样例:

1
2
5 3
4 5 1 3 2

输出样例:

1
1 2 3

个人代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int num[N],len = 0;
// len 代表着最后节点的下标 len 去0 无意义 我们把一个节点的左右节点定义为 2*i 2*i +1
void insert(int x) {
num[++len] = x;
int cur = len;
// 插入点到末尾后需要不断和他的父节点
while(cur/2 != 0 && num[cur] < num[cur>>1]){
swap(num[cur],num[cur>>1]);
cur = cur>>1;
}
}
int pop() {
int cur = 1;
swap(num[cur],num[len--]);
// 当我们需要删除一个节点时先把他移到末尾并且长度减一 (相当于把这个元素剥离出来了) 然后分别判断左右子节点是否可用且小于目标值保证把最小值提上去维护后保证父节点一定小于子节点
while((2*cur +1 <= len && num[2*cur + 1] < num[cur])
||(2*cur <= len && num[2*cur] < num[cur])){
if (len >= 2* cur + 1 && num[2*cur] > num[2*cur + 1]) {
swap(num[cur], num[2*cur + 1]);
cur = 2*cur + 1;
} else {
swap(num[cur], num[2*cur]);
cur = 2*cur;
}

}
return num[len+1];

}
int main() {
int n,m;cin>>n>>m;
for(int i=0;i<n;i++){
int temp;cin>>temp;
insert(temp);
}
for(int i=0;i<m;i++){
printf("%d ",pop());
}
}
/*
5 3
4 5 1 3 2


*/

板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int h[N], cnt;

void down(int u)
{
int t = u;
if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
swap(h[u], h[t]);
down(t);
}
}

int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
cnt = n;

for (int i = n / 2; i; i -- ) down(i);
// 个人不足如果他给我们全部的数据就可以从所以非叶节点开始下沉 保证把最小值放上去

while (m -- )
{
printf("%d ", h[1]);
h[1] = h[cnt -- ];
down(1);
}

puts("");

return 0;
}

模拟堆

维护一个集合,初始时集合为空,支持如下几种操作:

  1. I x,插入一个数 x;
  2. PM,输出当前集合中的最小值;
  3. DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
  4. D k,删除第 k 个插入的数;
  5. C k x,修改第 k 个插入的数,将其变为 x;

现在要进行 N 次操作,对于所有第 2 个操作,输出当前集合的最小值。

输入格式

第一行包含整数 N。

接下来 N 行,每行包含一个操作指令,操作指令为 I xPMDMD kC k x 中的一种。

输出格式

对于每个输出指令 PM,输出一个结果,表示当前集合中的最小值。

每个结果占一行。

数据范围

1≤N≤1e5
−1e9≤x≤1e9
数据保证合法。

输入样例:

1
2
3
4
5
6
7
8
9
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

输出样例:

1
2
-10
6

个人代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include<bits/stdc++.h>
using namespace std;
const int N = 100100;
// 记录每一次删除的时机
set<int>del;
map<int,int>mir;
// 存储第一个是值第二个是插入的时机
pair<int, int> num[N];
int len = 0;
int indexs = 1;

void insert(int x, int times) {
num[++len] = {x, times};
int cur = len;
while ((cur >> 1) > 0 && num[cur >> 1].first > num[cur].first) {
swap(num[cur], num[cur >> 1]);
cur = cur >>1;
}
}

pair<int, int> pop() {
swap(num[1], num[len--]);
int cur = 1;
while (
(2 * cur <= len && num[2 * cur] < num[cur]) ||
(2 * cur + 1 <= len && num[2 * cur + 1] < num [cur])) {
if (2 * cur + 1 <= len && num[2 * cur + 1] < num [2 * cur]) {
swap(num[cur], num[2 * cur + 1]);
cur = 2 * cur + 1;
} else {
swap(num[cur], num[2 * cur]);
cur = 2 * cur;
}
}
return num[len + 1];
}



int main() {
int n;
cin >> n;
int indes = 0;
while (n--) {
string op;
cin >> op;
if (op == "I") {
int val;
cin >> val;
insert(val, ++indes);
} else if (op == "PM") {
while (del.count(num[1].second) || (
mir.count(num[1].second) && mir[num[1].second] != num[1].first
)) {
pop();
}
cout << num[1].first << endl;
} else if (op == "DM") {
while (del.count(num[1].second) ||
(mir.count(num[1].second) && mir[num[1].second] != num[1].first)
) {
pop();
}
pop();
} else if (op == "D") {
int val;
cin >> val;
del.insert(val);
} else if (op == "C") {
int k, x;
cin >> k >> x;
insert(x,k);
mir[k] = x;
}
}
}

/*

8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

I x,插入一个数 x;
PM,输出当前集合中的最小值;
DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
D k,删除第 k个插入的数;
C k x,修改第 k个插入的数,将其变为 x;


感悟:细节,细节细节还他妈的是细节,代码没有反馈到所有的情况,例如以前没考虑到修改的操作,如果你把修改和删除放到一起不能保证当前的值是最小值 如果你在增加一个新的值来解决但不确定那个就需要用一个map来记录总归是难在没有办法实现从次数到位置的映射,这其实很容易实现也不知道脑子抽啥风了,理解的方向是给定两个数组一个 是从时间到空间的映射一个是从空间到时间的映射
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 100010;

int h[N], ph[N], hp[N], cnt;
// 通过两个数组就可以实现两个数组之间的映射, hp 从地址到 第几次操作的映射 ph 从第几次操作到地址的映射

void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
// 此处一直在困扰我肯能是最近太累了一定要早早睡觉了
}

void down(int u)
{
int t = u;
if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
heap_swap(u, t);
down(t);
}
}

void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}

int main()
{
int n, m = 0;
scanf("%d", &n);
while (n -- )
{
char op[5];
int k, x;
scanf("%s", op);
if (!strcmp(op, "I"))
{
scanf("%d", &x);
cnt ++ ;
m ++ ;
ph[m] = cnt, hp[cnt] = m;
h[cnt] = x;
up(cnt);
}
else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
else if (!strcmp(op, "DM"))
{
heap_swap(1, cnt);
cnt -- ;
down(1);
}
else if (!strcmp(op, "D"))
{
scanf("%d", &k);
k = ph[k];
heap_swap(k, cnt);
cnt -- ;
up(k);
down(k);
}
else
{
scanf("%d%d", &k, &x);
k = ph[k];
h[k] = x;
up(k);
down(k);
}
}

return 0;
}