一、二叉树的优点:
1. 二叉排序树是一种比较有用的折衷方案。
数组的搜索比较方便,可以直接用下标,但删除或者插入某些元素就比较麻烦。
链表与之相反,删除和插入元素很快,但查找很慢。
二叉排序树就既有链表的好处,也有数组的好处。
在处理大批量的动态的数据是比较有用。
3.平衡二叉树AVL都有哪些应用场景:查找、插入和删除在平均和最坏情况下都是O(log n),左右子树高度差小于1
二叉树支持动态的插入和查找,保证操作在O(height)时间,这就是完成了哈希表不便完成的工作,动态性。但是二叉树有可能出现worst-case,如果输入序列已经排序,则时间复杂度为O(N)
4.平衡二叉树/红黑树就是为了将查找的时间复杂度保证在O(logN)范围内。
所以如果输入结合确定,所需要的就是查询,则可以考虑使用哈希表,如果输入集合不确定,则考虑使用平衡二叉树/红黑树,保证达到最大效率。
如果你知道SGI/STL的set/map底层都是用红黑树(平衡二叉树的一种)实现的,相信你会对这些树大有兴趣。
6.计算机的核心是计算和储存,而树有效地解决了储存的问题和计算时程序调用的问题。
1) 计算机中的一个关键就是如何建立程序之间的调用关系。科学家们利用栈的结构很好地处理了这个问题,程序的递归和栈的操作实质上都是建立在树形结构的基础上的。
2) 堆是可以持久化管理和储存数据的数据结构。
实际上堆是一颗不用储存边的完全二叉树,可以通过节点间的关系展开。
7.树形结构在算法设计中也有着广泛的应用,其关键在于许多算法在设计的过程中采用了分治的思想,使用了递归求解的方法或者需要遍历可行解,这些程序的运行均可用树形结构表达,辅助人们理解。
以排序算法为例,经典的快速排序,归并排序均为递归求解,使用了分治的思想,而堆排序使用的数据结构——堆,在前文已经介绍了其与树的紧密联系。
8.树最强大的功能是进行储存查询,为此计算机科学家们创造了诸如红黑树,AVL树,B+树,字典树,堆,并查集等数据结构,它们被广泛地运用于计算机的各个方面。
缺点:
顺序存储,可能会浪费空间(在非完全二叉树的时候),但是读取某个指定的节点的时候效率比较高O(0)
链式存储,相对二叉树比较大的时候浪费空间较少,但是读取某个指定节点的时候效率偏低O(nlogn)
二、【二叉树的遍历】
深度优先搜索DFS:二叉树的前中后,层次遍历(递归实现)
struct TreeNode{
int v;
TreeNode* left;
TreeNode* right;
TreeNode(int x){
v=x;
left = NULL;
right = NULL;
}
};
void preOrder(TreeNode* proot){
if(!proot) return;
cout<<proot->v<<" ";
preOrder(proot->left);
preOrder(proot->right);
}
void midOrder(TreeNode* proot){
if(!proot) return;
midOrder(proot->left);
cout<<proot->v<<" ";
midOrder(proot->right);
}
void postOrder(TreeNode* proot){
if(!proot) return;
postOrder(proot->left);
postOrder(proot->right);
cout<<proot->v<<" ";
}
void levelOrder(TreeNode* proot){
queue<TreeNode*> arr;
arr.push(proot);
while(!arr.empty()){
TreeNode* tmp = arr.front();
cout<<tmp->v<<" ";
if(tmp->left) arr.push(tmp->left);
if(tmp->right) arr.push(tmp->right);
arr.pop();
}
}
int main()
{
TreeNode* p1 = new TreeNode(1);
TreeNode* p2 = new TreeNode(2);
TreeNode* p3 = new TreeNode(3);
TreeNode* p4 = new TreeNode(4);
TreeNode* p5 = new TreeNode(5);
TreeNode* p6 = new TreeNode(6);
TreeNode* p7 = new TreeNode(7);
TreeNode* p8 = new TreeNode(8);
p1->left = p2;
p2->left = p4;
p4->left = p7;
p1->right = p3;
p2->right = p5;
p3->right = p6;
p6->right = p8;
preOrder(p1);
cout<<endl;
midOrder(p1);
cout<<endl;
postOrder(p1);
cout<<endl;
levelOrder(p1);
return 0;
}
struct TreeNode{
int v;
TreeNode* left;
TreeNode* right;
TreeNode(int x){
v=x;
left = NULL;
right = NULL;
}
};
void preOrder(TreeNode* proot){
if(!proot) return;
cout<<proot->v<<" ";
preOrder(proot->left);
preOrder(proot->right);
}
void midOrder(TreeNode* proot){
if(!proot) return;
midOrder(proot->left);
cout<<proot->v<<" ";
midOrder(proot->right);
}
void postOrder(TreeNode* proot){
if(!proot) return;
postOrder(proot->left);
postOrder(proot->right);
cout<<proot->v<<" ";
}
void levelOrder(TreeNode* proot){
queue<TreeNode*> arr;
arr.push(proot);
while(!arr.empty()){
TreeNode* tmp = arr.front();
cout<<tmp->v<<" ";
if(tmp->left) arr.push(tmp->left);
if(tmp->right) arr.push(tmp->right);
arr.pop();
}
}
int main()
{
TreeNode* p1 = new TreeNode(1);
TreeNode* p2 = new TreeNode(2);
TreeNode* p3 = new TreeNode(3);
TreeNode* p4 = new TreeNode(4);
TreeNode* p5 = new TreeNode(5);
TreeNode* p6 = new TreeNode(6);
TreeNode* p7 = new TreeNode(7);
TreeNode* p8 = new TreeNode(8);
p1->left = p2;
p2->left = p4;
p4->left = p7;
p1->right = p3;
p2->right = p5;
p3->right = p6;
p6->right = p8;
preOrder(p1);
cout<<endl;
midOrder(p1);
cout<<endl;
postOrder(p1);
cout<<endl;
levelOrder(p1);
return 0;
}
二叉树前序,中序,后序遍历的非递归实现
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
vector<int> midorderTraversal(TreeNode *root) {
vector<int> vec;
if(root == NULL){return vec;}
stack<TreeNode*> st;
TreeNode *cur = root;
while(!st.empty() || cur){
while(cur){
st.push(cur);
cur = cur->left;
}
if(!st.empty()){
cur = st.top();
st.pop();
vec.push_back(cur->val);
cur = cur->right;
}
}
return vec;
}
vector<int> preorderTraversal(TreeNode *root) {
vector<int> vec;
if(root == NULL){return vec;}
stack<TreeNode*> st;
st.push(root);
TreeNode *cur = NULL;
while(!st.empty()){
cur = st.top();
st.pop();
vec.push_back(cur->val);
if(cur->right){
st.push(cur->right);
}
if(cur->left){
st.push(cur->left);
}
}
return vec;
}
vector<int> postorderTraversal(TreeNode *root) {
vector<int>vec;
if(root == NULL)
return vec;
stack<TreeNode*>st;
st.push(root);
TreeNode * cur = NULL;
while(!st.empty()){
cur = st.top();
if(cur->left == NULL && cur->right == NULL){
vec.push_back(cur->val);
st.pop();
}else{
if(cur->right){
st.push(cur->right);
cur->right = NULL;
}
if(cur->left){
st.push(cur->left);
cur->left = NULL;
}
}
}
return vec;
}
};
int main()
{
TreeNode n1(1);
TreeNode n2(2);
TreeNode n3(3);
TreeNode n4(4);
TreeNode n5(5);
TreeNode *p1 = &n1;
TreeNode *p2 = &n2;
TreeNode *p3 = &n3;
TreeNode *p4 = &n4;
TreeNode *p5 = &n5;
p1->left = p2;
p1->right = p3;
p2->left = p4;
p2->right = p5;
Solution s;
vector<int> res = s.midorderTraversal(p1);
for(int i=0; i < res.size();i++){
cout<<res[i]<<endl;
}
return 0;
}
BFS 广度优先搜索:广度优先搜索是遍历图的一种常用方法,广度优先搜索在进一步遍历图中顶点之前,先访问当前顶点的所有邻接结点。 a .首先选择一个顶点作为起始结点,并将其染成灰色,其余结点为白色。
b. 将起始结点放入队列中。
c. 从队列首部选出一个顶点,并找出所有与之邻接的结点,将找到的邻接结点放入队列尾部,将已访问过结点涂成黑色,没访问过的结点是白色。如果顶点的颜色是灰色,表示已经发现并且放入了队列,如果顶点的颜色是白色,表示还没有发现
d. 按照同样的方法处理队列中的下一个结点。
基本就是出队的顶点变成黑色,在队列里的是灰色,还没入队的是白色。
用一幅图来表示:
#include <iostream>
#include <queue>
#define N 5
using namespace std;
int maze[N][N] = {
{ 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0 },
{ 0, 1, 1, 1, 0 },
{ 1, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 0 }
};
int visited[N + 1] = { 0, };
void BFS(int start)
{
queue<int> Q;//这里只是简单一个例子,实际中Q常用一个邻接表来表示
Q.push(start);
visited[start] = 1;
while (!Q.empty())
{
int front = Q.front();
cout << front << " ";
Q.pop();
for (int i = 1; i <= N; i++)
{
if (!visited[i] && maze[front - 1][i - 1] == 1)
{
visited[i] = 1;
Q.push(i);
}
}
}
}
int main()
{
for (int i = 1; i <= N; i++)
{
if (visited[i] == 1)
continue;
BFS(i);
}
return 0;
}
DFS深度优先搜索在搜索过程中访问某个顶点后,需要递归地访问此顶点的所有未访问过的相邻顶点。
初始条件下所有节点为白色,选择一个作为起始顶点,按照如下步骤遍历:
a. 选择起始顶点涂成灰色,表示还未访问
b. 从该顶点的邻接顶点中选择一个,继续这个过程(即再寻找邻接结点的邻接结点),一直深入下去,直到一个顶点没有邻接结点了,涂黑它,表示访问过了
c. 回溯到这个涂黑顶点的上一层顶点,再找这个上一层顶点的其余邻接结点,继续如上操作,如果所有邻接结点往下都访问过了,就把自己涂黑,再回溯到更上一层。
d. 上一层继续做如上操作,知道所有顶点都访问过。
最简单的想法就是利用递归来实现:
#include <iostream>
#define N 5
using namespace std;
int maze[N][N] = {
{ 0, 1, 1, 0, 0 },
{ 0, 0, 1, 0, 1 },
{ 0, 0, 1, 0, 0 },
{ 1, 1, 0, 0, 1 },
{ 0, 0, 1, 0, 0 }
};
int visited[N + 1] = { 0, };
void DFS(int start)
{
visited[start] = 1;
for (int i = 1; i <= N; i++)
{
if (!visited[i] && maze[start - 1][i - 1] == 1)
DFS(i);
}
cout << start << " ";
}
int main()
{
for (int i = 1; i <= N; i++)
{
if (visited[i] == 1)
continue;
DFS(i);
}
return 0;
}
非递归的实现如下:
#include <iostream> #include <queue> #include <stack> #define N 5 using namespace std; int maze[N][N] = { { 0, 1, 1, 0, 0 }, { 0, 0, 1, 1, 0 }, { 0, 1, 1, 1, 0 }, { 1, 0, 0, 0, 0 }, { 0, 0, 1, 1, 0 } }; int visited[N + 1] = { 0, }; void DFS(int start) { stack<int> s; s.push(start); while(!s.empty()){ int cur = s.top(); s.pop(); if(visited[cur] == 0){ cout<<cur<<endl; } visited[cur] = 1; for(int i = 1; i <= N; i++){ if(visited[i] == 0 && maze[cur-1][i-1] == 1){ s.push(i); } } } } int main() { for (int i = 1; i <= N; i++) { if (visited[i] == 1) continue; DFS(i); } return 0; }
可以看出来,BFS用到了队列,DFS用到了栈
三、【二叉树的结点的最近的公共祖先】
1)该二叉树是搜索二叉树BST,如果两个节点的值都大于根节点,则遍历右子树查找一个处于两节点之间的值为最近祖先,如果两个节点的值都小于根节点,则遍历左子树查找一个两节点之间的值为最近祖先。
Node* SearchNearAncestor(Node* root,Node* node1,Node*node2)
{
if (root==NULL||
node1==NULL||
node2==NULL)
{
return NULL;
}
if(node1==node2||node1->_parent!=NULL)
{
return node1;
}
Node* cur=root;
while (cur)
{
if(cur->_key>node1->_key&&
cur->_key>node2->_key)
{
cur=cur->_left;
}
else if(cur->_key<node1->_key&&
cur->_key<node2->_key)
{
cur=cur->_right;
}
else
{
if(node1->_parent==node2)
return node2->_parent;
else if(node2->_parent==node1)
return node1->_parent;
else
return cur;
}
}
return NULL;
}
2)该二叉树为普通二叉树,但其有父节点,这时候,将node1的父节点和node2的parent->parent.......一直比较下去,直到二者相同,如果一直不同,则将node1再往上走,继续上步循环。
Node* NearAncetor(Node* root,Node* node1,Node* node2)
{
if(node1==root||node2==root)
{
return root;
}
Node* tmp;
while (node1!=NULL)
{
node1=node1->_parent;
tmp=node2;
while (tmp!=NULL)
{
if(node1==tmp->_parent)
return node1;
tmp=tmp->_parent;
}
}
return NULL ;
}
3)该二叉树为普通二叉树,他也没有父节点,这时候要分别递归左右子树,如果一个节点出现在左子树,另一个出现在右子树,则返回根节点,如果两个都出现在左,则最近祖先在子树,如果两个都出现在右,则最近祖先在右子树上。
Node* NearAncetor(Node* root,Node* node1,Node* node2)
{
if (root==NULL||
node1==NULL||
node2==NULL)
{
return NULL;
}
if(node1==root||node2==root)
{
return root;
}
Node* left=NearAncetor(root->_left,node1,node2);
Node* right=NearAncetor(root->_right,node1,node2);
if(left&&right)
return root;
if(left==NULL)
return right;
else
return left;
}
二叉搜索树全部代码:
#include<iostream>
using namespace std;
template<class K>
struct SearchTreeNode
{
typedef SearchTreeNode<K> Node;
Node* _left;
Node* _right;
Node* _parent;
K _key;
SearchTreeNode(const K& key)
:_left(NULL)
,_right(NULL)
,_parent(NULL)
,_key(key)
{}
};
template<class K>
class SearchTree
{
typedef SearchTreeNode<K> Node;
public:
SearchTree()
:_root(NULL)
{}
~SearchTree()
{
delete _root;
}
SearchTree(const SearchTree<K>& t)
{
_root=t._root;
}
Node* GetRoot()
{
return _root;
}
bool Find(const K& key)
{
if(_root==NULL)
return false;
Node* cur=_root;
while (cur)
{
if(cur->_key>key)
cur=cur->_left;
else if(cur->_key<key)
cur=cur->_right;
else
return true;
}
}
bool Insert(const K&key)
{
_Insert(_root,key);
return true;
}
void Inorder()
{
_Inorder(_root);
cout<<endl;
}
Node* SearchNearAncestor(Node* root,Node* node1,Node*node2)
{
if (root==NULL||
node1==NULL||
node2==NULL)
{
return NULL;
}
if(node1==node2||node1->_parent!=NULL)
{
return node1;
}
Node* cur=root;
while (cur)
{
if(cur->_key>node1->_key&&
cur->_key>node2->_key)
{
cur=cur->_left;
}
else if(cur->_key<node1->_key&&
cur->_key<node2->_key)
{
cur=cur->_right;
}
else
{
if(node1->_parent==node2)
return node2->_parent;
else if(node2->_parent==node1)
return node1->_parent;
else
return cur;
}
}
return NULL;
}
protected:
bool _Insert(Node*& root, const K& key)
{
if(root==NULL)
{
root=new Node(key);
return true;
}
if(root->_key>key)
{
return _Insert(root->_left,key);
}
else if(root->_key<key)
{
return _Insert(root->_right,key);
}
else
return false;
}
void _Inorder(Node* root)
{
Node* cur=root;
if(cur==NULL)
return;
_Inorder(cur->_left);
cout<<cur->_key<<" ";
_Inorder(cur->_right);
}
protected:
Node* _root;
};
//void testSearch()
//{
// int arr[]={3,4,1,5,2,6,8,7,9};
// SearchTree<int> t;
//
//
// for(int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
// {
// t.Insert(arr[i]);
// }
// t.Inorder();
//
//}
void test()
{
int arr[]={5,3,4,1,7,8,2,6,0,9};
SearchTree<int> t1;
for(int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
t1.Insert(arr[i]);
}
t1.Inorder();
SearchTreeNode<int>* root=t1.GetRoot();
SearchTreeNode<int>* node1=root->_left->_right;
SearchTreeNode<int>* node2=root->_right->_left;
SearchTreeNode<int>* ancetor=t1.SearchNearAncestor(root,node1,node2);
cout<<node1->_key<<"和"<<node2->_key<<"的最近祖先是"<<ancetor->_key<<endl;
SearchTreeNode<int>* node3=root->_right->_right->_right;
SearchTreeNode<int>* node4=root->_right->_left;
SearchTreeNode<int>* ancetor2=t1.SearchNearAncestor(root,node3,node4);
cout<<node3->_key<<"和"<<node4->_key<<"的最近祖先是"<<ancetor2->_key<<endl;
cout<<endl;
}
普通二叉树有父节点的全部代码:
#include <iostream>
using namespace std;
struct TreeNode
{
TreeNode* _left;
TreeNode* _right;
TreeNode* _parent;
int _key;
TreeNode(const int& key)
:_left(NULL)
,_right(NULL)
,_parent(NULL)
,_key(key)
{}
};
class BinrayTree
{
typedef TreeNode Node;
public:
BinrayTree()
:_root(NULL)
{}
~BinrayTree()
{
delete _root;
}
Node* GetRoot()
{
return _root;
}
bool Insert2(const int& key)
{
_Insert(_root,key);
return true;
}
void Inorder2()
{
_Inorder(_root);
cout<<endl;
}
Node* NearAncetor(Node* root,Node* node1,Node* node2)
{
if(node1==root||node2==root)
{
return root;
}
Node* tmp;
while (node1!=NULL)
{
node1=node1->_parent;
tmp=node2;
while (tmp!=NULL)
{
if(node1==tmp->_parent)
return node1;
tmp=tmp->_parent;
}
}
return NULL ;
}
protected:
bool _Insert(Node*& root,const int& key)
{
Node* node=new Node(key);
node->_key=key;
node->_left=node->_right=node->_parent=NULL;
if(root==NULL)
{
root=node;
return true;
}
if(root->_left == NULL && root->_key > key)
{
node->_parent=root;
root->_left=node;
return true;
}
if(root->_right == NULL && root->_key < key)
{
node->_parent=root;
root->_right=node;
return true;
}
if(root->_key > key)
_Insert(root->_left,key);
else if(root->_key < key)
_Insert(root->_right,key);
else
return true;
}
void _Inorder(Node* root)
{
Node* cur=root;
if(cur==NULL)
return;
_Inorder(cur->_left);
cout<<cur->_key<<" ";
_Inorder(cur->_right);
}
Node* _root;
};
void test2()
{
int arr[]={5,3,4,1,7,8,2,6,0,9};
BinrayTree t1;
for(int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
t1.Insert2(arr[i]);
}
t1.Inorder2();
TreeNode* root=t1.GetRoot();
TreeNode* node1=root->_left->_left;
TreeNode* node2=root->_right->_right;
cout<<node1->_key<<"和"<<node2->_key<<"的最近祖先是";
TreeNode* ancetor=t1.NearAncetor(root,node1,node2);
if(ancetor)
cout<<ancetor->_key<<endl;
cout<<endl;
}
普通二叉树且无父节点全部代码
#include <iostream>
using namespace std;
struct NoParentTreeNode
{
NoParentTreeNode* _left;
NoParentTreeNode* _right;
int _key;
NoParentTreeNode(const int& key)
:_left(NULL)
,_right(NULL)
,_key(key)
{}
};
class NoParentBinrayTree
{
typedef NoParentTreeNode Node;
public:
NoParentBinrayTree()
:_root(NULL)
{}
~NoParentBinrayTree()
{
delete _root;
}
Node* GetRoot()
{
return _root;
}
bool Insert2(const int& key)
{
_Insert(_root,key);
return true;
}
void Inorder2()
{
_Inorder(_root);
cout<<endl;
}
Node* NearAncetor(Node* root,Node* node1,Node* node2)
{
if (root==NULL||
node1==NULL||
node2==NULL)
{
return NULL;
}
if(node1==root||node2==root)
{
return root;
}
Node* left=NearAncetor(root->_left,node1,node2);
Node* right=NearAncetor(root->_right,node1,node2);
if(left&&right)
return root;
if(left==NULL)
return right;
else
return left;
}
protected:
bool _Insert(Node*& root,const int& key)
{
if(root==NULL)
{
root=new Node(key);
return true;
}
if(key>root->_key)
{
return _Insert(root->_right,key);
}
else if(key<root->_key)
{
return _Insert(root->_left,key);
}
else
return false;
}
void _Inorder(Node* root)
{
Node* cur=root;
if(cur==NULL)
return;
_Inorder(cur->_left);
cout<<cur->_key<<" ";
_Inorder(cur->_right);
}
Node* _root;
};
void test3()
{
int arr[]={5,3,4,1,7,8,2,6,0,9};
NoParentBinrayTree t1;
for(int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
t1.Insert2(arr[i]);
}
t1.Inorder2();
NoParentTreeNode* root=t1.GetRoot();
NoParentTreeNode* node1=root->_left->_left;
NoParentTreeNode* node2=root->_right->_right->_right;
NoParentTreeNode* ancetor=t1.NearAncetor(root,node1,node2);
cout<<node1->_key<<"和"<<node2->_key<<"的最近祖先是"<<ancetor->_key<<endl;
cout<<endl;
}
测试代码 #include "SearchTree.h"
#include "HaveParentUnsearchTree.h"
#include "NoParentSearchTree.h"
#include <cstdlib>
int main()
{
test();
test2();
test3();
system("pause");
return 0;
}
四、【多叉树】
伪代码求多叉树的深度:
int ans = -1 ;
void dfs(node x , int deep){
if ( x.sons.size() == 0 ) // 没有儿子结点了
{
ans = max(ans , deep ) ;
return ;
}
else {
for ( int i = 0;i < x.sons.size();i ++)
dfs(x.sons[i] , deep + 1) ;
}
}
多叉树的设计、建立、层次优先遍历和深度优先遍历
早起曾实现过一个简单的多叉树《实现一个多叉树》。其实现原理是多叉树中的节点有两个域,分别表示节点名以及一个数组,该数组存储其子节点的地址。实现了一个多叉树建立函数,用于输入格式为A B。A表示节点的名字,B表示节点的子节点个数。建立函数根据用户的输入,首先建立一个新的节点,然后根据B的值进行深度递归调用。用户输入节点的顺序就是按照深度递归的顺序。另外,我们实现了一个层次优先遍历函数。该函数用一个队列实现该多叉树的层次优先遍历。首先将根节点入队列,然后检测队列是否为空,如果不为空,将队列出队列,访问出队列的节点,然后将该节点的子节点指针入队列,依次循环下去,直至队列为空,终止循环,从而完成整个多叉树的层次优先遍历。
本文我们将还是介绍一个多叉树,其内容和之前的实现差不多。
首先,用户的多叉树数据存储在一个文件中,格式如下:
每行的第一个元素指定一个节点,其中第一行指定了该多叉树的根节点。第二个元素表示该节点有几个子节点,紧接着后面跟了几个子节点。
根据以上数据文件,其对应的多叉树应该是如下:
我们想得到结果是将书中的节点按深度进行输出,比如先输出深度最深的节点:x e j,然后输出深度为2的节点:d f i,之后再输出深度为1的节点:g cC z bBbB,最后输出根节点:aA。
按照深度将节点输出,很显然是用层次优先遍历的方法解决。层次优先遍历的实现原理就是从根节点开始,利用队列实现。
另外,我们想得到从根节点开始到叶子节点直接所有节点名字加起来最长的一个路径,比如上面的树中存在以下几条路径:
aA g d x
aA g d e
aA g d j
aA cC
aA z f
aA z i
aA bBbB
显然,在这些路径中,aA bBbB是所有路径上节点名字加起来最长的一个路径。求解从根节点到叶子节点上的所有路径,利用深度优先遍历更为合适。
下面我们讨论一下多叉树节点应该如何建立。首先多叉树的节点应该如何定义,节点除了有自身的名字外,还要记录其子节点有多少个,每个子节点在哪里,所以我们需要增加一个记录子节点个数的域,还要增加一个数组,用来记录子节点的指针。另外,还要记录多叉树中每个节点的深度值。
在读取数据文件的过程中,我们顺序扫描整个文件,根据第一个名字,建立新的节点,或者从多叉树中找到已经有的节点地址,将后续的子节点生成,并归属于该父节点,直至扫描完整个数据文件。
读取完整个文件后,也就建立了多叉树,之后,我们利用队列对多叉树进行广度优先遍历,记录各个节点的深度值。并将其按照深度进行输出。
获取从根节点到子节点路径上所有节点名字最长的路径,我们利用深度优先遍历,递归调用深度优先遍历函数,找到最长的那个路径。
初次之外,还需定义队列结构体,这里使用的队列是循环队列,实现相关的队列操作函数。还有定义栈的结构体,实现栈的相关操作函数。另外对几个内存分配函数、字符串拷贝函数、文件打开函数进行了封装。需要注意的一点就是当操作完成后,需要对已经建立的任何东西都要销毁掉,比如中途建立的队列、栈、多叉树等,其中还包含各个结构体中的指针域。
另外,函数测试是用户在命令行模式下输入程序名字后面紧跟数据文件的形式。
该程序的主要部分有如下几点:
1.多叉树节点的定义和生成一个新节点
2.数据文件的读取以及多叉树的建立
3.根据节点名字在多叉树中查找节点的位置
4.多叉树的层次优先遍历
5.多叉树的深度优先遍历
6.队列的定义以及相关操作函数实现
7.栈的定义以及相关操作函数实现
8.消毁相关已经建立好的队列、栈、多叉树等
9.测试模块
下面我们给出相关的程序实现,具体细节可以查看代码和注释说明。
// 多叉树的建立、层次遍历、深度遍历 #include <stdio.h> #include <stdlib.h> #include <string.h> #define M 100+1 // 宏定义,定义最大名字字母长度 // 定义多叉树的节点结构体 typedef struct node_t { char* name; // 节点名 int n_children; // 子节点个数 int level; // 记录该节点在多叉树中的层数 struct node_t** children; // 指向其自身的子节点,children一个数组,该数组中的元素时node_t*指针 } NODE; // 对结构体重命名 // 实现一个栈,用于后续操作 typedef struct stack_t { NODE** array; // array是个数组,其元素为NODE*型指针 int index; // 指示栈顶元素 int size; // 栈的大小 } STACK; // 重命名 // 实现一个队列,用于后续操作 typedef struct queue_t { NODE** array; // array是个数组,其内部元素为NODE*型指针 int head; // 队列的头 int tail; // 队列的尾 int num; // 队列中元素的个数 int size; // 队列的大小 } QUEUE; // 这里的栈和队列,都是用动态数组实现的,另一种实现方式是用链表 // 内存分配函数 void* util_malloc(int size) { void* ptr = malloc(size); if (ptr == NULL) // 如果分配失败,则终止程序 { printf("Memory allocation error!\n"); exit(EXIT_FAILURE); } // 分配成功,则返回 return ptr; } // 字符串赋值函数 // 对strdup函数的封装,strdup函数直接进行字符串赋值,不用对被赋值指针分配空间 // 比strcpy用起来方便,但其不是标准库里面的函数 // 用strdup函数赋值的指针,在最后也是需要free掉的 char* util_strdup(char* src) { char* dst = strdup(src); if (dst == NULL) // 如果赋值失败,则终止程序 { printf ("Memroy allocation error!\n"); exit(EXIT_FAILURE); } // 赋值成功,返回 return dst; } // 对fopen函数封装 FILE* util_fopen(char* name, char* access) { FILE* fp = fopen(name, access); if (fp == NULL) // 如果打开文件失败,终止程序 { printf("Error opening file %s!\n", name); exit(EXIT_FAILURE); } // 打开成功,返回 return fp; } // 实现一些栈操作 // 栈的初始化 STACK* STACKinit(int size) // 初始化栈大小为size { STACK* sp; sp = (STACK*)util_malloc(sizeof (STACK)); sp->size = size; sp->index = 0; sp->array = (NODE**)util_malloc(size * sizeof (NODE*)); return sp; } // 检测栈是否为空 // 如果为空返回1,否则返回0 int STACKempty(STACK* sp) { if (sp == NULL || sp->index <= 0) // 空 { return 1; } return 0; } // 压栈操作 int STACKpush(STACK* sp, NODE* data) { if (sp == NULL || sp->index >= sp->size) // sp没有被初始化,或者已满 { return 0; // 压栈失败 } sp->array[sp->index++] = data; // 压栈 return 1; } // 弹栈操作 int STACKpop(STACK* sp, NODE** data_ptr) { if (sp == NULL || sp->index <= 0) // sp为初始化,或者为空没有元素 { return 0; } *data_ptr = sp->array[--sp->index]; // 弹栈 return 1; } // 将栈消毁 void STACKdestroy(STACK* sp) { free(sp->array); free(sp); } // 以上是栈的操作 // 实现队列的操作 QUEUE* QUEUEinit(int size) { QUEUE* qp; qp = (QUEUE*)util_malloc(sizeof (QUEUE)); qp->size = size; qp->head = qp->tail = qp->num = 0; qp->array = (NODE**)util_malloc(size * sizeof (NODE*)); return qp; } // 入队列 int QUEUEenqueue(QUEUE* qp, NODE* data) { if (qp == NULL || qp->num >= qp->size) // qp未初始化或已满 { return 0; // 入队失败 } qp->array[qp->tail] = data; // 入队,tail一直指向最后一个元素的下一个位置 qp->tail = (qp->tail + 1) % (qp->size); // 循环队列 ++qp->num; return 1; } // 出队列 int QUEUEdequeue(QUEUE* qp, NODE** data_ptr) { if (qp == NULL || qp->num <= 0) // qp未初始化或队列内无元素 { return 0; } *data_ptr = qp->array[qp->head]; // 出队 qp->head = (qp->head + 1) % (qp->size); // 循环队列 --qp->num; return 1; } // 检测队列是否为空 int QUEUEempty(QUEUE* qp) { if (qp == NULL || qp->num <= 0) { return 1; } return 0; } // 销毁队列 void QUEUEdestroy(QUEUE* qp) { free(qp->array); free(qp); } // 以上是队列的有关操作实现 // 生成多叉树节点 NODE* create_node() { NODE* q; q = (NODE*)util_malloc(sizeof (NODE)); q->n_children = 0; q->level = -1; q->children = NULL; return q; } // 按节点名字查找 NODE* search_node_r(char name[M], NODE* head) { NODE* temp = NULL; int i = 0; if (head != NULL) { if (strcmp(name, head->name) == 0) // 如果名字匹配 { temp = head; } else // 如果不匹配,则查找其子节点 { for (i = 0; i < head->n_children && temp == NULL/*如果temp不为空,则结束查找*/; ++i) { temp = search_node_r(name, head->children[i]); // 递归查找子节点 } } } return temp; // 将查找到的节点指针返回,也有可能没有找到,此时temp为NULL } // 从文件中读取多叉树数据,并建立多叉树 void read_file(NODE** head, char* filename) { NODE* temp = NULL; int i = 0, n = 0; char name[M], child[M]; FILE* fp; fp = util_fopen(filename, "r"); // 打开文件 while (fscanf(fp, "%s %d", name, &n) != EOF) // 先读取节点名字和当前节点的子节点个数 { if (*head == NULL) // 若为空 { temp = *head = create_node(); // 生成一个新节点 temp->name = util_strdup(name); // 赋名 } else { temp = search_node_r(name, *head); // 根据name找到节点 // 这里默认数据文件是正确的,一定可以找到与name匹配的节点 // 如果不匹配,那么应该忽略本行数据 } // 找到节点后,对子节点进行处理 temp->n_children = n; temp->children = (NODE**)malloc(n * sizeof (NODE*)); if (temp->children == NULL) // 分配内存失败 { fprintf(stderr, "Dynamic allocation error!\n"); exit(EXIT_FAILURE); } // 如果分配成功,则读取后面的子节点,并保存 for (i = 0; i < n; ++i) { fscanf(fp, "%s", child); // 读取子节点 temp->children[i] = create_node(); // 生成子节点 temp->children[i]->name = util_strdup(child); // 读子节点赋名 } } // 读取完毕,关闭文件 fclose(fp); } // 实现函数1 // 将多叉树中的节点,按照深度进行输出 // 实质上实现的是层次优先遍历 void f1(NODE* head) { NODE* p = NULL; QUEUE* q = NULL; // 定义一个队列 STACK* s = NULL; // 定义一个栈 int i = 0; q = QUEUEinit(100); // 将队列初始化大小为100 s = STACKinit(100); // 将栈初始化大小为100 head->level = 0; // 根节点的深度为0 // 将根节点入队列 QUEUEenqueue(q, head); // 对多叉树中的节点的深度值level进行赋值 // 采用层次优先遍历方法,借助于队列 while (QUEUEempty(q) == 0) // 如果队列q不为空 { QUEUEdequeue(q, &p); // 出队列 for (i = 0; i < p->n_children; ++i) { p->children[i]->level = p->level + 1; // 对子节点深度进行赋值:父节点深度加1 QUEUEenqueue(q, p->children[i]); // 将子节点入队列 } STACKpush(s, p); // 将p入栈 } while (STACKempty(s) == 0) // 不为空 { STACKpop(s, &p); // 弹栈 fprintf(stdout, " %d %s\n", p->level, p->name); } QUEUEdestroy(q); // 消毁队列 STACKdestroy(s); // 消毁栈 } // 实现函数2 // 找到从根节点到叶子节点路径上节点名字字母个数最大的路径 // 实质上实现的是深度优先遍历 void f2(NODE* head, char* str, char** strBest, int level) { int i = 0; char* tmp = NULL; if (head == NULL) { return; } tmp = (char*)util_malloc((strlen(str) + strlen(head->name) + 1/*原程序中未加1*/) * sizeof (char)); sprintf(tmp, "%s%s", str, head->name); if (head->n_children == 0) { if (*strBest == NULL || strlen(tmp) > strlen(*strBest)) { free(*strBest); *strBest = util_strdup(tmp); } } for (i = 0; i < head->n_children; ++i) { f2(head->children[i], tmp, strBest, level + 1); } free(tmp); } // 消毁树 void free_tree_r(NODE* head) { int i = 0; if (head == NULL) { return; } for (i = 0; i < head->n_children; ++i) { free_tree_r(head->children[i]); } free(head->name); // free(head->children); // 消毁子节点指针数组 free(head); } int main(int argc, char* argv[]) { NODE* head = NULL; char* strBest = NULL; if (argc != 2) { fprintf(stderr, "Missing parameters!\n"); exit(EXIT_FAILURE); } read_file(&head, argv[1]); fprintf(stdout, "f1:\n"); f1(head); f2(head, "", &strBest, 0); fprintf(stdout, "f2:\n %s\n", strBest); free_tree_r(head); return EXIT_SUCCESS; }