【数据结构与算法JavaScript实现与应用】查找算法 —— 顺序查找 PK 二分法+排序

2,002 阅读6分钟

前言

在列表中查找数据有两种方式:顺序查找和二分查找。对于一个已排序的列表,二分法无疑效率更高,因为每次可排除一半的元素,但若是对于一个无序的列表,如果先对其排序,再用二分法查找和直接用顺序查找对比哪种效率更高呢?最近在看的《数据结构与算法Javascript描述》一书正好也有这样一个类似习题,不过此书中指定排序法为插入排序,就让我们一起用实践来解答这道题吧。

首先要说明的是排序有多种算法,一般情况下对于非大型数据集排序用基本排序法(冒泡、选择和插入法)即可,而基本排序法中一般认为速度最快的插入法,对于大型数据集排序需要用到高级排序法(希尔、归并和快速排序法),而高级排序法中一般认为快速排序法最快,所以这里我们分别选取插入法和快速排序法用来对非大型数据集和大型数据集做测试。

正题1: 顺序查找 PK 二分法+插入排序

顺序查找、二分法和插入排序的原理和算法实现都很简单,就不在此赘述,直接上代码

顺序查找

function seqSearch(arr, data) {
   for (var i = 0; i < arr.length; ++i) {
      if (arr[i] == data) {
         return i;
      }
   }
   return -1;
}

二分法查找

function binSearch(arr, data) {
   var upperBound = arr.length-1;
   var lowerBound = 0;
   while (lowerBound <= upperBound) {
      var mid = Math.floor((upperBound + lowerBound) / 2);
      if (arr[mid] < data) {
         lowerBound = mid + 1;
      }
      else if (arr[mid] > data) {
         upperBound = mid - 1;
      }
      else {
         return mid;
      }
   }
   return -1;
}

插入法排序

function insertionSort(dataStore) {
   var temp, inner;
   for (var outer = 1; outer <= dataStore.length-1; ++outer) {
      temp = dataStore[outer];
      inner = outer;
      while (inner > 0 && (dataStore[inner-1] >= temp)) {
         dataStore[inner] = dataStore[inner-1];
         --inner;
      }
      dataStore[inner] = temp;
   }
}

创建数组

function CreteArray(numElements) {
    var dataStore=[];
    for (var i = 0; i < numElements; ++i) {
        dataStore[i] = Math.floor(Math.random() * (numElements+1));
     }
     return dataStore;
}

测试主方法

function QueryTest(numElements){
    var nums = CreteArray(numElements);
    //从数组中随机取一个数用来在数组中查询
    var seachIndex=Math.floor(Math.random() * (numElements));
    var seachValue=nums[seachIndex];
    var startseq = new Date().getTime();
    var valInedx=seqSearch(nums,seachValue);
    var stopseq = new Date().getTime();
    var seqelapsed = stopseq - startseq;
    console.log("在 " +  numElements + " 个元素中顺序查找"+seachValue+"消耗时间: " + seqelapsed + " 毫秒;位置索引是"+valInedx);
    var startinser = new Date().getTime();
    insertionSort(nums);
    var stopinser = new Date().getTime();
    var inserelapsed = stopinser - startinser;
    console.log("对 " +  numElements + " 个元素执行插入排序耗时:" + inserelapsed + " 毫秒");
    var startbin = new Date().getTime();
    var binIndex=binSearch(nums,seachValue);
    var stopbin = new Date().getTime();
    var binelapsed = stopbin - startbin;
    console.log("在 " +  numElements + " 个已排序元素中用二分法查找元素"+seachValue+"消耗时间:" + binelapsed + " 毫秒;位置索引是"+binIndex);
    var binTotal=binelapsed+inserelapsed;
    console.log("在 " +  numElements + " 个未排序元素中先用插入法排序,再用二分法查找元素总消耗时间:" + binTotal+ " 毫秒");
}

测试结果

我们用长度为10000的数组做测试QueryTest(10000),得到如下结果

连续测试10次得出如下结果:

由此可看出:

在数据量不大时:对已排序的数组来说,二分法查找速度确实要优于顺序查找法,对于未排序的数组,顺序查找法的速度是要优于先用插入法排序,再用二分法查找的速度的,因为排序所花的时间远高于顺序查找所需时间

如果把数组长度设为100000(十万),运行程序:

多试几次,结果虽有一定波动,但差异不大,仍支持上述结论

如果将数组长度设为1000000(百万),运行程序:

可看出:

对于百万长度的有序数组,二分法查找仍不费吹灰之力

对比1万、10万、100万长度下的插入法排序,可看出:

数组长度扩大10倍,插入法排序所增加的时间却是几十近百倍

正题2: 顺序查找 PK 二分法+快速排序法

快速排序法

function qSort(arr)
{
    if (arr.length == 0) {
        return [];
    }
    var left = [];
    var right = [];
    var pivot = arr[0];
    for (var i = 1; i < arr.length; i++) {
       
        if (arr[i] < pivot) {
           
           left.push(arr[i]);
        } else {
           
           right.push(arr[i]);
        }
    }
    return qSort(left).concat(pivot, qSort(right));
}

测试主方法

function QueryTest1(numElements){
    var nums = CreteArray(numElements);
    //从数组中随机取一个数用来在数组中查询
    var seachIndex=Math.floor(Math.random() * (numElements));
    var seachValue=nums[seachIndex];
    var startseq = new Date().getTime();
    var valInedx=seqSearch(nums,seachValue);
    var stopseq = new Date().getTime();
    var seqelapsed = stopseq - startseq;
    console.log("在 " +  numElements + " 个元素中顺序查找"+seachValue+"消耗时间: " + seqelapsed + " 毫秒;位置索引是"+valInedx);
    var startq = new Date().getTime();
    qSort(nums);
    var stopq = new Date().getTime();
    var qelapsed = stopq - startq;
    console.log("对 " +  numElements + " 个元素执行快速排序耗时:" + qelapsed + " 毫秒");
    var startbin = new Date().getTime();
    var binIndex=binSearch(nums,seachValue);
    var stopbin = new Date().getTime();
    var binelapsed = stopbin - startbin;
    console.log("在 " +  numElements + " 个已排序元素中用二分法查找元素"+seachValue+"消耗时间:" + binelapsed + " 毫秒;位置索引是"+binIndex);
    var binTotal=binelapsed+qelapsed;
    console.log("在 " +  numElements + " 个未排序元素中先用插入法排序,再用二分法查找元素总消耗时间:" + binTotal+ " 毫秒");
}

测试结果

我们用长度为1000000(百万)的数组做测试QueryTest(1000000),得到如下结果

多试几次,结果与上图相似

再看下千万长度的数组

既然都试了千万,何不测试下上亿长度的数组,结果是:

在创建数组时就内存溢出了😅

由此我们可看出:

在数据量较大时:对已排序的数组来说,二分法查找速度仍要优于顺序查找法,对于未排序的数组,顺序查找法的速度是仍要优于先用插入法排序,再用二分法查找的速度的,因为排序所花的时间仍远高于顺序查找所需时间

鉴于快速法排序在百万数据排序时完爆插入法的优异表现,我们顺便再看下快速排序法在小数据量情况下和插入法的对比:

1.先看下长度是10000的数组:

2.长度是1000的数组:

3.长度是100的数组:

至此我们主要能够得出如下结论:

结论

1.对于有序数据集合,二分法查找效率高于顺序查找法,数据量大时更明显,因为顺序查找法随着数据量的增加查询次数时间应该是线性增加,当然跟目标元素位置有很大关系,而二分法增加的查询次数却很有限,比如数据总量从一百万增长到一千万,如果目标元素都处于集合末端,那么顺序查找相应的查询次数可能要增加近10倍,而二分法可能只需增加3-4次查询(10介于2的3次方8和2的4次方16之间)

2.对于无序数据集合,顺序查找法效率高于先排序,后用二分法查找的效率,因为排序所费时间就已经多于顺序查找所需时间,不论数据量大小都是如此,即使是最快的排序算法排序的效率都低于顺序查找的效率

3.在数据量很小时(几百几千)情况下,插入排序法效率比快速排序可能更高,但在大数据量情况下,快速排序法效率远高于插入法