热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

数据结构与算法C#版笔记查找(Search)

做数据库开发的程序员,可能每天都会处理各种各样的查询sql,这个就是查找(Search)。通过查询记录主键字段(即主关键码)或其它非唯一字段(即次关键码

做数据库开发的程序员,可能每天都会处理各种各样的查询sql,这个就是查找(Search)。通过查询记录主键字段(即主关键码)或其它非唯一字段(即次关键码)找到所需要的记录。

如果在查找的过程中,不改变原始数据(的数据结构),则这种查找称为静态查找(Static Search);如果找不到,需要向数据库里插入记录(或者找到了,需要从数据库里删除),这种在查找过程中需要动态调整原始数据(的数据结构),这种查找称为动态查找(Dynamic Search).

被查找的数据结构(比如数据库中的某张表)称为查找表,用于静态查找的称为静态查找表,反之则称为动态查找表。

一、静态查找

因为静态查找中不需要删除或新增记录,所以用顺序表比较适合。

1.1 顺序查找(Sequnce Search)

因为查找表为线性结构,所以也被称为线性查找(Linear Search),其思路很简单:从顺序表的一端向另一端逐个扫描,找到要的记录就返回其位置,找不到则返回失败信息(通常为-1)。

///

/// 顺序查找/// /// 要查找的顺序表(比如数组)/// 要查找的值/// 找到返回元素的下标&#43;1&#xff0c;否则返回-1static int SeqSearch(int[] arr, int key){int i &#61; -1;if (arr.Length <&#61; 1) { return i; }arr[0] &#61; key;//第一个元素约定用来存放要查找的值&#xff0c;这个位置也称为“监视哨”&#xff0c;当然这并不是必须的&#xff0c;只是为了遵守原书的约定而已(以下同)bool flag &#61; false;for (i &#61; 1; i

这种全表扫描的方法&#xff0c;虽然很容易理解&#xff0c;但是效率是很低的&#xff0c;特别是表中记录数很多时。如果查找表的记录本身是有序的&#xff0c;则可以用下面的办法改进效率

1.2 二分查找(Binary Search)

思路&#xff1a;因为查找表本身是有序的(比如从小到大排列)&#xff0c;所以不必傻傻的遍历每个元素&#xff0c;可以取中间的元素与要查找的值比较&#xff0c;比如查找值大于中间元素&#xff0c;则要查找的元素肯定在后半段&#xff1b;反之如果查找值小于中间元素&#xff0c;则要查找的元素在前半段&#xff1b;然后继续二分&#xff0c;如此反复处理&#xff0c;直到找到要找的元素。

///

/// 二分查找(适用于有序表)/// /// 要查找的有序表/// 要查找的值/// 找到则返回元素的下标&#43;1&#xff0c;否则返回-1static int BinarySearch(int[] arr, int key) {arr[0] &#61; key;//同样约定第一个位置存放要查找的元素值(仅仅只是约定而已)int mid &#61; 0;int flag &#61; -1;int low &#61; 1;int high &#61; arr.Length - 1;while (low<&#61;high){//取中点mid &#61; (low &#43; high) / 2;//查找成功&#xff0c;记录位置存放到flag中if (key &#61;&#61; arr[mid]) {flag &#61; mid;break;}else if (key 0){ return flag;//找到了}else {return -1;//没找到}}

二分查找性能虽然提高了不少&#xff0c;但是它要求查找表本身是有序的&#xff0c;这个条件太苛刻了&#xff0c;多数情况下不容易满足&#xff0c;那么如何将上面的二种方法结合在一起&#xff0c;又保证效率呢&#xff1f;

1.3 索引查找(Index Search)

思路&#xff1a;可以在查找表中选取一些关键记录&#xff0c;创建一个小型的有序表&#xff08;该表中的每个元素除了记录自身值外&#xff0c;还记录了对应主查找表中的位置&#xff09;&#xff0c;即索引表。查找时&#xff0c;先到索引表中通过索引记录大致判断要查找的记录在主表的哪个区域&#xff0c;然后定位到主表的相应区域中&#xff0c;仅搜索这一个区块即可。

因为索引表本身是有序的&#xff0c;所以查找索引表时&#xff0c;可先用前面提到的二分查找判断大致位置&#xff0c;然后定位到主表中&#xff0c;用顺序查找。

比如&#xff1a;要查找值为78的记录&#xff0c;先到索引表中二分查找&#xff0c;能知道该记录&#xff0c;应该在主表索引13至18 之间&#xff08;即第4段&#xff09;&#xff0c;然后定位到主表中的第4段顺序查找&#xff0c;如果找不到&#xff0c;则返回-1&#xff0c;反之则返回下标。

所以该方法的关键在于索引的建立&#xff01;以上图为例&#xff0c;在主表中挑选关键值创建索引时&#xff0c;要求该关键值以前的记录都比它小&#xff0c;这样创建的索引表才有意义。

其实该思路在很多产品中都有应用&#xff0c;比如数据库的索引以及Lucene.Net都可以看作索引查找的实际应用。

顺便提一下&#xff1a;如果查找主表记录超级多&#xff0c;达到海量的级别&#xff0c;最终创建的索引表记录仍然很多&#xff0c;这样二分法查找还是比较慢&#xff0c;这时可以在索引表的基础上再创建一个索引的索引&#xff0c;称之为二级索引&#xff0c;如果二级索引仍然记录太多&#xff0c;可以再创建三级索引

 

二、动态查找

动态查找中因为会经常要插入或删除元素&#xff0c;如果用数组来顺序存储&#xff0c;会导致大量的元素频繁移动&#xff0c;所以出于性能考虑&#xff0c;这次我们采用链式存储&#xff0c;并介绍一种新的树&#xff1a;二叉排序树(Binary Sort Tree)

上图就是一颗“二叉排序树 ”&#xff0c;其基本特征是&#xff1a;

1、不管是哪个节点&#xff0c;要么没有分支(即无子树)

2、如果有左分支&#xff0c;则左子树中的所有节点&#xff0c;其值都比它自身的值小

3、如果有右分支&#xff0c;则右子树中的所有节点&#xff0c;其值都比它自身的值大

2.1、二叉排序树的查找

思路&#xff1a;从根节点开始遍历&#xff0c;如果正好该根节点就是要找的值&#xff0c;则返回true&#xff0c;如果要查找的值比根节点大&#xff0c;则调整到右子树查找&#xff0c;反之调整到左子树。

///

/// 二叉排序树查找/// /// /// /// static bool BiSortTreeSearch(BiTree bTree, int key) {Node p;//如果树为空&#xff0c;则直接返回-1if (bTree.IsEmpty()) {return false;}p &#61; bTree.Root;while (p !&#61; null) {//如果根节点就是要找的if (p.Data &#61;&#61; key){return true;}else if (key > p.Data){//调整到右子树p &#61; p.RChild;}else {//调整到左子树p &#61; p.LChild;}}return false;}

注&#xff1a;上面的代码中&#xff0c;用到了BiTree这个类&#xff0c;在数据结构C#版笔记--树与二叉树 中可找到&#xff0c;为了验证该代码是否有效&#xff0c;可用下列代码测试一下&#xff1a;

//先创建树BiTree tree &#61; new BiTree(100);Node root &#61; tree.Root;Node p70 &#61; new Node(70);Node p150 &#61; new Node(150);root.LChild &#61; p70;root.RChild &#61; p150;Node p40 &#61; new Node(40);Node p80 &#61; new Node(80);p70.LChild &#61; p40;p70.RChild &#61; p80;Node p20 &#61; new Node(20);Node p45 &#61; new Node(45);p40.LChild &#61; p20;p40.RChild &#61; p45;Node p75 &#61; new Node(75);Node p90 &#61; new Node(90);p80.LChild &#61; p75;p80.RChild &#61; p90;Node p112 &#61; new Node(112);Node p180 &#61; new Node(180);p150.LChild &#61; p112;p150.RChild &#61; p180;Node p120 &#61; new Node(120);p112.RChild &#61; p120;Node p170 &#61; new Node(170);Node p200 &#61; new Node(200);p180.LChild &#61; p170;p180.RChild &#61; p200;//测试查找Console.WriteLine(BiSortTreeSearch(tree, 170));

2.2、二叉排序树的插入

逻辑&#xff1a;先在树中查找指定的值&#xff0c;如果找到&#xff0c;则不插入&#xff0c;如果找不到&#xff0c;则把要查找的值插入到最后一个节点下做为子节点&#xff08;即&#xff1a;先查找&#xff0c;再插入&#xff09;

///

/// 二插排序树的插入(即&#xff1a;先查找&#xff0c;如果找不到&#xff0c;则插入要查找的值)/// /// /// /// static bool BiSortTreeInsert(BiTree bTree, int key){Node p &#61; bTree.Root;Node last &#61; null;//用来保存查找过程中的最后一个节点 while (p !&#61; null){if (p.Data &#61;&#61; key){return true;}last &#61; p;if (key > p.Data){p &#61; p.RChild;}else{p &#61; p.LChild;}}//如果找了一圈&#xff0c;都找不到要找的节点&#xff0c;则将目标节点插入到最后一个节点下面p &#61; new Node(key);if (last &#61;&#61; null){bTree.Root &#61; p;}else if (p.Data

2.3 二叉排序树的创建

从刚才插入的过程来看&#xff0c;每个要查找的值&#xff0c;动态查找一次以后&#xff0c;就会被附加到树的最后&#xff0c;所以&#xff1a;"给定一串数字&#xff0c;将它们创建一棵二叉排序树"的思路就有了&#xff0c;依次把这些数字动态查找一遍即可。

///

/// 创建一颗二插排序树/// /// /// /// static void CreateBiSortTree(BiTree tree, int[] arr) {for (int i &#61; 0; i

2.4 二叉排序树的节点删除

这也是动态查询的一种情况&#xff0c;找到需要的节点后&#xff0c;如果存在&#xff0c;则删除该节点。可以分为几下四种情况&#xff1a;

a.待删除的节点&#xff0c;本身就是叶节点

这种情况下最简单&#xff0c;只要把这个节点删除掉&#xff0c;然后父节点的LChild或RChild设置为null即可

 

b.待删除的节点&#xff0c;只有左子树

思路&#xff1a;将本节点的左子树上移&#xff0c;挂到父节点下的LChild&#xff0c;然后删除自身即可

 

c.待删除的节点&#xff0c;只有右子树

思路&#xff1a;将自身节点的子树挂到父节点的子树&#xff0c;然后删除自身即可

 

d.待删除的节点&#xff0c;左、右子树都有

思路&#xff1a;这个要复杂一些&#xff0c;先找出自身节点子树中的分支的最后一个节点&#xff08;最小左节点&#xff09;&#xff0c;然后将它跟自身对调&#xff0c;同时将“最小左节点”下的分支上移。

 

以上逻辑综合起来&#xff0c;就得到了下面的方法:

///

/// 删除二叉排序树的节点/// /// /// /// static bool DeleteBiSort(BiTree tree, int key) {//二叉排序树为空if (tree.IsEmpty()) {return false;}Node p&#61;tree.Root;Node parent &#61; p;while (p!&#61;null){if (p.Data &#61;&#61; key){ if (tree.IsLeaf(p))//如果待删除的节点为叶节点{#regionif (p &#61;&#61; tree.Root) {tree.Root &#61; null;}else if (p &#61;&#61; parent.LChild){parent.LChild &#61; null;}else {parent.RChild &#61; null;}#endregion} else if ((p.RChild &#61;&#61; null) && (p.LChild !&#61; null)) //仅有左分支 {#regionif (p &#61;&#61; parent.LChild){parent.LChild &#61; p.LChild;}else {parent.RChild &#61; p.LChild;}#endregion}else if ((p.LChild &#61;&#61; null) && (p.RChild !&#61; null)) //仅有右分支{#regionif (p &#61;&#61; parent.LChild){parent.LChild &#61; p.RChild;}else{parent.RChild &#61; p.RChild;}#endregion}else //左&#xff0c;右分支都有{//原理&#xff1a;先找到本节点右子树中的最小节点(即右子树的最后一个左子节点)#regionNode q &#61; p;Node s &#61; p.RChild; while (s.LChild !&#61; null) {q &#61; s;s &#61; s.LChild;}Console.WriteLine("s.Data&#61;" &#43; s.Data &#43; ",p.Data&#61;" &#43; p.Data &#43; ",q.Data&#61;" &#43; q.Data);//然后将找到的最小节点与自己对调(因为最小节点是从右子树中找到的&#xff0c;所以其值肯定比本身要大)p.Data &#61; s.Data;if (q !&#61; p){//将q节点原来的右子树挂左边(这样最后一个节点的子树就调整到位了)q.LChild &#61; s.RChild;}else //s节点的父节点就是p时,将s节点原来的右树向上提(因为s已经换成p点的位置了&#xff0c;所以这个位置就不需要了&#xff0c;直接把它的右树向上提升即可){q.RChild &#61; s.RChild;}#endregion}return true;}else if (key>p.Data){parent &#61; p;p &#61; p.RChild;}else{parent &#61; p;p &#61; p.LChild;}}return false;}

 



推荐阅读
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 解决VS写C#项目导入MySQL数据源报错“You have a usable connection already”问题的正确方法
    本文介绍了在VS写C#项目导入MySQL数据源时出现报错“You have a usable connection already”的问题,并给出了正确的解决方法。详细描述了问题的出现情况和报错信息,并提供了解决该问题的步骤和注意事项。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
author-avatar
我还没公主
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有