許多基礎數據類型都和對象的集合有關。數據類型的值就是一組對象的集合,全部操做都是關於添加,刪除或是訪問集合中的對象。揹包(Bag),隊列(Quene)和棧(Stack) 它們的不一樣之處在於刪除或者訪問對象的順序不一樣。算法
1. API數組
Stack 和 Quene 都含有一個可以刪除集合中特定元素的方法。數據結構
實現上面API須要高級語言的特性:泛型,裝箱拆箱,可迭代(實現 IEnumerable 接口)。性能
1. 揹包spa
揹包是一種不支持從中刪除元素的集合類型——它的目的就是幫助用例收集元素並迭代遍歷全部元素。用例也可使用棧或者隊列,但使用 Bag 能夠說明元素的處理順序不重要。設計
2.先進先出隊列3d
隊列是基於先進先出(FIFO)策略的集合類型。code
3. 下壓棧對象
下壓棧(簡稱棧)是一種基於後進先出(LIFO)策略的集合類型。blog
應用例子:計算輸入字符串 (1+((2+3)*(4*5)))表達式的值。
使用雙棧解決:
1. 將操做數壓入操做數棧;
2. 將運算符壓入運算符棧;
3. 忽略作括號;
4. 在遇到右括號時,彈出一個運算符,彈出所需數量的操做數,並將運算符和操做數的運算結果壓入操做數棧。
2.用數組實現
實現下壓棧:
//想要數據類型可迭代,須要實現IEnumerable public class ResizingStack<Item> : IEnumerable<Item> { private Item[] a = new Item[1]; private int N = 0; public bool IsEmpty{ get { return N == 0; } } public int Size { get { return N; } } public int Count { get; set; } /// <summary> /// 使數組處於半滿 /// </summary> /// <param name="max"></param> private void Resize(int max) { Count = 0; Item[] temp = new Item[max]; for(var i = 0;i<N;i++) { temp[i] = a[i]; Count++; } a = temp; } public void push(Item item) { if (N == a.Length) Resize(a.Length * 2); a[N++] = item; } public Item Pop() { Item item = a[--N]; a[N] = default(Item); //避免對象遊離 if (N > 0 && N == a.Length / 4) Resize(a.Length/2); return item; } IEnumerator<Item> IEnumerable<Item>.GetEnumerator() { return new ResizingStackEnumerator<Item>(a); } public IEnumerator GetEnumerator() { return new ResizingStackEnumerator<Item>(a); } } class ResizingStackEnumerator<Item> : IEnumerator<Item> { private Item[] a; private int N = 0; public ResizingStackEnumerator(Item[] _a) { a = _a; N = a.Length-1; } public object Current => a[N--]; Item IEnumerator<Item>.Current => a[N--]; public void Dispose() { throw new NotImplementedException(); } public bool MoveNext() { return N > 0; } public void Reset() { throw new NotImplementedException(); } }
3.鏈表
鏈表是在集合類的抽象數據類型實現中表示數據的另外一種基礎數據結構。
定義:鏈表是一種遞歸的數據結構,它或者指向空,或者指向另外一個節點的引用,該節點含有一個泛型元素和一個指向另外一個鏈表的引用。
class Node<Item> { public Item item { get; set; } public Node<Item> Next { get; set; } }
1.構造鏈表
鏈表表示的是一列元素。
根據遞歸的定義,只須要一個 Node 類型的變量就能表示一條鏈表,只要保證它的 Next 值是 null 或指向另外一個 Node 對象,該對象的 Next 指向另外一條鏈表。
2.在表頭插入結點
在鏈表列表中插入新節點的最簡單位置是開始。要在首結點爲 first 的給定鏈表開頭插入字符串 not ,先將 first 保存在 oldfirst 中,而後將一個新結點賦予 first ,並將 first 的 item 設爲 not, Next 設置爲 oldfirst 。
在鏈表開頭插入一個結點所需的時間和鏈表長度無關。
3.從表頭刪除結點
只需將 first 指向 first.next 便可。first 原來指向的對象變成了一個孤兒,垃圾回收機制會將其回收。
一樣,該操做所需的時間和鏈表長度無關。
4.在表尾插入結點
當鏈表不止有一個結點時,須要一個指向鏈表最後結點的連接 oldlast,建立新的結點,last 指向新的最後結點。而後 oldlast.next 指向 last。
當鏈表只有一個結點時,首結點又是尾結點。只需將 last 指向新的結點,而後 first.next 指向 last。
5.其餘位置的插入和刪除操做
上述操做能夠很容易的實現,可是下面的操做比較複雜:
1. 刪除指定的結點
2. 在指定結點前插入一個新結點
這些操做須要咱們遍歷鏈表,它所需的時間和鏈表的長度成正比。想要實現任意插入和刪除結點須要使用雙向鏈表,其中每一個結點都含有兩個連接,分別指向上一個和下一個結點。
6. 遍歷
簡單實現:
public class Bag<Item> { private Node<Item> first; public void Add(Item item) { Node<Item> oldFirst = first; first = new Node<Item>() { item = item, Next = oldFirst }; } }
Bag<int> bags = new Bag<int>(); for (var i = 0; i < 10; i++) { bags.Add(i); } for (var x = bags.first; x != null; x = x.Next) { Console.WriteLine(x.item); }
實現 IEnumerable 接口 實現遍歷:
public class Bag<Item>: IEnumerable<Item> { public Node<Item> first; public void Add(Item item) { Node<Item> oldFirst = first; first = new Node<Item>() { item = item, Next = oldFirst }; } public IEnumerator<Item> GetEnumerator() { return new LineEnumerator<Item>(first); } IEnumerator IEnumerable.GetEnumerator() { return new LineEnumerator<Item>(first); } } public class LineEnumerator<Item> : IEnumerator<Item> { public Node<Item> first; public LineEnumerator(Node<Item> _first) { first = _first; } public Item Current { get { var oldfirst = first; first = first.Next; return oldfirst.item; } } object IEnumerator.Current => first; public void Dispose() { return; } public bool MoveNext() { if (first != null) return true; return false; } public void Reset() { throw new NotImplementedException(); } }
public static void LineTest() { Bag<int> bags = new Bag<int>(); for (var i = 0; i < 10; i++) { bags.Add(i); } foreach(var bag in bags) { Console.WriteLine(bag); } }
4. 用鏈表實現揹包
見上述代碼。
5. 用鏈表實現棧
Stack API 中 Pop() 刪除一個元素,按照前面的從表頭刪除結點實現,Push() 添加一個元素,按照前面在表頭插入結點。
public class Stack<Item> : IEnumerable<Item> { public Node<Item> first; private int N; public bool IsEmpty() { return first == null; //或 N == 0 } public int Size() { return N; } public void Push(Item item) { Node<Item> oldfirst = first; first = new Node<Item>() { item = item, Next = oldfirst }; N++; } public Item Pop() { Item item = first.item; first = first.Next; N--; return item; } public IEnumerator<Item> GetEnumerator() { return new StackLineIEnumerator<Item>(first); } IEnumerator IEnumerable.GetEnumerator() { return new StackLineIEnumerator<Item>(first); } } public class StackLineIEnumerator<Item> : IEnumerator<Item> { private Node<Item> first; public StackLineIEnumerator(Node<Item> _first) { first = _first; } public Item Current { get { var oldfirst = first; first = first.Next; return oldfirst.item; } } object IEnumerator.Current => throw new NotImplementedException(); public void Dispose() { return; } public bool MoveNext() { return first != null; } public void Reset() { throw new NotImplementedException(); } }
鏈表的使用達到了最優設計目標:
1. 能夠處理任意類型的數據;
2. 所需的空間老是和集合的大小成正比;
3. 操做所需的時間老是和集合的大小無關;
6. 用鏈表實現隊列
須要兩個實例變量,first 指向隊列的開頭,last 指向隊列的表尾。添加一個元素 Enquene() ,將結點添加到表尾(鏈表爲空時,first 和 last 都指向新結點)。刪除一個元素 Dequene() ,刪除表頭的結點(刪除後,當隊列爲空時,將 last 更新爲 null)。
public class Quene<Item> : IEnumerable<Item> { public Node<Item> first; public Node<Item> last; private int N; public bool IsEmpty() { return first == null; } public int Size() { return N; } public void Enquene(Item item) { var oldlast = last; last = new Node<Item>() { item = item, Next = null }; if (IsEmpty()) first = last; else oldlast.Next = last; N++; } public Item Dequene() { if (IsEmpty()) throw new Exception(); Item item = first.item; first = first.Next; if (IsEmpty()) last = null; N--; return item; } public IEnumerator<Item> GetEnumerator() { return new QueneLineEnumerator<Item>(first); } IEnumerator IEnumerable.GetEnumerator() { return new QueneLineEnumerator<Item>(first); } } public class QueneLineEnumerator<Item> : IEnumerator<Item> { private Node<Item> first; public QueneLineEnumerator(Node<Item> _first) { first = _first; } public Item Current { get { var oldfirst = first; first = first.Next; return oldfirst.item; } } object IEnumerator.Current => throw new NotImplementedException(); public void Dispose() { return; } public bool MoveNext() { return first != null ; } public void Reset() { throw new NotImplementedException(); } }
7. 總結
在結構化存儲數據集時,鏈表是數組的一種重要的替代方式。
數組和鏈表這兩種數據類型爲研究算法和更高級的數據結構打下了基礎。
基礎數據結構:
數據結構 | 優勢 | 缺點 |
數組 | 經過索引能夠直接訪問任意元素 | 在初始化時就須要知道元素的數量 |
鏈表 | 使用的空間大小和元素數量成正比 | 須要同引用訪問任意元素 |
在研究一個新的應用領域時,能夠按照如下步驟識別目標,定義問題和使用數據抽象解決問題:
1. 定義 API
2. 根據特定的應用場景開發用例代碼
3. 描述一種數據結構(即一組值的表示),並在 API 的實現中根據它定義類的實例變量。
4. 描述算法,即實現 API,並根據它應用於用例
5. 分析算法的性能