数据结构与算法专题——第九题 外排序
說到排序,大家第一反應基本上是內排序,是的,算法嘛,玩的就是內存,然而內存是有限制的,總有裝不下的那一天,此時就可以來玩玩外排序,當然在我看來,外排序考驗的是一個程序員的架構能力,而不僅僅局限于排序這個層次。
一:N路歸并排序
1.概序
我們知道算法中有一種叫做分治思想,一個大問題我們可以采取分而治之,各個突破,當子問題解決了,大問題也就KO了,還有一點我們知道內排序的歸并排序是采用二路歸并的,因為分治后有LogN層,每層兩路歸并需要N的時候,最后復雜度為NlogN,那么外排序我們可以將這個 “二” 擴大到 M,也就是將一個大文件分成M個小文件,每個小文件是有序的,然后對應在內存中我們開M個優先隊列,每個隊列從對應編號的文件中讀取TopN條記錄,然后我們從M路隊列中各取一個數字進入中轉站隊列,并將該數字打上隊列編號標記,當從中轉站出來的最小數字就是我們最后要排序的數字之一,因為該數字打上了隊列編號,所以方便我們通知對應的編號隊列繼續出數字進入中轉站隊列,可以看出中轉站一直保存了M個記錄,當中轉站中的所有數字都出隊完畢,則外排序結束。如果大家有點蒙的話,我再配合一張圖,相信大家就會一目了然,這考驗的是我們的架構能力。
圖中這里有個Batch容器,這個容器我是基于性能考慮的,當batch=n時,我們定時刷新到文件中,保證內存有足夠的空間。
2.構建
1) 生成數據
這個基本沒什么好說的,采用隨機數生成n條記錄。
#region 隨機生成數據 /// <summary> /// 隨機生成數據 ///<param name="max">執行生成的數據上線</param> /// </summary> public static void CreateData(int max){var sw = new StreamWriter(Environment.CurrentDirectory + "//demo.txt");for (int i = 0; i < max; i++){Thread.Sleep(2);var rand = new Random((int)DateTime.Now.Ticks).Next(0, int.MaxValue >> 3);sw.WriteLine(rand);}sw.Close(); } #endregion2) 切分數據
根據實際情況我們來決定到底要分成多少個小文件,并且小文件的數據必須是有序的,小文件的個數也對應這內存中有多少個優先隊列。
#region 將數據進行分份 /// <summary> /// 將數據進行分份 /// <param name="size">每頁要顯示的條數</param> /// </summary> public static int Split(int size){//文件總記錄數int totalCount = 0;//每一份文件存放 size 條 記錄List<int> small = new List<int>();var sr = new StreamReader((Environment.CurrentDirectory + "//demo.txt"));var pageSize = size;int pageCount = 0;int pageIndex = 0;while (true){var line = sr.ReadLine();if (!string.IsNullOrEmpty(line)){totalCount++;//加入小集合中small.Add(Convert.ToInt32(line));//說明已經到達指定的 size 條數了if (totalCount % pageSize == 0){pageIndex = totalCount / pageSize;small = small.OrderBy(i => i).Select(i => i).ToList();File.WriteAllLines(Environment.CurrentDirectory + "//" + pageIndex + ".txt", small.Select(i => i.ToString()));small.Clear();}}else{//說明已經讀完了,將剩余的small記錄寫入到文件中pageCount = (int)Math.Ceiling((double)totalCount / pageSize);small = small.OrderBy(i => i).Select(i => i).ToList();File.WriteAllLines(Environment.CurrentDirectory + "//" + pageCount + ".txt", small.Select(i => i.ToString()));break;}}return pageCount; } #endregion3) 加入隊列
我們知道內存隊列存放的只是小文件的topN條記錄,當內存隊列為空時,我們需要再次從小文件中讀取下一批的TopN條數據,然后放入中轉站繼續進行比較。
#region 將數據加入指定編號隊列/// <summary>/// 將數據加入指定編號隊列/// </summary>/// <param name="i">隊列編號</param>/// <param name="skip">文件中跳過的條數</param>/// <param name="list"></param>/// <param name="top">需要每次讀取的條數</param>public static void AddQueue(int i, List<PriorityQueue<int?>> list, ref int[] skip, int top = 100){var result = File.ReadAllLines((Environment.CurrentDirectory + "//" + (i + 1) + ".txt")).Skip(skip[i]).Take(top).Select(j => Convert.ToInt32(j));//加入到集合中foreach (var item in result)list[i].Eequeue(null, item);//將個數累計到skip中,表示下次要跳過的記錄數skip[i] += result.Count();}#endregion4) 測試
最后我們來測試一下:
數據量:short.MaxValue。
內存存放量:1200。
在這種場景下,我們決定每個文件放1000條,也就有33個小文件,也就有33個內存隊列,每個隊列取Top100條,Batch=500時刷新硬盤,中轉站存放332個數字(因為入中轉站時打上了隊列標記),最后內存活動最大總數為:sum=33100+500+66=896<1200。
時間復雜度為N*logN。當然這個“閥值”,我們可以再仔細微調。
總結
以上是生活随笔為你收集整理的数据结构与算法专题——第九题 外排序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不喜欢 merge 分叉,那就用 reb
- 下一篇: 翠香猕猴桃 和 薄皮核桃,快来下单