数据结构 C#描述 第四章
第四章:基本的查找算法
查找算法在计算机编程中一项很基础的任务,也被研究了很多年了。本章仅仅讲述有关查找的一个方面的问题――在数据集中查找给定的值。
在数据集中查找有两种基本的方法:顺序查找和二分查找。当数据集中的数据任意顺序时经常用顺序查找,当数据集中的数据有序存储时用二分查找。
顺序查找
最显而易见的查找方法就是在一系列数据中从头到尾的遍历数据集合来查找你想要照的数据,这就是顺序查找。
顺序查找的实现很容易。从数据集的第一个元素开始,比较每一个元素是否和你要查找的数据值相等。如果找到了相等的,那么这个查找就完毕。否则,查找就失败了
下面是实现顺序查找功能的一个算法:
public void SeqSearch(int[] arr, int sValue)
{
for (int index = 0; index < arr.Length - 1; index++)
if (arr[index] == sValue)
return true;
return false;
}
如果查找匹配成功,上述的函数就返回True并且退出。否则,就返回false。
下面是测试查找算法的代码:
using System;
using System.IO;
public class Chapter4
{
static void Main()
{
int[] numbers = new int[100];
StreamReader numFile = File.OpenText(@"F:"电子书系列"numbers.txt");
for (int i = 0; i < numbers.Length - 1; i++)
numbers[i] = Convert.ToInt32(numFile.ReadLine(), 10);
int searchNumber;
Console.Write("Enter a number to search for:");
searchNumber = Convert.ToInt32(Console.ReadLine(), 10);
bool found;
found = SeqSearch(numbers, searchNumber);
if (found)
Console.WriteLine(seachNumber + "is in the array!");
else
Console.WriteLine(searchNumber + "is not in the array");
}
static void SeqSearch(int[] arr, int sValue)
{
for (int index = 0; index < arr.Length - 1; index++)
if (arr[index] == sValue)
return true;
return false;
}
}
程序首先从一个文件中读取数据。文件中的数据是100个整数,以任意的顺序存储者。之后程序要求读者输入一个要查找的数据,然后调用 SeqSearch函数知行查找功能。
你也可以可以改写查找的函数,使得函数返回被查找的数在数据集中位置。如果没有查找就返回-1,代码如下:
using System;
using System.IO;
public class Chapter4
{
static void Main()
{
int[] numbers = new int[100];
StreamReader numFile = File.OpenText(@"F:"电子书系列"numbers.txt");
for (int i = 0; i < numbers.Length - 1; i++)
numbers[i] = Convert.ToInt32(numFile.Read());
int searchNumber;
Console.Write("Enter a number to search for:");
searchNumber = Convert.ToInt32(Console.ReadLine());
int foundAt;
foundAt = SeqSearch(numbers, searchNumber);
if (foundAt>=0)
Console.WriteLine(""n"+searchNumber + "is in the array at" +foundAt);
else
Console.WriteLine(searchNumber + "is not in the array");
}
static int SeqSearch(int[] arr, int sValue)
{
for (int index = 0; index < arr.Length - 1; index++)
if (arr[index] == sValue)
return index;
return -1;
}
}
查找最大值和最小值
计算机程序经常要求在一个数据集中查找最大和最小值。在一个有序的数据集中,查找这两个值很容易的。但是,在一个无序的数据集中查找就有一点点的挑战性了。
首先来看看在一个数据集中查找最小的值。算法如下:
1. 将数据集中第一个元素赋值给一个变量,作为最小值。
2. 开始循环遍历数据集,比较数据集中的每一个后序者和最小值变量的大小。
3. 如果现在访问的值比最小值还小,就将当前的值赋值给最小值变量。
4. 继续遍历数据集,直到最后。
5. 最小值就保存在变量中了。
让我们来看看函数,FindMin,算法如下:
static int FindMin(int[] arr)
{
int min = arr[0];
for (int index = 1; index < arr.Length - 1; index++)
if (arr[index] < min)
min = arr[index];
return min;
}
注意,数据集查找的开始位置是索引1不是0.第0个位置被认为是最小的值了,所以我们应该从所以为1开始比较。
在数据集中查找最大的值的算法也是相同的。我们首先也是将数据集中的第一个元素的值赋值给一个变量。然后开始遍历数据集,并且比较数据集中的每一个元素和变量中的值的大小,如果比变量中的数大,就用当前的值替换变量中的值,直到数据集变量完成。代码如下:
static int FindMax(int[] arr)
{
int max = arr[0];
for (int index = 1; index < arr.Length - 1; index++)
if (arr[index] > max)
max = arr[index];
return max;
}
另外一个版本的算法是返回最大值和最小值的索引值,而不是返回他们的真实的值。
二分法查找
当你要查找的数据记录是有序存储时,你可以用比顺序查找更加高效的算法来查找。这就是二分法查找。
为了理解二分法查找的工作原理,想象你正在尝试去在1到100之前猜你朋友选中的数据。你每猜一次,你朋友就告诉你是否猜对了,或者说你猜的数据大了还是小了。最好的办法就是选中50作为你猜的第一个数。如果朋友说你的猜的大了,那么你就再选猜25;如果朋友说你猜的小了,你就再选猜75.你每猜一次,你就在你的重新调整的最大值和最小值之间选择一个新的中间值来猜。只要你按照这种办法,你最终会猜正确。图4.1显示了这个过程
你可以把这种方法实现为一个算法,即二分查找算法。为了使用这个算法,我们首先必须把存储的数据排序。首先就是确定查找的上下限。然后我们计算出上限和下限的中间值,即用上限和下限相加除2.之后就用这个中间值作为索引来查找数据集中对应位置的数值,将这个数据值和要查找的数进行比较。如果要查找的数值比当前的中间位置的值小,就把用当前的中间索引减1之后的索引值作为新的上限。否则,就把当前的中间索引加1之后的索引作为新的下限。算法遍历直到上限和下限相等,这就说明数据集都已经查找完了。这时候,就会返回-1,那么要查找的元素就不在数据集中。
算法代码实现如下:
static int binSearch(int value, int[] arr)
{
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 (arr[mid] > value)
{
upperBound = mid - 1;
}
else
{
lowerBound = mid + 1;
}
}
return -1;
}
测试代码如下:
using System;
using System.IO;
using System.Diagnostics;
public class Timing
{
TimeSpan startingTime;
TimeSpan duration;
public Timing()
{
startingTime = new TimeSpan(0);
duration = new TimeSpan(0);
}
public void stopTime()
{
duration = Process.GetCurrentProcess().Threads[0].UserProcessorTime.Subtract(startingTime);
}
public void startTime()
{
GC.Collect();
GC.WaitForPendingFinalizers();
startingTime = Process.GetCurrentProcess().Threads[0].UserProcessorTime;
}
public TimeSpan getResult()
{
return duration;
}
}
class CArray
{
private int[] arr;
private int upper;
private int numElements;
public CArray(int size)
{
arr = new int[size];
upper = size - 1;
numElements = 0;
}
public void Insert(int item)
{
arr[numElements] = item;
numElements++;
}
public void DisplayElements()
{
for (int i = 0; i <= upper; i++)
Console.Write(arr[i] + " ");
}
public void Clear()
{
for (int i = 0; i <= upper; i++)
arr[i] = 0;
numElements = 0;
}
public void BubbleSort()
{
int temp;
for (int outer = upper; outer >= 1; outer--)
{
for (int inner = 0; inner <= outer - 1; inner++)
{
if ((int)arr[inner] > arr[inner + 1])
{
temp = arr[inner];
arr[inner] = arr[inner + 1];
arr[inner + 1] = temp;
}
}
}
}
public void SelectionSort()
{
int min, temp;
for (int outer = 0; outer <= upper; outer++)
{
min = outer;
for (int inner = outer + 1; inner <= upper; inner++)
if (arr[inner] < arr[min])
min = inner;
temp = arr[outer];
arr[outer] = arr[min];
arr[min] = temp;
}
}
public void InsertionSort()
{
int inner, temp;
for (int outer = 1; outer <= upper; outer++)
{
temp = arr[outer];
inner = outer;
while (inner > 0 && arr[inner - 1] >= temp)
{
arr[inner] = arr[inner - 1];
inner--;
}
arr[inner] = temp;
}
}
public int binSearch(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 (arr[mid] > value)
{
upperBound = mid - 1;
}
else
{
lowerBound = mid + 1;
}
}
return -1;
}
}
public class Chapter4
{
static void Main()
{
Random random = new Random();
CArray myNums = new CArray(9);
for (int i = 0; i < 9; i++)
myNums.Insert(random.Next(100));
myNums.BubbleSort();
Console.WriteLine();
myNums.DisplayElements();
int position = myNums.binSearch(77);
if (position >= 1)
Console.WriteLine("item was found");
else
Console.WriteLine("Not in the array");
Console.Read();
}
}
递归的二分法查找
虽然上一版本的二分法查找算法很正确,但是它并不是解决问题的真正的方案。二分法查找算法是一个递归的算法,通过不断的把数据集分为两部分直到找到要找的值,每一次划分,都将原问题分为更小。这样看问题的方式使得我们发现了递归的算法来执行二分法查找。
为了实现的递归的二分法查找,我们将改动一些代码。代码如下:
public int RbinSearch(int value, int lower, int upper)
{
if(lower>upper)
return -1;
else
{
int mid;
mid = (int)(upper + lower) / 2;
if (value < arr[mid])
RbinSeach(value, lower, mid - 1);
else if (value == arr[mid])
return mid;
else
RbinSearch(value, mid + 1, upper);
}
}
这个递归查找算法的一个主要的问题就是效率。当数据集中有1000个数据时,排序和递归比一般的二分法要慢10倍.
所以,递归算法常常用在其他地方,但是你要记住:当你实现一个递归的算法时,你都可以把它实现为一个一般的算法从而来比较二者的效率。
最后,在我们结束讨论二分法查找之间,我们要提醒一下,Array类有一个内置的二分查找方法。它要接受两个参数:数组名称和要查找的数据,这个方法返回数据在数组中的位置,-1就是没有找到。
为了说明这个方法,下面将改写代码:如:
public int Bsearch(int value)
{
return Array.BinarySearch(arr, value);
}