大数据存储中的查找与排序——一个更为精彩优美的查找算法 | 青训营笔记

484 阅读5分钟

前言

这是我参与「第四届青训营」笔记创作活动的第25天,在「HDFS 原理与应用」、「HDFS 高可用和高扩展机制分析」及其后续的一系列课程中,讲师们都提到了分布式存储系统中的查找与排序问题。众所周知,在计算机数据库系统中,“增、删、查、改”是最基础的,也是最重要、最频繁的操作。而查找算法和排序算法是在非物理层面决定数据“增删查改”速度和效率的核心因素。各种查找算法和排序算法都有各自的优缺点,适合在不同环境中使用,很难确切的说某个算法是最好的。不过,在大部分情况下,查找算法中的“二分查找算法”,排序算法中的“堆排序算法”和“快速排序算法”是我们较为常用的算法。在这里,我们以一道非常经典、常考的题目,结合二分查找算法与快速排序算法的思想,来介绍一个更为精彩的查找算法。

二分查找算法

二分查找算法的具体算法思想和实现在此不表。

(1)空间复杂度

(2)时间复杂度

我们知道,二分查找的过程可以用二叉树来描述,我们称其为判定树,且该判定树是一棵平衡二叉树。因此用二分查找算法查找到某个给定值的比较次数最多不会超过树的高度,在等概率查找时,查找成功的平均查找长度为log2(n+1)-1,其中2代表以2为底的对数函数(下同)。所以,二分查找的时间复杂度为O(log2n)。

快速排序算法

快速排序算法的具体算法思想和实现在此不表。

(1)空间复杂度

我们知道,快速排序是递归算法,需要借助递归工作栈来保存每层递归调用时的必要信息,其容量需要与递归调用的最大深度一致。最好情况下为O(log2n);最坏情况下,需要进行n-1次递归调用,栈的深度为为O(n)。故平均情况下,栈的深度为O(log2n),即快速排序的空间复杂度为O(log2n)。

(2)时间复杂度

根据快速排序算法的思想,显然快速排序的运行时间与划分是否对称有关。在最坏情况下,即初始排序表基本逆序或基本有序的情况下,快速排序的时间复杂度会达到O(n的平方);在理想状况下,快速排序能做到平衡划分,得到的两个子问题的大小均不超过n/2,此时,快速排序的时间复杂度为O(log2n)。

题目

(1)题目如下

编写一个算法,使之能够在数组L[1...n]中找出第k小的元素(即从小到大排序后处于第k个位置的元素)。

(2)题目分析

该题乍一看,「在不要求时间复杂度和空间复杂度的情况下」,直接使用排序算法对数组进行从小到大的排序,再提取L(k)即可,但显然其平均时间复杂度必在O(nlog2n)以上。此外,也可使用小顶堆算法,每次输出的堆顶元素都是最小值,其时间复杂度O(n+klog2n)。这里,介绍一个更为精彩的算法,其借助于快速排序的划分和二分查找的相关思想。

(3)算法思想介绍

从数组L[1...n]中选择枢轴pivot(随机或取第一个元素均可,假设其在数组中的位置为m)进行和快速排序一样的划分操作后,表L[1...n]被划分为L[1...m-1]和L[m+1...n],其中L(m) = pivot。 我们讨论m与k的大小关系:

  1. 当m = k时,pivot即所求,返回pivot。
  2. 当m < k时,所求元素一定在L[m+1...n]中,因此对L[m+1...n]递归查找第k-m小的元素。
  3. 当m > k时,所求元素一定在L[1...m-1]中,因此对L[1...m-1]递归查找第k小的元素。

算法展示

//以下为快速排序中的划分算法
int kth_elem(int a[],int low,int high,int k){
    int pivot = a[low];   //表中第一个元素设为枢轴,对表进行划分
    int low_temp = low;   //由于low会在划分中不断改变,因此将初始low保存,方便使用
    int high_temp = high; //由于high会在划分中不断改变,因此将初始high保存,方便使用
    while(low < high){    //循环跳出条件
        while(low < high && a[high] >= pivot)
             --high;
        a[low] = a[high]; //将比枢轴小的元素移动到左端
        while(low < high && a[low] <= pivot)
             ++low;
        a[high] = a[low]; //将比枢轴大的元素移动到右端
}
a[low] = pivot;  //枢轴元素存放到最终位置

//以下为借用二分查找思想的本算法
if(low == k)     //与k相同,直接返回
   return a[low];
else if(low > k) //在左半部分表中递归查找
   return kth_elem(a,low_temp,low-1,k);
else             //在右半部分表中递归查找
   return kth_elem(a,low+1,high_temp,k);
}

总结分析

在《算法导论》一书中,作者对类似问题给出了使用堆排序算法的答案,在某985计算机硕士研究生入学考试的压轴算法题中,命题者对该题做了「在时间复杂度和空间复杂度尽可能优的情况下」的限制,我们不知道使用堆排序算法和快速排序算法的答案在命题者的眼中“优”的程度有多大。不过,本篇笔记所介绍算法的时间复杂度在平均情况下可达到O(n),而空间复杂度则取决于划分的方法,是优于堆排序算法和快速排序算法的。同时,该算法代码精炼优美、整洁规律,相较于复杂的堆排序算法代码,显然更易于在考场/面试环境中手写/讲解作答。