codeup 9.7小节——数据结构专题(2)->堆

本文深入探讨了堆排序算法的原理与实现,通过具体示例解析了堆排序的过程。同时,文章还介绍了如何使用堆结构来解决序列合并问题,提供了一种高效寻找两序列最小和值的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题 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;
}

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

 

1.用二叉链表做存储结构,输入键值序列,利用查找、插入算法建立一棵二叉排序树。 2.按中序遍历这棵二叉排序树。 3.在二叉排序树上插入结点。 4.删除二叉排序树上的结点。 四、【参考程序清单】 #include <stdio.h> #include <stdlib.h> #include "Datahead.h" /*二叉树的链式存储表示*/ typedef int TElemType; /*由用户定义的实际数据类型*/ typedef struct BiTNode { TElemType data; struct BiTNode *lchild, *rchild; /*左右孩子指针*/ } BiTNode, *BiTree; /*结点类型*/ typedef int KeyType; /*二叉排序树使用整形数据*/ typedef int ElemType; /*二叉排序树使用整形数据*/ void main() { void Inorder(BiTree T); /*中序遍历二叉树*/ Status SearchBST(BiTree T, KeyType key, BiTree f, BiTree *p); Status InsertBST(BiTree *T, ElemType e); Status DeleteBST(BiTree *T, KeyType key); Status Delete(BiTree *p); Status EQ(KeyType key1, KeyType key2); Status LT(KeyType key1, KeyType key2); BiTree T; printf("please input Key(0 means end )\n"); /*请补充代码 */ /*所用函数,请参照课本算法*/ getch(); } /*中序遍历二叉树的简单写法 */ void Inorder(BiTree T) { if(T){ Inorder(T->lchild); printf("%c",T->data); Inorder(T->rchild); } } Status EQ(KeyType key1, KeyType key2){ if (key1==key2) return TRUE; else return FALSE; } Status LT(KeyType key1, KeyType key2){ if (key1<key2) return TRUE; else return FALSE; } Status SearchBST(BiTree T, KeyType key, BiTree f, BiTree *p) { /* 算法9.5(b) */ /* 在根指针T所指二叉排序树中递归地查找其关键字等于key的数据元素, */ /* 若查找成功,则指针p指向该数据元素结点,并返回TRUE, */ /* 否则指针p指向查找路径上访问的最后一个结点并返回FALSE, */ /* 指针f指向T的双亲,其初始调用值为NULL */ if (!T) { *p = f; return FALSE; } /* 查找成功 */ else if (EQ(key, T->data)) { *p = T; return TRUE; } /* 查找成功 */ else if (LT(key, T->data)) return SearchBST(T->lchild, key, T, p); /* 在左子树中继续查找 */ else return SearchBST(T->rchild, key, T, p); /* 在右子树中继续查找 */ } /* SearchBST */ Status InsertBST(BiTree *T, ElemType e) { /* 算法9.6 */ /* 当二叉排序树T中存在关键字等于e.key的数据元素时, */ /* 插入e并返回TRUE,否则返回FALSE */ BiTree p,s; if (!SearchBST(*T, e, NULL, &p)) { /* 查找成功 */ s = (BiTree)malloc(sizeof(BiTNode)); s->data = e; s->lchild = s->rchild = NULL; if (!p) *T = s; /* 插入 s 为新的根结点 */ else if (LT(e, p->data)) p->lchild=s; /* 插入s为左孩子 */ else p->rchild = s; /* 插入 s 为右孩子 */ return TRUE; } else return FALSE; /* 树中已有关键字相同的结点,再插入 */ } /* Insert BST */ Status DeleteBST(BiTree *T, KeyType key) { /* 算法9.7 */ /* 若二叉排序树T中存在关键字等于key的数据元素时, */ /* 则删除该数据元素结点p,并返回TRUE;否则返回FALSE */ if (!(*T)) return FALSE; /* 存在关键字等于key的数据元素 */ else { if (EQ(key, (*T)->data)) /* 找到关键字等于key的数据元素 */ return Delete(T); else if (LT(key, (*T)->data)) return DeleteBST(&((*T)->lchild),key); else return DeleteBST(&((*T)->rchild), key); } } /* DeleteBST */ Status Delete(BiTree *p) { /* 算法9.8 */ /* 从二叉排序树中删除结点p,并重接它的左或右子树 */ BiTree q, s; if (!((*p)->rchild)) { /* 右子树空则只需重接它的左子树 */ q = *p; *p = (*p)->lchild; free(q); } else if (!((*p)->lchild)) { /* 只需重接它的右子树 */ q = *p; *p = (*p)->rchild; free(q); } else { /* 左右子树均空 */ q = *p; s = (*p)->lchild; while (s->rchild) /* 转左,然后向右到尽头 */ { q = s; s = s->rchild; } (*p)->data = s->data; /* s指向被删结点的"后继" */ if (q != *p) q->rchild = s->lchild; /* 重接*q的右子树 */ else q->lchild = s->lchild; /* 重接*q的左子树 */ free(s); } return TRUE; } /* Delete */ 在vs中完成该题目要求(用c语言写)
最新发布
06-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值