談起堆棧,我想起兄弟。中國的漢語真是有意思,兄弟說的是弟,同理,堆棧,強調的是棧。棧是一種受限的線性表。我把數據結構的知識回顧下。數據結構是數據之間的關係。關係是廣泛存在的。是否是有點哲學的味道。那麼數據到底都有些什麼關係呢?咱們去銀行辦理業務,去坐車都須要排隊,新生入學站成一排軍訓,若是咱們把人看做數據,那麼此時的人和人的位置關係,即是線性的。除了線性結構,還有什麼結構呢?四世同堂的老人,他們一家人的血緣關係,如同一棵樹。這即是樹型結構。還有一種網狀的結構,稱爲圖,如城市的交通網。字典屬於什麼數據結構?它不是樹,不是圖,那它屬於線性結構嗎?字典顯然是個集合,若是字典的key是線性關係,例如從1開始的編號,或者是a-z的字母,那麼它能稱得上線性關係嗎?線性表是一種有限序列的集合。它是有順序的。字典的數據項徹底沒有任何邏輯,它只與key有關係。
瀏覽器
說了這麼多,總結一下:數據之間的關係分爲兩種,邏輯關係和非邏輯關係如圖所示:數據結構
今天我要說的棧是一種物理結構,它存儲了一組線性元素,這組元素在操做上有後進先出的特性。所以能夠看出,數據結構不只研究數據之間的關係,以及存儲,並且包括數據的操做。結構決定功能,正是有了棧這樣的存儲結構,所以,元素的操做上纔有本身的特性。app
接下來,咱們看一個棧具體應用的例子:有一組有序數字,須要把相鄰的數字序列取出來放在一塊兒,零散的數字單獨存放。例若有一組數字1,2,3,5,7,9,10,11,分組後的結果:(1,2,3),(5),(7),(9,10,11)。用程序如何實現呢?且看通常的實現方法,以下所示:性能
1 List<List<Int32>> sections = new List<List<int>>(); 2 sections.Add(new List<Int32>()); 3 //序號分組 4 for (int i = 0; i < numbers.Count; i++) 5 { 6 if (!((numbers[i + 1] - numbers[i]) == 1)) 7 { 8 sections[sections.Count - 1].Add(numbers[i]); 9 sections.Add(new List<Int32>()); 10 if (i + 1 == numbers.Count - 1) 11 { 12 sections[sections.Count - 1].Add(numbers[i + 1]); 13 break; 14 } 15 continue; 16 } 17 18 sections[sections.Count - 1].Add(numbers[i]); 19 if (i + 1 == numbers.Count - 1) 20 { 21 sections[sections.Count - 1].Add(numbers[i + 1]); 22 break; 23 } 24 }
咱們看看前輩寫的這段代碼,List嵌套,短短的程序,處處continue和break,這些都致使程序的可讀性變差。spa
且看個人實現:code
1 List<Stack<int>> list = new List<Stack<int>>(); 2 Stack<int> q = new Stack<int>(); 3 list.Add(q); 4 5 foreach (var n in numbers) 6 { 7 if (q.Count == 0) 8 { 9 //棧爲空時,直接放進去 10 q.Push(n); 11 } 12 else 13 { 14 //若是當前數字和棧中的數字沒有關係時,新建立一個棧,不然直接放到棧中。 15 if (n - 1 != list[list.Count - 1].Peek()) 16 { 17 var q1 = new Stack<int>(); 18 q1.Push(n); 19 list.Add(q1); 20 } 21 else 22 { 23 list[list.Count - 1].Push(n); 24 } 25 } 26 }
前輩的實現思路,是當前數字與下一個數字比較,看是否連續,個人實現思路,是當前數字與上一個數字比較。前輩的代碼中,兩次判斷是否元素遍歷到最後一個。個人只判斷第一個棧是否有數據。若是集合的數量比較大,我能夠更改個人代碼以下:orm
1 List<Stack<int>> list = new List<Stack<int>>(); 2 Stack<int> q = new Stack<int>(); 3 list.Add(q); 4 if (numbers.Count > 0) 5 { 6 q.Push(numbers[0]); 7 } 8 9 for (int i = 1; i < numbers.Count; i++) 10 { 11 //若是當前數字和棧中的數字沒有關係時,新建立一個棧,不然直接放到棧中。 12 if (numbers[i] - 1 != list[list.Count - 1].Peek()) 13 { 14 var q1 = new Stack<int>(); 15 q1.Push(numbers[i]); 16 list.Add(q1); 17 } 18 else 19 { 20 list[list.Count - 1].Push(numbers[i]); 21 } 22 }
此時,前輩和個人程序運行結果如圖:blog
你或許對這兩幅圖,以爲不就同樣嘛,一個是是躺着的,一個是站起來的。可是,別忘了,個人結果圖是能夠想象成家裏的桶,每一個桶裏裝的是連續的數字。那你或許會說,前輩的結果圖能夠想象爲抽屜,每一個抽屜裝的是連續的數字。好了,說到這兒,還真的無法區分孰優孰劣。那麼咱們看看,分組後這些數字的應用,先看前輩的:遞歸
1 //序號拼接 2 for (int i = 0; i < sections.Count; i++) 3 { 4 String seq = ""; 5 6 if (sections[i].Count >= intextCodeOrder.CitationNumber) 7 seq = "-"; 8 else if (sections[i].Count > 1) 9 { 10 seq = ","; 11 } 12 else 13 { 14 if (!sbNumber.ToString().Contains(sections[i][0].ToString())) 15 sbNumber.AppendFormat("{0},", sections[i][0]); 16 continue; 17 } 18 sbNumber.AppendFormat("{0}{1}{2},", sections[i][0], seq, sections[i][sections[i].Count - 1]); 19 }
解釋下這段代碼:若是連續的數字個數超過了某個值,用 「-」 號鏈接起來,不然的話用 「,」號鏈接。索引
再看看個人代碼:
1 foreach (var stack in list) 2 { 3 if (stack.Count == 1) 4 { 5 sbNumber.AppendFormat("{0},", Mapping(stack.Pop())); 6 } 7 else 8 { 9 if (stack.Count >= intextCodeOrder.CitationNumber) 10 join = "-"; 11 else if (stack.Count > 1) 12 { 13 join = ","; 14 } 15 int lastNumber = stack.Pop(); 16 int firstNumber = 0; 17 18 //根據棧先進後出的特性,取得第一個進棧的數字 19 while (stack.Count > 0) 20 { 21 firstNumber = stack.Pop(); 22 } 23 sbNumber.AppendFormat("{0}{1}{2},", Mapping(firstNumber), join, Mapping(lastNumber)); 24 } 25 }
看到這裏,發現我寫的這段代碼,有個小瑕疵,在找第一個進棧的數字,須要循環出棧。這個在數據量少的狀況下,不影響系統性能,在數據量大的狀況下,就該考慮其它的存儲結構了。本例子中,採用棧的優勢是可讀性強,分組的時候,程序能夠寫得簡短明瞭。並且還避免了集合的索引帶來的問題,常常要判斷,以避免出界。
固然棧的應用是十分普遍的,我只是列舉了一個列子罷了。在程序的遞歸調用上使用了棧,在瀏覽器以及ps的歷史記錄中發揮着做用。