看得見的資料結構Android版之棧結構的實現
零、前言
1.你應該很常用到方法裡邊再呼叫方法吧,你有沒有想過計算機是怎麼識別的
2.你肯定能感覺到,後呼叫的方法總是先返回,然後在上一個方法中在繼續運算
3.後進先出,現實世界看起來確實有點不公平,但在計算機世界似乎才是真理,而且作用非常大
4.本例操作演示原始碼: Android" target="_blank" rel="nofollow,noindex">希望你可以和我在Github一同見證:DS4Android的誕生與成長,歡迎star
1.留圖鎮樓:棧的最終實現的操作效果:

棧操作合集.gif
2.對於棧結構的簡介:
棧是一種線性的資料結構 特性:僅棧頂元素可見、後進先出LIFO 操作:push入棧 pop彈棧 peek檢視棧頂元素
一、定義棧的介面:IStack
棧是一種非常簡單的資料結構,方法也很少,但靈活運用還是要技巧的
個人感覺棧很純正,簡約,而不簡單。

棧操作.png
/** * 作者:張風捷特烈 * 時間:2018/8/17 0017:12:49 * 郵箱:[email protected] * 說明:棧的介面 */ public interface IStack<T>{ /** * 棧元素個數 * @return棧元素個數 */ int size(); /** * 棧元素容積 * @return 容積 */ int capacity(); /** * 是否為空 * @return是否為空 */ boolean isEmpty(); /** * 入棧 * @param el 元素 */ void push(T el); /** * 出棧 * @return 元素 */ T pop(); /** * 取出元素 * @return 元素 */ T peek(); }
二、棧的多種實現方式
同樣,棧也是抽象概念,需要去實現,本文會用前面寫過的陣列表和單鏈表分別實現棧
注: 雙鏈表
與 單鏈表
實現棧基本一致,從結構的簡單性來看 單鏈表
有優勢一些,所以未用雙鏈表
1.陣列表棧:
其實陣列表已經有棧的所有功能,這裡只是實現棧介面呼叫一下,最底層是陣列實現
/** * 作者:張風捷特烈 * 時間:2018/8/17 0017:12:56 * 郵箱:[email protected] * 說明:棧的陣列表實現 */ public class ArrayChartStack<T> implements IStack<T> { private ArrayChart<T> array;//成員變數 public ArrayChartStack(int capacity) { array = new ArrayChart<>(capacity); } public ArrayChartStack() { array = new ArrayChart<>(); } @Override public int size() { return array.size(); } @Override public int capacity() { return array.capacity(); } @Override public boolean isEmpty() { return array.isEmpty(); } @Override public T pop() { return array.remove(); } @Override public void push(T el) { array.add(el); } @Override public T peek() { return array.get(size() - 1); } }
棧的push入棧:
將元素插入到棧頂,即藍色元素(注意:棧規定----除棧頂外其他約束都是不可見和不可操作的)

push操作.gif
棧的peek檢視棧頂元素:

peek操作.gif
棧的pop出棧:將棧頂的元素彈出棧

pop操作.gif
2.單鏈表實現棧結構:
/** * 作者:張風捷特烈 * 時間:2018/11/23 0017:22:40 * 郵箱:[email protected] * 說明:棧的連結串列式集合實現 */ public class SingleLinkedStack<E> implements IStack<E> { private SingleLinkedChart<E> mSingleLinkedChart; public SingleLinkedStack() { mSingleLinkedChart = new SingleLinkedChart<>(); } @Override public int size() { return mSingleLinkedChart.size(); } @Override public int capacity() { return mSingleLinkedChart.size(); } @Override public boolean isEmpty() { return mSingleLinkedChart.isEmpty(); } @Override public void push(E el) { mSingleLinkedChart.add(el); } @Override public E pop() { return mSingleLinkedChart.remove(); } @Override public E peek() { return mSingleLinkedChart.get(0); } }
如果你覺得棧很簡單,可以自行研究一下[用棧平衡符號]、[字尾表示式]、[遞迴方法呼叫]
這三個是棧的經典應用,以後有機會要專門寫一篇來講述,本文只限於棧結構的實現,就不引申了。
三、連結串列和陣列表實現棧的比較
1.陣列表棧:ArrayChartStack測試
方法\數量 | 1000次 | 10000次 | 10W次 | 100W次 | 1000W次 |
---|---|---|---|---|---|
push | 0.0011秒 | 0.0034秒 | 0.0158秒 | 0.0726秒 | 1.020秒 |
pop | 0.0006秒 | 0.0025秒 | 0.0085秒 | 0.0280秒 | 0.1751秒 |
2.連結串列棧:SingleLinkedStack測試
方法\數量 | 1000次 | 10000次 | 10W次 | 100W次 | 1000W次 |
---|---|---|---|---|---|
push | 0.0005秒 | 0.0027秒 | 0.0075秒 | 0.3817秒 | 3.1550秒 |
pop | 0.0004秒 | 0.0022秒 | 0.0050秒 | 0.0223秒 | 0.1267秒 |
可見低數量下連結串列似乎更有優勢,因為一開始陣列表會經常擴容,越大擴容的次數越低
在1000W次的高次數下陣列表看似要優秀一點,但它實際上可能佔用了更大的空間
因為如果1000W次左右進行擴容,就有500W的空白空間,而連結串列則不會,雖然稍慢兩秒,還是可以接受的
綜合來看連結串列實現棧結構要優秀一些。
四、檢視的畫法
感覺本篇挺短的,就順帶把圖畫一下吧
1.繪製的思路:
一開始也挺鬱悶的,因為棧不能訪問非棧頂元素,那單用棧是畫不出底下的元素的
又想使用棧的方法進行測試,所以折中一下,用一個ArrayList跟棧同步盛放,都畫出來
進入和彈出動畫為了好區分,用兩個ValueAnimator控制,下面是成員變數
private Point mCoo = new Point(300, 200);//座標系 private Picture mGridPicture;//網格canvas元件 private Path mPath;//主路徑 private Paint mPaint;//主畫筆 private Paint mTxtPaint;//數字畫筆 private Paint mBoderPaint;//路徑畫筆 private Paint mCtrlPaint;//幾個圓的畫筆 //private IStack<StackBox<E>> mStackBoxes = new ArrayChartStack<>();//陣列表棧 private IStack<StackBox<E>> mStackBoxes = new SingleLinkedStack<>();// //用於繪製非棧頂元素(由於Stack無法獲取這些元素,所以此集合輔助繪製) private List<StackBox<E>> mUnSeeStackItemBox = new ArrayList<>(); private OnCtrlClickListener<StackView<E>> mOnCtrlClickListener;///點選監聽 private ValueAnimator mInAnimator;//入棧動畫 private ValueAnimator mOutAnimator;//出棧動畫 private boolean canAdd = true;//是否可新增---防止多次點選新增 private static final int OFFSET_OF_TXT_Y = 10;//文字的偏移 private static final Point[] CTRL_POS = new Point[]{//控制按鈕的點位 new Point(-120, 100),//新增 new Point(-120, 300 + 50),//移除 new Point(-120, 500 + 100),//檢視棧頂 }; private static int[] CTRL_COLOR = new int[]{//控制按鈕的顏色 0xff1EF519,//新增 0xffB946F4,//移除 0xff2992F2,//檢視棧頂 }; private static final String[] CTRL_TXT = new String[]{//控制按鈕的文字 "push",//新增 "pop",//移除 "peek",//檢視棧頂 }; private static final int CTRL_RADIUS = 70;//控制按鈕的半徑 private static final int BOTTOM_OF_STACK = 700;//控制按鈕的半徑 private static final int WIDTH_OF_STACK = 300;//控制按鈕的半徑 private static final int STACK_X = 400;//控制按鈕的半徑 private static final int STACK_Y = 100;//控制按鈕的半徑 private static final int LEN_ABOVE_STACK = 200;//控制按鈕的半徑 private int mCurStackTopLine = BOTTOM_OF_STACK;//當前棧頂線
2.一些成員的初始化
private void init() { //初始化主畫筆 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.BLUE); mPaint.setStrokeWidth(5); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setTextSize(50); //初始化主路徑 mPath = new Path(); //初始化文字畫筆 mTxtPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTxtPaint.setColor(Color.WHITE); mTxtPaint.setTextAlign(Paint.Align.CENTER); mTxtPaint.setTextSize(50); //初始化路徑畫筆 mBoderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBoderPaint.setColor(Color.BLACK); mBoderPaint.setStrokeWidth(4); mBoderPaint.setStyle(Paint.Style.STROKE); mGridPicture = HelpDraw.getGrid(getContext()); //初始化圓球按鈕畫筆 mCtrlPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mCtrlPaint.setColor(Color.RED); mCtrlPaint.setTextAlign(Paint.Align.CENTER); mCtrlPaint.setTextSize(30); //初始化時間流ValueAnimator mInAnimator = ValueAnimator.ofFloat(0, 1); mInAnimator.setRepeatCount(-1); mInAnimator.setDuration(2000); mInAnimator.setRepeatMode(ValueAnimator.REVERSE); mInAnimator.setInterpolator(new LinearInterpolator()); mInAnimator.addUpdateListener(animation -> { updateBall();//更新小球位置 invalidate(); }); //初始化時間流ValueAnimator---移除 mOutAnimator = ValueAnimator.ofFloat(0, 1); mOutAnimator.setRepeatCount(-1); mOutAnimator.setDuration(2000); mOutAnimator.setRepeatMode(ValueAnimator.REVERSE); mOutAnimator.setInterpolator(new LinearInterpolator()); mOutAnimator.addUpdateListener(animation -> { updateOutBall();//更新小球位置 invalidate(); }); }
3.核心的三個操作:
在加入ArrayList時,將StackBox的x,y座標根據元素個數進行初始計算:
stackBox.y = STACK_Y - BOTTOM_OF_STACK + Cons.BOX_HEIGHT * mStackBoxes.size();
當新增動畫開始時,直到動畫暫停,都要禁用新增
/** * 入棧 * * @param data 資料 */ public void addData(E data) { if (!canAdd) { return; } StackBox<E> stackBox = new StackBox<>(0, 0); stackBox.vY = 18; stackBox.data = data; stackBox.color = ColUtils.randomRGB(); stackBox.x = STACK_X; stackBox.y = STACK_Y - BOTTOM_OF_STACK + Cons.BOX_HEIGHT * mStackBoxes.size(); mStackBoxes.push(stackBox); mUnSeeStackItemBox.add(stackBox); StackBox box = mStackBoxes.peek();//更新棧頂點位 box.x = STACK_X; box.y = STACK_Y - LEN_ABOVE_STACK; mInAnimator.start(); canAdd = false; } /** * 檢視棧頂元素 */ public E findData() { if (mStackBoxes.isEmpty()) { Toast.makeText(getContext(), "棧為空", Toast.LENGTH_SHORT).show(); } if (mStackBoxes.size() > 0) { return mStackBoxes.peek().data; } return null; } /** * 彈棧 */ public void removeData() { if (mStackBoxes.isEmpty()) { Toast.makeText(getContext(), "棧為空", Toast.LENGTH_SHORT).show(); } if (mStackBoxes.size() > 0) { mOutAnimator.start(); canAdd = false; } }
4.入棧和出棧的動畫
這裡動態的判斷和修正棧頂的位置值,這是很關鍵的一步
/** * 入棧動畫 */ private void updateBall() { if (mStackBoxes.size() > 0) { StackBox ball = mStackBoxes.peek(); ball.x += ball.vX; ball.y += ball.vY; if (ball.y > mCurStackTopLine) { ball.y = mCurStackTopLine; mInAnimator.pause(); mCurStackTopLine = BOTTOM_OF_STACK - (mUnSeeStackItemBox.size()) * Cons.BOX_HEIGHT;//更新棧頂線 canAdd = true; } } } /** * 出棧動畫 */ private void updateOutBall() { if (mStackBoxes.size() > 0) { StackBox ball = mStackBoxes.peek(); ball.x += ball.vX; ball.y -= ball.vY; if (ball.y < -Cons.BOX_HEIGHT) { mStackBoxes.pop(); mUnSeeStackItemBox.remove(mUnSeeStackItemBox.size() - 1); mOutAnimator.pause(); mCurStackTopLine += Cons.BOX_HEIGHT; canAdd = true; } } }
5.核心的繪製方法:
/** * 繪製棧結構 * * @param canvas */ private void dataView(Canvas canvas) { mPath.moveTo(STACK_X, STACK_Y); mPath.rLineTo(0, BOTTOM_OF_STACK); mPath.rLineTo(WIDTH_OF_STACK, 0); mPath.rLineTo(0, -BOTTOM_OF_STACK); canvas.drawPath(mPath, mBoderPaint); mPaint.setColor(Color.GRAY); for (StackBox<E> box : mUnSeeStackItemBox) { canvas.drawRect(box.x, box.y, box.x + WIDTH_OF_STACK, box.y + Cons.BOX_HEIGHT, mPaint); canvas.drawRect(box.x, box.y, box.x + WIDTH_OF_STACK, box.y + Cons.BOX_HEIGHT, mBoderPaint); canvas.drawText((String) box.data, box.x + WIDTH_OF_STACK / 2, box.y + Cons.BOX_HEIGHT / 2 + 5, mTxtPaint); } mPaint.setColor(Color.BLUE); if (mStackBoxes.size() > 0) { StackBox<E> peek = mStackBoxes.peek(); canvas.drawRect(peek.x, peek.y, peek.x + WIDTH_OF_STACK, peek.y + Cons.BOX_HEIGHT, mPaint); canvas.drawText((String) peek.data, peek.x + WIDTH_OF_STACK / 2, peek.y + Cons.BOX_HEIGHT / 2 + 5, mTxtPaint); } }
後記:捷文規範
本系列後續更新連結合集:(動態更新)
看得見的資料結構Android版之雙鏈表篇(待完成)
看得見的資料結構Android版之棧(待完成)
看得見的資料結構Android版之佇列(待完成)
看得見的資料結構Android版之二叉樹篇(待完成)
看得見的資料結構Android版之二分搜尋樹篇(待完成)
看得見的資料結構Android版之AVL樹篇(待完成)
看得見的資料結構Android版之紅黑樹篇(待完成)
更多資料結構---以後再說吧
2.本文成長記錄及勘誤表
專案原始碼 | 日期 | 備註 |
---|---|---|
V0.1--github | 2018-11-23 | 看得見的資料結構Android版之棧結構的實現 |
3.更多關於我
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
我的github | 我的簡書 | 我的掘金 | 個人網站 |
4.宣告
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援

icon_wx_200.png