题目
写一个树的插入和遍历的算法,插入时按照单词的字典顺序排序(左边放比它“小”的单词,右边放比它“大”的单词),对重复插入的单词进行计数。
程序源码
#include <iostream>
#include <cstring>
#include <sstream> //vs2010中不加入这句话不能使用cin和cout,用gcc编译不用加
using namespace std;
struct Tnode
{
string word;
int count;
Tnode *left;
Tnode *right;
};
//根据word分配一个新的Tnode存储,并返回其引用
Tnode* GenNewTnode(const string&);
//将word按顺序放入树中,如果已存在则对应count加一
//返回插入树中节点的指针
Tnode* InsertWordToTree(Tnode*&,const string&);
//根据左,中,右的顺序遍历数的所有节点
void ShowTree(Tnode*,bool,int = 2);
int main()
{
cout<<"输入各个单词,输入“结束”结束"<<endl;
Tnode* tree = 0;
while(1)
{
string word;
cin>>word;
if(word == "结束")
break;
InsertWordToTree(tree,word);
}
ShowTree(tree,true/*省略第三个参数*/);
}
//根据word分配一个新的Tnode存储,并返回其引用
Tnode* GenNewTnode(const string& word)
{
Tnode* tN = new Tnode;
tN->word = word;
tN->count = 1;
tN->left=(tN->right=0);
return tN;
}
//将word按顺序放入树中,如果已存在则对应count加一
//返回插入树中节点的指针
Tnode* InsertWordToTree(Tnode*& root,const string& word)
{
Tnode* tN = root;
if(root == 0) //空树
{
root = GenNewTnode(word); //产生第一个根节点
return root;
}
while (1)
{
int oder = word.compare(tN->word);
if(oder == 0) //单词存在
{
tN->count++;
return tN;
}
Tnode*& nextTN = (oder<0)?(tN->left):(tN->right); //迭代搜索
if(nextTN == 0)
{
nextTN = GenNewTnode(word); //没找到,产生新的节点
return tN;
}
tN = nextTN;
}
}
//根据左,中,右的顺序遍历数的所有节点
//indent:是否需要输出空格
//spaces:节点之间插入多少空格
void ShowTree(Tnode* root,bool indent,int spaces)
{
if(root)
{
ShowTree(root->left,indent,spaces+2);
if(indent)
{
for(int i = 0;i < spaces;i++)
cout<<" ";
}
cout<<root->word<<"("<<root->count<<")"<<endl;
ShowTree(root->right,indent,spaces+2);
}
}
思路解析,问题总结和思考
整体思路是分别需要实现三个函数:分配新的节点存储空间、将单词放入节点、显示树。
分配新的节点空间:根据给定的单词将其放入存储中,返回存储的指针即可。
显示树:二叉树的遍历是计算机二级考试的常客,比较简单。这里采用左->中->右的递归搜索算法(中序遍历LDR)。
Ps:补充一点计算机二级知识,中->左->右为前序遍历(DLR),左->中->右为中序遍历(LDR),左->右->中为后序遍历(LRD)。已知前序和后序遍历不能唯一的确定这颗二叉树。但已知前中或后中遍历都可以唯一确定二叉树。
将单词放入节点:这个函数中使用到了指针的引用,以前没有使用过指针的引用,在犯了一些错误之后才对指针的引用才有所体会。犯了两个低级的错误:
- 定义了一个局部指针变量指向需要添加节点的位置,然后使用GenNewTnode()分配存储空间。由于局部变量在返回的时候会释放掉所以并不会产生新的节点,需要使用指针的引用指向需要添加节点的位置(上面代码中的nextTN)。
- 为了简化程序想将局部变量tN定义为指针引用进行迭代,但是忽略了引用类型一旦在初始化时给出了定义就不能再指向其他位置了,这一点和指针不一样。
其实将指针看成普通的变量之后,再去理解指针的引用就容易许多了。
注意不能给局部指针分配存储,因为返回时指针就会释放。但是可以给加去引运算的指针的指针分配空间(*tN=new
,tN为指针的指针)。所以上面的InsertWordToTree函数可以使用指针的指针进行改写。
Tnode* InsertWordToTree(Tnode*& root,const string& word)
{
Tnode** tN = &root;
if(root == 0) //空树
{
root = GenNewTnode(word); //产生第一个根节点
return root;
}
while (1)
{
int oder = word.compare((*tN)->word);
if(oder == 0) //单词存在
{
(*tN)->count++;
return *tN;
}
tN = (oder<0)?(&((*tN)->left)):(&((*tN)->right)); //迭代搜索
if(*tN == 0)
{
*tN = GenNewTnode(word); //没找到,产生新的节点
return *tN;
}
}
}
注意tN = (oder<0)?(&((*tN)->left)):(&((*tN)->right)); //迭代搜索
和*tN = GenNewTnode(word); //没找到,产生新的节点
这两句话的表达,要取左右两边节点地址的地址给tN,这样才能保证分配空间的首地址放到了左右指针中。
当然我们也可以即不使用指针的引用也不使用指针的指针,使用普通的指针也能完成上面任务但是代码要稍微复杂一些,如下:
Tnode* InsertWordToTree(Tnode*& root,const string& word)
{
Tnode* tN = root;
if(root == 0) //空树
{
root = GenNewTnode(word); //产生第一个根节点
return root;
}
while (1)
{
int oder = word.compare(tN->word);
if(oder == 0) //单词存在
{
tN->count++;
return tN;
}
Tnode* temp = (oder<0)?(tN->left):(tN->right); //迭代搜索
if(temp == 0)
{
if(oder<0)
{
tN->left = GenNewTnode(word); //没找到,产生新的节点
return tN->left;
}
else
{
tN->right = GenNewTnode(word);
return tN->right;
}
}
tN = temp;
}
}
注意上面的temp变量虽然和tN->left都指向同一个地址,但是由于没使用引用,这两个指针的地址并不相同。不能给temp分配空间,而是给tN->left分配空间,即tN->left指向分配空间的首地址。
ps:虽然叙述起来比较啰嗦,但其实搞明白后指针的指针,指针的引用没有那么难,可能是我表达能力有限吧┑( ̄Д  ̄)┍。