List<T>集合实现二叉查找算法
1.需求
如果需要在集合中匹配出某个元素的序列,并返回这个元素在集合中出现的次数。
2.线性方式
在没有更好的算法策略之前,我们通常实现的方式如下:
1 List<string> dataList = new List<string>() { "张三","李四", "李四", "李四", "李四", "王五", "李四"}; 2 3 string searchValue = "李四"; 4 List<string> matchList = new List<string>(); 5 foreach (var item in dataList) 6 { 7 if (item.Equals(searchValue)) 8 { 9 matchList.Add(item); 10 } 11 } 12 13 string msg = string.Format("班上叫李四的人有{0}个", matchList.Count); 14 Console.WriteLine(msg);
当然对于这种简易面向过程性的代码,我们也可以使用C#的特性将封装和通用性发挥的淋漓尽致,实现如下:
1 namespace ConsoleApplication1 2 { 3 4 public static class CollectionExtMethods 5 { 6 7 //从集合中匹配出符合条件的元素序列 8 public static IEnumerable<T> GetAll<T>(this List<T> myList, T serachValue) => 9 myList.Where(t => t.Equals(serachValue)); 10 11 //统计某个元素在集合中出现的次数 12 public static int CountAll<T>(this List<T> myList, T searchValue) => 13 myList.GetAll(searchValue).Count(); 14 15 16 } 17 18 class Program 19 { 20 21 static void Main(string[] args) 22 { 23 24 List<string> dataList = new List<string>() { "张三","李四", "李四", "李四", "李四", "王五", "李四"}; 25 26 int count = dataList.CountAll("李四"); 27 28 string msg = string.Format("班上叫李四的人有{0}个", count); 29 Console.WriteLine(msg); 30 31 32 string debug = string.Empty; 33 } 34 } 35 }
以上的代码将函数封装到了一个静态类中作为一个扩展的泛型方法来实现,这样调用起来就很方便(直接对象"."),另外使用了linq的简单函数来实现匹配和统计的需求,并使用了“表达式—函数体成员语法”的形式,是代码更加简洁。
这种实现逻辑相比大多数人都可想到,因为它是线性的直观的,类似于“数数”一样。但是这种方式对于数据量庞大的情况是会显得低效的,因为我们要遍历集合中每一个元素,如果集合有N个元素我们就要查找N次,下面我将提供一个更优的方式—“二叉查找”
3.二叉查找
封装:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace ConsoleApplication1 8 { 9 public static class CollectionExtMethods 10 { 11 12 //从集合中匹配获取指定元素的序列 13 public static T[] BinarySearchGetAll<T>(this List<T> myList, T searchValue) 14 { 15 //算法关键依赖项(此算法只针对有序的集合) 16 myList.Sort(); 17 18 //output输出集合 19 List<T> retObjs = new List<T>(); 20 21 //获取要查找元素在集合中的索引位置(BinarySearch方法一般会查找出中间段的位置) 22 int center = myList.BinarySearch(searchValue); 23 24 //如果没有匹配的元素,则直接返回没有任何元素的集合 25 if(center<= 0) return (retObjs.ToArray()); 26 27 //如果元素存在,则添加到输出集合中 28 retObjs.Add(myList[center]); 29 30 //往左查找 31 int left = center; 32 while (left>0&&myList[left-1].Equals(searchValue)) //当前元素和邻近的左边元素进行匹配 33 { 34 left -= 1;//进行左位移,逐个比较 35 retObjs.Add(myList[left]);//将相对的邻近元素添加到输出集合 36 } 37 38 //往右查找 39 int right = center; 40 while (right<myList.Count-1&&myList[right+1].Equals(searchValue))//当前元素和邻近的右边元素进行匹配 41 { 42 right += 1; 43 retObjs.Add(myList[right]);//将相对的邻近元素添加到输出集合 44 } 45 46 47 return (retObjs.ToArray()); 48 } // END BinarySearchGetAll() 49 50 51 //统计某个元素在集合中出现的次数 52 public static int BinarySearchCountAll<T>(this List<T> myList, T searchValue) => 53 myList.BinarySearchGetAll(searchValue).Count(); 54 55 } 56 }
调用:
1 static void Main(string[] args) 2 { 3 4 List<string> dataList = new List<string>() { "张三","李四", "李四", "李四", "李四", "王五", "李四"}; 5 6 int count = dataList.BinarySearchCountAll("李四"); 7 8 string msg = string.Format("班上叫李四的人有{0}个", count); 9 Console.WriteLine(msg); 10 11 12 string debug = string.Empty; 13 }
总结
“二叉查找”的查找概念就像它的名字一样,选取一个中间点,往两端找(你可以做一个剪刀手就是它的规律)
在数据量大的时候“二叉查找”的效率要高于“线性方式”,经过调试后你会发现,二叉查找不会查找所有的元素,而只会查找邻近相等的元素,一旦遇到不相等则会终止。
这一特点也暴露出了“二叉查找”的劣势,就是它必须要求集合是有序的,否则查找的结果将会是错误的。
感想
一个简简单单需求,就可以绕出奇妙的“二叉查找”算法,也许这就是编程魅力的所在,一种需求有无限的实现可能。另外,作为程序员不能往往去跟风追求框架、插件,而是要修炼好内功(算法、数据结构、设计模式)“这才是编程之道”
关注我,为你持续分享更多编码实用技巧。
知识改变命运