【数据结构-陪读笔记】第六章(查找)
一、线性查找
1.顺序查找
原理: 注意第一个位置上有值;从后面往前面找。
哨兵问题: 避免每次查找位置是否越界。
ASL: n - i + 1求和。
int Search(int a[] , int n , int key){ // n代表数据长度//
int i = n;
a[0] = key;
while(a[i] != key){
i --;
}
return i;
}
2.折半查找
原理: 注意判定树问题。
ASL:
成功:所有圆的就是成功的,所以计算每层圆的个数 * 层数 / 圆的个数
失败:所有方的就是失败的,方的层数 * 层的方的个数 / 方的个数
int Binary_Search(SqList L, ElemType key , int n){
int low = 0 , high = n - 1 , mid;
// 从0开始
while(low <= high){
mid = (low + high) / 2;
// 向下取整
if(L.elem[mid] == key){ // 正好查到
return mid;
}
else if(L.elem[mid] > key){ // 比它大,右缩小范围
high = mid - 1;
}
else{
low = mid + 1;
// 比它小, 左缩小范围
}
}
return -1;
}
3.平衡二叉树
Q:什么是平衡二叉树?
平衡二叉树的左子树-右子树的深度不超过1。
struct node{
int data , height;
node* lchild , rchild;
}//求出结点的高度
int get_Height(node* root){
if(root == NULL) return 0;
else root->height;
}//计算平衡因子
int get_BalanceFactor(node* root){
return get_Height(root->lchild) - get_Height(root->rchild);
}
Q:为什么平衡二叉树的ASL与logn为同样的数量级?
根据前面树的四个公式已知(公式链接),n个结点的二叉树的深度为logn向下取整+1,在树中查找一个数据,最多不会超过树的深度吧,所以平均查找长度也就和logn为同样的一个数量级。
【【数据结构-陪读笔记】第六章(查找)】Q:如何构建平衡二叉树?
文章图片
如果是那种RL的那种,记得从下往上依次更新即可。
//Right Rotation
void Right_Rotation(node* &root){
node * temp = root->lchild;
// temp当前指向的是B,root是A
root->lchild = temp->rchild;
// 让B结点的右孩子成为A结点的左孩子
temp->rchild = root;
// 让A结点成为B结点的右孩子
update_Height(root);
//分别更新AB结点的高度
update_Height(temp);
root = temp;
//根节点变化
}//更新高度函数,其左右孩子的高度的最大值+1
void update_Height(node* root){
root->height = max(get_Height(root->lchild) , get_Height(root->rchild) + 1);
}
4.B 树
文件系统搜索常用的一种数据结构。
特性
- 每个结点最多有m棵子树。
- 根节点如果是非叶子结点,则至少有2棵子树。(一分为二嘛)
- 除了根节点之外的任何的结点至少有向上取整【m/2】棵子树。
- 查找结点。
- 查找结点上的关键字。
当前B树有 N N N个关键字,如果查找不成功,则结点个数为 N + 1 N+1 N+1个,(我的理解是,当前的关键字的个数和结点个数的值是一致的。)这个数值肯定比当前结点数值要小嘛,所以可以得到
N + 1 ≤ 2 ? ? m / 2 ? l ? 1 N+1\leq{2*\lceil m/2 \rceil}^{l-1} N+1≤2??m/2?l?1
推导可以得到一个关于 l l l的公式,即
l ≤ l o g ? m / 2 ? ( N + 1 2 ) + 1 l\leq log_{\lceil m/2 \rceil}(\frac{N+1}{2})+1 l≤log?m/2??(2N+1?)+1
插入分析 Q1:何时产生分裂?为什么说该结点关键字个数不超过 m ? 1 m-1 m?1时不需要分裂?
根据树的定义,树的每个结点不能有超过m棵子树,m棵子树对应的根会有 m ? 1 m-1 m?1个关键字。(可以想想5个手指算上左右的空隙会有6个空隙。)
Q2:怎么插?
- 0 0 0到 ? m / 2 ? ? 1 \lceil m/2 \rceil-1 ?m/2??1这部分成为左子树; ? m / 2 ? \lceil m/2\rceil ?m/2?往上回到根节点上,其余放到右节点上;如果是根节点就让中间的结点不动,其余的结点往下放即可。
- 先根据树找到结点先插进去,随后再对树进行调整。
如果删除的是非终端叶子结点,将该结点子树的最小的叶子结点进行交换即可。所以书上写着有三种情况。
根据树的定义:除根以外的所有的结点都是 ? m / 2 ? \lceil m/2 \rceil ?m/2?当差了一个1,正好可以维持。所以当缺少了一个数值的时候,可以从左右借。
- 能删则删: 若删除结点关键字个数不小于 ? m / 2 ? \lceil m/2 \rceil ?m/2?,则直接删除。
- 能借则借: 若删除结点A的关键字数目等于 ? m / 2 ? ? 1 \lceil m/2 \rceil-1 ?m/2??1,且兄弟结点 B B B关键字数目大于 ? m / 2 ? ? 1 \lceil m/2 \rceil-1 ?m/2??1,设其双亲结点为 C C C,则从 C C C中下放 A A A一个数据,从 B B B中上交一个数据补充给 C C C。
- 不能合并: 若AB关键字数目都等于 ? m / 2 ? ? 1 \lceil m/2 \rceil-1 ?m/2??1,则直接从C下放一个数据给A,然后AB合并。如果导致父结点数据不够了,则依次往上进行调整。
B+树的数据都存在在叶子结点上,然后用一个链表把所有的叶子结点都串了起来。当数据存储的离根节点很近的时候,B树效果比B+树效果要好,B+数据都存在叶子结点上。
二、计算查找–Hash表 查找分为两种,一种是上面介绍的通过比较的查找,而另一种则是根据计算直接得出。关键字 k e y key key 和其对应的hash函数的 h a s h ( k e y ) hash(key) hash(key)合成一个表,成为hash表或者散列表。
hash表非常容易算错,一定要仔细仔细再仔细,算的是余数不是商。
构造方法
- 直接定值法:利用线性函数直接定义,但是发现其实本身并没有起到压缩的作用。
- 数字分析法:选取比较随机的几位进行叠加舍去进位作为hash地址。
- 平方取中法:关键字平方后利用中间的几位数字作为hash地址。
- 折叠法:关键字拆分成几个部分叠加和再合并。
- 随机数:random函数。
- 除留取余法:p选取质数(假设选取合数,余数必然都会是0,没什么意义了)或者不包含小于20的质因数的合数。 H a s h ( k e y ) = k e y % p , p < = M Hash(key)=key \%p, p<= M Hash(key)=key%p,p<=M
- 开放地址法:m代表hash表长,d有3种情况(1)线性探测再散列(线性。当发现起了冲突的时候+1,如果再起冲冲突再+1,相当于出了冲突之后不停的往后一个一个查找)(2)二次探测再散列: + 1 2 , ? 1 2 , + 2 2 , ? 2 2 . . . m / 2 2 +1^2,-1^2,+2^2,-2^2...{m/2}^2 +12,?12,+22,?22...m/22(3)伪随机数列。
- 再Hash:再给一个Hash函数再计算一次。
- 链地址:类似邻接表。
- 设置公共溢出区:但凡发生了冲突的,都写到公共溢出区。
- 关于状态因子的问题: α = 表 中 填 入 的 记 录 数 h a s h 表 长 \alpha=\frac{表中填入的记录数}{hash表长} α=hash表长表中填入的记录数?
装填因子本身可以理解为当前hash表的存储的利用率。如果本身 α \alpha α小,代表表中本身没有什么东西,所以发生冲突的可能性就小;反之大。
推荐阅读
- 宽容谁
- 我要做大厨
- 增长黑客的海盗法则
- 画画吗()
- 2019-02-13——今天谈梦想()
- 远去的风筝
- 三十年后的广场舞大爷
- 叙述作文
- 20190302|20190302 复盘翻盘
- 学无止境,人生还很长