小結:二叉樹的幾種實現方式

weixin_30588675發表於2020-04-05

前言

比較常規的二叉樹的實現方式是[結構體/物件+指標],看紫書的時候,裡面給出了幾種樹的實現方式,基本上是比較適合在比賽中使用的。

1.結構體+指標

struct Node {
	bool p; 
	int v;
	Node * left;
	Node * right;
	Node(): p(0), left(NULL), right(NULL) {}
};
Node* new_node() 
{
	return new Node();
}
void remove_tree(Node * u) 
{
	if(u == NULL) return;
	remove_tree(u->left);
	remove_tree(u->right);
	delete u;
}

最常規的實現方式,結構體中p用來標識是否存在/被賦值過。這一方式為動態分配記憶體,刪除樹或某一子樹時採用遞迴delete釋放記憶體。

 

2.陣列+ID

const int max_n = 1000, rootID = 1;
int val[max_n], left[max_n], right[max_n], nodeID;
bool present[max_n];

void new_tree()
{
	left[rootID] = right[rootID] = 0; present[rootID] = 0;
	nodeID = rootID;
}

int new_node()
{
	int u = ++nodeID;
	left[u] = right[u] = 0; present[u] = 0;
	return u;
}

由於動態分配記憶體是非常耗時的操作,因此我們想用靜態方式來替代。每一個節點擁有獨自的nodeID,根節點rootID為常量1,left[i],right[i]陣列是第i節點的子節點ID,相當於指標。

分配新節點只需要初始化++nodeID節點的各項值就好,省去了動態分配記憶體的時間。而刪除節點,若刪除節點i的左孩子,只需要left[i] = 0就行,免去了釋放記憶體的麻煩。分配新樹只需要將nodeID重置就行。

然而這種方法存在記憶體碎片無法利用的問題,由於nodeID是一直遞增的,若對樹的刪除操作較多,會導致陣列中很多部分不能再利用,或者說樹節點陣列規模難以確定。

 

3.結構體陣列+ID

struct Node {
	bool p; 
	int v;
	Node * left;
	Node * right;
	Node(): p(0), left(NULL), right(NULL) {}
};
const int max_n = 1000, rootID = 1;
int nodeID;
Node node[max_n];

void new_tree(Node *u)
{
	Node* u = &node[rootID];
	u->left = u->right = NULL; u->p = 0;
	nodeID = rootID;
}

Node* new_node()
{
	Node* u = &node[++nodeID];
	u->left = u->right = NULL; u->p = 0;
	return u;
}

指標訪問會比陣列下標快一些,但使用結構體更主要的原因還是因為能更好地將各項屬性組織起來,優於陣列的表達效果。

思路上與[陣列+ID]相差不同,同樣也有記憶體碎片無法利用的問題。

 

4.結構體陣列+記憶體池

struct Node {
	bool p;
	int v;
	Node * left;
	Node * right;
	Node(): p(0), left(NULL), right(NULL) {}
};

const int max_n = 1000;
queue<Node*> node_pool;
Node node[max_n];

void init()
{
	for(int i = 0; i < max_n; i++)
		node_pool.push(&node[i]);
}

Node* new_node()
{
	Node* u = node_pool.front(); node_pool.pop();
	u->left = u->right == NULL; u->p = 0;
	return u;
}

void delete_node(Node* u)
{
	node_pool.push(u);
}

為了解決記憶體碎片無法利用的問題,可以採用記憶體池管理。建立一個Node*的佇列,初始化時將Node陣列中所有項的指標入隊。分配新節點時,從佇列中出隊取指標;刪除節點時,將節點重新入隊即可。

 

總結

以上幾種實現方式,主要區別在於:1.記憶體分配是動態還是靜態;2.是否有記憶體碎片無法利用。根據題目特點,是否需要頻繁插入節點,是否會對樹進行刪除等等,選擇合適的實現方式。

轉載於:https://www.cnblogs.com/foolbird/p/10854455.html

相關文章