堆中的路径(小顶堆的建立以及堆排序)

前言

推排序中的小顶堆的建立,需要注意的是,哪怕是相同的数,不同的插入顺序最终建立堆都不一样。

题目

将一系列给定数字插入一个初始为空的小顶堆H[i]。随后对任意给定的下标i,打印从H[i]到根结点的路径。

输入格式

组测试第1行包含2个正整数N和M(≤1000),分别是插入元素的个数、以及需要打印的路径条数。下一行给出区间[-10000, 10000]内的N个要被插入一个初始为空的小顶堆的整数。最后一行给出M个下标。

输出格式

对输入中给出的每个下标i,在一行中输出从H[i]到根结点的路径上的数据。数字间以1个空格分隔,行末不得有多余空格。

样例

输入样例

5 3
46 23 26 24 10
5 4 3

输出样例

24 23 10
46 23 10
26 10

思路

完全二叉树,所以显然使用数组进行存储。
前言也提到了,就算是相同的数组序列,不同的插入顺序,最终出来的堆都可能不一样。所以不能直接对完整序列进行处理,需要一个个插入操作。
小顶堆的插入是这样的:

  1. 每次插入在接在最下层的空位的最右边,然后再进行调整;
  2. 调整的时候从按照从下到上,从右到左的顺序对非叶结点进行调整(这是因为叶结点显然满足小顶堆的要求)。每次调整目的都是要保证根结点比孩子结点小,如果不满足,就与孩子结点中更小的那一个互换,然后再对换下去的结点检查并调整直至到底层或者满足。
  3. 每次插入都会影响整体的树,所以所有的非叶结点都需要进行调整。

访问指定下标的路径,且从底到上的方法:
因为使用数组存储,所以很方便的就可以找到某个结点的孩子结点,或者,某个结点的父结点。对于某个下标为i的结点来说,i/2向下取整就是该结点的父结点的位置。
注意,用数组存储二叉树时最好从下标1开始存储,因为可以方便的里面上述性质。

实现

完整代码

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

const int MaxSize = 1001;

void myInsert(int a[], int p, int key);			//插入结点并执行调整程序
void myBuildHeap(int a[], int n);			//对整棵树进行调整,即对所有非叶结点进行调整操作
void myAdjust(int a[], int low, int high);	        //对单个非叶结点进行调整操作
void myShowPath(int a[], int p);			//找到并显示路径

int main() {
	int small_heap[MaxSize] = { 0, };
	int N, M;
	cin >> N >> M;
	for (int i = 1; i < N + 1; i++) {		//注意从1开始存储
		int temp;
		cin >> temp;
		myInsert(small_heap, i, temp);		//边输入就开始调整了
	}
	//到这里已经建堆完成了
	for (int i = 0; i < M; i++) {
		int p;
		cin >> p;
		myShowPath(small_heap, p);
	}

	return 0;
}

void myInsert(int a[], int p, int key) {	
	a[p] = key;
	myBuildHeap(a, p);
}

void myBuildHeap(int a[], int n) {
	for (int i = n / 2; i > 0; i--) 		//从最后一个非叶结点开始调整,这里注意对于一个结点数为n的CT来说,非叶结点有n/2个
		myAdjust(a, i, n);					
}

void myAdjust(int a[], int low, int high) {		//调整单个非叶结点
	int i = low;
	int j = 2 * i;					//从左子结点开始
	while (j <= high) {				//选取所有非叶结点
		if (j < high && a[j] > a[j + 1])	//双结点,先找出子结点里面最小的,不管单双结点,j最终指向两个子结点中的最小值
			j++;
		if (a[i] > a[j]) {			//判断是否要进行更换
			int temp = a[i];
			a[i] = a[j];
			a[j] = temp;
			i = j;
			j = 2 * i;
		}
		else
			break;
	}
}

void myShowPath(int a[], int p) {			//找到路径
	int flag = 0;
	while (p != 0) {
		if (flag == 0)
			flag = 1;
		else
			cout << " ";
		cout << a[p];
		p /= 2;
	}
	cout << endl;
}

调整单个非叶结点函数

绝对的核心算法,这里有以下几个点需要注意的:

  1. 对于小顶堆而言,只需要将父结点与子结点内更小的那个进行比较即可,不需要两个全部比较,调换的话也只需要与更小的那个呼唤即可
  2. 需要考虑到该非叶结点可能只有一个子结点的情况,因为CT的特殊性质,如果存在这种结点,其子结点位置必然处于最后一个位置,且肯定是左结点,只要对这个进行判断即可
  3. 若是一轮循环中没有发生互换,说明整棵树已经调整好了,因为从下往上调整的,所以底下的肯定也是调整好的
void myAdjust(int a[], int low, int high) {		//调整单个非叶结点
	int i = low;
	int j = 2 * i;					//从左子结点开始
	while (j <= high) {				//选取所有非叶结点
		if (j < high && a[j] > a[j + 1])	//双结点,先找出子结点里面最小的;单结点,就不变。不论单双结点,j最终指向子结点中的最小值。
			j++;
		if (a[i] > a[j]) {			//将父结点与值最小的子结点判断是否要进行更换
			int temp = a[i];
			a[i] = a[j];
			a[j] = temp;
			i = j;
			j = 2 * i;
		}
		else
			break;                           //如果这轮不发生更换的话,那说明整棵树已经调整好了,因为从下往上调整的,所以底下的肯定也是调整好的
	}
}

题目以外

都说到小顶堆的建立了,怎么能不加上推排序呢。
小顶堆的性质是根结点的数必定是整个树中最小的。
推排序就是每次将根结点的数与末尾的数提取出来,并将最后一个叶结点的数填充到根结点的位置再对根结点进行调整使其再次称为小顶堆,直至最后将所有元素按照顺序提取出来,就是一个有序序列了。
所以在建好堆之后加一个提取过程就行了

函数代码

排序顺带输出了

void myHeapSort(int a[], int n) {
	for (int i = 1; i < n + 1; i++) {
		cout << a[1] <<" ";
		mySwap(a, 1, n + 1 - i);        //交换
		myAdjust(a, 1, n - i);          //对根结点进行调整
	}
	cout << endl;
}

void mySwap(int a[], int x, int y) {
	int temp = a[x];
	a[x] = a[y];
	a[y] = temp;
}

猜你喜欢

转载自www.cnblogs.com/Za-Ya-Hoo/p/12731423.html
今日推荐