原文地址:https://www.cnblogs.com/xuzhp/p/4638937.html

基本查找算法 

 

一、查找的基本概念

查找,也可称检索,是在大量的数据元素中找到某个特定的数据元素而进行的工作。查找是一种操作。

二、顺序查找

针对无序序列的一种最简单的查找方式。

时间复杂度为O(n)。

三、折半查找

针对已排序序列的一种查找方式。并且只适用于顺序存储结构的序列。要求序列中的元素基本不变,在需要做删除和插入操作的时候,会影响检索效率。

时间复杂度为O(logN)。

四、B树

B树又称二叉排序树(Binary Sort Tree)。

1、概念:

   它或者是一棵空树;或者是具有下列性质的二叉树:

  (1)若左子树不空,则左子树上所有结点的值均小于左子树所在树的根结点的值;

  (2)若右子树不空,则右子树上所有结点的值均大于右子树所在树的根结点的值;

  (3)左、右子树也分别为二叉排序树;

2、B树的查找:

时间复杂度与树的深度的有关。

  步骤:若根结点的关键字值等于查找的关键字,成功。

  否则:若小于根结点的关键字值,递归查左子树。

  若大于根结点的关键字值,递归查右子树。

  若子树为空,查找不成功。

3、B树的插入:

首先执行查找算法,找出被插结点的父亲结点。

  判断被插结点是其父亲结点的左儿子还是右儿子。将被插结点作为叶子结点插入。

  若二叉树为空。则首先单独生成根结点。

  注意:新插入的结点总是叶子结点,所以算法复杂度是O(h)。

4、B树的删除:

  如果删除的结点没有孩子,则删除后算法结束;

  如果删除的结点只有一个孩子,则删除后该孩子取代被删除结点的位置;

  如果删除的结点有两个孩子,则选择该结点的后继结点(该结点右孩子为根的树中的左子树中的值最小的点)作为新的根,同时在该后继结点开始,执行前两种删除算法,删除算法结束。

5、B+树

一棵m阶的B+树满足下列条件:

(1)每个结点最多m个孩子。

(2)除根结点和叶子结点外,其它每个结点至少有ém/2ù个孩子。

(3)根结点至少有两个孩子。

(4)所有的叶子结点在同一层,且包含了所有关键字信息。

(5)有k个孩子的分支结点包含k个关键字。

例如:

五、散列(hash)表

关键字:哈希函数、装填因子、冲突、同义词;

关键字和和存储的地址建立一个对应的关系:

Add = Hash(key);

解决冲突方法:

开放定址法 – 探测方式:线性探测、二次探测。

分离链接法 – 利用链表的方式。

查找找效率不依赖于数据长度n,查找效率非常快,很多能达到O(1),查找的效率是a(装填因子)的函数,而不是n的函数。因此不管n多大都可以找到一个合适的装填因子以便将平均查找长度限定在一个范围内。

一 定义

二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

1. 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

3. 任意节点的左、右子树也分别为二叉查找树。

4. 没有键值相等的节点(no duplicate nodes)。

如下图,这个是普通的二叉树:

在此基础上,加上节点之间的大小关系,就是二叉查找树:

二 实现

在实现中,我们需要定义一个内部类Node,它包含两个分别指向左右节点的Node,一个用于排序的Key,以及该节点包含的值Value,还有一个记录该节点及所有子节点个数的值Number。

public class BinarySearchTreeSymbolTable<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TValue>
{private Node root;private class Node{public Node Left { get; set; }public Node Right { get; set; }public int Number { get; set; }public TKey Key { get; set; }public TValue Value { get; set; }
    public Node(TKey key, TValue value, int number){this.Key = key;this.Value = value;this.Number = number;}
}


}

查找

查找操作和二分查找类似,将key和节点的key比较,如果小于,那么就在Left Node节点查找,如果大于,则在Right Node节点查找,如果相等,直接返回Value。

该方法实现有迭代和递归两种。

递归的方式实现如下:

public override TValue Get(TKey key)
{TValue result = default(TValue);Node node = root;while (node != null){
    if (key.CompareTo(node.Key) &gt; 0){node = node.Right;}else if (key.CompareTo(node.Key) &lt; 0){node = node.Left;}else{result = node.Value;break;}
}
return result;

}

迭代的如下:

public TValue Get(TKey key)
{return GetValue(root, key);
}

private TValue GetValue(Node root, TKey key)
{
if (root == null) return default(TValue);
int cmp = key.CompareTo(root.Key);
if (cmp > 0) return GetValue(root.Right, key);
else if (cmp < 0) return GetValue(root.Left, key);
else return root.Value;
}

插入

插入和查找类似,首先查找有没有和key相同的,如果有,更新;如果没有找到,那么创建新的节点。并更新每个节点的Number值,代码实现如下:

public override void Put(TKey key, TValue value)
{root = Put(root, key, value);
}

private Node Put(Node x, TKey key, TValue value)
{
//如果节点为空,则创建新的节点,并返回
//否则比较根据大小判断是左节点还是右节点,然后继续查找左子树还是右子树
//同时更新节点的Number的值
if (x == null) return new Node(key, value, 1);
int cmp = key.CompareTo(x.Key);
if (cmp < 0) x.Left = Put(x.Left, key, value);
else if (cmp > 0) x.Right = Put(x.Right, key, value);
else x.Value = value;
x.Number = Size(x.Left) + Size(x.Right) + 1;
return x;
}

private int Size(Node node)
{
if (node == null) return 0;
else return node.Number;
}

插入操作图示如下:

下面是插入动画效果:

随机插入形成树的动画如下,可以看到,插入的时候树还是能够保持近似平衡状态:

最大最小值

如下图可以看出,二叉查找树的最大最小值是有规律的:

从图中可以看出,二叉查找树中,最左和最右节点即为最小值和最大值,所以我们只需迭代调用即可。

public override TKey GetMax()
{TKey maxItem = default(TKey);Node s = root;while (s.Right != null){s = s.Right;}maxItem = s.Key;return maxItem;
}

public override TKey GetMin()
{
TKey minItem = default(TKey);
Node s = root;
while (s.Left != null)
{
s = s.Left;
}
minItem = s.Key;
return minItem;
}

以下是递归的版本:

public TKey GetMaxRecursive()
{return GetMaxRecursive(root);
}

private TKey GetMaxRecursive(Node root)
{
if (root.Right == null) return root.Key;
return GetMaxRecursive(root.Right);
}

public TKey GetMinRecursive()
{
return GetMinRecursive(root);
}

private TKey GetMinRecursive(Node root)
{
if (root.Left == null) return root.Key;
return GetMinRecursive(root.Left);
}

Floor和Ceiling

查找Floor(key)的值就是所有<=key的最大值,相反查找Ceiling的值就是所有>=key的最小值,下图是Floor函数的查找示意图:

以查找Floor为例,我们首先将key和root元素比较,如果key比root的key小,则floor值一定在左子树上;如果比root的key大,则有可能在右子树上,当且仅当其右子树有一个节点的key值要小于等于该key;如果和root的key相等,则floor值就是key。根据以上分析,Floor方法的代码如下,Ceiling方法的代码类似,只需要把符号换一下即可:

public TKey Floor(TKey key)
{Node x = Floor(root, key);if (x != null) return x.Key;else return default(TKey);
}

private Node Floor(Node x, TKey key)
{
if (x == null) return null;
int cmp = key.CompareTo(x.Key);
if (cmp == 0) return x;
if (cmp < 0) return Floor(x.Left, key);
else
{
Node right = Floor(x.Right, key);
if (right == null) return x;
else return right;
}
}

删除

删除元素操作在二叉树的操作中应该是比较复杂的。首先来看下比较简单的删除最大最小值得方法。

以删除最小值为例,我们首先找到最小值,及最左边左子树为空的节点,然后返回其右子树作为新的左子树。操作示意图如下:

代码实现如下:

public void DelMin()
{root = DelMin(root);
}

private Node DelMin(Node root)
{
if (root.Left == null) return root.Right;
root.Left = DelMin(root.Left);
root.Number = Size(root.Left) + Size(root.Right) + 1;
return root;
}

删除最大值也是类似。

现在来分析一般情况,假定我们要删除指定key的某一个节点。这个问题的难点在于:删除最大最小值的操作,删除的节点只有1个子节点或者没有子节点,这样比较简单。但是如果删除任意节点,就有可能出现删除的节点有0个,1 个,2个子节点的情况,现在来逐一分析。

当删除的节点没有子节点时,直接将该父节点指向该节点的link设置为null。

当删除的节点只有1个子节点时,将该自己点替换为要删除的节点即可。

当删除的节点有2个子节点时,问题就变复杂了。

假设我们删除的节点t具有两个子节点。因为t具有右子节点,所以我们需要找到其右子节点中的最小节点,替换t节点的位置。这里有四个步骤:

1. 保存带删除的节点到临时变量t

2. 将t的右节点的最小节点min(t.right)保存到临时节点x

3. 将x的右节点设置为deleteMin(t.right),该右节点是删除后,所有比x.key最大的节点。

4. 将x的做节点设置为t的左节点。

整个过程如下图:

对应代码如下:

public void Delete(TKey key)
{root =Delete(root, key);

}

private Node Delete(Node x, TKey key)
{
int cmp = key.CompareTo(x.Key);
if (cmp > 0) x.Right = Delete(x.Right, key);
else if (cmp < 0) x.Left = Delete(x.Left, key);
else
{
if (x.Left == null) return x.Right;
else if (x.Right == null) return x.Left;
else
{
Node t = x;
x = GetMinNode(t.Right);
x.Right = DelMin(t.Right);
x.Left = t.Left;
}
}
x.Number = Size(x.Left) + Size(x.Right) + 1;
return x;
}

private Node GetMinNode(Node x)
{
if (x.Left == null) return x;
else return GetMinNode(x.Left);
}

以上二叉查找树的删除节点的算法不是完美的,因为随着删除的进行,二叉树会变得不太平衡,下面是动画演示。

三 分析

二叉查找树的运行时间和树的形状有关,树的形状又和插入元素的顺序有关。在最好的情况下,节点完全平衡,从根节点到最底层叶子节点只有lgN个节点。在最差的情况下,根节点到最底层叶子节点会有N各节点。在一般情况下,树的形状和最好的情况接近。

在分析二叉查找树的时候,我们通常会假设插入的元素顺序是随机的。对BST的分析类似与快速排序中的查找:

BST中位于顶部的元素就是快速排序中的第一个划分的元素,该元素左边的元素全部小于该元素,右边的元素均大于该元素。

对于N个不同元素,随机插入的二叉查找树来说,其平均查找/插入的时间复杂度大约为2lnN,这个和快速排序的分析一样,具体的证明方法不再赘述,参照快速排序。

四 总结

有了前篇文章 二分查找的分析,对二叉查找树的理解应该比较容易。下面是二叉查找树的时间复杂度:

它和二分查找一样,插入和查找的时间复杂度均为lgN,但是在最坏的情况下仍然会有N的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是后面要讲的平衡查找树的内容了。

分类: 数据结构
标签: 数据结构, 查找算法, 二叉树 上一篇:数据结构中的基本排序算法总结
» 下一篇:[软件架构]模块化编程思想及(C++)实践

数据结构中基本查找算法总结相关推荐

  1. c语言折半查找递归程序,C语言数据结构中二分查找递归非递归实现并分析

    C语言数据结构中二分查找递归非递归实现并分析 前言: 二分查找在有序数列的查找过程中算法复杂度低,并且效率很高.因此较为受我们追捧.其实二分查找算法,是一个很经典的算法.但是呢,又容易写错.因为总是考 ...

  2. 数据结构中各种排序算法比较

    数据结构中各种排序算法比较 1 快速排序(QuickSort)    快速排序是一个就地排序,分而治之,大规模递归的算法.从本质上来说,它是归并排序的就地版本.快速排序可以由下面四步组成. (1) 如 ...

  3. 数据结构中各种排序算法的稳定性比较

    1.简单选择排序 2.堆排序        (1和2是属于选择排序) 3.直接插入排序 4.希尔排序     (3和4属于插入排序,有时把改进后的直接插入排序叫做二分插入) 5.冒泡排序        ...

  4. 考研数据结构中的经典算法(总归纳)

    一.线性表 1.逆转顺序表中的所有元素 算法思想:第一个元素和最后一个元素对调,第二个元素和倒数第二个元素对调,--,依此类推. void Reverse(int A[], int n) { int ...

  5. JavaScript 实现数据结构中的所有算法---通俗易懂

    1.我以为JavaScript 很垃圾很简单,没有想到,它能实现C 语言和C++ 实现的数据结构和算法.一句话总结:大道至简.python 也是这样. 2.我主要是看了这个博主,很厉害的前端大牛. h ...

  6. php实现数据排序算法,PHP实现数据结构中的排序算法_PHP教程

    直接插入排序为止. [代码实现] 实现:与增量间隔的数比较,直到把大的数放到最后 1) { $d=intval($d / 2);//分组间隔,2为n值,n值减少时,移动的趟数和数据增多 $temp=N ...

  7. 邓俊辉数据结构学习心得系列——数据结构中所研究的算法

    写在前面的话: 本文只是个人学习邓俊辉老师C++数据结构的整理,包含了很多个人的见解(从内容到材料的组织形式).所整理的内容不保证逻辑性和完整性,仅供参考. 算法的基本性质: 有正确的输入 有正确的输 ...

  8. <<数据结构中最全的8种排序算法总结>>

    数据结构中最全的8种排序算法总结 1.插入排序 代码如下: 2.希尔排序 代码如下: 3.选择排序 代码如下: 4.堆排序 代码如下: 5.冒泡排序 代码如下: 6.快速排序 代码如下: 7.归并排序 ...

  9. 二分查找算法详解(经典二分和左右边界查找)

    目录 二分查找算法 1. 二分查找算法框架 2. 经典二分查找算法 问题1. 为什么while循环中使用<=号而不是用<号,右边区间right为什么要对数组大小减一? 问题2. 为什么 l ...

最新文章

  1. C#代码生成工具:文本模板初体验 使用T4批量修改实体框架(Entity Framework)的类名...
  2. MVC中一个表单实现多个提交按钮(一个action搞定添删改)
  3. android+mvp+登录案例,android mvp实现登录
  4. FFMPEG结构体分析:AVCodecContext(转)
  5. Tomcat启动超时问题Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds
  6. python 生成器_提高你的Python: 解释‘yield’和‘Generators(生成器)’
  7. sudoers 用户权限配置_使用sudo让普通用户获取root用户的权限
  8. 什么是BSP工程师?
  9. 02 unix文件系统和命令
  10. Docker下安装GitLab
  11. 使用kermit通过串口升级uboot
  12. 【14年浙江省赛 ZOJ 3780】Paint the Grid Again【图转换】
  13. java俄罗斯方块代码_[转载]java编写的一个俄罗斯方块(源代码)
  14. 异常:贴dependency报错
  15. php冒泡排序图解,PHP冒泡排序(Bubble Sort)代码实现图解
  16. JavaScript getMonth() 函数用法
  17. IDEA设置按键提示 Ctrl+p
  18. HTML+CSS实战(哈罗单车首页)
  19. ArcGIS学习教程(一)
  20. IC/FPGA一文练完

热门文章

  1. Linux文件系统十问
  2. 嵌入式linux文件系统
  3. php无法创建cookie,php-curl cookie无法成功创建
  4. python模块的定义_Python基础编程 模块的引入与定义
  5. python子进程修改父进程内变量_如何将父变量传递给python中的子进程?
  6. 读Java并发编程实践记录_原子性_锁_同步容器详解_任务执行
  7. 解决loaded more than 1 DLL from .libs和No metadata found in lib\site-packages两个错误
  8. python3列表生成式中的for循环与普通放在外面的for循环细微差异
  9. 二、搭建Apache服务器 模板引擎
  10. LeetCode 2116. 判断一个括号字符串是否有效(栈)