热门标签 | 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;}

 



推荐阅读
  • 深入解析链表成环问题:剑指Offer第22天的新视角
    本文将详细介绍链表成环问题的多种解法,包括哈希表法、JSON.stringify特殊解法及双指针法,并提供详尽的代码示例。阅读本文,你不仅能够掌握这一经典算法问题的核心技巧,还能了解到更多编程思维的拓展。 ... [详细]
  • 深入解析轻量级数据库 SQL Server Express LocalDB
    本文详细介绍了 SQL Server Express LocalDB,这是一种轻量级的本地 T-SQL 数据库解决方案,特别适合开发环境使用。文章还探讨了 LocalDB 与其他轻量级数据库的对比,并提供了安装和连接 LocalDB 的步骤。 ... [详细]
  • 本文详细记录了一位求职者在搜狐进行的两次面试经历,包括面试的具体时间、面试流程、技术问题及个人感受。通过本次面试,作者不仅获得了宝贵的经验,还成功拿到了搜狐的录用通知。 ... [详细]
  • 字符、字符串和文本的处理之Char类型
    .NetFramework中处理字符和字符串的主要有以下这么几个类:(1)、System.Char类一基础字符串处理类(2)、System.String类一处理不可变的字符串(一经 ... [详细]
  • 本文详细介绍了PHP中的回调函数及其多种实现方式,包括函数字符串、匿名函数、类静态方法和类方法。同时,探讨了闭包的概念及其在PHP中的应用,通过实例展示了如何利用闭包访问外部变量。 ... [详细]
  • 转自:http:blog.sina.com.cnsblog_67419c420100vmkt.html 1.为什么要使用blocks将一个blocks作为函数或者方法的参数传递,可 ... [详细]
  • [编程题] LeetCode上的Dynamic Programming(动态规划)类型的题目
    继上次把backTracking的题目做了一下之后:backTracking,我把LeetCode的动态规划的题目又做了一下,还有几道比较难的Medium的题和Hard的题没做出来,后面会继续 ... [详细]
  • 深入解析mt_allocator内存分配器(二):多线程与单线程场景下的实现
    本文详细介绍了mt_allocator内存分配器在多线程和单线程环境下的实现机制。该分配器以2的幂次方字节为单位分配内存,支持灵活的配置和高效的性能。文章分为内存池特性描述、内存池实现、单线程内存池实现、内存池策略类实现及多线程内存池实现等部分,深入探讨了内存池的初始化、内存分配与回收的具体实现。 ... [详细]
  • 使用Pandas DataFrame探索十大城市房价与薪资对比
    在本篇文章中,我们将通过Pandas库中的DataFrame工具,深入了解中国十大城市的房价与薪资水平,探讨哪些城市的生活成本更为合理。这是学习Python数据分析系列的第82篇原创文章,预计阅读时间约为6分钟。 ... [详细]
  • 本文详细记录了一位Java程序员在Lazada的面试经历,涵盖同步机制、JVM调优、Redis应用、线程池配置、Spring框架特性等多个技术点,以及高级面试中的设计问题和解决方案。 ... [详细]
  • 深入解析C++ Atomic编程中的内存顺序
    在多线程环境中,为了防止多个线程同时修改同一数据导致的竞争条件,通常会使用内核级同步对象,如事件、互斥锁和信号量等。然而,这些方法往往伴随着高昂的上下文切换成本。本文将探讨如何利用C++11中的原子操作和内存顺序来优化多线程编程,减少不必要的开销。 ... [详细]
  • 本文提供了多个关键点来帮助开发者提高Java编程能力,包括代码规范、性能优化和最佳实践等方面,旨在指导读者成为更加优秀的Java程序员。 ... [详细]
  • 本文详细介绍了Objective-C中的面向对象编程概念,重点探讨了类的定义、方法的实现、对象的创建与销毁等内容,旨在帮助开发者更好地理解和应用Objective-C的面向对象特性。 ... [详细]
  • 本文介绍了一个基本的同步Socket程序,演示了如何实现客户端与服务器之间的简单消息传递。此外,文章还概述了Socket的基本工作流程,并计划在未来探讨同步与异步Socket的区别。 ... [详细]
  • 基于51单片机的多项目设计实现与优化
    本文探讨了基于51单片机的多个项目的设计与实现,包括PID控制算法的开关电源设计、八音电子琴仿真设计、智能抽奖系统控制设计及停车场车位管理系统设计。每个项目均采用先进的控制技术和算法,旨在提升系统的效率、稳定性和用户体验。 ... [详细]
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社区 版权所有