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

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

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

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

一、静态查找

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

1.1 顺序查找(Sequnce Search)

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

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

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

1.2 二分查找(Binary Search)

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

        /// <summary>
        /// 二分查找(适用于有序表)
        /// </summary>
        /// <param name="arr">要查找的有序表</param>
        /// <param name="key">要查找的值</param>
        /// <returns>找到则返回元素的下标+1,否则返回-1</returns>
        static int BinarySearch(int[] arr, int key)
        {
            arr[0] = key;//同样约定第一个位置存放要查找的元素值(仅仅只是约定而已)
            int mid = 0;
            int flag = -1;
            int low = 1;
            int high = arr.Length - 1;

            while (low<=high)
            {
                //取中点
                mid = (low + high) / 2;

                //查找成功,记录位置存放到flag中
                if (key == arr[mid])
                {
                    flag = mid;
                    break;
                }
                else if (key < arr[mid]) //调整到左半区
                {
                    high = mid - 1;
                }
                //调整到右半区
                else
                {
                    low = mid + 1;
                }
            }

            if (flag > 0)
            {
                return flag;//找到了
            }
            else
            {
                return -1;//没找到
            }
        }

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

1.3 索引查找(Index Search)

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

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

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

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

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

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

 

二、动态查找

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

上图就是一颗“二叉排序树 ”,其基本特征是:

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

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

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

2.1、二叉排序树的查找

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

        /// <summary>
        /// 二叉排序树查找
        /// </summary>
        /// <param name="bTree"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        static bool BiSortTreeSearch(BiTree<int> bTree, int key)
        {
            Node<int> p;

            //如果树为空,则直接返回-1
            if (bTree.IsEmpty())
            {
                return false;
            }

            p = bTree.Root;

            while (p != null)
            {
                //如果根节点就是要找的
                if (p.Data == key)
                {
                    return true;
                }
                else if (key > p.Data)
                {
                    //调整到右子树
                    p = p.RChild;
                }
                else
                {
                    //调整到左子树
                    p = p.LChild;
                }
            }
            return false;
        }

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

            //先创建树
            BiTree<int> tree = new BiTree<int>(100);
            Node<int> root = tree.Root;
            Node<int> p70 = new Node<int>(70);
            Node<int> p150 = new Node<int>(150);
            root.LChild = p70;
            root.RChild = p150;
            Node<int> p40 = new Node<int>(40);
            Node<int> p80 = new Node<int>(80);
            p70.LChild = p40;
            p70.RChild = p80;
            Node<int> p20 = new Node<int>(20);
            Node<int> p45 = new Node<int>(45);
            p40.LChild = p20;
            p40.RChild = p45;
            Node<int> p75 = new Node<int>(75);
            Node<int> p90 = new Node<int>(90);
            p80.LChild = p75;
            p80.RChild = p90;

            Node<int> p112 = new Node<int>(112);
            Node<int> p180 = new Node<int>(180);
            p150.LChild = p112;
            p150.RChild = p180;

            Node<int> p120 = new Node<int>(120);
            p112.RChild = p120;

            Node<int> p170 = new Node<int>(170);
            Node<int> p200 = new Node<int>(200);
            p180.LChild = p170;
            p180.RChild = p200;

            //测试查找
            Console.WriteLine(BiSortTreeSearch(tree, 170));

2.2、二叉排序树的插入

逻辑:先在树中查找指定的值,如果找到,则不插入,如果找不到,则把要查找的值插入到最后一个节点下做为子节点(即:先查找,再插入)

        /// <summary>
        /// 二插排序树的插入(即:先查找,如果找不到,则插入要查找的值)
        /// </summary>
        /// <param name="bTree"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        static bool BiSortTreeInsert(BiTree<int> bTree, int key)
        {
            Node<int> p = bTree.Root;
            Node<int> last = null;//用来保存查找过程中的最后一个节点            

            while (p != null)
            {
                if (p.Data == key)
                {
                    return true;
                }
                last = p;

                if (key > p.Data)
                {
                    p = p.RChild;
                }
                else
                {
                    p = p.LChild;
                }
            }

            //如果找了一圈,都找不到要找的节点,则将目标节点插入到最后一个节点下面
            p = new Node<int>(key);
            if (last == null)
            {
                bTree.Root = p;
            }
            else if (p.Data < last.Data)
            {
                last.LChild = p;
            }
            else
            {
                last.RChild = p;
            }

            return false;
        }

2.3 二叉排序树的创建

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

        /// <summary>
        /// 创建一颗二插排序树
        /// </summary>
        /// <param name="tree"></param>
        /// <param name="arr"></param>
        /// <param name="index"></param>
        static void CreateBiSortTree(BiTree<int> tree, int[] arr)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                BiSortTreeInsert(tree, arr[i]);
            }
        }

2.4 二叉排序树的节点删除

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

a.待删除的节点,本身就是叶节点

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

 

b.待删除的节点,只有左子树

思路:将本节点的左子树上移,挂到父节点下的LChild,然后删除自身即可

 

c.待删除的节点,只有右子树

思路:将自身节点的右子树挂到父节点的左子树,然后删除自身即可

 

d.待删除的节点,左、右子树都有

思路:这个要复杂一些,先找出自身节点右子树中的左分支的最后一个节点(最小左节点),然后将它跟自身对调,同时将“最小左节点”下的分支上移。

 

以上逻辑综合起来,就得到了下面的方法:

        /// <summary>
        /// 删除二叉排序树的节点
        /// </summary>
        /// <param name="tree"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        static bool DeleteBiSort(BiTree<int> tree, int key)
        {

            //二叉排序树为空
            if (tree.IsEmpty())
            {
                return false;
            }

            Node<int> p=tree.Root;
            Node<int> parent = p;

            while (p!=null)
            {
                if (p.Data == key)
                {
                    if (tree.IsLeaf(p))//如果待删除的节点为叶节点
                    {
                        #region
                        if (p == tree.Root)
                        {

                            tree.Root = null;
                        }
                        else if (p == parent.LChild)
                        {

                            parent.LChild = null;
                        }
                        else
                        {

                            parent.RChild = null;
                        }
                         #endregion
                    }
                    else if ((p.RChild == null) && (p.LChild != null)) //仅有左分支
                    {
                        #region
                        if (p == parent.LChild)
                        {

                            parent.LChild = p.LChild;
                        }
                        else
                        {
                            parent.RChild = p.LChild;
                        }
                        #endregion
                    }
                    else if ((p.LChild == null) && (p.RChild != null)) //仅有右分支
                    {
                        #region
                        if (p == parent.LChild)
                        {
                            parent.LChild = p.RChild;
                        }
                        else
                        {
                            parent.RChild = p.RChild;
                        }
                        #endregion
                    }
                    else //左,右分支都有
                    {
                        //原理:先找到本节点右子树中的最小节点(即右子树的最后一个左子节点)
                        #region
                        Node<int> q = p;
                        Node<int> s = p.RChild;
                        while (s.LChild != null)
                        {
                            q = s;
                            s = s.LChild;
                        }
                        Console.WriteLine("s.Data=" + s.Data + ",p.Data=" + p.Data + ",q.Data=" + q.Data);

                        //然后将找到的最小节点与自己对调(因为最小节点是从右子树中找到的,所以其值肯定比本身要大)
                        p.Data = s.Data;

                        if (q != p)
                        {
                            //将q节点原来的右子树挂左边(这样最后一个节点的子树就调整到位了)
                            q.LChild = s.RChild;
                        }
                        else //s节点的父节点就是p时,将s节点原来的右树向上提(因为s已经换成p点的位置了,所以这个位置就不需要了,直接把它的右树向上提升即可)
                        {
                            q.RChild = s.RChild;
                        }
                        #endregion
                    }
                    return true;
                }
                else if (key>p.Data)
                {
                    parent = p;
                    p = p.RChild;
                }
                else
                {
                    parent = p;
                    p = p.LChild;
                }
            }
            return false;
        }

 

时间: 2025-01-26 21:14:54

数据结构与算法C#版笔记--查找(Search)的相关文章

数据结构与算法C#版笔记--排序(Sort)-下

5.堆排序(HeapSort) 在接触"堆排序"前,先回顾一下数据结构C#版笔记--树与二叉树 ,其中提到了"完全二叉树"有一些重要的数学特性: 上图就是一颗完全二叉树,如果每个节点按从上到下,从左至右标上序号,则可以用数组来实现顺性存储,同时其序号: 1.如果i>1,则序号为i的父结节序号为i/2(这里/指整除) 言外之意:整个数组前一半都是父节点,后一半则是叶节点 2.如果2*i<=n(这里n为整颗树的节点总数),则序号为i的左子节点序号为2*i 3

数据结构与算法C#版笔记--排序(Sort)-上

这里讨论的仅限于内部排序(即全部数据都在内存中,通过CPU运算处理元素排序),而且仅限顺序表排序(即不讨论链表,树状结构等结构的排序) 注:排序后的结果可以从小到大,或者从大到小,这只是一个相反的处理而已,为方便起见,本文中的方法都是从小到大排序 1.直接插入排序(InsertOrder) 思路:从第二个元素开始向后遍历,检查本身(后面称之为tmp)与前面相邻元素的大小,如果发现前面的元素更大,则依次从近及远(即倒序遍历)检查前面的所有元素,将比自身元素大的元素依次后移,这样最终将得到一个空位,

字符串搜索-Boyer-Moore算法 《数据结构与算法c++版》Adam Drozdek

问题描述 Boyer-Moore算法 <数据结构与算法c++版>Adam Drozdek 通常BM算法采用的是坏字符和好后缀算法(eg.,BM算法),我学习的<数据结构与算法c++版>Adam Drozdek版教材上使用了 全后缀和部分后缀规则,这种方法在网络上基本查不到. 作者解释原理时,一个关键的函数只是给出公式,基本上没有解释,如下: 请问怎么理解这个公式,尤其是公式中的s代表什么意思? 原书我已经在云盘共享,关于BM算法书上在P687面.希望能帮忙解决.

数据结构与算法系列(3)基础查找算法

前言 数据查找是基础的计算机编程工作,而且人们对它的研究已经很多年了.在本部分只会看到查找问 题的一个内容,即根据给定的数值在一个列表(数组)中进行查找. 有两种对列表内数据进行查找的方法:顺序查找和二驻查找.当数据项在列表内随机排列的时候可 以使用顺序查找,而当数据项在列表内有序排列的时候则会用到二叉查找. 1.顺序查找算法 最突出的查找类型就是从记录集的开始处顺次遍历每条记录,直到找到所需要的记录或者是到达数 据集的末尾.这就是所谓的顺序查找. 顺序查找(也称为线性查找)是非常容易实现的.从

《数据结构与算法 C语言版》—— 3.8习题

前言 "数据结构"是计算机程序设计的重要理论技术基础,是计算机学科的核心课程,也是计算机专业考研的必考课程,同时已成为其他理工科专业的热门课程.学好该课程,不仅对学习后续算法设计.数值分析.操作系统.编译原理等课程有很大帮助,而且在实际中有广泛的用途. 数据结构主要研究数据的各种组织形式以及建立在这些结构之上的各种运算的实现.它不仅为用计算机语言进行程序设计提供了方法性的理论指导,还在一个更高的层次上总结了程序设计的常用方法和常用技巧. "数据结构"课程的特点是概念

《Python Cookbook(第3版)中文版》——第1章 数据结构和算法 1.1 将序列分解为单独的变量

第1章 数据结构和算法 Python内置了许多非常有用的数据结构,比如列表(list).集合(set)以及字典(dictionary).就绝大部分情况而言,我们可以直接使用这些数据结构.但是,通常我们还需要考虑比如搜索.排序.排列以及筛选等这一类常见的问题.因此,本章的目的就是来讨论常见的数据结构和同数据有关的算法.此外,在collections模块中也包含了针对各种数据结构的解决方案. 1.1 将序列分解为单独的变量 1.1.1 问题 我们有一个包含 N 个元素的元组或序列,现在想将它分解为N

[转]MySQL索引背后的数据结构及算法原理

引用:http://blog.codinglabs.org/articles/theory-of-mysql-index.html 摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等.为了避免混乱,本文将只关注于BTree索引,因为这是平常使用MySQL时主要打交道的索引,至于哈希索引和全文索引本文暂不讨论. 文章

MySQL索引背后的数据结构及算法原理

看到的一篇关于MySql索引的介绍,感觉比较经典,直接转了. 本文转自张洋博客,原文链接:http://blog.codinglabs.org/articles/theory-of-mysql-index.html 摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等.为了避免混乱,本文将只关注于BTree索引,因为

基本数据结构和算法在Linux内核中使用

基本数据结构和算法在Linux内核中使用 gaufunga day ago 搬运工 Linux内核(源代码的链接在github). 1.链表.双向链表.无锁链表. 2.B+ 树,这是一些你无法在教科书上找到的说明. 一个相对简单的B+树的实现.我把它作为一个学习练习来帮助理解B+树是如何工作的.这同样也被证明是有用的. ... 一个在教科书中并不常见的技巧.最小的值在右侧而不是在左侧.所有在一个节点里用到的槽都在左侧,所有没有用到的槽包含了空值(NUL).大多数操作只简单地遍历所有的槽一次并在第