算法 树6 Complete Binary Search Tree

全部每周作业和视频思考题答案和解析 见 浙江大学 数据结构 思考题+每周练习答案汇总

题目:A Binary Search Tree (BST) is recursively defined as a binary tree which has the following properties:

  • The left subtree of a node contains only nodes with keys less than the node's key.
  • The right subtree of a node contains only nodes with keys greater than or equal to the node's key.
  • Both the left and right subtrees must also be binary search trees.

A Complete Binary Tree (CBT) is a tree that is completely filled, with the possible exception of the bottom level, which is filled from left to right.

Now given a sequence of distinct non-negative integer keys, a unique BST can be constructed if it is required that the tree must also be a CBT. You are supposed to output the level order traversal sequence of this BST.

Input Specification:
Each input file contains one test case. For each case, the first line contains a positive integer N (≤1000). Then N distinct non-negative integer keys are given in the next line. All the numbers in a line are separated by a space and are no greater than 2000.

Output Specification:
For each test case, print in one line the level order traversal sequence of the corresponding complete binary search tree. All the numbers in a line must be separated by a space, and there must be no extra space at the end of the line.

二进制搜索树(BST)递归地定义为具有以下属性的二进制树:

节点的左子树只包含键小于节点键的节点。

节点的右子树仅包含键大于或等于节点键的节点。

左子树和右子树也必须是二进制搜索树。

完整的二叉树(CBT)是一个完全填充的树,但底层可能例外,它是从左到右填充的。

现在给定一系列不同的非负整数键,如果要求树也必须是CBT,则可以构造唯一的BST。您应该输出这个BST的水平顺序遍历序列。

输入规格:

每个输入文件包含一个测试用例。对于每种情况,第一行包含一个正整数N(≤1000)。然后在下一行给出N个不同的非负整数键。一行中所有数字用空格隔开,且不大于2000。

输出规格:

对于每个测试用例,在一行中打印对应的完整二进制搜索树的层序顺序遍历序列。一行中的所有数字都必须用空格隔开,并且行的末尾不能有多余的空格。

  • Sample Input:

    10
    1 2 3 4 5 6 7 8 9 0
    

    Sample Output:

    6 3 8 1 5 7 9 0 2 4

解答:

当我把这个题终于调试通的时候,我真想大喊一声卧槽!!

本来核心算法也没用多少时间,但是怎么运行结果都不对,最后发现竟然是幼稚的冒泡算法写错了,而之前输出验证冒泡算法的时候我竟然把输出看走了眼,看成了对的。

其实算法很简单,这里简单介绍一下流程:

我们先把数据进行排序。

对于N足够大的完全二叉树,第一层一共1个数据,第二层2个数据,第三层4个数据,第四层8个数据。我们需要找到如果不能铺满一层,那那一层一共几个数据。

比如N = 10的时候,1 + 2 + 4 < 10,但是 1+2+4+8>10,所以只铺满了3层,第四层一共有3个数据,即10-1-2-4 = 3.

然后我们知道第4层如果要铺满,需要8个数据,所以铺一半,就是4个数据,而3<4,故这三个数据都在根节点的左边。同理如果12个数据,则最后一层的5个数据4个左边,1个在右边。

然后我们的preHandleData函数需要接受两个值,一个是起始点,一个是大小,然后找到中间的数据。

但是递归调用的时候,

void preHandleData(int N, int startL) {

    如果N等于0,说明这一边没有数了,直接返回。

    如果N等于1,把起始点索引的数插入树中,然后返回。

    处理N。判断左边几个数,右边几个数

    中间的数插入树中。
    preHandleData(左边的数量,左边起始点);
    preHandleData(右边的数量,右边起始点);

}

我们之所以不输出而是插入树中,是因为这个顺序正好是前序遍历的顺序,可以用来直接建立树。

然后层序遍历就好了。

后来我又想出了更简单的方法,但是没有在代码中实现。

就是不用建树,在每次递归的时候,在递归的程序里输入一个值代表位置,然后在调用的递归程序里把根按位置保存进去。

比如计算的第一个根保存在下标为0的位置,然后在调用的递归程序中,

输入分别为2*0+1和2*0+2

然后分别存入2*1+1和2*1+2和2*2+1和2*2+2 以此类推

如果第一个根保存在索引为1的位置则更容易看到规律:

可见每个节点的左子节点索引为当前索引的两倍,右子节点的索引为当前索引的两倍加1,这样递归的时候就可以根据当前节点算出递归到子树的索引然后传值了。(放在最后面)

代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include<queue>
using namespace std;
int Data[1000];

void ReadData(int N);
int OutCome[1000];
int Count = 0;
void CompleteTree(int N);

//利用前序来构建树
typedef int ElementType;
typedef struct Node *Position;
typedef Position Tree; // AVL树类型 
struct Node {
	ElementType Data; // 结点数据 
	Tree Left;     // 指向左子树 
	Tree Right;    // 指向右子树 
};
queue<Tree> myQuene;
Tree Root;
void LevelorderTraversal(Tree BT);
Tree insert(Tree Root, ElementType element);

int main(void) {
	int N;
	cin >> N;
	ReadData(N);

	CompleteTree(N);

	system("pause");
	return 0;
}

void ReadData(int N) {
	for (int i = 0;i<N;i++) {
		cin >> Data[i];
	}
	int temp;
	//冒泡算法第一次竟然写错了!!!结果程序调试了很久,都没发现这个竟然写错了!!!
	for (int i = N-1;i > 0;i--) {
		for (int j = 0;j<i;j++) {
			if (Data[j] > Data[j+1]) {
				temp = Data[j+1];
				Data[j+1] = Data[j];
				Data[j] = temp;
			}
		}
	}
	/*for (int i = 0;i<N;i++) {
		cout << Data[i] << " ";
	}
	cout << endl;*/
}

void preHandleData(int N, int startL) {
	//
	if (N == 0)return;
	else if (N == 1) {
		//cout << Data[startL] << " " << endl;
		Root = insert(Root, Data[startL]);
		Count++;
		return;
	}

	int NumR;
	int NumL;
	int Pow2 = 1;
	int Gens = 1;
	int Num2;
	for (int i = 0;i < 20;i++) {
		Pow2 *= 2;
		Gens += Pow2;
		if (Gens >= N) {
			Num2 = i;
			Gens -= Pow2;
			Pow2 /= 2;
			break;
		}
	}
	
	if (Gens + Pow2 >= N) {
		NumL = (Gens - 1) / 2 + (N - Gens);//左边一共几个数
		NumR = N - NumL - 1;//右边几个数
	}
	else {
		NumL = (Gens - 1) / 2 + Pow2;//左边一共几个数
		NumR = N - NumL - 1;//右边几个数
	}

	//cout << NumL << " " << NumR << " " << Data[startL + NumL] << " "<< endl;
	Root = insert(Root, Data[startL + NumL]);

	
	preHandleData(NumL, startL);
	
	preHandleData(NumR, startL + NumL + 1);

}

void CompleteTree(int N) {
	preHandleData(N,0);
	LevelorderTraversal(Root);
	cout << OutCome[0];
	for (int i = 1;i<N;i++) {
		cout << " "<<OutCome[i];
	}
}
Tree insert(Tree Root, ElementType element) {
	// 将X插入AVL树T中,并且返回调整后的AVL树 
	if (!Root) { //若插入空树,则新建包含一个结点的树 
		Root = (Tree)malloc(sizeof(struct Node));
		Root->Data = element;
		Root->Left = Root->Right = NULL;
	} //if (插入空树) 结束 
	else if (element < Root->Data) {
		//插入T的左子树 
		Root->Left = insert(Root->Left, element);
	} // else if (插入左子树) 结束 

	else if (element > Root->Data) {
		// 插入T的右子树 
		Root->Right = insert(Root->Right, element);
	} // else if (插入右子树) 结束 
	return Root;
}

void LevelorderTraversal(Tree BT)
{
	Tree T;

	if (!BT) return; // 若是空树则直接返回 
	Count = 0;
	myQuene.push(BT);
	while (!myQuene.empty()) {
		T = myQuene.front();
		myQuene.pop();
		OutCome[Count++] = T->Data;
		if (T->Left)    myQuene.push(T->Left);
		if (T->Right)  myQuene.push(T->Right);
	}
}

运行全部通过:

前面说过的简单算法,不需要建树,实现如下:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include<queue>
using namespace std;
int Data[1000];
int OutCome[1000];

void ReadData(int N);
void CompleteTree(int N);

int main(void) {
	int N;
	cin >> N;
	ReadData(N);

	CompleteTree(N);

	system("pause");
	return 0;
}

void ReadData(int N) {
	for (int i = 0;i<N;i++) {
		cin >> Data[i];
	}
	int temp;
	//冒泡算法第一次竟然写错了!!!结果程序调试了很久,都没发现这个竟然写错了!!!
	for (int i = N - 1;i > 0;i--) {
		for (int j = 0;j<i;j++) {
			if (Data[j] > Data[j + 1]) {
				temp = Data[j + 1];
				Data[j + 1] = Data[j];
				Data[j] = temp;
			}
		}
	}
}

void preHandleData(int N, int startL ,int Pos) {
	if (N == 0)return;
	else if (N == 1) {
		OutCome[Pos] = Data[startL];
		return;
	}
	int NumR;
	int NumL;
	int Pow2 = 1;
	int Gens = 1;
	int Num2;
	for (int i = 0;i < 20;i++) {
		Pow2 *= 2;
		Gens += Pow2;
		if (Gens >= N) {
			Num2 = i;
			Gens -= Pow2;
			Pow2 /= 2;
			break;
		}
	}
	if (Gens + Pow2 >= N) {
		NumL = (Gens - 1) / 2 + (N - Gens);//左边一共几个数
		NumR = N - NumL - 1;//右边几个数
	}
	else {
		NumL = (Gens - 1) / 2 + Pow2;//左边一共几个数
		NumR = N - NumL - 1;//右边几个数
	}
	OutCome[Pos] = Data[startL + NumL];
	preHandleData(NumL, startL,2*Pos);
	preHandleData(NumR, startL + NumL + 1, 2 * Pos+1);

}

void CompleteTree(int N) {
	preHandleData(N, 0,1);
	cout << OutCome[1];
	for (int i = 2;i<=N;i++) {
		cout << " " << OutCome[i];
	}
}

测试也是全部通过。

发布了174 篇原创文章 · 获赞 394 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/tiao_god/article/details/105121347