二叉树存储方式转换

存储方式转换及循环输入 API

最近更新于 2022-10-03, 发布于 2022-08-12

0. 数据结构

typedef struct TreeNode {
	int data = 0;
	TreeNode* lchild = nullptr;
	TreeNode* rchild = nullptr;
} TreeNode, * BiTree;

1. 链式存储初始化

1.1. 一般二叉树 (控制台输入)

相当于读取字符串, 转存为整型数据.

#define _CRT_SECURE_NO_WARNINGS

// The variable level is used for building levels
// and does not require user input.
// So using buildBiTree() is perfectly fine;
BiTree buildBiTree(size_t level = 1)
{
	#define CLRSTDIN do { while (getchar() != '\n') { continue; }} while (0)
	
	#define INDENT(m_indsize) do { for (size_t index = 0; index < m_indsize; ++index) { std::cout << "    "; }} while (0)

	// Welcome guide;
	if (level == 1) {
		std::cout << "A binary tree is being built. " << std::endl;
		std::cout << "Enter data in the node after prompting, " << std::endl;
		std::cout << "or enter any non-digit character except \'\\n\' for empty node. " << std::endl << std::endl;
		std::cout << "Root: ";
	}

	// test empty input and rewinding;
	char temp = '\0';
	while ((temp = getchar()) == '\n') {
		// move back to the end of the line;
		std::cout << "\033[1A";
		for (size_t index = 0; index < (level - 1) * 4 + 8; ++index) {
			std::cout << "\033[1C";
		}
		if (level == 1) {
			std::cout << "\033[2D";
		}
	}
	ungetc(temp, stdin);

	// load in data;
	TreeNode* node = new TreeNode;
	if (scanf("%d", &(node->data)) == 1) {
		// clear stdin for the next empty-input test;
		CLRSTDIN;

		INDENT(level);
		std::cout << "Lchild: ";
		node->lchild = buildBiTree(level + 1);
		if (node->lchild == nullptr) {
			std::cout << "\r\033[1A\033[K";
			INDENT(level);
			std::cout << "Lchild: NULL" << std::endl;
		}

		INDENT(level);
		std::cout << "Rchild: ";
		node->rchild = buildBiTree(level + 1);
		if (node->rchild == nullptr) {
			std::cout << "\r\033[1A\033[K";
			INDENT(level);
			std::cout << "Rchild: NULL" << std::endl;
		}

		if (node->lchild == nullptr && node->rchild == nullptr) {
			std::cout << "\r\033[1A\033[K";
			std::cout << "\r\033[1A\033[K";
		}

		return node;
	}
	else { // empty child;
		// clear stdin for the next empty-input test;
		CLRSTDIN;
		// reprint output for null root; 
		if (level == 1) {
			std::cout << "\r\033[1A\033[K"; 
			std::cout << "Root: NULL" << std::endl; 
		}

		return nullptr;
	}
}

复杂结点(带名字的数据节点)的录入如下:

typedef struct TreeNode {
	struct {
		char name[21] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
		int size = 0;
	} data;
	TreeNode* lchild = nullptr;
	TreeNode* rchild = nullptr;
} TreeNode, * BiTree;

BiTree buildBiTree(size_t level = 1)
{
	#define CLRSTDIN do { while (getchar() != '\n') { continue; }} while (0)
	
	#define INDENT(m_indsize) do { for (size_t index = 0; index < m_indsize; ++index) { std::cout << "    "; }} while (0)

	// Welcome guide;
	if (level == 1) {
		std::cout << "A binary tree is being built. " << std::endl;
		std::cout << "Enter the name (20 char at most) and data of the node after prompting (e.g. \"nodeName 100\"), " << std::endl;
		std::cout << "or enter \"null node\" for empty node. " << std::endl << std::endl;
		std::cout << "The quotation marks are not neccessary for input. " << std::endl;
		std::cout << "Root: ";
	}

	// test empty input and rewinding;
	char temp = '\0';
	while ((temp = getchar()) == '\n') {
		// move back to the end of the line;
		std::cout << "\033[1A";
		for (size_t index = 0; index < (level - 1) * 4 + 8; ++index) {
			std::cout << "\033[1C";
		}
		if (level == 1) {
			std::cout << "\033[2D";
		}
	}
	ungetc(temp, stdin);

	// load in data;
	TreeNode* node = new TreeNode;
	if (scanf("%s %d", &(node->data.name), & (node->data.size)) == 2) {
		// clear stdin for the next empty-input test;
		CLRSTDIN;

		INDENT(level);
		std::cout << "Lchild: ";
		node->lchild = buildBiTree(level + 1);
		if (node->lchild == nullptr) {
			std::cout << "\r\033[1A\033[K";
			INDENT(level);
			std::cout << "Lchild: NULL" << std::endl;
		}

		INDENT(level);
		std::cout << "Rchild: ";
		node->rchild = buildBiTree(level + 1);
		if (node->rchild == nullptr) {
			std::cout << "\r\033[1A\033[K";
			INDENT(level);
			std::cout << "Rchild: NULL" << std::endl;
		}

		if (node->lchild == nullptr && node->rchild == nullptr) {
			std::cout << "\r\033[1A\033[K";
			std::cout << "\r\033[1A\033[K";
		}

		return node;
	}
	else { // empty child;
		// clear stdin for the next empty-input test;
		CLRSTDIN;

		return nullptr;
	}
}
1.2. 一般二叉树 (由遍历序列生成)

前序序列和中序序列可以确定一棵二叉树.

1.2.1. 递归
BiTree buildPreInBiTree(int* preList, int* inList, size_t preHead, size_t preTail, size_t inHead, size_t inTail)
{
	TreeNode* result = new TreeNode;
	result->data = preList[preHead];

	if (preHead != preTail) {
		// find preList[preHead] in inList;  
		size_t indRoot = 0;
		for (indRoot = inHead; indRoot < inTail; ++indRoot) {
			if (preList[preHead] == inList[indRoot]) {
				break;
			}
		}

		result->lchild = (indRoot != inHead) ? buildPreInBiTree(preList, inList, preHead + 1, preHead + (indRoot - inHead), inHead, indRoot - 1) : nullptr;
		result->rchild = (indRoot != inTail) ? buildPreInBiTree(preList, inList, preHead + (indRoot - inHead) + 1, preTail, indRoot + 1, inTail) : nullptr;
	}

	return result;
}

// API;
BiTree buildBiTree(int* preList, int* inList, size_t size)
{
	return buildPreInBiTree(preList, inList, 0, size - 1, 0, size - 1);
}
1.2.2. 非递归
BiTree buildPreInBiTree(int* preList, int* inList, size_t n)
{
	if (n == 0) {
		return nullptr;
	}

	size_t preInd = 0;
	size_t inInd = 0;
	BiTree result = new TreeNode; // header of tree; in case that the root has no lchild;

	typedef struct stackNode {
		TreeNode* tnode = nullptr;
		stackNode* next = nullptr;
	} stackNode, * stack;

	#define STKPUSH(m_elem) do { mov = new stackNode; mov->tnode = m_elem; mov->next = stk->next; stk->next = mov; ++preInd; } while (0)

	#define STKPOP do { stk->next = mov->next; delete mov; mov = stk->next; ++inInd; } while (0)

	#define LBUILDPUSH(m_elem) do { m_elem = new TreeNode; m_elem->data = preList[preInd]; stk->next->tnode->lchild = m_elem; STKPUSH(m_elem); } while (0)

	#define RBUILDPUSH(m_elem) do { m_elem = new TreeNode; m_elem->data = preList[preInd]; stk->next->tnode->rchild = m_elem; STKPOP; STKPUSH(m_elem); } while (0)

	stack stk = new stackNode; // header of stk;
	stackNode* mov = nullptr;
	STKPUSH(result);
	--preInd;

	// build root;
	TreeNode* cache = nullptr;
	LBUILDPUSH(cache); // now preInd is the index of the preListNext of the node just having been built; we will keep this definition for preInd during the process;

	// preInd will always reach n before inInd does;
	while (preInd < n) {
		while (preInd < n && preList[preInd] != inList[inInd]) {
			LBUILDPUSH(cache);
		}
		// build leaf;
		LBUILDPUSH(cache);
		if (preInd == n) {
			break;
		}

		// trace back to a node with rchild;
		while (mov->next->tnode->data == inList[inInd + 1]) { // rchild does not exist;
			STKPOP;
		}

		// build rchild;
		RBUILDPUSH(cache);
		if (preInd == n) {
			break;
		}

		// check if lchild exists;
		if (mov->next->tnode->data == inList[inInd + 1]) { // in inList mov->tnode is followed by an ancester root;
			STKPOP;
			// build rchild;
			RBUILDPUSH(cache);
		}
		if (preInd == n) {
			break;
		}
	}

	return result->lchild;
}
1.3. 正则二叉树 (由遍历序列生成)

正则二叉树没有度为 1 的结点, 可以利用前序序列和后序序列生成.

BiTree buildNormalBiTree(int* preList, int* postList, size_t preHead, size_t preTail, size_t postHead, size_t postTail)
{
	BiTree result = new TreeNode;
	result->data = preList[preHead];

	if (preHead != preTail) {
		// find preList[preHead + 1] in postList;
		size_t indL = 0;
		for (indL = postHead; indL <= postTail; ++indL) {
			if (postList[indL] == preList[preHead + 1]) {
				break;
			}
		}
		// find postList[postHead - 1] in preList;
		size_t indR = 0;
		for (indR = preHead; indR <= preTail; ++indR) {
			if (preList[indR] == postList[postTail - 1]) {
				break;
			}
		}

		result->left = buildNormalBiTree(preList, postList, preHead + 1, indR - 1, postHead, indL);
		result->right = buildNormalBiTree(preList, postList, indR, preTail, indL + 1, postTail - 1);
	}
	else {
		result->left = nullptr;
		result->right = nullptr;
	}

	return result;
}

2. 链式存储转为顺序存储

顺序存储下虚结点设为空指针, 整型数据转存为字符串.

#include <cmath>

char** btree2Arr(BiTree tree, size_t& h)
{
	// queue push;
	#define QUEPUSH(elem) do { mov = new queNode; mov->tnode = elem; rear->next = mov; rear = rear->next; ++queSize; } while (0)

	// queue data structure;
	typedef struct queNode {
		TreeNode* tnode = nullptr;
		queNode* next = nullptr;
	} queNode, *queue;

	// init que;
	queue que = new queNode; // header node of queue;
	queNode* front = que; // front of queue;
	queNode* rear = que; // rear of queue;
	size_t queSize = 0;

	// set que;
	queNode* mov = new queNode;
	mov->tnode = tree;
	rear->next = mov;
	rear = rear->next;
	++queSize;

	// init level traversal;
	size_t cacheSize = 0;
	front = que->next;
	h = 0; // count level of tree;
	bool allNull = false; // traversal ending tag;

	// start level traversal;
	while (!allNull) {
		++h;
		cacheSize = queSize; // freeze queSize;
		queSize = 0;
		allNull = true;

		for (size_t index = 0; index < cacheSize; ++index) {
			if (front->tnode != nullptr) {
				if (front->tnode->left != nullptr) {
					QUEPUSH(front->tnode->left);
					allNull = false;
				}
				else {
					QUEPUSH(nullptr);
				}

				if (front->tnode->right != nullptr) {
					QUEPUSH(front->tnode->right);
					allNull = false;
				}
				else {
					QUEPUSH(nullptr);
				}
			}
			else {
				QUEPUSH(nullptr);
				QUEPUSH(nullptr);
			}

			front = front->next;
		}
	}

	// init array;
	size_t arrSize = (size_t)pow(2, h) - 1;
	char** result = new char*[arrSize];
	size_t dataSize = (size_t)log((size_t)pow(2, sizeof(int) * 8)) + 1;

	for (size_t index = 0; index < arrSize; ++index) {
		if (que->next->tnode != nullptr) {
			result[index] = new char[dataSize];
			for (size_t jndex = 0; jndex < dataSize; ++jndex) { // init;
				result[index][jndex] = '\0';
			}

			// int to char*; one may print data to file as well;
			sprintf(result[index], "%d", que->next->tnode->data);
		}
		else { // empty node will be interpreted as nullptr;
			result[index] = nullptr;
		}

		// pop que; use rear as cache;
		rear = que->next;
		que->next = rear->next;
		delete rear;
	}

	return result;
}

3. 顺序存储转为链式存储

BiTree levBuildTree(char** arr, size_t h)
{
	if (h == 0) {
		return nullptr;
	}

	// now non-empty root;
	// push the first level, i.e. root;
	BiTree result = new TreeNode;
	result->data = atoi(arr[0]);
	size_t curInd = 1;

	// que push;
	#define QUEPUSH(elem) do { mov = new queNode; mov->tnode = elem; rear->next = mov; rear = rear->next; ++queSize; } while (0)

	typedef struct queNode {
		TreeNode* tnode;
		queNode* next;
	} queNode, *queue;

	// init queue;
	queue que = new queNode; // header of queue;  
	queNode* rear = que;
	size_t queSize = 0;

	// set queue;
	queNode* mov = new queNode;
	mov->tnode = result;
	rear->next = mov;
	rear = rear->next;
	++queSize;

	// init level traversal;
	size_t cacheSize = 0;
	TreeNode* newTreeNode = nullptr;

	for (size_t curH = 1; curH < h; ++curH) {
		cacheSize = queSize; // freeze queSize;
		queSize = 0;
		for (size_t index = 0; index < cacheSize; ++index) {
			if (que->next->tnode != nullptr) {
				if (arr[curInd] != nullptr) {
					newTreeNode = new TreeNode;
					newTreeNode->data = atoi(arr[curInd]);
					que->next->tnode->left = newTreeNode;
					QUEPUSH(newTreeNode);
				}
				else {
					QUEPUSH(nullptr);
				}
				++curInd;

				if (arr[curInd] != nullptr) {
					newTreeNode = new TreeNode;
					newTreeNode->data = atoi(arr[curInd]);
					que->next->tnode->right = newTreeNode;
					QUEPUSH(newTreeNode);
				}
				else {
					QUEPUSH(nullptr);
				}
				++curInd;
			}
			else {
				QUEPUSH(nullptr);
				++curInd;
				QUEPUSH(nullptr);
				++curInd;
			}

			// pop queue;
			queNode* cache = que->next;
			que->next = cache->next;
			delete cache;
		}
	}

	return result;
}

4. 循环输入构建二叉树 API

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

int main(void) {
	char isAgain = '\n'; 
	do {
		if (isAgain != '\n') {
			while (getchar() != '\n') {
				continue; 
			}
		}
		BiTree tree = buildBiTree();
		/* programs */
		printf(isBST(tree) ? "true\n" : "false\n");

		printf("Hit ENTER to exit. Enter any non-\'\\n\' character to build a new tree. \n\n");
	} while ((isAgain = getchar()) != '\n');
}

附录

重要内容更新日志

2022-10-03 修复了以控制台输入建立空树时, 根节点输入不会自动修正显示为 NULL 的问题. 修复了循环输入构建二叉树时, 从第二次开始根节点输入光标错位的问题.