第十一章 查找

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

 

 


前言

d70bd80e4e7da5f4399f496f5bbb01e7.png

 

c87f4bace86dc830fb1de882a882f5a2.png

平均查找长度ASL:Σ(pi*ci)概率*次数


 

一、静态查找

(一)顺序查找

1.查找最值

9198313d1f6ee814fca29a55f2f12d50.png

朴素查找:2(n-1)

同时利用两个数据找min,max 3n/2

2.查找质数

 

0cae6b9d1a606f20f4767d1dc9e12b92.png

埃氏筛法思路:O(nloglogn) 

5a5039b7af74b886a0f7ef7b2e410cce.png

分析:

埃氏筛法:每一个数,若能分解为k个不相同的质因数,会被筛掉k次。

合数限定:为防止多次删除,采用合数限定法 ,所以不能简单删除所有倍数,而是由当前找到的质数合成的数。

f79ca9f87fffc0741ae5712279eba26d.png

线性筛法(欧拉筛法):

173a3eea73c7a816621811c3e4dabda9.png

665a045a7b10f7198a10b4a38f319085.png

8347f5ef56e9fa10d6e16b0951419840.png

埃氏筛法 n较小时快,线性筛法n大时快

//朴素筛法
int primes[N], cnt;     // primes[]存储所有素数
bool st[N];         // st[x]存储x是否被筛掉

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i]) continue;//筛掉就跳过
        primes[cnt ++ ] = i;
        for (int j = i + i; j <= n; j += i)
            st[j] = true;//倍数被筛
    }
}
//欧拉筛法
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;//没筛则放入
        for (int j = 0; primes[j] <= n / i; j ++ )//循环条件时i*当前质数在范围内
        {
            st[primes[j] * i] = true;//筛掉i*当前质数
            if (i % primes[j] == 0) break;
//如果当前质数是i的质数,则一定为i的最小质数,从此跳出
        }
    }
}

(二)折半查找(二分查找)

 1.二分查找

(查找小于等于val的最右值)

ans的下一个位置也是>val的最左值

int find(int l, int r, int val) {
int mid, ans = l;//给个初始值,否则没找到的话访问a[ans]容易RE
while(l <= r) {
mid = l + r >> 1;//等价于(l + r) / 2,这里加法优先级大于位移运算可以打开括号
if(a[mid] <= val) ans = mid, l = mid + 1;
else r = mid - 1;
}
if(a[ans] == val) return ans;//有可能一直没找到
return -1;
}
//这样写找到的val的位置就是区间内该值出现的最靠右的位置。
//不论找没找到,ans的下一位就是第一个大于val的位置。

查找>=val的最左值 ,ans的上一个位置,也是<val的最右值

if(a[mid] >= val) ans = mid, r = mid - 1;//优先考虑往左调整区间

else l = mid + 1;
最多与└log2n┘+1个元素比较

2.区间查询([a,b))

[a,b),low为>=a的最小值,high为<b的最大值

3.快速求幂

328ba519eef6cb5338f2bef62e22b2f1.png

4.二分答案(找到第一个大于val的位置)

bool check(int x, int val) {//具体题目传入的不一定是int
return x > val;
}
int find(int l, int r, int val) {//同理,不一定返回int
int l = 1, r = n, mid, ans = -1;
    while(l <= r) {
    mid = l + r >> 1;
    if(check(a[mid], val)) ans = mid, r = mid - 1;
    else l = mid + 1;    
    }
return ans;
}

应用:见一道趣题p65 

5.快速查找(快速查找排序为第k的元素)O(n)

快排+二分

43be8ca2e510e9f4c45e186ec607d7d4.png

e34c87a4d44f8ced9d18951aaa52cf49.png

(三)索引查找

8a82d97d4dc9e9f241b3816740c814c6.png

(四)习题

查找失败需要多走一步,所以n+1

b6a2a9dfc417070a5291e25aca915ea0.png

!!构造等概率判定树,失败时,多走一个,分母为14,第四层有6个节点,第三层有两个节点有单独空孩子。

2da8760c018e32663b4134e64c50f76c.png

二、二叉查找树(BST)

3c633a43a4530d87ac7e28e3b21be982.png

BST的中序遍历即为节点数据中序排序结果,所以重构BST只需要知道前序或后序或层序

1.查找O(logd)

3136842ffc0634681452f3b76f7321dd.png

 39cd1fd6a3885135231ca4a0ca73f5fa.png

2.插入(查找不成功时)

0a96f500dd4e135f0e721e7d357d917c.png

BST结构取决于元素插入顺序

3.删除

先二分查找node。若找到:

1.如果node无孩子,是叶子节点,则直接删,父节点相应指针域为空。

2.如果node只有一个孩子,将孩子指向父节点,父节点的相应指针域改为其孩子。

3.如果node有两个孩子,找到后继节点(被删除节点的右孩子的最左祖孙),替换删除节点,删除后继节点(此节点情况一定属于1或2)

2b8872b2b499f0c80d072dc5d1f53e37.png

167bcfe4e139c932e84f88a6b557a4ba.png

2606dbbe1912270d13b1811a3e62b2c3.png

三、AVL树

9173ef747c0e88caf1e1243d6e1ed2d9.png

  1. 每个节点都存储一个关键字值。
  2. 对于任意节点,它的左子树和右子树都是AVL树。
  3. 对于任意节点,其左子树中的关键字值小于等于节点的关键字值,而其右子树中的关键字值大于等于节点的关键字值。
  4. 每个节点都有一个平衡因子(Balance Factor),它表示其左子树的高度减去右子树的高度。平衡因子可以是 -1、0 或 1。
  5. 对于AVL树中的每个节点,其平衡因子必须为 -1、0 或 1。如果一个节点的平衡因子不在这个范围内,那么它就不是AVL树,需要进行平衡操作以恢复平衡性。

 

class AVLNode {
    int key;            // 节点的关键字值
    int height;         // 节点的高度
    AVLNode left;       // 左子节点
    AVLNode right;      // 右子节点
​
    public AVLNode(int key) {
        this.key = key;
        this.height = 1;
        this.left = null;
        this.right = null;
    }
}
// AVLTree表示AVL树
public class AVLTree {
    AVLNode root;       // 树的根节点
​
    // 获取节点的高度
    private int getHeight(AVLNode node) {
        if (node == null)
            return 0;
        return node.height;
    }
​
    // 获取节点的平衡因子
    private int getBalanceFactor(AVLNode node) {
        if (node == null)
            return 0;
        return getHeight(node.left) - getHeight(node.right);
    }
​
    // 更新节点的高度
    private void updateHeight(AVLNode node) {
        int leftHeight = getHeight(node.left);
        int rightHeight = getHeight(node.right);
        node.height = Math.max(leftHeight, rightHeight) + 1;
    }
// 执行左旋操作
    private AVLNode leftRotate(AVLNode node) {
        AVLNode newRoot = node.right;
        AVLNode subtree = newRoot.left;
​
        newRoot.left = node;
        node.right = subtree;
​
        updateHeight(node);
        updateHeight(newRoot);
​
        return newRoot;
    }
​
    // 执行右旋操作
    private AVLNode rightRotate(AVLNode node) {
        AVLNode newRoot = node.left;
        AVLNode subtree = newRoot.right;
​
        newRoot.right = node;
        node.left = subtree;
​
        updateHeight(node);
        updateHeight(newRoot);
​
        return newRoot;
    }
}

bbb705f932fe180fd1ad46665e5cb2d2.png

右旋转:原根node的左孩子成为新根newroot,newroot的右子树subtree变成原根node的左孩子。更新node和newroot的高度。

A的深度减1,B和C的深度加1

A的高度只增不减,B的高度只减不增,C的高度一定不变

左旋转:node的右孩子成为newroot,newroot的左子树subtree成为node的右孩子。更新高度。

插入节点

失衡类型:

插入节点自上而下层层比较,一定会成为叶节点。为保证平衡,再从下而上逐个判断平衡因子。使用递归做法。

RL型上层R高,下层L高,需要自下而上,先右转解决下层,再左转上层。

 

244dd168873ed26e3b844eb76ad46554.png

 

96dd84aa579cf3a7a24b357fa6fa7744.png

 // 插入节点到AVL树中
    public void insert(int key) {
        root = insertNode(root, key);
    }
​
    private AVLNode insertNode(AVLNode node, int key) {
        if (node == null)
            return new AVLNode(key); 
        if (key < node.key) {
            node.left = insertNode(node.left, key);
        } else if (key > node.key) {
            node.right = insertNode(node.right, key);
        } else {
            return node;
        } // 忽略重复的关键字值
​
        updateHeight(node);
​
        int balanceFactor = getBalanceFactor(node);
​
        // 左左情况,执行右旋
        if (balanceFactor > 1 && key < node.left.key)
            return rightRotate(node);
​
        // 右右情况,执行左旋
        if (balanceFactor < -1 && key > node.right.key)
            return leftRotate(node);
​
        // 左右情况,先对左子树左旋,再对当前节点右旋
        if (balanceFactor > 1 && key > node.left.key) {
        //key > node.left.key相当于左孩子的右子树比左子树高。
        //也可写成为getHeight(node.left.left)<getHeight(node.left.right)
            node. Left = leftRotate(node. Left);
            return rightRotate(node);
        }
​
        // 右左情况,先对右子树右旋,再对当前节点左旋
        if (balanceFactor < -1 && key < node.right.key) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }
​
        return node;
    }

删除节点(结合插入的做法与BST树的删除)

    // 删除节点
    public void delete(int key) {
        root = deleteNode(root, key);
    }
​
    private AVLNode deleteNode(AVLNode node, int key) {
        if (node == null)
            return null;
​
        if (key < node.key)
            node.left = deleteNode(node.left, key);
        else if (key > node.key)
            node.right = deleteNode(node.right, key);
        else {
            // 找到要删除的节点
​
            if (node.left == null && node.right == null) {
                // 叶节点,直接删除
                node = null;
            } else if (node.left == null) {
                // 只有右子树,用右子树替换当前节点
                node = node.right;
            } else if (node.right == null) {
                // 只有左子树,用左子树替换当前节点
                node = node.left;
            } else {
                // 左右子树都存在,找到右子树中的最小节点
                AVLNode minNode = findMinNode(node.right);
                node.key = minNode.key;
                node.right = deleteNode(node.right, minNode.key);
            }
        }
​
        if (node == null)
            return null;
​
        updateHeight(node);
​
        int balanceFactor = getBalanceFactor(node);
​
        // 左左情况,执行右旋
        if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
            return rightRotate(node);
​
        // 左右情况,先对左子树左旋,再对当前节点右旋
        if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }
​
        // 右右情况,执行左旋
        if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
            return leftRotate(node);
​
        // 右左情况,先对右子树右旋,再对当前节点左旋
        if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }
​
        return node;
    }
    private AVLNode findMinNode(AVLNode node) {
        AVLNode current = node;
        while (current.left != null) {
            current = current.left;
        }
        return current;
    }
​

伪代码(注意判断旋转类型的内判断条件有误eg,LR:外getHeight(node.left>getHeight(node.right)

getHeight(node.left.left<getHeight(node.left.right))
            

1e8ab1ba81fc6941355837a374e174a3.png

树高上界

为使结点数尽可能少,需要层数尽可能多,所以左右子树高度差都会构造为1,所以左右子树的高度一个为d-1,一个为d-2,由此可以构造递归算法。

93ce3748661813ec552670cc94c6ddd2.png

f5282badb88eb4d772dc105db997d5b9.png

平衡树应用:

AVL、红黑树、B(+)树、

习题

68bb1562c475aaaa6277188d700a1baf.png

ba39632877f49106517de31b8a127d36.png

 

四、散列查找

散列函数:一般情况下,需在关键字与记录在表中的存储位置之间建立 一个函数关系,以 H(key) 作为关键字为 key 的记录在表中 的位置,通常称这个函数 H(key) 为散列函数。
散列表:根据设定的 散列函数 H(key) 和提供的 处理冲突的方法,将一组关键字 映象到一个地址连续的地址空间上,并以关键字在地址空间中的 “象”作为相应记录在表中的 存储位置,如此构造所得的查找表称之为 散列表

1.构造散列函数

1.直接散列函数
        取关键字本身或关键字的某个线性函数值作为散列地址
 
2.数字分析法
        设n个d位数的关键字,由r个不同的符号组成,此r个符号在关键字各位出现的频率不一定相同:
        ➢ 在某些位上均匀分布,即每个符号出现的次数都接近于n/r次;
        ➢ 在另一些位上分布不均匀。
        则 选择其中分布均匀的s位作为散列地址,即 H(key) =“key中数字均匀分布的s位
3.平方取中法
        关键字平方后的中间几位作为散列地址。
        求“关键字 的平方值” 的目的是“ 扩大差别”和“ 贡献均衡”。关键字的各位都在平方值的中间几位有所贡献
4.折叠法
        关键字位数较长时,可将关键字 分割成位数相等的几部分(最后一部分位数可以不同)
        这几部分的叠加和(舍去高位的进位)作为散列地址。位数由存储地址的位数确定
        ➢ 移位叠加法,即将每部分的最后一位对齐,然后相加;
        ➢ 边界叠加法,即把关键字看作一纸条,从一端向另一端沿边界逐次折叠,然后 对齐相加。(s形排位)
5.除留余数法
        取关键字被某个不大于散列表长度m的数p除后的余数作为散列地址
         H(key)=key MOD p (p≤m)        • 一般取小于表长的最大质数
6.随机数法
        选择一个随机函数,取关键字的随机函数值作为散列地址

2.冲突处理

开放地址法
再散列法
链地址法
公共溢出区法

(1)开放地址

e9fe31b9910abf8be31e4b99ff1ea85f.png

对增量 d i 有三种取法:
1) 线性探测再散列 (linear probing)
d i = c x   i
一般情况: c=1
2) 平方探测再散列(二次探测再散列)
di = 1^2 , -1^2 , 2^2 , -2^2 , …, 或者 di=1^2 , 2^2 , 3^2 , …
3) 随机探测再散列
d i 是一组伪随机数列
bcdcd2822275641d43818f05e7b21ab2.png

 

(2)再散列法

860d8cf1a67e1421afef7d78355a3c06.png

(3)链地址法

将所有散列地址相同的记录都链接在同一链表中。

6bb0c0b538c2f4fb6e2c79a52bdb9cde.png

afbde6e79e47019bf52664d4aa671021.png

 成功的分母是插入的地址的总个数,失败的分母是散列空间的大小 ASL失败=16/7

(4)公共溢出区

f5f93dd1771ee00a3227b2f989ce1953.png

3.散列表查找

1)给定K值,根据构造表时所用的散列函数求散列地址 j
2)若此位置无记录, 则查找不成功
3)如果有记录,比较关键字
4)如果和给定的关键字相等则成功
5)否则根据构造表时设定的冲突处理的方法计算“下一地址”, 重复2)
可能需要查重,避免在表装满或重复比较同一个关键字!
如果散列表始终留有空位,可以不用查重(线性探测?)
29bd879172aa11b6e247d4f183815ebc.png
 
 
fdd78f605ce6bc36759b6615a7fd4afb.png
//11种结果等可能,需要移动的次数为10到2和两个1 ASL失败=56/11
e4518a97a8eeee41b43c8eaf38063aba.png

 

7bbb817f51e1fb3350fd4461d564c0d0.png

习题

请回答采用线性探测再散列和链地址法处理冲突构建的哈希表中,查找失败时的平均查找长度如何计算?

例:已知一组关键字(19,14,23,1,68,20,84,27,55,11,10,79)

       哈希函数为:H(key)=key MOD 13, 哈希表长为m=15,

       设每个记录的查找概率相等,采用以上两种方法处理冲突,查找失败时的平均查找长度各是多少
b8d03d44b4ca298db91a2068b3342af2.png

ps:查找失败得平均长度分母是MOD后面得数也就是13

3bb3db4460565b161613efd16bd1eee3.png


总结

第十三周

1.折半查找法的查找速度一定比顺序查找法快。❌

2.关于二分查找算法二分查找算法能适用于有序的链表。❌ 链表不好找中间 顺序表✔

3.衡量查找算法效率的主要标准是( )平均查找长度

4.已知序列20,26,41,57,60,78,81,98,108,121,129,则用折半查找法查找81需要进行( )次比较.

依次比较78 108 81 共三次

第十四周

1.二叉排序树的查找效率和二叉排序树的髙度有关。✔

2.For a binary search tree, if its pre-order travel sequence is { 56, 28, 12, 35, 77, 64, 68, 72 }, then 68 is the parent of 72.

二叉查找树的中序遍历结果为升序序列 12,28,35,56,64,68,72,77,用中序划分左右子树,用前序找根。根56 划分左右。68和72在右子树。右子树中77为根。77的左子树中64为根。64的右子树中,68为根,72为其右孩子。

3.For a binary search tree, if its post-order travel sequence is { 30, 35, 45, 28, 64, 77, 68, 56 }, then 30 is the parent of 28.

中序 28,30,35,45,56,64,68,77

后序根为56,左子树的根28,28的右孩子中45为根,45的左孩子中35为根,35的左孩子中30为根

4.任何二叉搜索树中同一层的结点从左到右是有序的(从大到小)❌

有序,但是从小到大

5.平衡二叉树

Insert { 9, 8, 7, 2, 3, 5, 6, 4 } one by one into an initially empty AVL tree. How many of the following statements is/are FALSE?

  • the total number of rotations made is 4 (Note: double rotation counts 2 and single rotation counts 1)❌
  • the expectation (round to 0.01) of access time is 2.75✔(平均比较次数)
  • there are 1 nodes with a balance factor of -1❌

a56520eaf83b5145804ba33882cc674c.png

3.If there are 14 nodes in an AVL tree, then the maximum depth of the tree is __5__. The depth of an empty tree is defined to be 0.

考虑结点数到最大高度比较难,可考虑高度所需要的最少节点数

根据树高上界的应用:N(d)=N(d-1)+N(d-2)+1 N(1)=1,N(2)=2. N(5)=12 N(6)=20 所以14个结点最多能够构造五层

4.平衡树的平均查找时间 O(logn)

第十五周题目

1.当采用线性探测冲突解决策略时,非空且有空闲空间的散列表中无论有多少元素,不成功情况下的期望查找次数总是大于成功情况下的期望查找次数✔

2.假设在构建散列表时,采用线性探测解决冲突。若连续插入的n个关键字都是同义
词,则查找其中最后插入的关键字时,所需进行的比较次数为( N)

需要n-1次错误查找加最后一次正确插入

3.现有长度为 5、初始为空的散列表 HT,散列函数 H(k)=(k+4)%5,用线性探查再散列法解决冲突。若将关键字序列 2022,12,25 依次插入 HT 中,然后删除关键字 25,则 HT 中查找失败的平均查找长度为:7635f7774e36f3e554831663886f873b.png

 

题目

1.有一个有序表为{139123241456275778295100},当折半查找值为82的结点时,  C    次比较后查找成功。 45->77->95->82

    A.  11           B  5             C   4           D    8

2.!!!在各种查找方法中,平均查找长度与结点个数n无关的查找方法是  散列查找法

3.390a00cd395b31e0c4f9ecc3bf53d76b.png

4.下面关于哈希查找的说法,不正确的是(A) 。\nA .采用链地址法处理冲突时,查找一个元素的时间是相同的\nB .采用链地址法处理冲突时,若插入规定总是在链首,则插入任一个元素的时间是相同\n的\nC.用链地址法处理冲突,不会引起二次聚集现象\nD .用链地址法处理冲突,适合表长不确定的情况

5.设哈希表长为14,哈希函数是H(key)=key%11 ,表中已有数据的关键字为15,38,61, 84 共四个,现要将关键字为49 的元素加到表中,用二次探测法解决冲突,则放入的位置是(D) 。\nA .3 B . 5 C.8D. 9  

012345678910
    15386184   

            49->(5)->5+1->5-1->5+4=9√

6.注意如何计算avl失败 

78c6424abc0a43528ce13ba8bd25c058.png

7.二分查找表必须有序,且表只能以顺序方式存储

8.如果一个线性表既能较快的查找,又能适应动态变化的要求,则可采用(    )查找法。

在索引顺序表中,实现分块查找,在等概率查找情况下,其平均查找长度不仅与表中元素个数有关,而且与每块中元素个数有关。

c90c22072766c40cd029703c2e8bf7b9.png

题25

分块检索中,若索引表和各块内均用顺序查找,则有900个元素的线性表分成(___30___   )块最好:若分成25块,其平均查找长度为(__(25+30)/2 + 1____   )。

c7bb4b89d0c1d07789a90cba6d0177a7.png

bbe0de575def62b0f875b618c23ac2f2.png

9.对大小均为n的有序表和无序表分别进行顺序查找,在等概率查找的情况下,查找成功时的平均查找长度是相同的,而对于查找失败,它们的平均查找长度是不同的。(   ✔ )

顺序查找有序表时可以利用有序这个信息,在查找失败时提前终止查找。而无序表每次查找失败时,都必须要从第一个元素比较至最后一个元素。(顺序查找落空时可以提前停止)

10.顺序查找n个元素的顺序表,若查找成功,则比较关键字的次数最多为(____n__   )次;当使用监视哨时,若查找失败,则比较关键字的次数为(____n+1__   )。

监视哨查找失败会到达数组a[0]处,与设定的数值重合而停止遍历。eeb68b2820512fb993e3549ca522bc94.png

11.

关于杂凑查找说法不正确的有几个(    )                                           

(1)采用链地址法解决冲突时,查找一个元素的时间是相同的 ❌  (2)采用链地址法解决冲突时,若插入规定总是在链首,则插入任一个元素的时间是相同的  ✔ (3)用链地址法解决冲突易引起聚集现象      ❌(线性探测容易聚集)(4)再哈希法不易产生聚集✔
12.

Hash表的平均查找长度不随表中结点数目的增加而增加,而与装填因子有关。(    )负载因子 (装填因子)是散列表的一个重要参数,它反映散列表的装满程度。( )

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值