經典演算法題每日演練——第二十五題 塊狀連結串列

一線碼農發表於2014-03-04

  在資料結構的世界裡,我們會認識各種各樣的資料結構,每一種資料結構都能解決相應領域的問題,每一種資料結構都像

是降龍十八掌中的某一掌,掌掌斃命。。。 當然每個資料結構,有他的優點,必然就有它的缺點,那麼如何創造一種資料結構

來將某兩種資料結構進行揚長避短,那就非常完美了。這樣的資料結構也有很多,比如:雙端佇列,還有就是今天講的 塊狀連結串列,

   我們都知道 陣列 具有 O(1)的查詢時間,O(N)的刪除,O(N)的插入。。。

                   連結串列 具有 O(N)的查詢時間,O(1)的刪除,O(1)的插入。。。 

   那麼現在我們就有想法了,何不讓“連結串列”和“陣列”結合起來,來一起均攤CURD的時間,做法將陣列進行分塊,然後用指標相連線,

比如我有N=100個元素,那麼最理想情況下,我就可以將陣列分成x=10段,每段b=10個元素(排好序),那麼我可以用√N的時

間找到段,因為段中的元素是已經排好序的,所以可以用lg√N的時間找到段中的元素,那麼最理想的複雜度為√N+lg√N≈√N。。。

   下面我們看看怎麼具體使用:

一:結構定義

    這個比較簡單,我們在每個連結串列節點中定義一個 頭指標,尾指標 和 一個陣列節點。

 1         public class BlockLinkNode
 2         {
 3             /// <summary>
 4             /// 指向前一個節點的指標
 5             /// </summary>
 6             public BlockLinkNode prev;
 7 
 8             /// <summary>
 9             /// 指向後一個節點的指標
10             /// </summary>
11             public BlockLinkNode next;
12 
13             /// <summary>
14             /// 連結串列中的陣列
15             /// </summary>
16             public List<int> list;
17         }

 

二: 插入

  剛才也說了,每個連結串列節點的資料是一個陣列塊,那麼問題來了,我們是根據什麼將陣列切開呢?總不能將所有的資料都放在一個

連結串列的節點吧,那就退化成陣列了,在理想的情況下,為了保持√N的陣列個數,所以我們定了一個界限2√N,當連結串列中的節點陣列

的個數超過2√N的時候,當下次插入資料的時候,我們有兩種做法:

① 在元素的陣列插入處,將當前陣列切開,插入元素處之前為一個連結串列節點,插入元素後為一個連結串列節點。

② 將元素插入陣列後,將陣列從中間位置切開。

 1         /// <summary>
 2         /// 新增元素只會進行塊狀連結串列的分裂
 3         /// </summary>
 4         /// <param name="node"></param>
 5         /// <param name="num"></param>
 6         /// <returns></returns>
 7         private BlockLinkNode Add(BlockLinkNode node, int num)
 8         {
 9             if (node == null)
10             {
11                 return node;
12             }
13             else
14             {
15                 /*
16                  *  第一步:找到指定的節點
17                  */
18                 if (node.list.Count == 0)
19                 {
20                     node.list.Add(num);
21 
22                     total = total + 1;
23 
24                     return node;
25                 }
26 
27                 //下一步:再比較是否應該分裂塊
28                 var blockLen = (int)Math.Ceiling(Math.Sqrt(total)) * 2;
29 
30                 //如果該節點的陣列的最後位置值大於插入值,則此時我們找到了連結串列的插入節點,
31                 //或者該節點的next=null,說明是最後一個節點,此時也要判斷是否要裂開
32                 if (node.list[node.list.Count - 1] > num || node.next == null)
33                 {
34                     node.list.Add(num);
35 
36                     //最後進行排序下,當然可以用插入排序解決,O(N)搞定
37                     node.list = node.list.OrderBy(i => i).ToList();
38 
39                     //如果該陣列裡面的個數大於2*blockLen,說明已經過大了,此時需要對半分裂
40                     if (node.list.Count > blockLen)
41                     {
42                         //先將資料插入到資料庫
43                         var mid = node.list.Count / 2;
44 
45                         //分裂處的前段部分
46                         var firstList = new List<int>();
47 
48                         //分裂後的後段部分
49                         var lastList = new List<int>();
50 
51                         //可以在插入點處分裂,也可以對半分裂(這裡對半分裂)
52                         firstList.AddRange(node.list.Take(mid));
53                         lastList.AddRange(node.list.Skip(mid).Take(node.list.Count - mid));
54 
55 
56                         //開始分裂節點,需要新開闢一個新節點
57                         var nNode = new BlockLinkNode();
58 
59                         nNode.list = lastList;
60                         nNode.next = node.next;
61                         nNode.prev = node;
62 
63                         //改變當前節點的next和list
64                         node.list = firstList;
65                         node.next = nNode;
66                     }
67 
68                     total = total + 1;
69 
70                     return node;
71                 }
72 
73                 return Add(node.next, num);
74             }
75         }

 

二:刪除

   跟插入道理一樣,既然有裂開,就有合併,同樣也定義了一個界限值√N /2  ,當連結串列陣列節點的陣列個數小於這個界限值

的時候,需要將此節點和後面的連結串列節點進行合併。

 1         /// <summary>
 2         /// 從塊狀連結串列中移除元素,涉及到合併
 3         /// </summary>
 4         /// <param name="node"></param>
 5         /// <param name="num"></param>
 6         /// <returns></returns>
 7         private BlockLinkNode Remove(BlockLinkNode node, int num)
 8         {
 9             if (node == null)
10             {
11                 return node;
12             }
13             else
14             {
15                 //第一步: 判斷刪除元素是否在該節點內
16                 if (node.list.Count > 0 && num >= node.list[0] && num <= node.list[node.list.Count - 1])
17                 {
18                     //定義改節點的目的在於防止remove方法假刪除的情況發生
19                     var prevcount = node.list.Count;
20 
21                     node.list.Remove(num);
22 
23                     total = total - (prevcount - node.list.Count);
24 
25                     //下一步: 判斷是否需要合併節點
26                     var blockLen = (int)Math.Ceiling(Math.Sqrt(total) / 2);
27 
28                     //如果當前節點的陣列個數小於 blocklen的話,那麼此時改節點需要和後一個節點進行合併
29                     //如果該節點時尾節點,則放棄合併
30                     if (node.list.Count < blockLen)
31                     {
32                         if (node.next != null)
33                         {
34                             node.list.AddRange(node.next.list);
35 
36                             //如果下一個節點的下一個節點不為null,則將下下個節點的prev賦值
37                             if (node.next.next != null)
38                                 node.next.next.prev = node;
39 
40                             node.next = node.next.next;
41                         }
42                         else
43                         {
44                             //最後一個節點不需要合併,如果list=0,則直接剔除該節點
45                             if (node.list.Count == 0)
46                             {
47                                 if (node.prev != null)
48                                     node.prev.next = null;
49 
50                                 node = null;
51                             }
52                         }
53                     }
54 
55                     return node;
56                 }
57 
58                 return Remove(node.next, num);
59             }
60         }

 

四: 查詢

    在理想的情況下,我們都控制在√N,然後就可以用√N的時間找到區塊,lg√N的時間找到區塊中的指定值,當然也有人在查詢

的時候做 連結串列的合併和分裂,這個就有點像伸展樹一樣,在查詢的時候動態調整,拼的是均攤情況下的複雜度。這裡順便提醒你一

下,其實你也可以這麼做。。。

 1         public string Get(int num)
 2         {
 3             var blockIndex = 0;
 4             var arrIndex = 0;
 5 
 6             var temp = blockLinkNode;
 7 
 8             while (temp != null)
 9             {
10                 //判斷是否在該區間內
11                 if (temp.list.Count > 0 && num >= temp.list[0] && num <= temp.list[temp.list.Count - 1])
12                 {
13                     arrIndex = temp.list.IndexOf(num);
14 
15                     return string.Format("當前資料在第{0}塊中的{1}個位置", blockIndex, arrIndex);
16                 }
17 
18                 blockIndex = blockIndex + 1;
19                 temp = temp.next;
20             }
21 
22             return string.Empty;
23         }

 

好了,CURD都分析好了,到這裡大家應該對 塊狀連結串列 有個大概的認識了吧,這個程式碼是我下午抽閒寫的,沒有仔細測試,

最後是總的程式碼:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 
  6 namespace ConsoleApplication3
  7 {
  8     class Program
  9     {
 10         static void Main(string[] args)
 11         {
 12             List<int> list = new List<int>() { 8959, 30290, 18854, 7418, 28749, 17313, 5877, 27208, 15771, 4335 };
 13 
 14             //list.Clear();
 15 
 16             //List<int> list = new List<int>();
 17 
 18             //for (int i = 0; i < 100; i++)
 19             //{
 20             //    var num = new Random((int)DateTime.Now.Ticks).Next(0, short.MaxValue);
 21 
 22             //    System.Threading.Thread.Sleep(1);
 23 
 24             //    list.Add(num);
 25             //}
 26 
 27 
 28             BlockLinkList blockList = new BlockLinkList();
 29 
 30             foreach (var item in list)
 31             {
 32                 blockList.Add(item);
 33             }
 34 
 35             //var b = blockList.IsExist(333);
 36             //blockList.GetCount();
 37 
 38             Console.WriteLine(blockList.Get(27208));
 39 
 40 
 41             #region MyRegion
 42             ////隨機刪除150個元素
 43             //for (int i = 0; i < 5000; i++)
 44             //{
 45             //    var rand = new Random((int)DateTime.Now.Ticks).Next(0, list.Count);
 46 
 47             //    System.Threading.Thread.Sleep(2);
 48 
 49             //    Console.WriteLine("\n**************************************\n當前要刪除元素:{0}", list[rand]);
 50 
 51             //    blockList.Remove(list[rand]);
 52 
 53             //    Console.WriteLine("\n\n");
 54 
 55             //    if (blockList.GetCount() == 0)
 56             //    {
 57             //        Console.Read();
 58             //        return;
 59             //    }
 60             //} 
 61             #endregion
 62 
 63             Console.Read();
 64         }
 65     }
 66 
 67     public class BlockLinkList
 68     {
 69         BlockLinkNode blockLinkNode = null;
 70 
 71         public BlockLinkList()
 72         {
 73             //初始化節點
 74             blockLinkNode = new BlockLinkNode()
 75             {
 76                 list = new List<int>(),
 77                 next = null,
 78                 prev = null
 79             };
 80         }
 81 
 82         /// <summary>
 83         /// 定義塊狀連結串列的總長度
 84         /// </summary>
 85         private int total;
 86 
 87         public class BlockLinkNode
 88         {
 89             /// <summary>
 90             /// 指向前一個節點的指標
 91             /// </summary>
 92             public BlockLinkNode prev;
 93 
 94             /// <summary>
 95             /// 指向後一個節點的指標
 96             /// </summary>
 97             public BlockLinkNode next;
 98 
 99             /// <summary>
100             /// 連結串列中的陣列
101             /// </summary>
102             public List<int> list;
103         }
104 
105         /// <summary>
106         /// 判斷指定元素是否存在
107         /// </summary>
108         /// <param name="num"></param>
109         /// <returns></returns>
110         public bool IsExist(int num)
111         {
112             var isExist = false;
113 
114             var temp = blockLinkNode;
115 
116             while (temp != null)
117             {
118                 //判斷是否在該區間內
119                 if (temp.list.Count > 0 && num >= temp.list[0] && num <= temp.list[temp.list.Count - 1])
120                 {
121                     isExist = temp.list.IndexOf(num) > 0 ? true : false;
122 
123                     return isExist;
124                 }
125 
126                 temp = temp.next;
127             }
128 
129             return isExist;
130         }
131 
132         public string Get(int num)
133         {
134             var blockIndex = 0;
135             var arrIndex = 0;
136 
137             var temp = blockLinkNode;
138 
139             while (temp != null)
140             {
141                 //判斷是否在該區間內
142                 if (temp.list.Count > 0 && num >= temp.list[0] && num <= temp.list[temp.list.Count - 1])
143                 {
144                     arrIndex = temp.list.IndexOf(num);
145 
146                     return string.Format("當前資料在第{0}塊中的{1}個位置", blockIndex, arrIndex);
147                 }
148 
149                 blockIndex = blockIndex + 1;
150                 temp = temp.next;
151             }
152 
153             return string.Empty;
154         }
155 
156         /// <summary>
157         /// 將元素加入到塊狀連結串列中
158         /// </summary>
159         /// <param name="num"></param>
160         public BlockLinkNode Add(int num)
161         {
162             return Add(blockLinkNode, num);
163         }
164 
165         /// <summary>
166         /// 新增元素只會進行塊狀連結串列的分裂
167         /// </summary>
168         /// <param name="node"></param>
169         /// <param name="num"></param>
170         /// <returns></returns>
171         private BlockLinkNode Add(BlockLinkNode node, int num)
172         {
173             if (node == null)
174             {
175                 return node;
176             }
177             else
178             {
179                 /*
180                  *  第一步:找到指定的節點
181                  */
182                 if (node.list.Count == 0)
183                 {
184                     node.list.Add(num);
185 
186                     total = total + 1;
187 
188                     return node;
189                 }
190 
191                 //下一步:再比較是否應該分裂塊
192                 var blockLen = (int)Math.Ceiling(Math.Sqrt(total)) * 2;
193 
194                 //如果該節點的陣列的最後位置值大於插入值,則此時我們找到了連結串列的插入節點,
195                 //或者該節點的next=null,說明是最後一個節點,此時也要判斷是否要裂開
196                 if (node.list[node.list.Count - 1] > num || node.next == null)
197                 {
198                     node.list.Add(num);
199 
200                     //最後進行排序下,當然可以用插入排序解決,O(N)搞定
201                     node.list = node.list.OrderBy(i => i).ToList();
202 
203                     //如果該陣列裡面的個數大於2*blockLen,說明已經過大了,此時需要對半分裂
204                     if (node.list.Count > blockLen)
205                     {
206                         //先將資料插入到資料庫
207                         var mid = node.list.Count / 2;
208 
209                         //分裂處的前段部分
210                         var firstList = new List<int>();
211 
212                         //分裂後的後段部分
213                         var lastList = new List<int>();
214 
215                         //可以在插入點處分裂,也可以對半分裂(這裡對半分裂)
216                         firstList.AddRange(node.list.Take(mid));
217                         lastList.AddRange(node.list.Skip(mid).Take(node.list.Count - mid));
218 
219 
220                         //開始分裂節點,需要新開闢一個新節點
221                         var nNode = new BlockLinkNode();
222 
223                         nNode.list = lastList;
224                         nNode.next = node.next;
225                         nNode.prev = node;
226 
227                         //改變當前節點的next和list
228                         node.list = firstList;
229                         node.next = nNode;
230                     }
231 
232                     total = total + 1;
233 
234                     return node;
235                 }
236 
237                 return Add(node.next, num);
238             }
239         }
240 
241         /// <summary>
242         /// 從塊狀連結串列中移除元素
243         /// </summary>
244         /// <param name="num"></param>
245         /// <returns></returns>
246         public BlockLinkNode Remove(int num)
247         {
248             return Remove(blockLinkNode, num);
249         }
250 
251         /// <summary>
252         /// 從塊狀連結串列中移除元素,涉及到合併
253         /// </summary>
254         /// <param name="node"></param>
255         /// <param name="num"></param>
256         /// <returns></returns>
257         private BlockLinkNode Remove(BlockLinkNode node, int num)
258         {
259             if (node == null)
260             {
261                 return node;
262             }
263             else
264             {
265                 //第一步: 判斷刪除元素是否在該節點內
266                 if (node.list.Count > 0 && num >= node.list[0] && num <= node.list[node.list.Count - 1])
267                 {
268                     //定義改節點的目的在於防止remove方法假刪除的情況發生
269                     var prevcount = node.list.Count;
270 
271                     node.list.Remove(num);
272 
273                     total = total - (prevcount - node.list.Count);
274 
275                     //下一步: 判斷是否需要合併節點
276                     var blockLen = (int)Math.Ceiling(Math.Sqrt(total) / 2);
277 
278                     //如果當前節點的陣列個數小於 blocklen的話,那麼此時改節點需要和後一個節點進行合併
279                     //如果該節點時尾節點,則放棄合併
280                     if (node.list.Count < blockLen)
281                     {
282                         if (node.next != null)
283                         {
284                             node.list.AddRange(node.next.list);
285 
286                             //如果下一個節點的下一個節點不為null,則將下下個節點的prev賦值
287                             if (node.next.next != null)
288                                 node.next.next.prev = node;
289 
290                             node.next = node.next.next;
291                         }
292                         else
293                         {
294                             //最後一個節點不需要合併,如果list=0,則直接剔除該節點
295                             if (node.list.Count == 0)
296                             {
297                                 if (node.prev != null)
298                                     node.prev.next = null;
299 
300                                 node = null;
301                             }
302                         }
303                     }
304 
305                     return node;
306                 }
307 
308                 return Remove(node.next, num);
309             }
310         }
311 
312         /// <summary>
313         /// 獲取塊狀連結串列中的所有個數
314         /// </summary>
315         /// <returns></returns>
316         public int GetCount()
317         {
318             int count = 0;
319 
320             var temp = blockLinkNode;
321 
322             Console.Write("各節點資料個數為:");
323 
324             while (temp != null)
325             {
326                 count += temp.list.Count;
327 
328                 Console.Write(temp.list.Count + ",");
329 
330                 temp = temp.next;
331             }
332 
333             Console.WriteLine("總共有:{0} 個元素", count);
334 
335             return count;
336         }
337     }
338 }
View Code

 

 

相關文章