數據結構Java實現05----棧:順序棧和鏈式堆棧


 

一、堆棧的基本概念:

堆棧(也簡稱作棧)是一種特殊的線性表,堆棧的數據元素以及數據元素間的邏輯關系和線性表完全相同,其差別是線性表允許在任意位置進行插入和刪除操作,而堆棧只允許在固定一端進行插入和刪除操作。

先進后出:堆棧中允許進行插入和刪除操作的一端稱為棧頂,另一端稱為棧底。堆棧的插入和刪除操作通常稱為進棧或入棧,堆棧的刪除操作通常稱為出棧或退棧。

備注:棧本身就是一個線性表,所以我們之前討論過線性表的順序存儲和鏈式存儲,對於棧來說,同樣適用。

 

二、堆棧的抽象數據類型:

數據集合:

堆棧的數據集合可以表示為a0,a1,…,an-1,每個數據元素的數據類型可以是任意的類類型。

操作集合:

(1)入棧push(obj):把數據元素obj插入堆棧。

(2)出棧pop():出棧, 刪除的數據元素由函數返回。

(3)取棧頂數據元素getTop():取堆棧當前棧頂的數據元素並由函數返回。

(4)非空否notEmpty():若堆棧非空則函數返回true,否則函數返回false。

 

三、順序棧:

順序存儲結構的堆棧稱作順序堆棧。其存儲結構示意圖如下圖所示:

42c4b8c6-f428-468d-b5aa-2cf9f4ae86e2

1、順序棧的實現:

(1)設計Stack接口

(2)實現SequenceStack類

注:棧是線性表的特例,線性表本身就是用數組來實現的。於是,順序棧也是用數組實現的。

代碼實現:

(1)Stack.java:(Stack接口)

 1 public interface Stack {
 2 
 3     //入棧
 4     public void push(Object obj) throws Exception;
 5 
 6     //出棧
 7     public Object pop() throws Exception;
 8 
 9     //獲取棧頂元素
10     public Object getTop() throws Exception;
11 
12     //判斷棧是否為空
13      public boolean isEmpty();
14 }

 

 

(2)SequenceStack.java:

 1 //順序棧
 2 public class SequenceStack implements Stack {
 3 
 4     Object[] stack; //對象數組(棧用數組來實現)
 5     final int defaultSize = 10; //默認最大長度
 6     int top; //棧頂位置(的一個下標):其實可以理解成棧的實際長度
 7     int maxSize; //最大長度
 8 
 9     //如果用無參構造的話,就設置默認長度
10     public SequenceStack() {
11         init(defaultSize);
12     }
13 
14     //如果使用帶參構造的話,就調用指定的最大長度
15     public SequenceStack(int size) {
16         init(size);
17     }
18 
19     public void init(int size) {
20         this.maxSize = size;
21         top = 0;
22         stack = new Object[size];
23     }
24 
25     //獲取棧頂元素
26     @Override
27     public Object getTop() throws Exception {
28         // TODO Auto-generated method stub
29         if (isEmpty()) {
30             throw new Exception("堆棧為空!");
31         }
32 
33         return stack[top - 1];
34     }
35 
36     //判斷棧是否為空
37     @Override
38     public boolean isEmpty() {
39         // TODO Auto-generated method stub
40         return top == 0;
41     }
42 
43     //出棧操作
44     @Override
45     public Object pop() throws Exception {
46         // TODO Auto-generated method stub
47         if (isEmpty()) {
48             throw new Exception("堆棧為空!");
49         }
50         top--;
51 
52         return stack[top];
53     }
54 
55     //入棧操作
56     @Override
57     public void push(Object obj) throws Exception {
58         // TODO Auto-generated method stub
59         //首先判斷棧是否已滿
60         if (top == maxSize) {
61             throw new Exception("堆棧已滿!");
62         }
63         stack[top] = obj;
64         top++;
65     }
66 }

2、測試類:

設計一個順序棧,從鍵盤輸入十個整數壓進棧,然后再彈出棧,並打印出棧序列。

代碼實現:

(3)Test.java:

 1 import java.util.Scanner;
 2 
 3 public class Test {
 4     public static void main(String[] args) throws Exception {
 5         SequenceStack stack = new SequenceStack(10);
 6 
 7         Scanner in = new Scanner(System.in);
 8         int temp;
 9         for (int i = 0; i < 10; i++) {
10             System.out.println("請輸入第" + (i + 1) + "個整數:");
11             temp = in.nextInt();
12             stack.push(temp);
13         }
14 
15         //遍歷輸出
16         while (!stack.isEmpty()) {
17             System.out.println(stack.pop());
18         }
19     }
20 }

運行效果:

9fd3385e-ab5b-4fc1-852f-4afe8ec9b304

 

四、Java中棧與堆的區別:

棧(stack):(線程私有)

  是一個先進后出的數據結構,通常用於保存方法(函數)中的參數,局部變量。在java中,所有基本類型和引用類型的引用都在棧中存儲。棧中數據的生存空間一般在當前scopes內(就是由{...}括起來的區域)。

堆(heap):(線程共享)

  是一個可動態申請的內存空間(其記錄空閑內存空間的鏈表由操作系統維護),C中的malloc語句所產生的內存空間就在堆中。在java中,所有使用new xxx()構造出來的對象都在堆中存儲,當垃圾回收器檢測到某對象未被引用,則自動銷毀該對象。所以,理論上說java中對象的生存空間是沒有限制的,只要有引用類型指向它,則它就可以在任意地方被使用。

 

五、hashCode與對象之間的關系:

如果兩個對象的hashCode不相同,那么這兩個對象肯定也不同。

如果兩個對象的hashCode相同,那么這兩個對象有可能相同,也有可能不同。

總結一句:不同的對象可能會有相同的hashCode;但是如果hashCode不同,那肯定不是同一個對象

代碼舉例:

 1 public class StringTest {
 2 
 3     public static void main(String[] args) {
 4 
 5         //s1 和 s2 其實是同一個對象。對象的引用存放在棧中,對象存放在方法區的字符串常量池
 6         String s1 = "china";
 7         String s2 = "china";
 8 
 9         //凡是用new關鍵創建的對象,都是在堆內存中分配空間。
10         String s3 = new String("china");
11 
12         //凡是new出來的對象,絕對是不同的兩個對象。
13         String s4 = new String("china");
14 
15         System.out.println(s1 == s2);  //true
16         System.out.println(s1 == s3);
17         System.out.println(s3 == s4);
18         System.out.println(s3.equals(s4));
19 
20         System.out.println("\n-----------------\n");
21       /*String很特殊,重寫從父類繼承過來的hashCode方法,使得兩個
22        *如果字符串里面的內容相等,那么hashCode也相等。
23        **/
24 
25         //hashCode相同
26         System.out.println(s3.hashCode());  //hashCode為94631255
27         System.out.println(s4.hashCode());  //hashCode為94631255
28 
29         //identityHashCode方法用於獲取原始的hashCode
30         //如果原始的hashCode不同,表明確實是不同的對象
31 
32         //原始hashCode不同
33         System.out.println(System.identityHashCode(s3)); //2104928456
34         System.out.println(System.identityHashCode(s4)); //2034442961
35 
36         System.out.println("\n-----------------\n");
37 
38         //hashCode相同
39         System.out.println(s1.hashCode());  //94631255
40         System.out.println(s2.hashCode());  //94631255
41 
42         //原始hashCode相同:表明確實是同一個對象
43         System.out.println(System.identityHashCode(s1));  //648217993
44         System.out.println(System.identityHashCode(s2));  //648217993
45     }
46 }

 

上面的代碼中,注釋已經標明了運行的結果。通過運行結果我們可以看到,s3和s4的字符串內容相同,但他們是兩個不同的對象,由於String類重寫了hashCode方法,他們的hashCode相同,但原始的hashCode是不同的。

 

六、鏈式堆棧:

  鏈式存儲結構的堆棧稱作鏈式堆棧

  與單鏈表相同,鏈式堆棧也是由一個個結點組成的,每個結點由兩個域組成,一個是存放數據元素的數據元素域data,另一個是存放指向下一個結點的對象引用(即指針)域next。

  堆棧有兩端,插入數據元素和刪除數據元素的一端為棧頂,另一端為棧底。鏈式堆棧都設計成把靠近堆棧頭head的一端定義為棧頂

依次向鏈式堆棧入棧數據元素a0, a1, a2, ..., an-1后,鏈式堆棧的示意圖如下圖所示: 

44298c1d-2054-4dd5-be7d-1f7e4cd5f43f

1、設計鏈式堆棧:

(1)Node.java:結點類

 1 //結點類
 2 public class Node {
 3 
 4     Object element; //數據域
 5     Node next;  //指針域
 6 
 7     //頭結點的構造方法
 8     public Node(Node nextval) {
 9         this.next = nextval;
10     }
11 
12     //非頭結點的構造方法
13     public Node(Object obj, Node nextval) {
14         this.element = obj;
15         this.next = nextval;
16     }
17     
18     //獲得當前結點的后繼結點
19     public Node getNext() {
20         return this.next;
21     }
22 
23     //獲得當前的數據域的值
24     public Object getElement() {
25         return this.element;
26     }
27 
28     //設置當前結點的指針域
29     public void setNext(Node nextval) {
30         this.next = nextval;
31     }
32 
33     //設置當前結點的數據域
34     public void setElement(Object obj) {
35         this.element = obj;
36     }
37 
38     public String toString() {
39         return this.element.toString();
40     }
41 }

(2)Stack.java:

 1 //棧接口
 2 public interface Stack {
 3 
 4     //入棧
 5     public void push(Object obj) throws Exception;
 6 
 7     //出棧
 8     public Object pop() throws Exception;
 9 
10     //獲得棧頂元素
11     public Object getTop() throws Exception;
12 
13     //判斷棧是否為空
14     public boolean isEmpty();
15 }

 

(3)LinkStack.java:

 1 public class LinkStack implements Stack {
 2 
 3     Node head;  //棧頂指針
 4     int size;  //結點的個數
 5 
 6     public LinkStack() {
 7         head = null;
 8         size = 0;
 9     }
10 
11     @Override
12     public Object getTop() throws Exception {
13         // TODO Auto-generated method stub
14         return head.getElement();
15     }
16 
17     @Override
18     public boolean isEmpty() {
19         // TODO Auto-generated method stub
20         return head == null;
21     }
22 
23     @Override
24     public Object pop() throws Exception {
25         // TODO Auto-generated method stub
26         if (isEmpty()) {
27             throw new Exception("棧為空!");
28         }
29         Object obj = head.getElement();
30         head = head.getNext();
31         size--;
32         return obj;
33     }
34 
35     @Override
36     public void push(Object obj) throws Exception {
37         // TODO Auto-generated method stub
38         head = new Node(obj, head);
39         size++;
40     }

(4)Test.java:測試類

 1 import java.util.Scanner;
 2 
 3 public class Test {
 4 
 5     public static void main(String[] args) throws Exception {
 6         //SequenceStack stack = new SequenceStack(10);
 7         LinkStack stack = new LinkStack();
 8         Scanner in = new Scanner(System.in);
 9         int temp;
10         for (int i = 0; i < 10; i++) {
11             System.out.println("請輸入第" + (i + 1) + "個整數:");
12             temp = in.nextInt();
13             stack.push(temp);
14         }
15         //遍歷輸出
16         while (!stack.isEmpty()) {
17             System.out.println(stack.pop());
18         }
19     }
20 }

運行效果:

342f95fa-7e65-40cb-8bc5-599022308993

 

 

七、堆棧的應用:

堆棧是各種軟件系統中應用最廣泛的數據結構之一。括號匹配和表達式計算是編譯軟件中的基本問題,其軟件設計中都需要使用堆棧。

  • 括號匹配問題
  • 表達式計算

1、括號匹配問題:

假設算術表達式中包含圓括號,方括號,和花括號三種類型。使用棧數據結構編寫一個算法判斷表達式中括號是否正確匹配,並設計一個主函數測試。

比如:

{a+[b+(c*a)/(d-e)]}    正確

([a+b)-(c*e)]+{a+b}    錯誤,中括號的次序不對

括號匹配有四種情況:

1.左右括號匹配次序不正確

2.右括號多於左括號

3.左括號多於右括號

4.匹配正確

下面我們就通過代碼把這四種情況列舉出來。

代碼實現:

 1 public class Test {
 2 
 3     //方法:將字符串轉化為字符串數組
 4     public static String[] expToStringArray(String exp) {
 5         int n = exp.length();
 6         String[] arr = new String[n];
 7         for (int i = 0; i < arr.length; i++) {
 8             arr[i] = exp.substring(i, i + 1);
 9         }
10         return arr;
11     }
12 
13     //方法:括號匹配問題的檢測
14     public static void signCheck(String exp) throws Exception {
15         SequenceStack stack = new SequenceStack();
16         String[] arr = Test.expToStringArray(exp);
17         for (int i = 0; i < arr.length; i++) {
18             if (arr[i].equals("(") || arr[i].equals("[") || arr[i].equals("{")) { //當碰到都是左邊的括號的時候,統統壓進棧
19                 stack.push(arr[i]);
20             } else if (arr[i].equals(")") && !stack.isEmpty() && stack.getTop().equals("(")) { //當碰到了右小括號時,如果匹配正確,就將左小括號出棧
21                 stack.pop();
22             } else if (arr[i].equals(")") && !stack.isEmpty() && !stack.getTop().equals("(")) {
23                 System.out.println("左右括號匹配次序不正確!");
24                 return;
25             } else if (arr[i].equals("]") && !stack.isEmpty() && stack.getTop().equals("[")) {
26                 stack.pop();
27             } else if (arr[i].equals("]") && !stack.isEmpty() && !stack.getTop().equals("[")) {
28                 System.out.println("左右括號匹配次序不正確!");
29                 return;
30             } else if (arr[i].equals("}") && !stack.isEmpty() && stack.getTop().equals("{")) {
31                 stack.pop();
32             } else if (arr[i].equals("}") && !stack.isEmpty() && !stack.getTop().equals("{")) {
33                 System.out.println("左右括號匹配次序不正確!");
34                 return;
35             } else if (arr[i].equals(")") || arr[i].equals("]") || arr[i].equals("}") && stack.isEmpty()) {
36                 System.out.println("右括號多於左括號!");
37                 return;
38             }
39         }
40         if (!stack.isEmpty()) {
41             System.out.println("左括號多於右括號!");
42         } else {
43             System.out.println("括號匹配正確!");
44         }
45     }
46 
47 
48     public static void main(String[] args) throws Exception {
49 
50         String str = "([(a+b)-(c*e)]+{a+b}";
51         //括號匹配的檢測
52         Test.signCheck(str);
53     }
54 }

運行效果:

1c921aae-8a2e-4b70-bf4a-caf812781afc

 

上方代碼中,第50行是一個錯誤的括號表達式,於是運行結果也很明顯了。

 

2、表達式計算:

比如:

  3+(6-4/2)*5=23 

后綴表達式為:3642/-5*+# (#符號為結束符)

現在要做的是:

使用鏈式堆棧,設計一個算法計算表達式,當我們輸入后綴表達式后,能輸出運行結果。

代碼實現:

 1 public class Test {
 2 
 3     //方法:使用鏈式堆棧,設計一個算法計算表達式
 4     public static void expCaculate(LinkStack stack) throws Exception {
 5         char ch; //掃描每次輸入的字符。
 6         int x1, x2, b = 0; //x1,x2:兩個操作數 ,b字符的ASCII碼
 7         System.out.println("輸入后綴表達式並以#符號結束:");
 8         while ((ch = (char) (b = System.in.read())) != '#') {
 9             //如果是數字,說明是操作數則壓入堆棧
10             if (Character.isDigit(ch)) {
11                 stack.push(new Integer(Character.toString(ch)));
12             }
13             //如果不是數字,說明是運算符
14             else {
15                 x2 = ((Integer) stack.pop()).intValue();
16                 x1 = ((Integer) stack.pop()).intValue();
17                 switch (ch) {
18                     case '+':
19                         x1 += x2;
20                         break;
21                     case '-':
22                         x1 -= x2;
23                         break;
24                     case '*':
25                         x1 *= x2;
26                         break;
27                     case '/':
28                         if (x2 == 0) {
29                             throw new Exception("分母不能為零!");
30                         } else {
31                             x1 /= x2;
32                         }
33                         break;
34                 }
35                 stack.push(new Integer(x1));
36             }
37         }
38         System.out.println("后綴表達式計算結果是:" + stack.getTop());
39     }
40 
41     public static void main(String[] args) throws Exception {
42         LinkStack stack = new LinkStack();
43         //(2+3)*(3-1)/2=5的后綴表達式為:23+31-*2/
44         //方法:鍵盤輸入后綴表達式,輸出的得到計算結果
45         Test.expCaculate(stack);
46 
47     }
48 }

 

運行效果:

196bd089-8fad-4256-bcda-a9a4ab4acc20

 


免責聲明!

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



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