數組和鏈表是常用的兩種數據結構,在翻看了Stack類,Iterable接口,Iterator接口,Queue的Java源碼后,嘗試用數組和鏈表來自己實現一下棧和隊列。
自己造過輪子后,實實在在的理解了輪子,才能真正深入的使用輪子。代碼如下:
說明:Stackable是我自己寫的接口
數組實現棧
public class SugarArrayStack<E> implements Stackable<E> ,Iterable<E>{
private static final int DEFAULT_CAPACITY = 2;
private int N = 0;
private E[] a;
public SugarArrayStack() {
resize(DEFAULT_CAPACITY);
}
public SugarArrayStack(int size){
resize(size);
}
private void resize(int size){
E[] temp = (E[])new Object[size];
for(int i = 0; i < N; i++){
temp[i] = a[i];
}
a = temp;
}
@Override
public void push(E e){
if(N >= a.length - 1){
resize(a.length * 2);
}
a[N++] = e;
}
@Override
public E pop(){
E temp = peek();
a[--N] = null;
if(N >= 0 && N == a.length / 4){
resize(a.length / 2);
}
return temp;
}
@Override
public E peek(){
int index = N - 1;
return a[index];
}
@Override
public int size(){
return N;
}
@Override
public boolean isEmpty(){
return N == 0;
}
@Override
public Iterator<E> iterator() {
return new ArrayIterator(N);
}
private class ArrayIterator implements Iterator<E>{
private int currentIndex;
public ArrayIterator(int currentIndex) {
this.currentIndex = currentIndex;
}
@Override
public boolean hasNext() {
return currentIndex > 0;
}
@Override
public E next() {
currentIndex--;
return a[currentIndex];
}
}
}
鏈表實現棧隊列
public class SugarLinkedQueue<E> implements Iterable<E>{
private Node<E> mHeadNode;
private Node<E> mTailNode;
private int mCount = 0;
public void enqueue(E e){
Node<E> newTailNode = new Node<>(e,null);
if(mCount == 0){
mTailNode = newTailNode;
mHeadNode = newTailNode;
}else{
Node<E> oldTailNode = mTailNode;
mTailNode = newTailNode;
oldTailNode.nextNode = newTailNode;
}
mCount ++;
}
public E dequeue(){
if(isEmpty()){
throw new IllegalStateException("Please enqueue an element firstly");
}
mCount --;
E tempE = mHeadNode.e;
mHeadNode = mHeadNode.nextNode;
return tempE;
}
public int size(){
return mCount;
}
public boolean isEmpty(){
return mCount == 0;
}
@Override
public Iterator<E> iterator() {
return new QueueIterator(mHeadNode);
}
private class QueueIterator implements Iterator<E>{
private Node<E> currentNode;
public QueueIterator(Node<E> currentNode) {
this.currentNode = currentNode;
}
@Override
public boolean hasNext() {
return currentNode != null;
}
@Override
public E next() {
E currentE = currentNode.e;
currentNode = currentNode.nextNode;
return currentE;
}
}
private class Node<E>{
private E e;
private Node<E> nextNode;
public Node(E e, Node<E> nextNode) {
this.e = e;
this.nextNode = nextNode;
}
}
}
鏈表實現棧
public class SugarLinkedStack<E> implements Stackable<E> ,Iterable<E>{
private Node<E> headNode;
private int count = 0;
@Override
public boolean isEmpty() {
return count == 0;
}
@Override
public int size() {
return count;
}
@Override
public void push(E e) {
count++;
headNode = new Node<>(e, headNode);
}
@Override
public E pop() {
if(count <= 0){
throw new IllegalStateException("please push an element firstly");
}
count --;
E e = headNode.e;
headNode = headNode.nextNode;
return e;
}
@Override
public E peek() {
return headNode.e;
}
@Override
public Iterator<E> iterator() {
return new LinkedIterator(headNode);
}
private class LinkedIterator implements Iterator<E>{
private Node<E> currentNode;
public LinkedIterator(Node<E> currentNode) {
this.currentNode = currentNode;
}
@Override
public boolean hasNext() {
return currentNode != null;
}
@Override
public E next() {
E tempE = currentNode.e;
currentNode = currentNode.nextNode;
return tempE;
}
}
private class Node<E> {
private E e;
private Node<E> nextNode;
private Node(E e, Node<E> nextNode) {
this.e = e;
this.nextNode = nextNode;
}
}
}
小結
在實現過程中,產生了一些心得和感受,如下:
- 新建一個Java類,順序是:先初始化成員變量,然后再調用它的構造方法。因此,某些場景上,一些操作是放在成員變量上還是放在構造方法里是有選擇和考量的
- 鏈表這種數據結構天生包含迭代的編程思想,用遞歸來解決遇到的問題
- 數組這種數據結構使用索引訪問元素(指針的使用感),操作數組序號來解決遇到的問題
- 用鏈表的實現,插入和刪除元素的操作和元素數量多少沒有關系。
- 深刻理解Iterator遍歷原理,這個接口提供了集合遍歷的邏輯封裝,邏輯分為兩個部分:hasNext方法和next方法。這兩個方法合在一起產生出遞歸的效果,從而實現集合遍歷
自制棧實踐
是時候使用自己造的輪子了
問題表述:給出一個字符串的算術表達式,求其值。比如“((( 10 + (34 - 24))*5)-(200/11))”
思路分析:用兩個棧,一個保存運算符,一個保存操作數。將操作數壓入操作數棧,將運算符壓入運算符棧,忽略左括號,每遇到右括號,彈出一個運算符,彈出所需數量的操作數,計算后所得結果再壓入操作數棧。
代碼如下:
public static void main(String[] args) {
String expression = "((( 10 + (34 - 24))*5)-(200/11))";
float result = calculate(expression);
System.out.println("result = "+result);
}
private static float calculate(String expression) {
if (expression != null && expression.length() > 0) {
Stackable<Character> operatorsStack = new SugarArrayStack<>();
Stackable<Float> valueStack = new SugarLinkedStack<>();
StringBuilder sb = new StringBuilder();
char[] charArray = expression.toCharArray();
for (char c : charArray) {
switch (c) {
case '(':
case ' ':
break;
case '+':
case '-':
case '*':
case '/':
pushNumberValue(valueStack, sb);
operatorsStack.push(c);
break;
case ')':
pushNumberValue(valueStack, sb);
Float firstPop = valueStack.pop();
Character operation = operatorsStack.pop();
float f;
switch (operation) {
case '+':
f = valueStack.pop() + firstPop;
break;
case '-':
f = valueStack.pop() - firstPop;
break;
case '*':
f = valueStack.pop() * firstPop;
break;
case '/':
f = valueStack.pop() / firstPop;
break;
default:
throw new IllegalArgumentException("this operator haven't been supported now");
}
valueStack.push(f);
break;
default:
sb.append(c);
}
}
return valueStack.peek();
}
return -1;
}
private static void pushNumberValue(Stackable<Float> valueStack, StringBuilder sb) {
int length = sb.toString().length();
if(length > 0){
float value = Float.parseFloat(sb.toString());
valueStack.push(value);
sb.delete(0, length);
}
}
如上例子,最終輸出的結果是:
result = 81.818184
這種計算存在一些缺陷:
- 需要左右括號包裝算術表達式
- 在一個括號里,只能進行兩個操作數的計算
小結
到此,我們用自己造的棧,實現了任意長度下的四則算術運算,今天的天氣也比昨天格外得好了。
參考資料
- 算法 Algorithms Fourth Edition By Robert Sedgewick & Kevin Wayne