在列表中查找数据有两种方式:顺序查找
和 二分查找
。顺序查找
适用于元素随机排列的列表;二分查找
适用于元素已排序的列表;(二分查找效率更高,但是查找之前需要额外的时间对列表中的元素排序)
【一】 顺序查找(又称为线性查找)
function seqSearch(arr, data) {
for (var i = 0; i < arr.length; ++i) {
if (arr[i] == data) {
return true;
}
}
return false;
}
<!-- 返回索引 -->
function seqSearch(arr, data) {
for (var i = 0; i < arr.length; ++i) {
if (arr[i] == data) {
return i;
}
}
return -1;
}
需要注意的是这种方式,比内置的Array.indexof()方法慢。
1.查找最大值和最小值
function findMin(arr) {
var min = arr[0];
for (var i = 1; i < arr.length; ++i) {
if (arr[i] < min) {
min = arr[i];
}
}
return min;
}
2.使用自组织数据
对数据的查找遵循“80-20原则”。
[80-20原则:指对某一数据集执行的80%的查找操作都是对其中20%的数据元素进行查找。自组织的方式会把这20%的数据置于数据集的起始位置,这样便可以通过一个简单的顺序查找快速找到他们];
其实,类似这种“80-20原则”的概率分布被称为帕累托(Pareto)分布,他是有帕累托(Vilfredo Pareto)在19世纪末期研究收入和财富的分布时发现的。
下面对正常的顺序查找加入自组织方式
function seqSearch(arr, data) {
for (var i = 0; i < arr.length; ++i) {
if (arr[i] == data) {
if (i > 0) {
<!-- swap(arr,i,i-1); -->
arr.swap(i,i-1);
}
return true;
}
}
return false;
}
swap() 方法是对数组的数据进行调换的方法;
Array.prototype.swap = function(index1, index2) {
let temp = this[index1];
this[index1] = this[index2];
this[index2] = temp;
return this;
}
另外一种添加自组织数据的方法: 将找到的数据移动到数据集的其实位置,但是如果这个元素已经接近其实位置,则不会对它进行交换。我们只对距离数据集起始位置一定范围外的元素进行交换。我们只需要定义哪些是离数据集起始位置足够近的元素。再次参照“80-20原则”,我们确定原则:
仅当数据位于数据集的前20%元素之外时,该数据才需要被重新移动到数据集的起始位置。
function seqSearch(arr, data) {
for (var i = 0; i < arr.length; ++i) {
if (arr[i] == data && i > (arr.length * 0.2)) {
swap(arr,i,0);
return true;
}
else if (arr[i] == data) {
return true;
}
}
return false;
}
【二】 二分查找算法
如果要查找的数据是有序的,二分查找算法比顺序查找算法更高效。
举个简单的例子,我买了件衣服,朋友觉得不错,想知道衣服的价格,让朋友来猜衣服的价格。我告诉他结果,无非就三种情况出现,直接猜对了,猜大了,猜小了;将这个策略实现为二分查找算法,不过,这个算法只对有序的数据集有效。
算法描述如下:
(1)将数组的第一个位置设为下边界
(2)将数组的最后一个元素所在的位置设置为上边界(数组的长度减一)
(3)若下边界等于或小于上边界,做如下操作
a.将中点位置设置为(上边界加下边界)除以2
b.如果中点的元素小于查询的值,则将下边界设置为中点元素所在下标加1
c.如果中点的元素大于查询的值,则将上边界设置为中点元素所在下边减1
d.否则中点元素即为要查找的数据,可以进行返回
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;
}
var nums = [];
for (var i = 0; i < 100; ++i) {
nums[i] = Math.floor(Math.random() * 101);
}
insertionsort(nums);
dispArr(nums);
print();
putstr("Enter a value to search for: ");
var val = parseInt(readline());
var retVal = binSearch(nums, val);
if (retVal >= 0) {
print("Found " + val + " at position " + retVal);
}
else {
print(val + " is not in array.");
}
计算重复次数(在二分查找的基础上,向上和向下查找相同的值)
function count(arr, data) {
var count = 0;
var position = binSearch(arr, data);
if (position > -1) {
++count;
for (var i = position-1; i > 0; --i) {
if (arr[i] == data) {
++count;
}
else {
break;
}
}
for (var i = position+1; i < arr.length; ++i) {
if (arr[i] == data) {
++count;
}
else {
break;
}
}
}
return count;
}
【三】 查找文本数据
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(arr) {
var temp, inner;
for (var outer = 1; outer <= arr.length-1; ++outer) {
temp = arr[outer];
inner = outer;
while (inner > 0 && (arr[inner-1] >= temp)) {
arr[inner] = arr[inner-1];
--inner;
}
arr[inner] = temp;
}
}
var words = read("words.txt").split(" ");
insertionsort(words);
var word = "rhetoric";
var start = new Date().getTime();
var position = binSearch(words, word);
var stop = new Date().getTime();
var elapsed = stop - start;
if (position >= 0) {
print("Found " + word + " at position " + position + ".");
print("Binary search took " + elapsed + " milliseconds.");
}
else {
print(word + " is not in the file.");
}
文章结尾总结:在这个高速处理器的时代,除非面向大数据集,否则要测量顺序查找和二分查找耗时上的区别变得越来越困难。
然而,处理大数据集时二分查找要比顺序查找速度快,这一观点在数学理论上已经得到证明。在决定算法性能的每一步循环嵌套中,二分查找减少了一半的查找量。