第五章学习小结
1.二叉树的遍历以及哈夫曼树是本章的重点。通过PTA和小组讨论,让我更能想清楚二叉树在存储的时候的模样,也能利用链式和顺序结构操作二叉树,总结如下;哈夫曼树比较清晰,主要是构造(要点是先取权值最小的树作为左右子树构造一棵新的二叉树)与计算WPL=(该结点的权值*对应的结点的路径长度)的求和。
2 //存储结构:
//顺序: 3 typedef struct 4 {Elemtype data;//该结点的数据 5 int lch,rch;//该结点左右孩子的下标 6 }TNode; 7 typedef struct 8 {TNode tn[MAX];//TNode *tn 动态申请 9 int root; 10 }Tree; 11 //访问写法:T.tn[i].lch 12 //链式: 13 typedef struct TNode 14 {Elemtype data; 15 struct TNode *lch,*rch; 16 }TNode,*TNodeList;//链表TNodeList可以代表树 17 //访问写法:T->lch 18 19 //建立二叉链表(先序)(递归) 20 void createBTree(BTree &T) 21 { 22 cin>>ch; 23 if(ch=='#') T=NULL://终止条件 24 else 25 { 26 T=new BTNode;//每一棵树(包括子树)的根结点 27 T->data=ch; 28 createBTree(T->lchild);//递归创建左子树 29 createBTree(T->rchild);//递归创建右子树 30 } 31 } 32 33 //各种遍历访问:先序、中序、后序、层次 34 //中序(递归) 35 void InOrder(BTree T) 36 {if(T)//二叉树非空 37 { 38 InOrder(T->lchild);//A 39 operate(T->data);//B 40 InOrder(T->rchild);//C 41 } 42 }//先序:B、A互换 ;后序:B、C互换 43 //中序(非递归,用栈)(链式存储) 44 void InOrder(BTree T) 45 {InitStack(S); 46 BTNode *p=T,*q=new BTNode; 47 while(p||!StackEmpty(S)) 48 { 49 if(p)//如果树非空 50 {push(S,p);//根指针先入栈,接着是所有左子树的所有左孩子入栈 51 p=p->lchild;//遍历此结点的左子树,深度由浅到深直到叶子 52 } 53 else 54 {pop(S,q);//把已经入栈的左子树的左结点 由深度深到浅 出栈,用q记录 55 cout<<q->data;//输出当前子树的根结点q的值 56 p=q->rchild;//让p指向当前结点q的右孩子,如果q没有右孩子,不执行if(p),继续让栈里面的元素出栈;
//如果q不是叶子结点(有右孩子),就可以让q的右孩子在push(S,p)入栈
57 }
58 }
59 }
60 //先序(非递归,用栈)(链式存储) 61 void PreOrder(BTree T) 62 {stack<struct Tnode *> s;
63 s.push(root); //根结点先入
64 struct Tnode *nd;
65 while (!s.empty())
66 {
67 nd= s.top(); //先取栈顶元素
68 operate(nd);
69 s.pop(); //再出栈顶元素
70 if (nd->right != NULL) //先右孩子进
71 s.push(nd->right);
72 if (nd->left != NULL)//再左孩子进
73 s.push(nd->left);
74 }
75 }
76 //层次(非递归,用队)(顺序存储)
77 void LevelOrder(Tree t)
78 {
79 queue<int>Q;//定义一个辅助的数据结构:队列Q ,用来存储待访问的结点编号即下标(int类型)
80 Q.push(t.root);// 用下标代替根结点,根结点先入队,此时队列里只有根节点下标,(不一定
81 //会按123456…… ,下一个队员将会是这个结点的左右孩子 ,)
82 //这样就保证了结点是从上往下、从左往右进入队列的,再按先进先出的规则输出,就能满足“层次遍历 ”
84 int k;
85 bool flag=false;//为了按格式输出
87 while(!Q.empty())//( 先将根结点入队, 才能先保证Q.empty()非空 )
88 {
89 k=Q.front();//先获取队头元素,用下标代表结点
90 Q.pop();//队头元素出队
91 if(t.data[k].lchild==-1 && t.data[k].rchild==-1)//如果这个队头元素是叶子结点
92 {
93 if(flag==false)//第一个输出
94 {
95 cout<<k;
96 flag=true;
97 }
98 else
99 cout<<" "<<k;//输出正在叶子结点的下标
100 }
101 else//如果 这个队头元素不是叶子结点 ,让它的非空左右孩子依次入队 等待判断输出
102 {
103 if(t.data[k].lchild!=-1) Q.push(t.data[k].lchild) ;//t.data[k].lchild代表的是k号结点的左孩子的下标
104 if(t.data[k].rchild!=-1) Q.push(t.data[k].rchild) ;
105 }
106 } //while
107 }
几点:
- 二叉树的子树是有左右之分的,其次序不能任意颠倒,如先序遍历的123与132(根结点是1,2和3是它的孩子)是不同的;
- bool *a; a=new bool[n]();可以实现a数组的初始化,全为false;
2.心得:在List leaves中知道了层次遍历可以利用队列先进先出的特点实现,让我眼前一亮;而在用非递归的方法实现遍历要用到栈,这些都和之前的知识联系在了一起;还在小组讨论中发现,可以根据不同遍历的特性更容易地达到某些操作要求,这次就是利用了层次遍历的最后结点即为叶子结点的特点。
3.目标回顾:这一章刚接触树的时候没能及时反应过来它使用顺序、链式存储时的操作,模模糊糊地被动跟着进度,但是现在已经明朗了许多;
下期目标:目前感觉图也不是一个省油的灯,这章得花更多心思多琢磨,进度也不能拖。