學習筆記之使用java實現數據結構的棧

前言

關於棧相信各位讀者也很熟悉,屬於數據結構中很常見的一種。在數據結構書中國一般都是使用c語言去實現棧這種數據結構。確實,因爲c語言有指針能夠更好地操作內存,而且運行速度快很適合作爲寫數據結構地語言。但是因爲學習了java,所以今天也來嘗試一下使用java來實現棧。同時簡單介紹一下什麼是棧。

什麼是棧?

棧是一種給數據結構所以肯定是用愛存儲數據的。他是一種線性的數據結構,結構邏輯簡單點來說就是“先進先出”。例如拿一個羽毛球筒,我們從上面放羽毛球進去,那麼拿出來的時候肯定只能從上面一個一個取出來(別跟我說你可以從下面去取出來,下面堵死打不開的),那麼要拿到下面的羽毛球就只能把上面的羽毛球先取出來才能拿到。相對應的放入羽毛球叫做入棧或者壓棧,取出羽毛球叫做出棧,全部取出來叫做清空棧,把整個羽毛球筒都扔掉叫做銷燬棧。應該可以理解吧。

那麼哪些地方有用到棧呢?其實很多。例如我們打開文件夾的時候,當我們點擊上一層是不是一層一層的往上面走呢,後打開的先沒了;又例如瀏覽網頁的時候,當我們點擊後退的時候,是不是也是這樣的道理。除此之外還有很多,就等你們慢慢去了解啦。接下倆讓我們看看怎麼用java去實現棧

順序棧

我們都知道數據結構有兩種物理存儲方式:順序和鏈式。什麼意思呢?像數組那樣一整塊連續的內存就是順序;像鏈表那樣不是連續的一塊內存,而是用指針把之間聯繫起來的就是鏈式。順序棧就是使用和數組一樣的物理結構的。

創建接口

我們創建一個棧,首先要規定要這個棧要實現的接口。例如入棧出棧等。在c語言中他是寫成了一些函數,但是在java,他可以是棧這個類的方法,然後讓棧去實現這些接口,話不多說,先看看這些接口:

public interface StackInter<T> {
	
	//初始化棧
	void initStack();
	
	//清空棧
	void clearStack();
	
	//元素入棧
	void pushElem(T elem);
	
	//棧頂元素出棧
	T popTop();
	
	//獲取棧頂元素
	T getTop();
	
	//判斷棧是否爲空
	Boolean isStackEmpty();
	
	//獲取當前棧中有幾個元素
	int getElemNums();
}

這裏我使用了泛型,所以可以給各種類型的棧使用,相當於數據結構中的ElemType。關於接口的意義上面也都有解釋,相信可以明白的。實現這個接口的類後就會有相應得方法了。

創建棧類

定好接口後,接下來要做的肯定就是構造棧這個類了。我們上面已經定好了接口所以現在就要去實現這個接口,並指定泛型。看代碼:

import entity.Student;
public class StudentSequeneStack implements StackInter<Student> {
}


package entity;
public class Student {
	private String name;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}

這裏我自己新建了一個Student得類型作爲這個棧得數據類型,後面棧存儲得數據類型就都是Student。

關於棧得方法上面已經定義好接口了,那麼棧還需要什麼屬性呢?存放數據得數組,長度,棧頂位置,每次拓增得數目。瞭解需要得屬性後看看代碼:

public class StudentSequeneStack implements StackInter<Student> {

	//top表示棧頂的位置,size表示棧的大小,increment表示每次擴大的大小
	private Student[] studentArray;;
	int top = 0;
	int size = 0;
	int increment = 10;
	}

這樣四個元素都有了。接下來重點就是去實現剛纔我們定義好得接口了。我們先看總的代碼,再一個一個來說明一下:

import entity.Student;

public class StudentSequeneStack implements StackInter<Student> {

	//top表示棧頂的位置,size表示棧的大小,increment表示每次擴大的大小
	private Student[] studentArray;;
	int top = 0;
	int size = 0;
	int increment = 10;
	
	//初始化棧
	@Override
	public void initStack() {
		// TODO Auto-generated method stub
		if (studentArray!=null) {
			studentArray = new Student[10];
			size = 10;
		}
	}

	//清空棧
	@Override
	public void clearStack() {
		// TODO Auto-generated method stub
		if(top!=0) {
			Student[] students = new Student[10];
			studentArray = new Student[10];
			top = 0;
			size = 10;
		}
	}

	//元素入棧
	@Override
	public void pushElem(Student elem) {
		// TODO Auto-generated method stub
		//棧滿了就拓展棧
		if (top>=size) {
			Student[] students = new Student[top+increment];
			for(int i = 0;i<top;i++) {
				students[i] = studentArray[i];
			}
			studentArray = students;
		}
		studentArray[top++] = elem;
	}

	//出棧
	@Override
	public Student popTop() {
		// TODO Auto-generated method stub
		if(top!=0) return studentArray[--top];
		else return null;
	}

	//獲取棧頂元素
	@Override
	public Student getTop() {
		// TODO Auto-generated method stub
		if(top!=0) return studentArray[top-1];
		else return null;
	}

	//判斷棧是否爲空
	@Override
	public Boolean isStackEmpty() {
		// TODO Auto-generated method stub
		if(top==0) return true;
		else return false;
	}

	//獲取棧中有多少個元素
	@Override
	public int getElemNums() {
		// TODO Auto-generated method stub
		return top;
	}
}

代碼看起來很多,但其實並不複雜,我們一個一個來看:

  • 初始化棧:這裏加個判斷,如果還沒初始化得話,調用這個方法後就會進行初始化,分配內存。之所以把新建數組對象放在這個地方是因爲放在類中得話加載類得時候就會分配內存,如果用不到得話就會浪費內存了。分配完內存後再對size進行初始化。這裏我設置初始值爲10.
  • 入棧:這裏要分爲兩種情況:如果內存夠的話直接放進數組,並讓top+1就行。如果不夠得話就要手動自行拓展內存,並把原來得數據搬移過去。在C語言中有realloc這個函數來拓展內存比較方便,java沒有,但是,我們手動一下也是可以得嘛。分配完內存之後記得把原來得studentArray指向新的數組,並讓top+1.這裏可能有讀者會問怎麼不用釋放原來得數組得內存?欸這就是java得強大之處了,他有垃圾回收機制,對於沒用得對象會自動釋放回收內存,不用我們自己去手動釋放,只要把指向他得引用去掉就可以了。
  • 出棧:判斷一下棧頂是否有元素,有得話棧頂得元素返回並讓top-1.沒有得話返回null。
  • 返回棧頂元素:這個和出棧差不多。
  • 清空棧:這個清空棧只要把top=0就可以了。因爲內存已經分配好,不用去把內存中得數值置null,沒有必要也浪費時間。
  • 判斷棧是否爲空:看看top是不是爲0就可以了。
  • 返回棧中元素個數:直接返回top。
    最後有讀者會問怎麼沒有銷燬棧啊?欸就是我剛纔講的,java有垃圾回收機制,把指向他得引用變量=null就可以了。

測試一下

寫完了我們就來測試一下我們得棧吧。簡單設置幾組數據:

import Stucture.LinearStack;
import Stucture.StudentSequeneStack;
import entity.Student;

public class Main {
	
	
	public static void main(String[] args) {
		Student student1 = new Student();
		student1.setAge(12);
		student1.setName("mike");
		Student student2 = new Student();
		student2.setAge(13);
		student2.setName("sali");
		
		LinearStack<Student> stuLStack = new LinearStack<Student>();
		stuLStack.pushElem(student1);
		stuLStack.pushElem(student2);
		System.out.println(stuLStack.getTop().getName());
		
		stuLStack.popTop();
		System.out.println(stuLStack.getTop().getName());
		System.out.println(String.valueOf(stuLStack.getElemNums()));
		
		stuLStack.clearStack();
		if (stuLStack.getTop()==null)
		System.out.println("棧爲空");
		}

然後我們來運行一下:
在這裏插入圖片描述
對啦,說明我們得簡易棧模型建立成功了。

鏈棧

剛纔說了有兩種類型,所以有順序棧就有鏈棧了。鏈棧和順序棧用起來是差不多但是物理結構是不一樣的。實現方式不一樣。鏈棧可以利用到零碎的空間,不像順序棧需要一整塊的空間。接下來看看怎麼實現吧。

接口上面已經定義好了,繼續複用就可以了。

節點類

和順序棧不同的是,鏈棧的每個節點都需要一個指向下一個元素的引用,每一個節點都需要兩個域:數據域和指針域。說的玄乎,但其實就是上面我說的一個是Student這個對象的引用一個是節點的引用。看代碼:

package Stucture;

//節點
public class Node<T> {
	T t;
	Node<T> elem;
}

這裏我用了泛型,這樣的話,對於不同的數據類型可以對應不同的泛型。例如我們上面的Student類,那麼我們就可以指定泛型爲Student。爲什麼這裏不像c語言那樣把具體的數據放進去,例如把name和age這兩個數據放進去而是放了一個Student引用呢?這其實和結構體是一樣的,外部只需要關注每個節點的數據是什麼而不需要關注什麼引用指針。外部只需要傳入Student這種對象就ok了,不用去關注其他的,這也是一種封裝的思想。那麼定好了節點類,接下來看看棧類怎麼寫,先看看代碼在再一個個個來解析:

package Stucture;

public class LinearStack<T> implements StackInter<T> {
	private Node<T> topElem = null;
	private int size = 0;
	
	//棧的初始化
	@Override
	public void initStack() {
		// TODO Auto-generated method stub
		
	}

	//清空棧
	@Override
	public void clearStack() {
		// TODO Auto-generated method stub
		
		Node<T> e;
		//讓每個節點指向null
		while(topElem!=null && topElem.elem!=null) {
			e = topElem.elem;
			topElem.elem = null;
			topElem = e;
		}
		topElem = null;
		size = 0;
	}

	//新元素入棧
	@Override
	public void pushElem(T elem) {
		// TODO Auto-generated method stub
		//創建一個節點
		Node<T> e = new Node<>();
		e.t = elem;
		e.elem = topElem;
		
		//把棧頂指向新的元素
		topElem = e;
		size++;
	}

	//棧頂出棧
	@Override
	public T popTop() {
		// TODO Auto-generated method stub
		if(topElem!=null) {
			Node<T> e = topElem;
			topElem = topElem.elem;
			T t = e.t;
			e.elem = null;
			size--;
			return t;
		}else {
			return null;
		}
	}

	//獲取棧頂元素
	@Override
	public T getTop() {
		// TODO Auto-generated method stub
		if(topElem!=null) return topElem.t;
		else return null;
	}

	//判斷棧是否爲空
	@Override
	public Boolean isStackEmpty() {
		// TODO Auto-generated method stub
		if(topElem!=null) return false;
		else return true;
	}

	//獲取棧中的元素數目
	@Override
	public int getElemNums() {
		// TODO Auto-generated method stub
		return size;
	}
}
  • 首先第一個因爲我們要適配不同的數據類型所以用到了泛型,在新建棧對象的時候只需要指定泛型就可以指定該棧的數據類型而不用去再建一個棧類。但爲什麼在順序棧的時候不這麼做呢?因爲泛型是不確定的,不知道元素的實際大小所以沒辦法去開闢空間,java語法也不允許這種做法,所以只能是一種類型的數據寫一種棧類。因而這也是鏈棧的優勢之一。
  • 第二個我們需要繼承接口,這個是棧的接口,因爲我們棧類使用了泛型,這裏的泛型保持一致就行。這樣就有了棧需要的所有方法了。
  • 然後是定義需要的屬性:棧頂節點的地址,長度。通過棧頂節點可以遍歷棧,長度可以用來判斷棧是否爲空。
  • 然後就是重寫棧接口的方法了,這裏和順序棧有所不同我們也一個個來看一下:
  1. 棧的初始化:因爲不需要開闢空間所以不需要初始化。
  2. 入棧:這個棧是以鏈表的形式儲存的,所以棧頂肯定就是head節點了。所以當有新的元素入棧的時候這個新的元素就成爲新的head節點。
  3. 出棧:棧頂元素出棧,棧頂元素所指向的下個元素成爲新的節點,然後記得讓他的next引用指向null,這樣的話他就成爲了“垃圾”等待系統回收,不用我們自己去釋放內存。size減一。記得判斷棧頂元素是否爲空。
  4. 獲取棧頂元素:直接把棧頂元素返回。
  5. 獲取棧內元素數目:返回size。
  6. 清空棧:這個比順序棧要麻煩一點,因爲需要去清除每個節點的引用使得其可以被系統回收。所以依次把每個節點的next引用都指空,並把top引用指向null。這樣就清空了。size置零。
  7. 判斷棧是否爲空:判斷size是否爲0.
    這樣一個鏈棧就寫好了。是不是感覺也不難呢?事實上java比C語言的實現是要簡單一些的,java不用去開闢內存釋放內存這些操作。接下來看看測試吧

測試鏈棧

先看代碼:

import Stucture.LinearStack;
import Stucture.StudentSequeneStack;
import entity.Student;

public class Main {
	
	
	public static void main(String[] args) {
		Student student1 = new Student();
		student1.setAge(12);
		student1.setName("mike");
		Student student2 = new Student();
		student2.setAge(13);
		student2.setName("sali");
		
		StudentSequeneStack sqStack = new StudentSequeneStack();
		sqStack.initStack();
		
		for(int i = 0;i<9;i++) 
			sqStack.pushElem(student1);
		System.out.println(sqStack.popTop().getName());
		System.out.println(String.valueOf(sqStack.getElemNums()));
		
		for(int i = 0;i<9;i++)
			sqStack.pushElem(student2);
		System.out.println(sqStack.getTop().getName());
		System.out.println(String.valueOf(sqStack.getElemNums()));
		
		sqStack.clearStack();
		System.out.println(String.valueOf(sqStack.getElemNums()));
		}

上面的代碼也很簡單,就是新建兩個Student對象再簡單進行棧的操作,看看測試結果:
在這裏插入圖片描述
結果符合我們的預想,這樣一個簡單的鏈棧就實現了。

小結

棧是數據結構中比較常見而且相對簡單的一種數據結構。在學習的時候看起來好像很簡單,但其實實際寫起來的話一開始還是有一點難以下手。特別是習慣了c語言的寫法,再轉思想用java寫可能會比較困難。但是當我們親手寫出來之後肯定印象會更加深的,也會更加了解這兩種語言之間的差別。上面實現的是簡單的棧模型,有哪些地方不足讀者可以在評論區留言。
·
·
·

參考資料

《數據結構》吳偉民
《漫畫算法》魏夢舒

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章