一、本章内容小结
本章学习了树和二叉树。重点学习了二叉树的遍历算法还有哈夫曼树,二叉树的遍历算法的作用不单单是遍历,它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。
1.树:包括空树和非空树,包括三种存储结构:双亲表示法、孩子表示法、孩子兄弟表示法(应用较为普遍);还学习了森林和二叉树的转换。
树的基本术语包括结点、结点的度:分支的个数,树的度:树中所有结点的度的最大值,树的深度:数钟叶子结点所在的最大层次,叶子结点、分支结点、孩子结点、兄弟结点和祖先结点等。我觉得结点的度、树的度、树的深度比较容易混淆,而各类表示关系的结点可将树的图形结构看成一张遗传系谱图,各结点间的关系显得很直观。
2.二叉树----结点的度小于等于2,有5种不同形态,有5个性质,这些性质都可以通过数学计算推导出来;有两种特殊的二叉树:满二叉树(又称完美二叉树)、完全二叉树,我觉得可以用它们结点的度加以区分。
存储结构:顺序存储:适用于不需要修改的完全二叉树,而链式存储的适用范围较广,结构如下:
1 //二叉链表 2 typedef struct BiTNode 3 { 4 TElemType data; 5 struct BiTNode *lchild, *rchild; 6 }BiTNode, *BiTree; 7 8 //三叉链表 9 typedef struct TriTNode 10 { 11 TElemType data; 12 struct TriTNode *lchid, *parent, *rchild; 13 }TriTNode, *TriTree;
先左后右的二叉树遍历算法:先(根)序遍历算法、中(根)序遍历算法、后(根)序遍历算法,其代码实现有两种:
递归算法:
1 //先序遍历 2 void PreOrderTraverse(BiTree T) 3 { 4 if(T) 5 { 6 cout<<T->data; 7 PreOrderTraverse(T->lchild); 8 PreOrderTraverse(T->rchild); 9 } 10 } 11 12 //中序遍历 13 void InOrderTraverse(BiTree T) 14 { 15 if(T) 16 { 17 InOrderTraverse(T->lchild); 18 cout<<T->data; 19 InOrderTraverse(T->rchild); 20 } 21 } 22 23 //后序遍历 24 void PostOrderTraverse(BiTree T) 25 { 26 if(T) 27 { 28 PostOrderTraverse(T->lchild); 29 PostOrderTraverse(T->rchild); 30 cout<<T->data; 31 } 32 }
迭代算法:
1 //先序遍历 2 void PreOrderIteration(BiTree T) 3 { 4 stack<BiTree> s; 5 BiTree p; 6 if(T!=NULL) s.push(T); 7 while(!s.empty()) 8 { 9 p=s.top(); 10 s.pop(); 11 cout<<p->data<<" "; 12 if(p->rchild!=NULL) s.push(p->rchild); 13 if(p->lchild!=NULL) s.push(p->lchild); 14 } 15 } 16 17 //中序遍历 18 void InOrderIteration(BiTree T) 19 { 20 stack<BiTree> s; 21 BiTree p=T; 22 while(p!=NULL || !s.empty()) 23 { 24 while(p!=NULL)//一直走到左尽头 25 { 26 s.push(p); //期间结点入栈 27 p=p->lchild; 28 } 29 p=s.top(); //左已访问右未访问 30 s.pop(); 31 cout<<p-data<<" "; 32 p=p->rchild; //准备遍历右子树 33 } 34 }
个人觉得递归算法比迭代算法更易于理解,而且这两种算法的时间复杂度都为O(n),时间复杂度也都为O(n)。但迭代算法在一些方面也有优势,其运用了栈和队列的思想,而第五章pta的实践1的List Leave就运用了队列的思想。
二叉树遍历算法的应用举例:
1 //先序建立二叉树 2 void CreateBiTree(BiTree &T) 3 { 4 cin>>ch; 5 if(ch=='#') T=NULL; 6 else 7 { 8 T=new BiTNode; 9 T->data=ch; 10 CreateBiTree(T->lchild); 11 CreateBiTree(T->rchild); 12 } 13 } 14 15 //先序复制二叉树 16 void Copy(BiTree T, BiTree &NewT) 17 { 18 if(T==NULL) 19 { 20 NewT=NULL; return; 21 } 22 else 23 { 24 NewT=new BiTNode; 25 NewT->data=T->data; 26 Copy(T->lchild,NewT->lchild); 27 Copy(T->rchild,NewT->rchild); 28 } 29 } 30 31 //计算二叉树深度 32 int Depth(BiTree T) 33 { 34 if(T=NULL) return 0; 35 else 36 { 37 DepthLeft=Depth(T->lchild); 38 DepthRight=Depth(T->rchild); 39 return(1+max(DepthLeft,DeptjRight)); 40 } 41 } 42 43 //计算二叉树结点总数 44 int NodeCount(BiTree T) 45 { 46 if(T=NULL) return 0; 47 else 48 { 49 return NodeCount(T->lchild)+NodeCount(T->rchild)+1;//每一个+1,代表每一个结点!!! 50 } 51 }
3.线索二叉树:保存了前驱和后继两个信息,利用了空链域
1 //二叉树的二叉线索存储定义 2 typedef struct BiThrNode 3 { 4 TElemType data; 5 struct BiThrNode *lchild, *rchild; 6 int LTag, RTag; 7 }BiThrNode, *BiThrTree;
通过对标志域LTag和RTag存储数值的判断来决定lchild和rchild域的指向!
4.哈夫曼树:又称最优树,约定其左分支代表0,右分支代表1,便构造出了哈夫曼编码;哈夫曼树的构造也有一定的方法可循。
相关概念:路径、路径长度、树的路径长度、权、结点的带权路径长度、树的带权路径长度。
哈夫曼树的存储表示:
1 typedef struct 2 { 3 int weight; //结点的权值 4 int parent, lchild, rchild; //结点的双亲、左孩子、右孩子的下标 5 }HTNode, *HuffmanTree; //动态分配数组存储哈夫曼树
哈夫曼编码满足两个性质:
A.哈夫曼编码是前缀编码。
B.哈夫曼编码是最优前缀编码。
二、实践心得
在限时测试时,把层序遍历和先序遍历弄混了,后来也分清了二者的区别。在看过慕课的带你打代码后,能及时地复现出来,但过段时间又会有点模糊,需要定时地回顾一下。打实践1的树的同构时,顺带回顾了typedef struct和struct定义结构体的不同之处,也看了一些博客,https://blog.csdn.net/haiou0/article/details/6877718,这一篇就讲得很细!