树、二叉树、查找算法
作者:互联网
一、思维导图
二、概念笔记
1)
结点的度:结点拥有的子树数。
树的度:树中所有结点的度的最大值。
2)
二叉树的五种基本形态:空树、只有根结点、右子树为空树、左子树为空树、左右子树均不为空树
3)
二叉树的性质
性质1:在二叉树的第 i 层上至多有2^(i-1)个结点(i≥1)。
归纳法证明:
归纳基:i = 1 层时,只有一个根结点: 2i-1 = 20 = 1,命题成立。
归纳假设:假设对所有的 j,1≤ j < i,命题成立。即第j层上至多有2j-1个结点。
归纳证明:j=i时,命题成立。
∵二叉树上每个结点至多有两棵子树,且由归纳
假设有:第i-1层上至多有2i-2个结点
∴第 i=j 层的结点数至多 = 2i-2 2 = 2i-1 。
命题成立(证毕)
性质2:深度为 k 的二叉树上至多含 2^(k)-1个结点(k≥1)。
性质3:对任何一棵二叉树,若它含有n0 个叶子结点、n2 个度为2的结点,则必存在关系式:n0 = n2+1。
证明:
设二叉树上结点总数 n = n0 + n1 + n2,
∵二叉树上分支总数 b = n1+2n2,①
而b = n-1 = n0 + n1 + n2 – 1 ②
由①②, n0 = n2 + 1
除根结点外,其余结点都有一个分 支进入,设b为分支总数,则n=b+1。
3)⭐重要概念辨析:特殊的二叉树:
满二叉树:指的是深度为k且含有2^(k)-1个结点的二 叉树。
特点:是每一层上的结点数都是最大结点数。(结点全满)
完全二叉树:树中所含的 n 个结点和满二叉树中 编号为 1 至 n 的结点一一对应。
特点:⑴叶子结点只可能在层次最大 的两层出现;⑵对任一结点,若其右 分支下的子孙的最大层次为l,则其左 分支下的子孙的最大层次为l或l+1。(最后一层可不满的满二叉树)
完全二叉树的性质:
性质4:具有n个结点的完全二叉树的深度为 log(2)n +1(向下取整)
性质5:若对含 n 个结点的完全二叉树从上到下且 从左至右进行 1 至 n 的编号,则对完全二叉树中 任意一个编号为 i 的结点: (1)若 i=1,则该结点是二叉树的根,无双亲,否 则,编号为i/2(向下)的结点为其双亲结点;
(2)若 2i>n,则该结点无左孩子,否则,编号为 2i 的结点为其左孩子结点;
(3)若 2i+1>n,则该结点无右孩子结点,否则,编 号为2i+1 的结点为其右孩子结点。
4)二叉树的存储结构:
顺序:特点
◼ 一组地址连续的存储单元存储各结点(如一维数组)
◼ 自根而下、自左而右存储结点;
◼ 按完全二叉树上的结点位置进行编号和存储
※在最坏的情况下,一个深度为 k 且只有 k 个结点的单支树(树中不存在度为2的 结点)却需要2 k -1的一维数组。
缺点:空间利用率太低
链式:
二叉链表的结点结构:(lchild|data|rchild) 题型:空指针数的计算
三叉链表的结点结构:(parent|lchild|data|rchild)
5)遍历二叉树
递归:
void Inorder (BiTree T)
{ // 中序遍历二叉树
if (T) {
Inorder(T->lchild); // 遍历左子树
cout<<T->data; // 访问结点
Inorder(T->rchild); // 遍历右子树
}
}
非递归:(使用栈)
void InOrderTraverse(BiTree T){
InitStack(S); p = T;
声明q;//存放栈顶弹出元素
while (p || !StackEmpty(S)){
if (p){
Push(S, p);
p = p->lchild;
}else{
Pop(S, q);
cout << q->data;
p = q->rchild;
}
}
}
6)使用字符串建立二叉树的存储结构:
void CreateBiTree(BiTree &T){ //先序次序输入结点 cin>>ch;
if (ch==‘#’) T = NULL;
else {
T = new BiTNode;
T->data = ch; // 生成根结点 CreateBiTree(T->lchild); // 构造左子树 CreateBiTree(T->rchild); // 构造右子树
}
} // CreateBiTree
7)线索二叉树:遍历二叉树的结果是:求得结点的一个线性序列。指向该线性序列中的“前驱”和 “后继” 的指针, 称作“线索”。
包含“线索”的存储结构, 称作“线索链表”。 与其相应的二叉树,称作 “线索二叉树”。
作用:线索二叉树能简化找到下一个节点的这个过程
8)森林和二叉树的转换
由森林转换成二叉树的转换规则为:
若 F = Φ,则 B = Φ;否则,
由 ROOT( T1 ) 对应得到 Node(root);
由 (t11, t12, …, t1m ) 对应得到 LBT;
由 (T2, T3,…, Tn ) 对应得到 RBT。
由二叉树转换为森林的转换规则:
若 B = Φ, 则 F = Φ;否则,
由 Node(root) 对应得到 ROOT( T1 );
由LBT 对应得到 ( t11, t12, …,t1m);
由RBT 对应得到 (T2, T3, …, Tn)。
9)对树的访问没有中根遍历
10)如图
11)哈夫曼树:n在所有含 n 个叶子结点、并带相同权值的 m 叉树中,必存在一棵其带权路径长度取最小值的树,称为“最优树”。
12)哈夫曼编码:编码目标:总编码长度最短!(出现频率越大的字符,其Huffman编码越短。)
让待传字符串中出现次数较多的字符采用尽可能短的编码,则转换的二进制字符串便可以减少。
关键:要设计长度不等的编码,则必须使任一字符的编码都不是另一个字符的编码的前缀。
符合这种编码要求的编码方式称为前缀编码。
*左分支用"0"表示,右分支用"1"表示。
13)二叉排序树
特性:
¨若它的左子树不空,则左子树上所有结点的值均小于根结点的值;
¨若它的右子树不空,则右子树上所有结点的值均大于根结点的值;
¨它的左、右子树也都分别是二叉排序树。
14)二叉排序树的查找(递归)
BSTree SearchBST(BSTree T,KeyType key) {
if((!T) || key==T->data.key) return T; else if (key<T->data.key) //在左子树中继续查找 return SearchBST(T->lchild,key);
else //在右子树中继续查找
return SearchBST(T->rchild,key);
} // SearchBST
15)二叉排序树的删除:
◼被删除的结点是叶子
◼被删除的结点只有左子树或只有右子树
◼被删除的结点既有左子树又有右子树
16)二叉排序树的查找性能:
平均查找长度和二叉树的形态有关,即,
最好:log2n(形态匀称,与二分查找的判定树相似)
最坏: (n+1)/2(单支树)
17)平衡二叉树(又称AVL树): 是二叉排序树的另一种形式,其特点为: 树中每个结点左、右子树深度之差(称为平衡因子BF) 的绝对值不大于1。
AVL树的平均查找长度和logn是同数量级的
17)B-树(又称多路平衡查找树)】
定义:一棵m阶B-树或者是一棵空树,或者是满足 下列要求的m叉树:
• 每个节点至多m个孩子节点(至多有m-1个关键字)
• 除根节点外,其他节点至少有m/2个孩子节点(即 至少有m/2-1个关键字);(向上取整)
• 若根节点不是叶子节点,根节点至少两个孩子节点
18)B+树:
一棵m阶B+树满足下列条件:
每个分支节点至多有m棵子树。 根节点或者没有子树,或者至少有两棵子树
除根节点,其他每个分支节点至少有m/2棵子树 (向上取整)
有n棵子树的节点有n个关键字。
叶子节点包含全部关键字及指向相应记录的指针
⚫ 叶子节点按关键字大小顺序链接
⚫ 叶子节点直接指向数据文件中的记录。
所有分支节点(可看成是分块索引的索引表)
⚫ 包含它的各个子节点(即下级索引的索引块) 中最大关键字及指向子节点的指针。
三、疑难杂症:
①表达式二叉树
先缀表达式获取二叉树
分析:
从左往右遍历先缀表达式,发现操作符就将其入栈,发现操作符的第二个操作数之后,将它们组织成最小的子树,然后操作符出栈,继续遍历下一个字符。在这个过程中,操作数是不入栈的,栈里只有操作符,当操作符组织成最小计算单元之后就将其出栈。当栈空的时候,说明先缀表达式遍历完毕。
代码:
void ExpressionBinaryTree::buildBTreeByPreffixE()
{
root = new BinaryTreeNode();
char c;
cout << "->请输入前缀表达式,以=结尾." << endl;
cout << "->:";
cin >> c;
stack *> parentStack;//用于保存存放父结点
BinaryTreeNode *pointer = root;//用于指向下一个保存数据的结点
string blankStr = "";
double tempDouble = 0;
string tempStr;//用于输入流,将浮点数转换成字符串
while (c != '='``)
{
switch (c)
{
case '+':
case '-':
case '*':
case '/':
pointer->setValue(c + blankStr);//设置当前结点的值
pointer->setLeftChild(new BinaryTreeNode());//生成左结点
parentStack.push(pointer);
pointer = pointer->getLeftChild();
break;
}
if (isdigit(c))
{
std::cin.putback(c);
std::cin >> tempDouble;
stringstream sss;
sss << tempDouble;
sss >> tempStr;
pointer->setValue(tempStr);
pointer = parentStack.top();
while` `(pointer->getRightChild() != NULL)
{
parentStack.pop();//找到按前序遍历的下一个结点
if (parentStack.empty())
return;
pointer = parentStack.top();
}
pointer->setRightChild(new BinaryTreeNode());//找到了按前序遍历的下一个结点位置并生成结点
pointer = pointer->getRightChild();
}
std::cin >> c;
}
}
⭐中缀表达式获取二叉树(括号以及优先级的处理)
如果从右开始计算,每次计算结果都是下一个操作符的第二个操作数,那么遍历结束之后,结果就出来了。用代码实现可以用两个栈,一个栈保存从左到右的操作符,另一个栈保存从左到右的操作数
其实括号也是优先级的问题。处理括号时按照处理优先级问题的逻辑,即右括号的优先级是最高的。在压入右括号的时候不用看后面的操作符了,右括号就是最高的,应该直接将从左括号到右括号中的表达式组成子树,然后压入到操作数栈中。
代码如下:
//比较优先级
bool ExpressionBinaryTree::aIsGreaterOrEqualThanB(char a, char b)
{
switch (a)
{
case '*':
case '/':
return true;
case '+':
case '-':
if (b == '*' || b == '/')
return false;
return true;
case '(':
return false;
}
return false;
}
//中缀表达式转换成二叉树
void ExpressionBinaryTree::buildBTreeByInfixE()//构造中缀表达式二叉树
{
root = new BinaryTreeNode<string>();
char c;
cout << "->请输入中缀表达式,以=结尾." << endl;
cout << "->:";
cin >> c;
stack<BinaryTreeNode<string> *> opd;//操作数栈 //为了方便统一管理,操作数和操作符全部定义为string类型
stack<string> opt;//操作符栈
double tempDouble = 0;
string tempStr;//用于输入流,将浮点数转换成字符串
string blankStr = "";
while (c != '=')
{
switch (c)
{
case '+':
case '-':
case '*':
case '/':
while (!opt.empty() && aIsGreaterOrEqualThanB(opt.top().c_str()[0], c))//如果栈顶操作符优先级高于读入操作符优先级,则表名应该先计算栈顶操作符
{
BinaryTreeNode<string> *secondOpd = opd.top();
opd.pop();
BinaryTreeNode<string> *firstOpd = opd.top();
opd.pop();//从操作数栈取出两个操作数
opd.push(new BinaryTreeNode<string>(opt.top(), firstOpd, secondOpd));//将操作数和操作符组成一个新结点存入栈中
opt.pop();
}
opt.push(c + blankStr);//将读入操作符入栈
break;
case '(':
opt.push(c + blankStr);//遇到左括号直接入栈
break;
case ')':
while (!opd.empty() && opt.top().c_str()[0] != '(')//为了防止冗赘括号,但未检测括号不匹配
{
BinaryTreeNode<string> *secondOpd = opd.top();
opd.pop();
BinaryTreeNode<string> *firstOpd = opd.top();
opd.pop();//从操作数栈取出两个操作数
opd.push(new BinaryTreeNode<string>(opt.top(), firstOpd, secondOpd));//将操作数和操作符组成一个新结点存入栈中
opt.pop();
}
opt.pop();//将左括号出栈
break;
}
if (isdigit(c))
{
std::cin.putback(c);
std::cin >> tempDouble;
stringstream sss;
sss << tempDouble;
sss >> tempStr;
opd.push(new BinaryTreeNode<string>(tempStr));
}
std::cin >> c;
}
while (!opt.empty())
{
BinaryTreeNode<string> *secondOpd = opd.top();
opd.pop();
BinaryTreeNode<string> *firstOpd = opd.top();
opd.pop();//从操作数栈取出两个操作数
opd.push(new BinaryTreeNode<string>(opt.top(), firstOpd, secondOpd));//将操作数和操作符组成一个新结点存入栈中
opt.pop();
}
root = opd.top();//此时操作数栈中唯一元素即为根元素
opd.pop();
}
作者:vBackSlash
链接:http://www.imooc.com/article/273872?block_id=tuijian_wz
来源:慕课网
②一组需要检索的数据(里面可能有多个属性,比如书本 记录,里面可能有ISBN号、书名、作者、出版社等),其 本身没有排序。如何对该组数据进行处理提高查找的效率? 算法的时间复杂度由谁来决定?(未解决)
标签:结点,操作数,opd,BinaryTreeNode,算法,查找,二叉树,节点 来源: https://www.cnblogs.com/dcftx/p/12780777.html