看得見的數據結構Android版之棧篇

零、前言

1.你應該很經常使用到方法裏邊再調用方法吧,你有沒有想過計算機是怎麼識別的
2.你確定能感受到,後調用的方法老是先返回,而後在上一個方法中在繼續運算
3.後進先出,現實世界看起來確實有點不公平,但在計算機世界彷佛纔是真理,並且做用很是大
4.本例操做演示源碼:但願你能夠和我在Github一同見證:DS4Android的誕生與成長,歡迎stargit

1.留圖鎮樓:棧的最終實現的操做效果:

棧操做合集.gif

2.對於棧結構的簡介:
棧是一種線性的數據結構
特性:僅棧頂元素可見、後進先出LIFO
操做:push入棧 pop彈棧 peek查看棧頂元素
複製代碼

1、定義棧的接口:IStack

棧是一種很是簡單的數據結構,方法也不多,但靈活運用仍是要技巧的
我的感受棧很純正,簡約,而不簡單。github

棧操做.png

/**
 * 做者:張風捷特烈
 * 時間:2018/8/17 0017:12:49
 * 郵箱:1981462002@qq.com
 * 說明:棧的接口
 */
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();
}
複製代碼

2、棧的多種實現方式

一樣,棧也是抽象概念,須要去實現,本文會用前面寫過的數組表和單鏈表分別實現棧
注:雙鏈表單鏈表實現棧基本一致,從結構的簡單性來看單鏈表有優點一些,因此未用雙鏈表編程

1.數組表棧:

其實數組表已經有棧的全部功能,這裏只是實現棧接口調用一下,最底層是數組實現canvas

/**
 * 做者:張風捷特烈
 * 時間:2018/8/17 0017:12:56
 * 郵箱:1981462002@qq.com
 * 說明:棧的數組表實現
 */
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查看棧頂元素:bash

peek操做.gif

棧的pop出棧:將棧頂的元素彈出棧微信

pop操做.gif


2.單鏈表實現棧結構:
/**
 * 做者:張風捷特烈
 * 時間:2018/11/23 0017:22:40
 * 郵箱:1981462002@qq.com
 * 說明:棧的鏈表式集合實現
 */
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);
    }
}
複製代碼

若是你以爲棧很簡單,能夠自行研究一下[用棧平衡符號]、[後綴表達式]、[遞歸方法調用]
這三個是棧的經典應用,之後有機會要專門寫一篇來說述,本文只限於棧結構的實現,就不引伸了。數據結構


3、鏈表和數組表實現棧的比較

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的空白空間,而鏈表則不會,雖然稍慢兩秒,仍是能夠接受的
綜合來看鏈表實現棧結構要優秀一些。dom


4、視圖的畫法

感受本篇挺短的,就順帶把圖畫一下吧ide

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);
    }
}
複製代碼

本系列後續更新連接合集:(動態更新)


後記:捷文規範

2.本文成長記錄及勘誤表
項目源碼 日期 備註
V0.1--github 2018-11-23 看得見的數據結構Android版之棧結構的實現
3.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
個人github 個人簡書 個人掘金 我的網站
4.聲明

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持


icon_wx_200.png
相關文章
相關標籤/搜索