数据结构——浙大网易云课堂记录(一)树(上篇)
2018-03-02 23:26
405 查看
前菜:查找
顺序查找
二分查找
二分查找判定树
树的一些概念
树的表示
课后题
二叉树
几种特殊二叉树
二叉树性质
二叉树存储结构
二叉树的递归遍历(以链式存储为例)
先序遍历
中序遍历
后序遍历
递归遍历时候的路径
二叉树的非递归遍历
中序遍历
先序遍历
后续遍历
层序遍历
遍历二叉树的应用
输出叶子节点
求二叉树的高度
由两种遍历序列确定二叉树
课堂链接
静态查找:集合中记录是固定的,没有删除和插入操作。
动态查找:有插入、删除操作。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/4e004c0a5a1a581b61b6517c76e8e9d9)
常规写法是这样:
注意到每次for循环中总要判断
这种查找方法,平均查找次数
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/02e5b932d232fcbe8a89f36024dc62de)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/b82f68d9077887deefaac534ead46ec7)
代码如下:
算法复杂度
[Note]: 如果
中查找8,为了方便,直接用
初始:
进入比较
此时
为了避免这种情况,每次
若想用
二分查找判定树
假设查找11个元素。如果查找元素在位置6,则只需要查找一次;如果查找位置在4,则分别在1-6之间,3-6之间,4-6之间查找,
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/2997d604c8987c1fa34de8ea22d35072)
启示:用树来存放数据,查删改可以快很多。
概念:
节点的度(Degree):节点的子树个数
树的度:树所有节点的最大度数
叶节点(Leaf):度数为0的节点
子节点(Child)、父节点(Father)、兄弟节点(Sibling)、祖先节点(Ancestor)、子孙节点(Descendant)
路和路径长度:节点n1到nk的路径为一个节点序列
节点的层次(Level):根节点层次为1
树的高度:最深层次
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/c2149343c3a3bfde1b53cc57e647b9ee)
用数组:太复杂
用链表:浪费空间
所以,用另一种方法表示
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/09dbeb8df5837ae84ef7a505328a7447)
把脖子向左歪45度,看到一棵很漂亮的二叉树
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/a3e2bf95c5735cfbad7800349b806173)
A.1
B.5
C.6
D.11
一棵度为 m的树有n个节点。若每个节点直接用m个链指向相应的儿子,则表示这个树所需要的总空间是n*(m+1) (假定每个链以及表示节点的数据域都是一个单位空间)。当采用儿子/兄弟(First Child/Next Sibling)表示法时,所需的总空间是:
A.3n
B.2n
C.n*m
D.n*(m-1)
完美二叉树(Perfect Binary Tree)/满二叉树(Full Binary Tree)
完全二叉树(Complete Binary Tree)
深度为
记非空二叉树中,度数为0、1、2的节点分别为
pf:
从树的根节点开始往下看,下方的边看做是上方的节点的贡献
节点总数=边的总数+1节点总数=边的总数+1
n0+n1+n2=0∗n0+1∗n1+2∗n2n0+n1+n2=0∗n0+1∗n1+2∗n2
化简可得结果
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/5e96c4ad5397e1c495e91d021aa6884f)
链表存储
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/1ed91876a2cacf0a05badf7743f107d2)
先访问根节点
先序遍历左子树
先序遍历右子树
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/cbbe21df6cc9ed7b46c2f74a935b9267)
中序遍历
中序遍历左子树
访问根节点
中序遍历右子树
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/363fc2da36b9da460c6ee24ef5edb336)
后序遍历
后序遍历左子树
后序遍历右子树
访问根节点
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/d2470370041f23100828abb6305392eb)
递归遍历时候的路径
按图中路径,每个节点会被遇到三次,在不同相遇时刻访问该节点,对应不同的遍历方式。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/9c26ca4ce358d0a96f3f4a750a59bf79)
中序遍历
遇到一个节点,就把它压栈,并去遍历它的左子树。
当左子树遍历结束后,从栈顶弹出这个节点并访问它。
按其右指针去中序遍历右子树。
先序遍历
遇到一个节点,访问它,并去
遍历它的左子树。
先序遍历右子树。
后续遍历
遇到一个节点,压栈。
如果有左子树,访问它的左子树,否则访问右子树。
如果没有左子树,也没有右子树,访问这个节点。
层序遍历基本过程:根节点先入队,然后
从队列中取出一个元素
访问这个元素
如果这个元素的左、右儿子非空,将左、右儿子按顺序入队。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/583ea5eaa6bc915eb3421bca75158245)
在访问节点过程中增加判断,若左右子树都为空,输出该节点。
求二叉树的高度
分别求左子树和右子树的高度,取两者中最大值,加一。
由两种遍历序列确定二叉树
对一棵所有节点没有左儿子(或没有右儿子),它的前序、后序遍历结果是一样的。所以要由遍历序列确定一棵树,还需要加上中序遍历。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201803/5630c2791d85859a08767123ed45883a)
[Note]:找根节点:先序的根节点在(子)序列的第一个,后序的根节点在(子)序列的最后一个。
顺序查找
二分查找
二分查找判定树
树的一些概念
树的表示
课后题
二叉树
几种特殊二叉树
二叉树性质
二叉树存储结构
二叉树的递归遍历(以链式存储为例)
先序遍历
中序遍历
后序遍历
递归遍历时候的路径
二叉树的非递归遍历
中序遍历
先序遍历
后续遍历
层序遍历
遍历二叉树的应用
输出叶子节点
求二叉树的高度
由两种遍历序列确定二叉树
课堂链接
前菜:查找
根据某个给定的关键字K,从集合R中找出与K相同的记录。静态查找:集合中记录是固定的,没有删除和插入操作。
动态查找:有插入、删除操作。
顺序查找
e.g. 静态查找例子:表如下,表头指针Tbl,Tbl -> Length = 10表示表的长度。查找时候从表尾,从后往前查找,返回元素K的下标。表头(下标为0)不存放数据。
常规写法是这样:
/* 在Tbl 到Tbl[1]中查找元素K的位置 */ int SequentialSearch (StaticTable *Tbl, ElementType K){ int i; // now index for(i = Tbl -> Length; i > 0 && Tbl -> Element[i] != K; i--); return i; }
注意到每次for循环中总要判断
i > 0,这是为了防止查找越界。那么换一种思路,如果查找到最后一个元素,其实也是到达查找的边界。把下标为0的元素的值设为K,最终总会得到
Tbl -> Element[i] = K,i的值若为0,则说明遍历了整个列表都没查找到K;若不为0,则返回位置i。这样可以减少循环中的一次比较次数。
int SequentialSearch (StaticTable *Tbl, ElementType K){ int i; // now index Tbl -> Element[0] = K; // Sentinel element for(i = Tbl -> Length; i > 0 && Tbl -> Element[i] != K; i--); return i; // returns: the index of Element K ; 0 if K is no found }
这种查找方法,平均查找次数
N/2,时间复杂度
O(N)。
二分查找
前提:数据有序存放。代码如下:
int BinarySearch( StaticTable *Tbl, ElementType K) { int left, right, mid, NotFound = -1; // 初始值 left = 1; right = Tbl -> length; while (left <= right) { mid = ( left + right) / 2; // 计算中间元素坐标 if (Tbl -> Element[mid] < K){ left = mid + 1; }else if (Tbl -> Element[mid] > k){ right = mid - 1; }else{ return mid } } return NotFound; }
算法复杂度
O(logN)。
[Note]: 如果
left和
right的更新由
left = mid+1、
right = mid-1改为
left = mid、
right = mid,结果会出错,这跟
while里面的条件有关的。本例中条件为
left > right时(查找失败)退出查找,假如在三个数
6 9 10
中查找8,为了方便,直接用
left、
mid、
right代表
6、
9、
10。
初始:
left = 6 right = 10
进入比较
mid = 9 9 > 8 -->left不变 left = 6 10 > 8 -->right变为mid的值 right = 9 mid = 6 6 < 8 --> left变为mid的值 left = 6 6 < 10 --> right不变 right = 9
此时
left和
right相邻,并不破坏while循环规则
left <= right,因此进入死循环,永远在6和9之间查找8(当然是找不到的了)。
为了避免这种情况,每次
left和
right都会避开
mid值,哪怕最后只剩两个元素时候,如果mid恰好是
left或
right中的一个,则
left+1和
right+1中有一个会进行,总能找到
mid;如果mid都不等于
left或
right中的任一个,则
left+1和
right+1都会执行,最终带来
left > right的结果,结束循环。
若想用
left = mid和
right = mid来作为更新条件,则需改写
while (left + 1 < right) { mid = ( left + right) / 2; // 计算中间元素坐标 if (Tbl -> Element[mid] < K){ left = mid; }else { right = mid - 1; } } // 剩下两个元素,需要判断 if (Tbl -> Element[left] == K) return left; if (Tbl -> Element[right] == K) return right; return NotFound;
二分查找判定树
假设查找11个元素。如果查找元素在位置6,则只需要查找一次;如果查找位置在4,则分别在1-6之间,3-6之间,4-6之间查找,
mid的值以此更新:
6 -> 3 -> 4,在判定数上可以找到该路径。二分查找判定树性质如下:(注:
ASL指平均成功查找次数)
启示:用树来存放数据,查删改可以快很多。
树的一些概念
一棵有N个节点的数,它的边有N-1条。树是连通N个节点所用边最少的数据结构。
概念:
节点的度(Degree):节点的子树个数
树的度:树所有节点的最大度数
叶节点(Leaf):度数为0的节点
子节点(Child)、父节点(Father)、兄弟节点(Sibling)、祖先节点(Ancestor)、子孙节点(Descendant)
路和路径长度:节点n1到nk的路径为一个节点序列
n1, n2, ... ,nk,这条路径上边的长度为该路径长度。
节点的层次(Level):根节点层次为1
树的高度:最深层次
树的表示
用数组:太复杂
用链表:浪费空间
所以,用另一种方法表示
把脖子向左歪45度,看到一棵很漂亮的二叉树
课后题
在分量1~11的数组中按从小到大顺序存放11个元素,如果进行二分查找,查找次数最少的元素位于什么位置?A.1
B.5
C.6
D.11
一棵度为 m的树有n个节点。若每个节点直接用m个链指向相应的儿子,则表示这个树所需要的总空间是n*(m+1) (假定每个链以及表示节点的数据域都是一个单位空间)。当采用儿子/兄弟(First Child/Next Sibling)表示法时,所需的总空间是:
A.3n
B.2n
C.n*m
D.n*(m-1)
二叉树
几种特殊二叉树
斜二叉树(Skewed Binary Tree)完美二叉树(Perfect Binary Tree)/满二叉树(Full Binary Tree)
完全二叉树(Complete Binary Tree)
二叉树性质
第i层最多有
2^(i-1)个节点
深度为
k的二叉树最大节点总数为
2^k - 1(每层累加)
记非空二叉树中,度数为0、1、2的节点分别为
n0、n1、n2,则有
n0 = n2 + 1。
pf:
从树的根节点开始往下看,下方的边看做是上方的节点的贡献
节点总数=边的总数+1节点总数=边的总数+1
n0+n1+n2=0∗n0+1∗n1+2∗n2n0+n1+n2=0∗n0+1∗n1+2∗n2
化简可得结果
二叉树存储结构
完全二叉树的顺序存储链表存储
typedef struct TreeNode *BinTree; typedef BinTree Position; struct TreeNode { ElementType Data; BinTree Left; BinTree Right; }
二叉树的递归遍历(以链式存储为例)
先序遍历先访问根节点
先序遍历左子树
先序遍历右子树
void PreOrderTraversal( BinTree BT) { if (BT) { printf("%d", BT -> Data); PreOrderTraversal( BT -> Left); PreOrderTraversal( BT -> Right); } }
中序遍历
中序遍历左子树
访问根节点
中序遍历右子树
void InOrderTraversal( BinTree BT) { if (BT) { InOrderTraversal( BT -> Left); printf("%d", BT -> Data); InOrderTraversal( BT -> Right); } }
后序遍历
后序遍历左子树
后序遍历右子树
访问根节点
void PostOrderTraversal( BinTree BT) { if (BT) { PostOrderTraversal( BT -> Left); PostOrderTraversal( BT -> Right); printf("%d", BT -> Data); } }
递归遍历时候的路径
按图中路径,每个节点会被遇到三次,在不同相遇时刻访问该节点,对应不同的遍历方式。
二叉树的非递归遍历
思路:遇到节点的时候,不该访问的话就压入栈,访问便弹出。中序遍历
遇到一个节点,就把它压栈,并去遍历它的左子树。
当左子树遍历结束后,从栈顶弹出这个节点并访问它。
按其右指针去中序遍历右子树。
void InOrderTraversal(BinTree BT){ BinTree T = BT; Stack S = CreateStack(MAXZIZE); // 创建并初始化堆栈 while(T || !isEmpty(S)){ while(T){ // 一直向左,将沿途节点入栈 Push(S, T); T = T -> Left; } while(!isEmpty(S)){ T = Pop(S); // 节点弹出堆栈 printf("%d", T -> Data); // 访问节点 T = T -> Right; // 转向右子树 } } }
先序遍历
遇到一个节点,访问它,并去
遍历它的左子树。
先序遍历右子树。
void PreOrderTraversal(BinTree BT){ BinTree T = BT; Stack S = CreateStack(MAXZIZE); while(T || !isEmpty(S)){ while(T){ printf("%d", T -> Data); Push(T, S); T = T -> Left; } while(!isEmpty(S)){ T = Pop(S); T = T -> Right; } } }
后续遍历
遇到一个节点,压栈。
如果有左子树,访问它的左子树,否则访问右子树。
如果没有左子树,也没有右子树,访问这个节点。
void PostOrderTraversal(BinTree BT){ BinTree T = BT; Stack S = CreateStack(MAXZIZE); while(T || !isEmpty(S)){ while(T){ Push(T, S); T = T -> Left; } T = Pop(S); // 到达最左端 if(!T -> Right && !isEmpty(S)){ printf("%d", T -> Data); }else { T = T -> Right; } } }
层序遍历
遇到一个节点,如果访问它的左儿子,不保存当前位置信息,那右儿子就会“丢失”。此时的思路是把该父节点,或是右儿子,通过栈/队列存起来。层序遍历基本过程:根节点先入队,然后
从队列中取出一个元素
访问这个元素
如果这个元素的左、右儿子非空,将左、右儿子按顺序入队。
void LevelOrderTraversal(BinTree BT){ Queue Q; Q = CreateQueue(MaxSize); AddQ(Q, BT); // 根节点入队 while(!isEmpty(Q)){ T = Delete(Q); printf("%d",T -> Data ); if(T -> Left) AddQ(T -> Left); if(T -> Right) Push(T -> Right); } }
遍历二叉树的应用
输出叶子节点在访问节点过程中增加判断,若左右子树都为空,输出该节点。
void PreOrderPrintfLeave(BinTree BT){ if(BT){ if(!T -> Left && !T -> Right){ printf('%d', T -> Data); } PreOrderPrintfLeave(T -> Left); PreOrderPrintfLeave(T -> Right); } }
求二叉树的高度
分别求左子树和右子树的高度,取两者中最大值,加一。
int PostOrderGetHeight(BinTree BT){ int HL, HR, MaxH; if(BT){ HL = PostOrderGetHeight(BT -> Left); HR = PostOrderGetHeight(BT -> Right); MaxH = (HL > HR) ? HL : HR; return MaxH + 1; }else { return 0; } }
由两种遍历序列确定二叉树
对一棵所有节点没有左儿子(或没有右儿子),它的前序、后序遍历结果是一样的。所以要由遍历序列确定一棵树,还需要加上中序遍历。
[Note]:找根节点:先序的根节点在(子)序列的第一个,后序的根节点在(子)序列的最后一个。
相关文章推荐
- 数据结构——浙大网易云课堂记录(一)树(中篇)
- SQL Server 存储(2/8):理解数据记录结构
- 8.2日讲座记录-高级数据结构
- 数据结构与算法学习——选择排序【使用上篇的冒泡排序】
- 记录下:Mysql导出表结构及表数据 mysqldump用法 - T00ls
- Berkeley DB的数据存储结构——哈希表(Hash Table)、B树(BTree)、队列(Queue)、记录号(Recno)
- MOOC浙大数据结构 — 03-树1 树的同构 (25分)
- MOOC浙大数据结构 — 08-图7 公路村村通 (30分)
- JVM学习记录-JVM的内存结构管理和运行时数据区理解
- 记录一下python的数据结构
- 数据结构3.11答案 递归的查询单链表的其中的记录,以及最大能够让堆栈crash的值
- 记录我的数据结构(C语言)学习历程(2017年3月30号开始)
- MySQL入门--显示指定数据表的字段结构、插入指定字段的值(记录)、查看指定表中指定字段的所有记录
- Maximum Subsequence Sum浙大数据结构
- MOOC浙大数据结构 — 08-图8 How Long Does It Take (25分)
- 记录学习的点滴(树形结构数据SQL检索语句)
- OpenCV 学习记录3 数据结构和基本绘图
- 网易云课堂 浙江大学-陈越、何钦铭-数据结构基础习题集(自测)
- 数据结构课程设计——图的建立和遍历(邻接矩阵+邻接表)和最短路径dijkstra路径记录
- ORACLE空间管理实验1:探索LMT表空间管理下数据文件头的结构及位图中区的记录方式