PAT分类-堆

目录

堆的基础理论

堆的基本操作

堆排序

A1098 Insertion or Heap Sort

A1147 Heaps

A1155 Heap Paths


堆的基础理论

堆是一棵完全二叉树。

树中每个节点都不小于(>=)(或不大于(<=))其左右孩子节点的值。

>= : 大顶堆,<= : 小顶堆。

堆一般用于优先队列的实现。优先队列默认大顶堆。

堆的基本操作

const int maxn = 100;
int heap[maxn], n = 10; //n为元素个数
// 以大堆顶为例
// 向下调整 O(logN)
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 ++; //j是较大的孩子节点
        if(heap[j] > heap[i]) { //孩子比父亲大
            swap(heap[j], heap[i]);
            i = j;
            j = i * 2; //向下移动
        }
        else break;
    }
}
//建堆
void createHeap() {
    for(int i = n / 2; i >= 1; i --)  //从最后一个非叶子节点倒着下调
        downAjust(i, n);
}
//删除堆顶元素
void deleteTop() {
    heap[1] = heap[n --];
    downAdjust(1, n);
}

//如果要添加元素,需要上调
//向上调整
void upAdjust(int low, int high) {  
    int i = high, j = i / 2; //i为欲调整节点,j为其父亲
    while(j >= low) {
        if(heap[j] < heap[i]) { //父亲边孩子小
            swap(heap[j], heap[i]);
            i = j;
            j = i / 2; //上移
        }
        else break;
    }
}
//插入元素
void insert(int x) {
    heap[++ n] = x;
    upAdjust(1, n);
}

堆排序

使用堆数据结构进行排序。

void heapSort() {
    createHeap();
    for(int i = n; i > 1; i --) { //倒着枚举,直到堆中只有一个元素
        swap(heap[i], heap[1]);
        downAdjust(1, i - 1);
    }
}

A1098 Insertion or Heap Sort

题意:判断是插入排序还是堆排序。

解法:每一步排序判断当前临时数组是否和目标数组想等。实现插入排序和堆排序即可。

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

const int maxn = 110;
int origin[maxn], changed[maxn], tempOri[maxn];

int n;
bool isSame(int A[], int B[]) {
	for(int i = 1; i <= n;i ++)
		if(A[i] != B[i]) return false;
	return true;
} 

void printArray(int A[]) {
	for(int i = 1; i <= n; i ++) {
		if(i > 1) printf(" ");
		printf("%d", A[i]);
	}
}

bool insertionSort() {
	bool flag = false;
	for(int i = 2; i <= n; i ++) {
		if(i != 2 && isSame(tempOri, changed)) {
			flag = true;
		}
		sort(tempOri + 1, tempOri + i + 1);
		if(flag == true)
			return true;
	}
	return false;
} 

void downAdjust(int low, int high) {
	int i = low, j = i * 2;
	while(j <= high) {
		if(j + 1 <= high && tempOri[j + 1] > tempOri[j])
			j ++;
		if(tempOri[j] > tempOri[i]) {
			swap(tempOri[j], tempOri[i]);
			i = j;
			j = i * 2;
		}
		else 
			break;
	}
}

void heapSort() {
	bool flag = false;
	for(int i = n / 2; i >= 1; i --)
		downAdjust(i, n); //建堆 
	for(int i = n; i > 1; i --) {
		if(i != n && isSame(tempOri, changed)) 
			flag = true;
		swap(tempOri[i], tempOri[1]);
		downAdjust(1, i - 1); //调整堆顶
		if(flag == true)  {
			printArray(tempOri);
			return;
		}
	}
}

int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &origin[i]);
		tempOri[i] = origin[i];
	}
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &changed[i]);
	}
	if(insertionSort()) {
		printf("Insertion Sort\n");
		printArray(tempOri);
	}
	else {
		printf("Heap Sort\n");
		for(int i = 1; i <= n; i ++)
			tempOri[i] = origin[i];
		heapSort();
	}
	return 0;
}

A1147 Heaps

题意:判断完全二叉树是最大堆还是最小堆,还是不是堆。

解法:堆是完全二叉树,可以实现树的遍历。先判断是不是最大堆或最小堆,从n / 2个节点(非叶子节点)开始倒着判断,如果不满足父亲节点同时>=或同时<=子节点,则不是最大堆或最小堆。

然后堆的后序遍历就是完全二叉树的后序遍历。左子树root * 2,右子树root * 2 + 1。后序遍历的结果存在一个数组v中然后输出。

#include<cstdio>
#include<vector>
using namespace std;

const int maxn = 1010;
int CBT[maxn];
int m, n;
bool isMaxHeap(int CBT[]) {
	bool flag = true;
	for(int i = n / 2; i > 0; i --) {
		int j = i * 2;
		if(j + 1 <= n) {
			if(CBT[i] >= CBT[j] && CBT[i] >= CBT[j + 1]) flag = true;
			else {
				flag = false;
				break;	
			};
		}
		else if(j + 1 > n) {
			if(CBT[i] < CBT[j]) {
				flag = false;
				break;
			}
		}
	}
	return flag;
}

bool isMinHeap(int CBT[]) {
	bool flag = true;
	for(int i = n / 2; i > 0; i --) {
		int j = i * 2;
		if(j + 1 <= n) {
			if(CBT[i] <= CBT[j] && CBT[i] <= CBT[j + 1]) flag = true;
			else {
				flag = false;
				break;	
			};
		}
		else if(j + 1 > n) {
			if(CBT[i] > CBT[j]) {
				flag = false;
				break;
			}
		}
	}
	return flag;
}

int cnt = 0;
void postorder(int root, vector<int> & v) {
	if(root > n) return;
	postorder(root * 2, v);
	postorder(root * 2 + 1, v);
	v.push_back(CBT[root]);
}

int main() {
	scanf("%d %d", &m, &n);
	for(int i = 0; i < m; i ++) {
		for(int j = 1; j <= n; j ++)
			scanf("%d", &CBT[j]);
		if(isMaxHeap(CBT)) {
			printf("Max Heap\n");
		}
		else if(isMinHeap(CBT)) {
			printf("Min Heap\n");
		}
		else {
			printf("Not Heap\n");
		}
		vector<int> v;
		postorder(1, v);
		for(int i = 0; i < v.size(); i ++) {
			if(i > 0) printf(" ");
			printf("%d", v[i]);
		}
		printf("\n");
	}
	return 0;
}

A1155 Heap Paths

题意:输出堆中所有从顶点到叶子的路径。并判断最大堆最小堆和不是堆。

解法:递归打印堆路径。递归边界root > n表示空节点,返回。当处理到叶子节点(root * 2 > n)时堆路径已完整,将路径保存到ans数组。非叶子节点时,将节点加入路径然后递归进入左子树和右子树。注意题目中要求右子树必须出现在左子树前面。这是通过先序遍历实现的。递归时先进入左子树后进入右子树,保存路径时则先保存右子树后保证左子树。(递归栈) 

最大堆和最小堆和非堆按照所有路径是否都有序判断即可。

#include<cstdio>
#include<vector>
#include<set>
using namespace std;
 
const int maxn = 1010;
int CBT[maxn];
int n;
vector<int> tempPath, path;
vector<vector<int>> ans;


void printHeapPath(int root) {
	if(root > n) return;
	if(root * 2 > n) { //叶子节点
		tempPath.push_back(CBT[root]);
		path = tempPath;
		ans.push_back(path);
//		for(int i = 0; i < path.size(); i ++) {
//			printf("%d", path[i]);
//			if(i < path.size() - 1) printf(" ");
//			else printf("\n");
//		}
		tempPath.pop_back();
		return;
	}
	tempPath.push_back(CBT[root]);
	printHeapPath(root * 2);
	printHeapPath(root * 2 + 1);
	tempPath.pop_back();
}

int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &CBT[i]);
	}
	printHeapPath(1);
	bool isMinHeap = true, isMaxHeap = true;
	for(int i = ans.size() - 1; i >= 0; i --) {
		for(int j = 0; j < ans[i].size(); j ++) {
			printf("%d", ans[i][j]);
			if(j > 0) {
				if(ans[i][j - 1] > ans[i][j]) isMinHeap = false;
				if(ans[i][j - 1] < ans[i][j]) isMaxHeap = false;
			}
			if(j < ans[i].size() - 1) printf(" ");
			else printf("\n");
		}
	}
	if(isMinHeap == true) printf("Min Heap\n");
	else if(isMaxHeap == true) printf("Max Heap\n");
	else printf("Not Heap\n");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值