20162330 2017-2018-1《程序設計與數據結構》第五週學習總結


2017-2018-1 學習總結目錄: 1 2 3 5 6 7 9 10 11 12
html



目錄


  • 0. 教材學習內容總結
    • 0.1 集合的介紹
    • 0.2 棧集合
    • 0.3 繼承、多態和泛型
    • 0.4 棧的ADT
    • 0.5 使用棧:計算後綴表達式
    • 0.6 異常
    • 0.7 使用數組實現棧
    • 0.8 ArrayStack類
    • 0.9 將引用做爲鏈
    • 0.10 管理鏈表
    • 0.11 沒有鏈的元素
    • 0.12 使用鏈實現棧
    • 0.13 使用 java.util.Stack 類實現棧
    • 0.14 包
    • 分析Java Collections API中的Stack類


教材學習內容總結

第14章 棧

集合的介紹

  • 集合是 收集組織 其餘對象的對象。java

  • 分類:線性集合(排成一行)、非線性集合(層次、網絡等方式組織)。
    【推論】棧集合中元素的添加及刪除都在一端進行,因此 棧集合屬於線性集合node

  • 集合中元素之間的組織方式取決於:
    ① 元素加入集合的次序;
    ② 元素之間的某些固有關係。git

  • 抽象數據類型:(abstract data type)簡稱ADT,其值和操做都沒有被定義。(隱藏細節:集合)算法

  • 數據結構:用來實現集合的基本程序結構集合。數組

  • Java Collections API:(application programming interface)使用不一樣方式實現的幾類集合的一組類。(包含API)安全

棧集合

  • 處理元素方式:LIFO(後進先出)網絡

  • 棧頂:元素入棧或出棧的一端。數據結構

    在解決問題時,若是要訪問非頂端元素,則不適合使用棧。併發

  • 注意pop和peek操做的區別:pop爲 刪除 棧頂元素,peek爲 查看 棧頂元素。

    從單詞上理解,pop有蹦出、離開之意,就至關於彈出了棧中元素;而peek有偷看之意,就至關於查看棧中元素。

繼承、多態和泛型

  • 類型兼容(多態)、類型檢查(編譯)。

  • 泛型:(保存、操做、管理)直到實例化時才肯定類型的對象。

棧的ADT

  • 從僞代碼中就能清楚地瞭解棧接口中的各個抽象方法:
public interface Stack<T>
{
    //  Adds the specified element to the top of the stack.
    public void push (T element);

    //  Removes and returns the top element from the stack.
    public T pop();

    //  Returns a reference to the top element of this stack without removing it.
    public T peek();

    //  Returns true if this stack contains no elements and false otherwise.
    public boolean isEmpty();

    //  Returns the number of elements in the stack.
    public int size();

    //  Returns a string representation of the stack.
    public String toString();
}

【注意】棧接口是用泛型 T 定義的,實現這個接口時,要用一個具體類型取代 T。

使用棧:計算後綴表達式

  • 後綴表達式: <操做數> <操做數> <運算符>

    後綴表達式不用考慮優先級和括號,比中綴表達式更易於計算。

  • 棧是計算後綴表達式時使用的理想數據結構。

異常

  • 潛在異常:
    ① 入棧時棧滿;(數據結構)
    ② 出棧時棧空;(後綴表達式不正確)
    ③ 掃描完表達式完成計算時棧中的值多於1個。(後綴表達式不正確)

  • 處理異常的方式:
    ① 能夠經過預先檢查棧空以免異常:
if (!theStack.isEmpty())
    element = theStack.pop();

② 當異常發生時,使用 try-catch 語句處理:

try {
            element = theStack.pop();
        } catch (EmptyStackException exception) {
            System.out.println("No elements available");
        }

③ 先設置拋出異常,再自定義一個異常類:

public class EmptyCollectionException extends RuntimeException
{
    /**
     * Sets up this exception with an appropriate message.
     * @param collection String representing the name of the collection
     */
    public EmptyCollectionException (String collection)
    {
        super ("The " + collection + " is empty.");
    }
}

使用數組實現棧

  • 管理容量:須要時自動擴展。

ArrayStack類

  • 數組實現的棧將棧底放在下標爲 0 的位置。(順序、連續)

  • 關鍵代碼:
    不能實例化泛型數組,可是能夠強制轉換:stack = (T[]) (new Object[DEFAULT_CAPACITY]);
    擴容時,先定義一個兩倍原來容量的泛型數組,再將原來數組遍歷賦值到新的數組裏面去,在將新數組引用賦給原數組引用:
/**
     * Creates a new array to store the contents of this stack with twice the capacity of the old one.
     */
    private void expandCapacity() {
        T[] larger = (T[]) (new Object[stack.length * 2]);

        for (int index = 0; index < stack.length; index++)
            larger[index] = stack[index];

        stack = larger;
    }
  • 須要實現的方法:
//刪除並返回棧頂元素
    public T pop() {
        T result = null;  //置空臨時變量的引用
        if (!isEmpty()) {  //確保棧不空
            count--;
            result = stack[count];
            stack[count] = null;
        } else
            try {
                isEmpty();
            } catch (EmptyStackException exception) {
                System.out.println("No elements available");
            }
        return result;
    }

    //返回棧頂元素的引用
    public T peek() {
        if (isEmpty()) {
            try {
                isEmpty();
            } catch (EmptyStackException exception) {
                System.out.println("No elements available");
            }
        }
        return stack[count - 1];
    }

    public boolean isEmpty() {
        return (count == 0);
    }

    public int size() {
        return count;
    }
  • 注意 push 和 pop 操做的初始條件,push一個元素的前提是 棧不滿,pop一個元素的前提是 棧不空

將引用做爲鏈

  • 鏈式結構:使用對象引用變量創建聯繫。(自指示)

  • 鏈表(對象之間指向關係)
    結點(存儲對象)

  • 注意:必須使用一個 單獨的 引用變量指向表中第一個結點。結點的 next 引用爲 null 時表示結束。

  • 數組有固定大小,鏈表沒有容量限制。

管理鏈表

  • 訪問元素:必須訪問第一個元素。

  • 插入結點:首先,新節點的 next 指向 current 指向的下一個結點,而後結束當前結點的 next 引用重置指向新節點。(頭插法、尾插法、表中插入)

  • 刪除結點:第1個結點(將表頭引用指向當前第2個結點)、中間節點(前一個的 next 指向當前結點的 next 引用所指向的結點)。

  • 哨兵結點:不用考慮如何處理第1個結點。

沒有鏈的元素

  • 雙向鏈表:維護第1個結點和最後一個結點。

使用鏈實現棧

  • 注意初始時構造方法中將表頭變量設置爲 null,元素個數設置爲 0。
//--------------------------------------------------------------------
    //  Creates an empty stack using the default capacity.
    //--------------------------------------------------------------------
    public LinkedStack() {
        count = 0;
        top = null;
    }
  • 須要實現的方法:
//刪除並返回棧頂元素
    public void push(T element) {
        LinearNode<T> eNode = new LinearNode<>(element);  //新元素入棧對應新的對象
        eNode.setNext(top);  //新結點next引用指向棧頂
        top = eNode;
        count++;
    }

    //返回當前棧頂所保存元素的引用
    public T peek() throws EmptyStackException {
        return top.getElement();
    }

    public boolean isEmpty() {
        return count == 0;
    }

    public int size() {
        return count;
    }
  • push、pop等操做的每一個步驟都含有一次比較或者賦值,因此其複雜度都是O(1)。

使用 java.util.Stack 類實現棧

  • java.util.Stack 類(擴展)派生於Vector類,可是有些功能違背了棧的假設。

  • 包的組織:通常按代碼功能組織爲包(集合,異常)。

  • 使用package XXX聲明引入的包。


分析Java Collections API中的Stack類

  • 首先在API中Stack類的類聲明語句public class Stack<E> extends Vector<E>體現了其特性,而Vector又擴展了AbstractList類:
public class Vector<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

而該類又繼承了其餘方法,從而使得Stack類實現了List、Collection等接口:

中文版API介紹Stack類說:

它經過五個操做對類 Vector 進行了擴展 ,容許將向量視爲堆棧。它提供了一般的 push 和 pop 操做,以及取堆棧頂點的 peek 方法、測試堆棧是否爲空的 empty 方法、在堆棧中查找項並肯定到堆棧頂距離的 search 方法。

  • 因爲這個類依賴於vector(向量),因此它既展現出 vector 的特性也展現出棧的特性。
    相比起書中實現的棧,在Stack類中加入了一種特殊方法:search(返回對象在棧中的位置,即第一次出現時與棧頂之間的距離)
public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }

這個方法將對象o做爲一個棧中的元素,先獲取其位置,即最後一次出現(離棧頂最近)時的索引值,最小值爲 1,若是此對象不存在或者檢測對象爲空值,則會返回 -1,舉個例子:

結果體現得很明顯了。
  • 相比起書中實現的棧,除了額外提供了search方法以外,我還發現search、peek 和 pop 方法在聲明時,public後出現了新的關鍵字 synchronized,它的功能是保證在同一時刻最多隻有一個線程執行該段代碼,就是說當兩個併發線程訪問同一個方法時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。對於synchronized 方法,更詳細的解釋是:

    synchronized 方法控制對類成員變量的訪問,每一個類實例都對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態。(由於至多隻有一個可以得到該類實例對應的鎖)

    這樣一來就能夠避免類成員變量訪問衝突,但若是爲一些時間複雜度高的方法增長對象鎖,就會明顯下降其效率。

  • 除此以外,因爲Stack類是繼承類,因此一些方法中的屬性是從Vector類繼承而來,好比說:pop方法
public synchronized E pop() {
        E obj;
        int len = size();

        obj = peek();
        removeElementAt(len - 1);

        return obj;
    }
其中的`removeElementAt(int index)`方法就是繼承而來:

```
public synchronized void removeElementAt(int index) {
    modCount++;
    if (index >= elementCount) {  //索引參數越界
        throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
    }
    else if (index < 0) {  //索引參數越界
        throw new ArrayIndexOutOfBoundsException(index);
    }

    int j = elementCount - index - 1;
    if (j > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, j);  //元素後移
    }
    elementCount--;  //元素個數減1
    elementData[elementCount] = null;  //置空
}
- 再看看peek方法:

public synchronized E peek() {
int i = size();
if (i == 0)
throw new EmptyStackException();
return elementAt(i - 1);
}

這裏又調用了Vector中的`elementAt(int index)`方法:
    ```
    public synchronized E elementAt(int index) {
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
        }

        return elementData(index);
    }

removeElementAt(int index)方法同樣,elementAt(int index)方法也首先判斷參數是否合法,以後就直接調用elementData(int index)返回具體的對象值:

E elementData(int index) {
        return (E) elementData[index];  //保存Stack中的每一個元素
    }
  • 總的來講,Stack源碼是經過數組實現的,再加上Vector中的擴容和其餘方法屬性,除此以外還使用了關鍵字synchronized確保了線程安全,這應該就是其設計中的巧妙之處吧。

【返回目錄】


教材學習中的問題和解決過程

  • 【問題】:ArrayStack類的expandCapacity方法中的 stack = larger 代碼不太明白它的意思,larger是新定義的泛型數組,其空間是原來 stack 數組的兩倍,直接使用遍歷後的新數組就能夠了,爲何又將新容量的數組賦值給原來的 stack 數組?

  • 解決方案 :(詢問後總結)
    完整擴容方法以下:
private void expandCapacity() {
        T[] larger = (T[]) (new Object[stack.length * 2]);

        for (int index = 0; index < stack.length; index++)
            larger[index] = stack[index];

        stack = larger;
    }

在建立新大小的泛型數組larger後,再將stack中的元素遍歷進去,可是最後又要將larger賦給容量更小的stack。原來我覺得stack是原來的引用,因此將larger數組賦給stack時就認爲是將容量大數組的賦值給了一個容量小的數組,不可以實現。詢問了張旭升以後,發現我對原stack引用理解有誤,在給stack數組賦larger數組的時候,至關於更新了原數組,stack數組在被更新時就已經改變了默認的容量,從而實現擴容效果。

【返回目錄】


代碼調試中的問題和解決過程

  • 【問題】:在設計LinkedStack類中的push方法時,Junit測試出現紅條,添加的元素並無在刪除時顯示。

  • 解決方案 :(嘗試)
    我最初設計方法時,仔細看了書中的文字步驟:
    其中前兩步比較重要:第一步是建立一個新結點,其中包含一個指向要放置到棧中對象的引用。接着設置新結點的 next 引用指向當前棧頂(若是棧爲空,則它爲null)
    看完這兩段話,原來我想要使用條件語句判斷棧頂元素,可是仔細一想,若是棧頂元素爲空,在給新結點賦值或者被新結點的引用指向時並不會因非空賦值語句形成影響,因而個人代碼以下:
public void push(T element) {
        LinearNode<T> eNode = new LinearNode<>();
        eNode.setNext(top);  //新結點指向棧頂
        top = eNode;
        count++;
    }

可是在進行Junit測試時並無成功,pop或者peek方法返回的值一直爲空,在仔細看了第一條賦值語句後,發現我定義的結點中並無傳入push元素,因此就一直保持了棧默認爲空的狀態,因此只須要將傳入的element加入便可:LinearNode<T> eNode = new LinearNode<>(element);
最初出現這個錯誤是由於忽略了泛型結點默認的初始值。

【返回目錄】


代碼託管

  • 統計的代碼包含了第四周的實驗一,因此是第四周和第五週一共的代碼量:
    (statistics.sh腳本的運行結果截圖)


上週考試錯題總結

  • 上週國慶放假無考試,因此總結第三週的錯題:

  • 【錯題1】A __________________ search looks through the search pool one element at a time.
    A .binary
    B .clever
    C .insertion
    D .selection
    E .linear

  • 錯誤緣由:我以爲二分查找每次也是搜索比較一箇中間元素,錯選A。
    加深理解:線性查找會逐一在查找池中查找(迭代)一個元素;二分查找每次也在查找池中查找一個元素,可是並非逐一,每次會篩選掉一半。
    【注意】look through 在這裏並非瀏覽之意,而是 「逐一檢查」 的意思。

  • 【錯題2】A linear search always requires more comparisons than a binary search.
    A .true
    B .false

  • 錯誤緣由:考慮狀況不全面,錯選A。
    加深理解:若是正在搜索的元素是列表中的第一個元素,那麼線性查找比二分查找須要的比較次數更少。

【返回目錄】


結對及互評

本週結對學習狀況

  • 莫禮鍾本週比第三週的狀態好一點,雖然實驗只完成了第一個和第五個,設計的方法比較簡單,可是都是本身作的。在學習十四章的過程當中,他已經基本掌握瞭如何使用數組實現棧,其中一些比較重要的方法(push、pop)我已經看着他寫了一遍,但願多熟練已掌握的內容,而且再恢復一些學習狀態。

  • 20162319
    • 結對學習內容
      • 線性表
      • 用數組實現棧(push、pop方法的實現)


其餘(感悟、思考等,可選)

  本週算是比較忙的一週了,國慶以後的第一週並不輕鬆,除了運動會的一些瑣事以外,本週的學習任務真心有點多。本週在課堂上咱們又複習了查找與排序的內容,個人掌握狀況還算過關,對於棧、隊列這部份內容我選擇了「先聽課再看書」的方式。到如今爲止,棧的基本內容掌握了,隊列的掌握狀況還差不少,雲班課的測試成績也不算高。本週個人狀態有些降低,主要由於睡眠時間不足致使,下週我會將精力集中回來,而且平衡好完成團隊任務和我的任務的時間。

  • 【附1】教材及考試題中涉及到的英語:

    Chinese English Chinese English
    線性集合 linear collection 默認值 default
    非線性集合 nonlinear collection 自指示 self-referential
    封裝 encapsulate 動態 dynamic
    類型兼容 type compatibility heap
    中綴 infix 哨兵結點 sentinel node
    前綴 prefix 虛位結點 dummy node
    矢量 vector 常量 constant(s)
    操做數 operand(s) 指定的 designated
    優化 optimize 空閒存儲區 free store
  • 【附2】本週小組博客

【返回目錄】


學習進度條

  • 代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
    目標 5000行 30篇 400小時
    第一週 234/234 1/28 14/14 瞭解算法效率、大O符號等理論內容
    第二週 255/489 1/29 12/26 瞭解敏捷的團隊、泛型的使用
    第三週 436/925 2/31 10/36 瞭解一些查找和排序的算法
    第四周 977/1902 3/34 10/46 掌握實現線性結構
    第五週 800/2702 2/36 12/58 掌握實現棧集合
  • 計劃學習時間:14小時

  • 實際學習時間:12小時

  • 有效學習時間:5小時

  • 改進狀況:學習內容有所增長,本週個人效率極低,下週必須恢復到以前的狀態,學習時務必心無旁騖。


參考資料

【返回目錄】

相關文章
相關標籤/搜索