棧(Stack) --- C# 自定義和微軟官方的區別

暗香殘留發表於2021-05-09

最近在學習演算法基礎,本篇文章作為一個記錄,也算是一次實踐和總結。(順便也深入C#執行時學習一下)

 

目錄

1. 棧是什麼

2. Stack 自定義實現

3. Stack C#官方實現

4. 區別

5. 總結

 

1. 棧是什麼

  棧是一種特殊的線性表(資料邏輯結構中的一種,定義是其組成元素之間具有線性關係的一種線性結構),其插入和刪除操作只能在一端進行。

1 個人理解:
2 是一種最基本的資料結構,資料結構方面必須夯實的基礎知識

 特徵(也可以說是應用場景):

  後進先出(佇列則為先進先出)

2. Stack 自定義實現

Github: https://github.com/HadesKing/DataStructureAndAlgorithms

程式碼路徑:BasicDataStructure\src\LD.BasicDataStructures\Stacks

Stack實現方式有兩種:順序儲存和鏈式儲存(順序棧和鏈式棧)

為了便於使用,我對棧進行了抽象,形成了一個介面IStack

棧(Stack) --- C# 自定義和微軟官方的區別
 1 // Copyright (c) 劉迪. All rights reserved.
 2 //
 3 // Author: 劉迪
 4 // Create Time: 2021-04-30
 5 //
 6 // Modifier: xxx
 7 // Modifier time: xxx
 8 // Description: xxx
 9 //
10 // Modifier: xxx
11 // Modifier time: xxx
12 // Description: xxx
13 // 
14 // 
15 //
16 
17 using System;
18 
19 namespace LD.BasicDataStructures.Stacks
20 {
21     /// <summary>
22     /// 棧的介面 
23     /// </summary>
24     /// <typeparam name="T">type of data element</typeparam>
25     /// <remarks>
26     /// 主要用於定義棧的公共操作
27     /// </remarks>
28     public interface IStack<T>
29     {
30         /// <summary>
31         /// Gets the number of elements contained in the Stack.
32         /// </summary>
33         Int32 Count { get; }
34 
35         /// <summary>
36         /// Verify that it is empty.
37         /// </summary>
38         /// <returns>
39         /// <c>true</c> Is empty.
40         /// <c>false</c> Not is empty.
41         /// </returns>
42         bool IsEmpty();
43 
44         /// <summary>
45         /// Add a data element to the stack.
46         /// </summary>
47         /// <param name="element">value of data element</param>
48         /// <returns>
49         /// <c>true</c> The operation was successful.
50         /// <c>false</c> The operation failed.
51         /// </returns>
52         bool Push(T element);
53 
54         /// <summary>
55         /// Pop up a data element from the stack.If there is no data element in the stack, it returns null.
56         /// </summary>
57         /// <returns>
58         /// Data element being popped.If there is no data element in the stack, it returns null.
59         /// </returns>
60         T Pop();
61 
62         /// <summary>
63         /// Get a data element from the stack.If the stack is empty, return null.
64         /// </summary>
65         /// <returns>
66         /// Data element.If the stack is empty, return null
67         /// </returns>
68         T Get();
69 
70     }
71 }
IStack

順序棧的實現

棧(Stack) --- C# 自定義和微軟官方的區別
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6 
  7 // Copyright (c) 劉迪. All rights reserved.
  8 //
  9 // Author: 劉迪
 10 // Create Time: 2021-05-01 
 11 //
 12 // Modifier: xxx
 13 // Modifier time: xxx
 14 // Description: xxx
 15 //
 16 // Modifier: xxx
 17 // Modifier time: xxx
 18 // Description: xxx
 19 // 
 20 // 
 21 //
 22 
 23 namespace LD.BasicDataStructures.Stacks
 24 {
 25     /// <summary>
 26     /// Implementation of sequential stack
 27     /// </summary>
 28     /// <typeparam name="T">Type of data</typeparam>
 29     public sealed class SequentialStack<T> : IStack<SequentialNode<T>>
 30     {
 31         private SequentialNode<T>[] m_sequentialNodes = null;
 32         private Int32 m_dataElementNumber = 0;
 33 
 34         /// <summary>
 35         /// 建構函式
 36         /// </summary>
 37         public SequentialStack() : this(10)
 38         {
 39         }
 40 
 41         /// <summary>
 42         /// 建構函式
 43         /// </summary>
 44         /// <param name="capacity">Stack capacity</param>
 45         public SequentialStack(UInt32 capacity)
 46         {
 47             m_sequentialNodes = new SequentialNode<T>[capacity];
 48         }
 49 
 50         /// <summary>
 51         /// Gets the number of elements contained in the Stack.
 52         /// </summary>
 53         public Int32 Count => m_dataElementNumber;
 54 
 55         /// <summary>
 56         /// Verify that it is empty.
 57         /// </summary>
 58         /// <returns>
 59         /// <c>true</c> Is empty.
 60         /// <c>false</c> Not is empty.
 61         /// </returns>
 62         public bool IsEmpty()
 63         {
 64             return m_dataElementNumber == 0;
 65         }
 66 
 67         /// <summary>
 68         /// Add a data element to the stack.
 69         /// </summary>
 70         /// <param name="element">value of data element</param>
 71         /// <returns>
 72         /// <c>true</c> The operation was successful.
 73         /// <c>false</c> The operation failed.
 74         /// </returns>
 75         public bool Push(SequentialNode<T> element)
 76         {
 77             bool result = false;
 78             if (null != element)
 79             {
 80                 if (m_dataElementNumber != m_sequentialNodes.Length)
 81                 {
 82                     m_sequentialNodes[m_dataElementNumber] = element;
 83                     m_dataElementNumber++;
 84                     result = true;
 85                 }
 86                 else
 87                 {
 88                     //擴容
 89                     var tmp = m_sequentialNodes;
 90                     m_sequentialNodes = new SequentialNode<T>[2 * m_dataElementNumber];
 91                     tmp.CopyTo(m_sequentialNodes, 0);
 92                     result = Push(element);
 93                 }
 94             }
 95 
 96             return result;
 97         }
 98 
 99         /// <summary>
100         /// Pop up a data element from the stack.
101         /// If there is no data element in the stack, it returns null.
102         /// </summary>
103         /// <returns>
104         /// Data element being popped.If there is no data element in the stack, it returns null.
105         /// </returns>
106         public SequentialNode<T> Pop()
107         {
108             if (0 != m_dataElementNumber)
109             {
110                 m_dataElementNumber--;
111                 return m_sequentialNodes[m_dataElementNumber];
112             }
113 
114             return null;
115         }
116 
117         /// <summary>
118         /// Get a data element from the stack.If the stack is empty, return null.
119         /// </summary>
120         /// <returns>
121         /// Data element.If the stack is empty, return null
122         /// </returns>
123         public SequentialNode<T> Get()
124         {
125             if (0 != m_dataElementNumber)
126             {
127                 return m_sequentialNodes[m_dataElementNumber - 1];
128             }
129 
130             return null;
131         }
132 
133     }
134 }
SequentialStack(順序棧)

鏈式棧的實現

棧(Stack) --- C# 自定義和微軟官方的區別
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6 
  7 // Copyright (c) 劉迪. All rights reserved.
  8 //
  9 // Author: 劉迪
 10 // Create Time: 2021-05-01 
 11 //
 12 // Modifier: xxx
 13 // Modifier time: xxx
 14 // Description: xxx
 15 //
 16 // Modifier: xxx
 17 // Modifier time: xxx
 18 // Description: xxx
 19 // 
 20 // 
 21 //
 22 
 23 namespace LD.BasicDataStructures.Stacks
 24 {
 25     /// <summary>
 26     /// Implementation of chain storage stack
 27     /// </summary>
 28     /// <typeparam name="T">Type of data</typeparam>
 29     public sealed class LinkedStack<T> : IStack<SinglyLinkedNode<T>>
 30     {
 31 
 32         private SinglyLinkedNode<T> m_topNode = null;
 33         private Int32 m_count = 0;
 34 
 35         /// <summary>
 36         /// Gets the number of elements contained in the Stack.
 37         /// </summary>
 38         public Int32 Count => m_count;
 39 
 40         public LinkedStack()
 41         {
 42 
 43         }
 44 
 45         /// <summary>
 46         /// Verify that it is empty.
 47         /// </summary>
 48         /// <returns>
 49         /// <c>true</c> Is empty.
 50         /// <c>false</c> Not is empty.
 51         /// </returns>
 52         public bool IsEmpty()
 53         {
 54             return null == m_topNode;
 55         }
 56 
 57         /// <summary>
 58         /// Add a data element to the stack.
 59         /// </summary>
 60         /// <param name="element">value of data element</param>
 61         /// <returns>
 62         /// <c>true</c> The operation was successful.
 63         /// <c>false</c> The operation failed.
 64         /// </returns>
 65         public bool Push(SinglyLinkedNode<T> element)
 66         {
 67             bool result = false;
 68             if (null != element)
 69             {
 70                 if (null == m_topNode)
 71                 {
 72                     m_topNode = element;
 73                 }
 74                 else
 75                 {
 76                     var tmpNode = m_topNode;
 77                     element.Next = tmpNode;
 78                     m_topNode = element;
 79                 }
 80 
 81                 m_count++;
 82                 result = true;
 83             }
 84 
 85             return result;
 86         }
 87 
 88         /// <summary>
 89         /// Pop up a data element from the stack.If there is no data element in the stack, it returns null.
 90         /// </summary>
 91         /// <returns>
 92         /// Data element being popped.If there is no data element in the stack, it returns null.
 93         /// </returns>
 94         public SinglyLinkedNode<T> Pop()
 95         {
 96             SinglyLinkedNode<T> returnNode = null;
 97             if (null != m_topNode)
 98             {
 99                 returnNode = m_topNode;
100                 m_topNode = returnNode.Next;
101                 m_count--;
102             }
103 
104             return returnNode;
105         }
106 
107         /// <summary>
108         /// Get a data element from the stack.If the stack is empty, return null.
109         /// </summary>
110         /// <returns>
111         /// Data element.If the stack is empty, return null
112         /// </returns>
113         public SinglyLinkedNode<T> Get()
114         {
115             return m_topNode;
116         }
117 
118     }
119 }
LinkedStack(鏈式棧)

 

3. Stack C# 官方實現

Github: https://github.com/dotnet/runtime

Stack 檔案路徑:src\libraries\System.Collections\src\System\Collections\Generic\Stack.cs

棧(Stack) --- C# 自定義和微軟官方的區別
  1 // Licensed to the .NET Foundation under one or more agreements.
  2 // The .NET Foundation licenses this file to you under the MIT license.
  3 
  4 /*=============================================================================
  5 **
  6 **
  7 ** Purpose: An array implementation of a generic stack.
  8 **
  9 **
 10 =============================================================================*/
 11 
 12 using System.Diagnostics;
 13 using System.Diagnostics.CodeAnalysis;
 14 using System.Runtime.CompilerServices;
 15 
 16 namespace System.Collections.Generic
 17 {
 18     // A simple stack of objects.  Internally it is implemented as an array,
 19     // so Push can be O(n).  Pop is O(1).
 20 
 21     [DebuggerTypeProxy(typeof(StackDebugView<>))]
 22     [DebuggerDisplay("Count = {Count}")]
 23     [Serializable]
 24     [System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
 25     public class Stack<T> : IEnumerable<T>,
 26         System.Collections.ICollection,
 27         IReadOnlyCollection<T>
 28     {
 29         private T[] _array; // Storage for stack elements. Do not rename (binary serialization)
 30         private int _size; // Number of items in the stack. Do not rename (binary serialization)
 31         private int _version; // Used to keep enumerator in sync w/ collection. Do not rename (binary serialization)
 32 
 33         private const int DefaultCapacity = 4;
 34 
 35         public Stack()
 36         {
 37             _array = Array.Empty<T>();
 38         }
 39 
 40         // Create a stack with a specific initial capacity.  The initial capacity
 41         // must be a non-negative number.
 42         public Stack(int capacity)
 43         {
 44             if (capacity < 0)
 45                 throw new ArgumentOutOfRangeException(nameof(capacity), capacity, SR.ArgumentOutOfRange_NeedNonNegNum);
 46             _array = new T[capacity];
 47         }
 48 
 49         // Fills a Stack with the contents of a particular collection.  The items are
 50         // pushed onto the stack in the same order they are read by the enumerator.
 51         public Stack(IEnumerable<T> collection)
 52         {
 53             if (collection == null)
 54                 throw new ArgumentNullException(nameof(collection));
 55             _array = EnumerableHelpers.ToArray(collection, out _size);
 56         }
 57 
 58         public int Count
 59         {
 60             get { return _size; }
 61         }
 62 
 63         bool ICollection.IsSynchronized
 64         {
 65             get { return false; }
 66         }
 67 
 68         object ICollection.SyncRoot => this;
 69 
 70         // Removes all Objects from the Stack.
 71         public void Clear()
 72         {
 73             if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
 74             {
 75                 Array.Clear(_array, 0, _size); // Don't need to doc this but we clear the elements so that the gc can reclaim the references.
 76             }
 77             _size = 0;
 78             _version++;
 79         }
 80 
 81         public bool Contains(T item)
 82         {
 83             // Compare items using the default equality comparer
 84 
 85             // PERF: Internally Array.LastIndexOf calls
 86             // EqualityComparer<T>.Default.LastIndexOf, which
 87             // is specialized for different types. This
 88             // boosts performance since instead of making a
 89             // virtual method call each iteration of the loop,
 90             // via EqualityComparer<T>.Default.Equals, we
 91             // only make one virtual call to EqualityComparer.LastIndexOf.
 92 
 93             return _size != 0 && Array.LastIndexOf(_array, item, _size - 1) != -1;
 94         }
 95 
 96         // Copies the stack into an array.
 97         public void CopyTo(T[] array, int arrayIndex)
 98         {
 99             if (array == null)
100             {
101                 throw new ArgumentNullException(nameof(array));
102             }
103 
104             if (arrayIndex < 0 || arrayIndex > array.Length)
105             {
106                 throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, SR.ArgumentOutOfRange_Index);
107             }
108 
109             if (array.Length - arrayIndex < _size)
110             {
111                 throw new ArgumentException(SR.Argument_InvalidOffLen);
112             }
113 
114             Debug.Assert(array != _array);
115             int srcIndex = 0;
116             int dstIndex = arrayIndex + _size;
117             while (srcIndex < _size)
118             {
119                 array[--dstIndex] = _array[srcIndex++];
120             }
121         }
122 
123         void ICollection.CopyTo(Array array, int arrayIndex)
124         {
125             if (array == null)
126             {
127                 throw new ArgumentNullException(nameof(array));
128             }
129 
130             if (array.Rank != 1)
131             {
132                 throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
133             }
134 
135             if (array.GetLowerBound(0) != 0)
136             {
137                 throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array));
138             }
139 
140             if (arrayIndex < 0 || arrayIndex > array.Length)
141             {
142                 throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, SR.ArgumentOutOfRange_Index);
143             }
144 
145             if (array.Length - arrayIndex < _size)
146             {
147                 throw new ArgumentException(SR.Argument_InvalidOffLen);
148             }
149 
150             try
151             {
152                 Array.Copy(_array, 0, array, arrayIndex, _size);
153                 Array.Reverse(array, arrayIndex, _size);
154             }
155             catch (ArrayTypeMismatchException)
156             {
157                 throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array));
158             }
159         }
160 
161         // Returns an IEnumerator for this Stack.
162         public Enumerator GetEnumerator()
163         {
164             return new Enumerator(this);
165         }
166 
167         /// <internalonly/>
168         IEnumerator<T> IEnumerable<T>.GetEnumerator()
169         {
170             return new Enumerator(this);
171         }
172 
173         IEnumerator IEnumerable.GetEnumerator()
174         {
175             return new Enumerator(this);
176         }
177 
178         public void TrimExcess()
179         {
180             int threshold = (int)(_array.Length * 0.9);
181             if (_size < threshold)
182             {
183                 Array.Resize(ref _array, _size);
184                 _version++;
185             }
186         }
187 
188         // Returns the top object on the stack without removing it.  If the stack
189         // is empty, Peek throws an InvalidOperationException.
190         public T Peek()
191         {
192             int size = _size - 1;
193             T[] array = _array;
194 
195             if ((uint)size >= (uint)array.Length)
196             {
197                 ThrowForEmptyStack();
198             }
199 
200             return array[size];
201         }
202 
203         public bool TryPeek([MaybeNullWhen(false)] out T result)
204         {
205             int size = _size - 1;
206             T[] array = _array;
207 
208             if ((uint)size >= (uint)array.Length)
209             {
210                 result = default!;
211                 return false;
212             }
213             result = array[size];
214             return true;
215         }
216 
217         // Pops an item from the top of the stack.  If the stack is empty, Pop
218         // throws an InvalidOperationException.
219         public T Pop()
220         {
221             int size = _size - 1;
222             T[] array = _array;
223 
224             // if (_size == 0) is equivalent to if (size == -1), and this case
225             // is covered with (uint)size, thus allowing bounds check elimination
226             // https://github.com/dotnet/coreclr/pull/9773
227             if ((uint)size >= (uint)array.Length)
228             {
229                 ThrowForEmptyStack();
230             }
231 
232             _version++;
233             _size = size;
234             T item = array[size];
235             if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
236             {
237                 array[size] = default!;     // Free memory quicker.
238             }
239             return item;
240         }
241 
242         public bool TryPop([MaybeNullWhen(false)] out T result)
243         {
244             int size = _size - 1;
245             T[] array = _array;
246 
247             if ((uint)size >= (uint)array.Length)
248             {
249                 result = default!;
250                 return false;
251             }
252 
253             _version++;
254             _size = size;
255             result = array[size];
256             if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
257             {
258                 array[size] = default!;
259             }
260             return true;
261         }
262 
263         // Pushes an item to the top of the stack.
264         public void Push(T item)
265         {
266             int size = _size;
267             T[] array = _array;
268 
269             if ((uint)size < (uint)array.Length)
270             {
271                 array[size] = item;
272                 _version++;
273                 _size = size + 1;
274             }
275             else
276             {
277                 PushWithResize(item);
278             }
279         }
280 
281         // Non-inline from Stack.Push to improve its code quality as uncommon path
282         [MethodImpl(MethodImplOptions.NoInlining)]
283         private void PushWithResize(T item)
284         {
285             Debug.Assert(_size == _array.Length);
286             Grow(_size + 1);
287             _array[_size] = item;
288             _version++;
289             _size++;
290         }
291 
292         /// <summary>
293         /// Ensures that the capacity of this Stack is at least the specified <paramref name="capacity"/>.
294         /// If the current capacity of the Stack is less than specified <paramref name="capacity"/>,
295         /// the capacity is increased by continuously twice current capacity until it is at least the specified <paramref name="capacity"/>.
296         /// </summary>
297         /// <param name="capacity">The minimum capacity to ensure.</param>
298         public int EnsureCapacity(int capacity)
299         {
300             if (capacity < 0)
301             {
302                 throw new ArgumentOutOfRangeException(nameof(capacity), capacity, SR.ArgumentOutOfRange_NeedNonNegNum);
303             }
304 
305             if (_array.Length < capacity)
306             {
307                 Grow(capacity);
308                 _version++;
309             }
310 
311             return _array.Length;
312         }
313 
314         private void Grow(int capacity)
315         {
316             Debug.Assert(_array.Length < capacity);
317 
318             int newcapacity = _array.Length == 0 ? DefaultCapacity : 2 * _array.Length;
319 
320             // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
321             // Note that this check works even when _items.Length overflowed thanks to the (uint) cast.
322             if ((uint)newcapacity > Array.MaxLength) newcapacity = Array.MaxLength;
323 
324             // If computed capacity is still less than specified, set to the original argument.
325             // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize.
326             if (newcapacity < capacity) newcapacity = capacity;
327 
328             Array.Resize(ref _array, newcapacity);
329         }
330 
331         // Copies the Stack to an array, in the same order Pop would return the items.
332         public T[] ToArray()
333         {
334             if (_size == 0)
335                 return Array.Empty<T>();
336 
337             T[] objArray = new T[_size];
338             int i = 0;
339             while (i < _size)
340             {
341                 objArray[i] = _array[_size - i - 1];
342                 i++;
343             }
344             return objArray;
345         }
346 
347         private void ThrowForEmptyStack()
348         {
349             Debug.Assert(_size == 0);
350             throw new InvalidOperationException(SR.InvalidOperation_EmptyStack);
351         }
352 
353         public struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator
354         {
355             private readonly Stack<T> _stack;
356             private readonly int _version;
357             private int _index;
358             private T? _currentElement;
359 
360             internal Enumerator(Stack<T> stack)
361             {
362                 _stack = stack;
363                 _version = stack._version;
364                 _index = -2;
365                 _currentElement = default;
366             }
367 
368             public void Dispose()
369             {
370                 _index = -1;
371             }
372 
373             public bool MoveNext()
374             {
375                 bool retval;
376                 if (_version != _stack._version) throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
377                 if (_index == -2)
378                 {  // First call to enumerator.
379                     _index = _stack._size - 1;
380                     retval = (_index >= 0);
381                     if (retval)
382                         _currentElement = _stack._array[_index];
383                     return retval;
384                 }
385                 if (_index == -1)
386                 {  // End of enumeration.
387                     return false;
388                 }
389 
390                 retval = (--_index >= 0);
391                 if (retval)
392                     _currentElement = _stack._array[_index];
393                 else
394                     _currentElement = default;
395                 return retval;
396             }
397 
398             public T Current
399             {
400                 get
401                 {
402                     if (_index < 0)
403                         ThrowEnumerationNotStartedOrEnded();
404                     return _currentElement!;
405                 }
406             }
407 
408             private void ThrowEnumerationNotStartedOrEnded()
409             {
410                 Debug.Assert(_index == -1 || _index == -2);
411                 throw new InvalidOperationException(_index == -2 ? SR.InvalidOperation_EnumNotStarted : SR.InvalidOperation_EnumEnded);
412             }
413 
414             object? System.Collections.IEnumerator.Current
415             {
416                 get { return Current; }
417             }
418 
419             void IEnumerator.Reset()
420             {
421                 if (_version != _stack._version) throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
422                 _index = -2;
423                 _currentElement = default;
424             }
425         }
426     }
427 }
View Code

 PS:

  深入瞭解了一下,CopyTo和Resize都會呼叫Buffer.Memmove方法

4. 區別

  優點 缺點 適用場景
自定義實現

小而美

1. 實現了棧的基本功能

2. 面向介面程式設計

3. 對資料元素做了進一步的封裝

 

1. 引數場景考慮不全面(例如:容量沒有進行校驗,使用UInt32型別的原因)

2. 當容量不足時,進行當前容量2倍的擴充,很容易造成浪費

知曉大概容量的少量資料操作

官方實現

大而全

1. 功能全面,並且根據不同場景做了很多定製化的功能(為了保證其通用性)

2. 資料元素完全泛型實現

無法自定義一些操作

 

PS:

  自定義的實現本質是一個實驗性專案,這裡做對比,主要目的是感受和官方實現之間的差距(為了學習)

 

5. 總結

  • 實現:場景考慮不夠全面(對於資料的理解和校驗明顯不足)
  • 技術:對於C#理解不夠深入(runtime原始碼需要進一步深入學習和檢視)

 

---------------------------------- 人生格言 ---------------------------------------------------------------

輸入決定輸出,沒有輸入就沒有輸出。

 

相關文章