第4章.基础算法查找
数据查找是基础的计算机编程工作,而且人们对它的研究已经很多年了。本章只会看到查找问题的一个内容,即根据给定的数值在一个列表(数组)中进行查找。
有两种对列表内数据进行查找的方法:顺序查找和二叉查找。当数据项在列表内随机排列的时候可以使用顺序查找,而当数据项在列表内有序排列的时候则会用到二叉查找。
4.1 顺序查找算法
最突出的查找类型就是从记录集的开始处顺次遍历每条记录,直到找到所要的记录或者是到达数据集的末尾。这就是所谓的顺序查找。
顺序查找(也被称为线性查找)是非常容易实现的。从数组的起始处开始,把每个访问到的数组元素依次和所要查找的数值进行比较。如果找到匹配的数据项,就结束查找操作。如果遍历到数组的末尾仍没有产生匹配,那就说明此数值不在数组内。
下面是一个执行顺序查找操作的函数:
static bool SeqSearch(int[] arr, int sValue) { for (int index = 0; index < arr.Length; index++) { if (arr[index] == sValue) { return true; } } return false; }
如果发现匹配,那么函数会立刻返回True并且退出。如果到达数组的末尾,函数还没有返回True,那么要查找的数值就不在数组内,而函数则会返回False。
下面这个程序用来测试顺序查找的实现:
namespace 第4章.基础查找算法 { class Program { static void Main(string[] args) { int[] numbers = new int[100]; StreamReader numFile = File.OpenText(@"e:\\numbers.txt"); for (int i = 0; i < numbers.Length; i++) { numbers[i] = Convert.ToInt32(numFile.ReadLine(), 10); int searcherNumber; Console.Write("Enter a number to search for: "); searcherNumber = Convert.ToInt32(Console.ReadLine(), 10); bool found; found = SeqSearch(numbers, searcherNumber); if (found) Console.WriteLine(searcherNumber + " is in the array."); else Console.WriteLine(searcherNumber + " is not in the array."); } } static bool SeqSearch(int[] arr, int sValue) { for (int index = 0; index < arr.Length; index++) { if (arr[index] == sValue) { return true; } } return false; } } }
程序首先会通过文本文件中读取一组数组开始运行,数组是由前100个整数组成的,而且是按照部分随机的顺序进行存储的,随后,程序会提示用户输入所要查找的数,并且调用SeqSearch函数来进行查找。
当然,用户也可以编写顺序查找函数。这样当找到要查找的数值时,函数就会返回此数值在数组内的位置。而当没有找到要查找的数值时,函数就会返回-1。首先来看一看新函数:
static int SeqSearch(int[] arr, int sValue) { for (int index = 0; index < arr.Length; index++) { if (arr[index] == sValue) { return index; } } return -1; }
下面这个程序使用了上述函数:
namespace 第4章.基础查找算法 { class Program { static void Main(string[] args) { int[] numbers = new int[100]; StreamReader numFile = File.OpenText(@"e:\\numbers.txt"); for (int i = 0; i < numbers.Length - 1; i++) { numbers[i] = Convert.ToInt32(numFile.ReadLine(), 10); int searcherNumber; Console.Write("Enter a number to search for: "); searcherNumber = Convert.ToInt32(Console.ReadLine(), 10); int foundAt; foundAt = SeqSearch(numbers, searcherNumber); if (foundAt >= 0) Console.WriteLine(searcherNumber + " is in the array."); else Console.WriteLine(searcherNumber + " is not in the array."); } } static int SeqSearch(int[] arr, int sValue) { for (int index = 0; index < arr.Length; index++) { if (arr[index] == sValue) { return index; } } return -1; } } }
4.1.1 查找最小值和最大值
人们经常要求计算机程序从数组(或者其它数据结构)里查找到最小值和最大值。在一个有序的数组中,查找最小值和最大值是很容易的工作。但是,在一个无序的数组中,这就是一个不小的挑战了。
下面就从了解如何找到数组的最小值开始吧。算法是:
1.把数组的第一个元素作为最小值赋值给一个变量。
2.开始循环遍历数组,并且把每一个后继数组元素与最小值变量进行比较。
3.如果当前访问到的数组元素小于最小值,就把此元素赋值给最小值变量。
4.继续上述操作直到访问到最后一个数组元素为止。
5.最小值就是存储在变量内的数值了。
下面来看看试下此算法的函数FindMin:
namespace 第4章.基础查找算法 { class Program { static void Main(string[] args) { int[] Test = { 99, 12, 10, 88, 1010, 1 }; int n = FindMin(Test); Console.WriteLine(n); Console.ReadKey(); } static int FindMin(int[] arr) { int min = arr[0]; for (int i = 1; i < arr.Length; i++) { if (arr[i] < min) { min = arr[i]; } } return min; } } }
清注意数组查找是从第1个元素的位置开始的,而不是从第0个元素的位置开始。第0个元素的位置再循环开始前会获得最小值,因此开始进行比较操作是在第1个元素的位置上。
在数组内查找最大值的算法和查找最小值的方法相同,先把数组的首元素赋值给一个保存最大值的变量。接着循环遍历数组,把每个数组元素与存储在变量内的数值进行比较。如果访问到的数值大于当前,就进行替换。下面是代码:
namespace 第4章.基础查找算法 { class Program { static void Main(string[] args) { int[] Test = { 9999, 99, 12, 10, 88, 1010, 1, 1111 }; int n = FindMax(Test); Console.WriteLine(n); Console.ReadKey(); } static int FindMax(int[] arr) { int max = arr[0]; for (int i = 1; i < arr.Length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } } }
上述两个函数的另外一种替换写法是返回最大值或最小值在数组内的位置,而不是返回实际的数值。
4.1.2 自组织数据加快顺序查找速度
当要查找的数据元素就在数据集合的开始处时就会产生最快速的成功查找。通过找到数据项后把它移动到数据集合开始处的方法可以确保成功定位数据项。
这种策略的含义就是通过把频繁查找的数据项放在数据集合开始处的方法来最小化查找的次数。最终的结果就是所有最频繁查找的数据项都会被放置在数据集合的开始部分。这是自组织的一个实例,这是因为数据集合不是在程序运行之前由程序员组织的,而是在程序运行期间由程序自身组织的。
既然要查找的数据大概会遵循“80-20”规则,这使得允许数据进行组织变得有意义了。其中,“80-20”原则意味着在数据集合80%的查找操作都是为了查找数据集合内20%的数据。自组织将最终把20%的数据放在数据集合的开始部分,这样顺序查找就可以快速的找到它们了。
像这样的概率分布被称为是帕累托分布,它是以19世纪后期通过研究收入和财富的扩散而发现这类概率分布的科学家Vifredo Pareto的名字命名的。
参考链接:
https://www.cnblogs.com/lged/p/5889814.html
4.2 二叉查找算法
当要查找的记录从头到尾都是有序排列的时候,为找到数值可以执行一种比顺序查找更加有效的查找。这种查找被称为是二叉查找。
如1-100之间猜任意值的示例。
这里可以把这种策略作为一种算法来实现,即二叉查找算法。为了使用这种算法,首先需要把数据按顺序(最好是升序方式)存储到数组内(当然其它数据结构也可行)。算法的第一步就是设置查找的上界和下界。在查找的开始,这就意味着是数组的上限和下限。然后,通过把上限和下限相加后除以2的操作就可以计算出数组中间的点。接着把存储在中间点上的数组元素与要查找的数值进行比较。如果两者相同,那么就表示找到了该数值,同时查找算法也就此结束。如果要查找的数值小于中间点的值,那么就通过从中间点减一的操作计算出新的上限。否则,若要是要查找的数值大于中间点的值,那么就把中间点加一求出新的下限。此算法反复执行直到下限和上限相等时终止,这也就意味着已经对数组全部查找完了。如果发生这种情况,那么就返回-1,这表示数组中不存在要查找的数值。
代码如下所示:
namespace 第4章.基础查找算法 { class Program { static void Main(string[] args) { int[] n = { 1, 3, 5, 7, 9, 11, 13 }; int result = binSearch(n, 1); Console.WriteLine(result); //result为数组索引 Console.ReadKey(); } static int binSearch(int[] arr, int value) { int upperBound, lowerBound, mid; upperBound = arr.Length - 1; lowerBound = 0; while (lowerBound <= upperBound) { mid = (upperBound + lowerBound) / 2; if (arr[mid] == value) return mid; else if (value < arr[mid]) upperBound = mid - 1; else lowerBound = mid + 1; } return -1; } } }
4.3 递归二叉查找算法
namespace 第4章.基础查找算法 { class Program { static void Main(string[] args) { int[] n = { 1, 3, 5, 7, 9, 11, 13 }; int result = RbinSearch(n, 7, 1, 6); Console.WriteLine(result); //result为数组索引 Console.ReadKey(); } static int RbinSearch(int[] arr, int value, int lower, int upper) { if (lower > upper) { return -1; } else { int mid; mid = (int)(lower + upper) / 2; if (arr[mid] == value) return mid; else { if (arr[mid] < value) lower = mid + 1; else upper = mid - 1; return RbinSearch(arr, value, lower, upper); } } } } }
同迭代算法相比,递归二叉算法的主要问题是它的效率。当这两种算法对含有1000个元素的数组就行排序的时候,递归算法始终比迭代算法慢了10倍。