最近在學習演算法基礎,本篇文章作為一個記錄,也算是一次實踐和總結。(順便也深入C#執行時學習一下)
目錄
1. 棧是什麼
棧是一種特殊的線性表(資料邏輯結構中的一種,定義是其組成元素之間具有線性關係的一種線性結構),其插入和刪除操作只能在一端進行。
1 個人理解: 2 是一種最基本的資料結構,資料結構方面必須夯實的基礎知識
特徵(也可以說是應用場景):
後進先出(佇列則為先進先出)
2. Stack 自定義實現
Github: https://github.com/HadesKing/DataStructureAndAlgorithms
程式碼路徑:BasicDataStructure\src\LD.BasicDataStructures\Stacks
Stack實現方式有兩種:順序儲存和鏈式儲存(順序棧和鏈式棧)
為了便於使用,我對棧進行了抽象,形成了一個介面IStack
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 }
順序棧的實現
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 }
鏈式棧的實現
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 }
3. Stack C# 官方實現
Github: https://github.com/dotnet/runtime
Stack 檔案路徑:src\libraries\System.Collections\src\System\Collections\Generic\Stack.cs
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 }
PS:
深入瞭解了一下,CopyTo和Resize都會呼叫Buffer.Memmove方法
4. 區別
優點 | 缺點 | 適用場景 | |
自定義實現 |
小而美 1. 實現了棧的基本功能 2. 面向介面程式設計 3. 對資料元素做了進一步的封裝
|
1. 引數場景考慮不全面(例如:容量沒有進行校驗,使用UInt32型別的原因) 2. 當容量不足時,進行當前容量2倍的擴充,很容易造成浪費 |
知曉大概容量的少量資料操作 |
官方實現 |
大而全 1. 功能全面,並且根據不同場景做了很多定製化的功能(為了保證其通用性) 2. 資料元素完全泛型實現 |
無法自定義一些操作 |
PS:
自定義的實現本質是一個實驗性專案,這裡做對比,主要目的是感受和官方實現之間的差距(為了學習)
5. 總結
- 實現:場景考慮不夠全面(對於資料的理解和校驗明顯不足)
- 技術:對於C#理解不夠深入(runtime原始碼需要進一步深入學習和檢視)
---------------------------------- 人生格言 ---------------------------------------------------------------
輸入決定輸出,沒有輸入就沒有輸出。