List<T>集合實現二叉查詢演算法
阿新 • • 發佈:2021-06-27
1.需求
如果需要在集合中匹配出某個元素的序列,並返回這個元素在集合中出現的次數。
2.線性方式
在沒有更好的演算法策略之前,我們通常實現的方式如下:
1 List<string> dataList = new List<string>() { "張三","李四", "李四", "李四", "李四", "王五", "李四"}; 2 3 string searchValue = "李四"; 4 List<string> matchList = new List<string>(); 5foreach (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 }
總結
“二叉查詢”的查詢概念就像它的名字一樣,選取一箇中間點,往兩端找(你可以做一個剪刀手就是它的規律)
在資料量大的時候“二叉查詢”的效率要高於“線性方式”,經過除錯後你會發現,二叉查詢不會查詢所有的元素,而只會查詢鄰近相等的元素,一旦遇到不相等則會終止。
這一特點也暴露出了“二叉查詢”的劣勢,就是它必須要求集合是有序的,否則查詢的結果將會是錯誤的。
感想
一個簡簡單單需求,就可以繞出奇妙的“二叉查詢”演算法,也許這就是程式設計魅力的所在,一種需求有無限的實現可能。另外,作為程式設計師不能往往去跟風追求框架、外掛,而是要修煉好內功(演算法、資料結構、設計模式)“這才是程式設計之道”
關注我,為你持續分享更多編碼實用技巧。