數據結構之堆棧


      談起堆棧,我想起兄弟。中國的漢語真是有意思,兄弟說的是弟,同理,堆棧,強調的是。棧是一種受限的線性表。我把數據結構的知識回顧下。數據結構是數據之間的關系。關系是普遍存在的。是不是有點哲學的味道。那么數據到底都有些什么關系呢?我們去銀行辦理業務,去坐車都需要排隊,新生入學站成一排軍訓,如果我們把人看作數據,那么此時的人和人的位置關系,便是線性的。除了線性結構,還有什么結構呢?四世同堂的老人,他們一家人的血緣關系,如同一棵樹。這便是樹型結構。還有一種網狀的結構,稱為,如城市的交通網。字典屬於什么數據結構?它不是樹,不是圖,那它屬於線性結構嗎?字典顯然是個集合,如果字典的key是線性關系,例如從1開始的編號,或者是a-z的字母,那么它能稱得上線性關系嗎?線性表是一種有限序列的集合。它是有順序的。字典的數據項完全沒有任何邏輯,它只與key有關系。

     說了這么多,總結一下:數據之間的關系分為兩種,邏輯關系和非邏輯關系如圖所示:

    

      今天我要說的棧是一種物理結構,它存儲了一組線性元素,這組元素在操作上有后進先出的特性。因此可以看出,數據結構不僅研究數據之間的關系,以及存儲,而且包括數據的操作。結構決定功能,正是有了棧這樣的存儲結構,因此,元素的操作上才有自己的特性。

      接下來,我們看一個棧具體應用的例子:有一組有序數字,需要把相鄰的數字序列取出來放在一起,零散的數字單獨存放。例如有一組數字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,這些都導致程序的可讀性變差。

且看我的實現:

 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             }

     前輩的實現思路,是當前數字與下一個數字比較,看是否連續,我的實現思路,是當前數字與上一個數字比較。前輩的代碼中,兩次判斷是否元素遍歷到最后一個。我的只判斷第一個棧是否有數據。如果集合的數量比較大,我可以更改我的代碼如下:

 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             }

此時,前輩和我的程序運行結果如圖:

                       

 

      你或許對這兩幅圖,覺得不就一樣嘛,一個是是躺着的,一個是站起來的。但是,別忘了,我的結果圖是可以想象成家里的桶,每個桶里裝的是連續的數字。那你或許會說,前輩的結果圖可以想象為抽屜,每個抽屜裝的是連續的數字。好了,說到這兒,還真的沒法區分孰優孰劣。那么我們看看,分組后這些數字的應用,先看前輩的:

 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的歷史記錄中發揮着作用。

 

      

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM