问题 A: 算法10-10,10-11:堆排序

时间限制: 1 Sec  内存限制: 32 MB
提交: 183  解决: 131
[​​​提交​​​][​​状态​​​][​​讨论版​​][命题人:外部导入]

题目描述

堆排序是一种利用堆结构进行排序的方法,它只需要一个记录大小的辅助空间,每个待排序的记录仅需要占用一个存储空间。

首先建立小根堆或大根堆,然后通过利用堆的性质即堆顶的元素是最小或最大值,从而依次得出每一个元素的位置。

堆排序的算法可以描述如下:

在本题中,读入一串整数,将其使用以上描述的堆排序的方法从小到大排序,并输出。

 

输入

输入的第一行包含1个正整数n,表示共有n个整数需要参与排序。其中n不超过100000。

第二行包含n个用空格隔开的正整数,表示n个需要排序的整数。

输出

只有1行,包含n个整数,表示从小到大排序完毕的所有整数。

请在每个整数后输出一个空格,并请注意行尾输出换行。

样例输入

10
2 8 4 6 1 10 7 3 5 9

样例输出

1 2 3 4 5 6 7 8 9 10

提示

在本题中,需要按照题目描述中的算法完成堆排序的算法。

堆排序对于元素数较多的情况是非常有效的。通过对算法的分析,不难发现在建立含有n个元素的堆时,总共进行的关键字比较次数不会超过4n,且n个节点的堆深度是log2n数量级的。因此,堆排序在最坏情况下的时间复杂度是O(nlog2n),相对于快速排序,堆排序具有同样的时间复杂度级别,但是其不会退化。堆排序较快速排序的劣势是其常数相对较大。

//简单堆排序实现

#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=100010;
int heap[maxn],n;

void downAdjust(int low,int high){
int i=low,j=i*2;//i调整结点 j孩子结点
while(j<=high){
if(j+1<=high&&heap[j+1]>heap[j]) j++;
if(heap[i]<heap[j]){//子节点大
swap(heap[i],heap[j]);
i=j;
j=2*i;
}else{
break;
}
}
}

void CreateHeap(){
for(int i=n/2;i>=1;i--){//最后一个非叶子结点开始下调整
downAdjust(i,n);
}
}

void heapSort(){
CreateHeap();
for(int i=n;i>1;i--){
swap(heap[1],heap[i]);//注意是i
downAdjust(1,i-1);//注意是i
}
}

int main(){
// freopen("inputa.txt","r",stdin);
while(cin>>n){
for(int i=1;i<=n;i++){
cin>>heap[i];
}
heapSort();
for(int i=1;i<=n;i++){
cout<<heap[i]<<" ";
}
cout<<endl;
}
return 0;
}

写测试数据的太懒了。。。。直接暴力也能过。。。

#include<iostream>
#include<algorithm>
using namespace std;
int n,a[1000010];//数组太大 只能定义为全局
int main(){
// freopen("inputa.txt","r",stdin);
while(cin>>n){
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n);
for(int i=0;i<n;i++) cout<<a[i]<<" ";
cout<<endl;
}
return 0;
}

问题 B: 序列合并

时间限制: 1 Sec  内存限制: 128 MB
提交: 327  解决: 99
[​​​提交​​​][​​状态​​​][​​讨论版​​][命题人:外部导入]

题目描述

有两个长度都为N的序列A和B,在A和B中各取一个数相加可以得到N2个和,求这N2个和中最小的N个。

输入

第一行一个正整数N(1 <= N <= 100000)。
第二行N个整数Ai,满足Ai <= Ai+1且Ai <= 109
第三行N个整数Bi,满足Bi <= Bi+1且Bi <= 109

输出

输出仅有一行,包含N个整数,从小到大输出这N个最小的和,相邻数字之间用空格隔开。

样例输入

3
2 6 6
1 4 8

样例输出

3 6 7

提示

建议用最小堆实现。

 

优先队列大顶堆实现,暴力的基础上进行了一点优化:除第一次A[0]+B[0~n]大顶堆排序

(此时q中存了n个元素,且是一个大顶堆  q.top为最大元素,接下来暴力枚举的元素与q.top比较即可,大就覆盖q.top(又会自动排序))

然后每次算出一个(如第二次)A[1]+B[0~n]若小于q.top()说明在前n小之列,覆盖q.top()

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=100010;
int a[maxn],b[maxn],c[maxn];
int n;
priority_queue<int> q;//插入优先队列的元素自动进行堆排序(默认大顶堆) 且每次只能访问堆顶元素

int main(){
// freopen("inputb.txt","r",stdin);
int x,y,index=0;
while(cin>>n){
// q.clear();//没有clear但其实每次输出前都pop()空了
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++) {
cin>>b[i];
q.push(a[0]+b[i]);//自动进行堆排序 默认q.top()最大
}

for(int i=1;i<n;i++){
for(int j=0;j<n;j++){
if(a[i]+b[j]<q.top()) {
q.pop();
q.push(a[i]+b[j]);
}else{
break;
}
}
}

for(int i=0;i<n;i++){
c[i]=q.top();
q.pop();
}
for(int i=n-1;i>=0;i--){
cout<<c[i]<<" ";
}
cout<<endl;
}
return 0;
}

 

问题 C: 合并果子(堆)

时间限制: 1 Sec  内存限制: 128 MB
提交: 113  解决: 96
[​​​提交​​​][​​状态​​​][​​讨论版​​][命题人:外部导入]

题目描述

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

    每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

    因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

    例如有3种果子,数目依次为1,2,9。可以先将 1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为 12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。

输入

输入文件fruit.in包括两行,第一行是一个整数n(1 <= n <= 30000),表示果子的种类数。第二行包含n个整数,用空格分隔,第i个整数ai(1 <= ai <= 20000)是第i种果子的数目。

输出

输出文件fruit.out包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于231。

样例输入

10
3 5 1 7 6 4 2 5 4 1

样例输出

120

从小到大排序,每次取最小的两个合并,得到一个新的数列(比合并前少一个元素,因为2个合并成了一个),再次对新数列排序(其实只用删除合并的两个数字再将合并后的新数字插入适当的位置即可)。不断循环,直至只剩一个元素(只剩下一堆了)

排序次数过多,每次只取最小的两个元素,小顶堆实现最好不过

方法1:自己实现小顶堆

#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=30010;
int heap[maxn],n;

void downAdjust(int low,int high){
int i=low,j=i*2;
while(j<=high){
if(j+1<=high&&heap[j+1]<heap[j]) j++;
if(heap[j]<heap[i]){
swap(heap[i],heap[j]);
i=j;
j=i*2;
}else{
break;//一旦顶点比孩子小 一定跳出 否则死循环
}
}
}

void CreateHeap(){
for(int i=n/2;i>=1;i--){
downAdjust(i,n);
}
}

int getTop(){
return heap[1];
}

//删除堆顶元素
int PopHeap(){
heap[1]=heap[n--];
downAdjust(1,n);//对堆顶元素做一次向下调整即可
}

//upAjust 向上调整
void upAjust(int low,int high){
int i=high,j=i/2;
while(j>=low){
if(heap[j]>heap[i]){
swap(heap[i],heap[j]);
i=j;
j=i/2;
}else{
break;//千万不能忘
}
}
}

//插入结点
void Push(int x){
heap[++n]=x;
upAjust(1,n);
}


int main(){
int x,y,sum;
while(cin>>n){
sum=0;
for(int i=1;i<=n;i++) cin>>heap[i];
CreateHeap();//建堆 千万别忘了
while(n>1){
x=getTop();PopHeap();
y=getTop();PopHeap();
Push(x+y);
sum+=x+y;
}
cout<<sum<<endl;
}
return 0;
}

方法2.直接调用优先队列

改变排序依据,变成小顶堆即可

#include<iostream>
#include<queue>
using namespace std;
int main(){
priority_queue<int,vector<int>,greater<int> > q;//greater小顶堆 小的优先级高
int n,t,x,y,sum;
while(cin>>n){
sum=0;
for(int i=0;i<n;i++){
cin>>t;
q.push(t);
}
while(q.size()>1){
x=q.top();q.pop();
y=q.top();q.pop();
q.push(x+y);
sum+=x+y;
}
cout<<sum<<endl;
}
return 0;
}

方法三:墓地的数据实在太弱了,直接线性地插入也能过

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int n,a[30010],b[30010];
while(cin>>n){
for(int i=0;i<n;i++){
cin>>a[i];
}
sort(a,a+n);
int sum=0;
while(1){
int x=a[0]+a[1],index=0,k=1;
sum+=x;
for(int i=2;i<n;i++){
if(x<a[i]&&k){
b[index++]=x;
b[index++]=a[i];
k--;
}else{
b[index++]=a[i];
}
}
if(k==1){//和是最大的一个
b[index++]=x;
}
n=index;
for(int i=0;i<n;i++) a[i]=b[i];
if(index==1) break;
}
cout<<sum<<endl;
}
return 0;
}

本题的思想就是后面哈夫曼树的建树过程