資料結構基礎學習之(串與陣列)

h_dj發表於2019-04-05

主要知識點學習

  • 串的基本概念及其抽象資料型別描述
  • 串的儲存結構
  • 串的基本操作實現
  • 陣列的定義、操作和儲存結構
  • 矩陣的壓縮儲存

一、 串

字串(串): 是由n(n>=0)各字元組成的有限序列

1. 串的抽象資料型別

public interface IString {
    void clear();//置空
    boolean isEmpty();//判空
    int length();//長度
    char charAt(int index);//獲取指定下標的字元
    IString substring(int begin,int end);//擷取子串
    IString insert(int offset,IString str);//插入
    IString delete(int begin,int end);//刪除
    IString concat(IString str);//串連線
    int compareTo(IString str) ;//比較
    int indexOf(IString str,int begin);//子串定位
}
複製程式碼

2. 順序串及其實現

  1. 儲存結構示意圖

4.1順序串儲存結構.png

  1. 求子串操作
 /**
     * 擷取子串
     *
     * @param begin 開始索引
     * @param end   結束索引
     * @return
     */
    @Override
    public IString substring(int begin, int end) {
        //1 判斷開始擷取位置是否合法
        if (begin < 0)
            throw new StringIndexOutOfBoundsException("起始位置不能小於0");

        //2 判斷結束擷取位置是否合法
        if (end > this.length)
            throw new StringIndexOutOfBoundsException("結束位置不能大於串的當前長度: end:" + end + "  length:" + length);

        //3. 開始位置不能大於結束位置
        if (begin > end)
            throw new StringIndexOutOfBoundsException("開始位置不能大於結束位置");

        if (begin == 0 && end == this.length) {
            return new SeqString(this);
        } else {
            //建立擷取的字元陣列
            char[] buffer = new char[end - begin];
            //複製字元
            for (int i = begin; i < end; i++) {
                buffer[i] = this.values[i];
            }
            return new SeqString(buffer);

        }
    }
複製程式碼
  1. 插入操作
    public IString insert(int offset, IString str) {
        //判斷偏移量是否合法
        if (offset < 0 || offset > this.length)
            throw new StringIndexOutOfBoundsException("插入位置不合法!");

        //獲取插入串的長度
        int len = str.length();
        //計算所需的長度
        int newCount = this.length + len;
        //如果所需的長度大於串陣列的容量
        if (newCount > this.values.length) {
            //擴充容量
            allocate(newCount);
        }

        //為插入的串騰出位置(往後移動len個位置)
        for (int i = this.length - 1; i >= 0; i--) {
            this.values[len + i] = this.values[i];
        }
        //複製
        for (int i = 0; i < len; i++) {
            this.values[offset + i] = str.charAt(i);
        }
        this.length = newCount;
        return this;
    }
複製程式碼
  1. 刪除操作
 public IString delete(int begin, int end) {
        //1 判斷開始擷取位置是否合法
        if (begin < 0)
            throw new StringIndexOutOfBoundsException("起始位置不能小於0");

        //2 判斷結束擷取位置是否合法
        if (end > this.length)
            throw new StringIndexOutOfBoundsException("結束位置不能大於串的當前長度: end:" + end + "  length:" + length);

        //3. 開始位置不能大於結束位置
        if (begin > end)
            throw new StringIndexOutOfBoundsException("開始位置不能大於結束位置");

        for (int i = 0; i < this.length - end; i++) {
            this.values[begin + i] = this.values[end + i];
        }
        this.length = this.length - (end - begin);
        return this;
    }
複製程式碼
  1. 模式匹配操作

一、Brute-Force模式匹配演算法

  • 操作過程示意圖(網上搜尋所得)

brute-force.gif

  • 程式碼實現
public int indexOf_BF(SeqString text, SeqString p, int begin) {
        //判斷開始匹配的位置是否合法
        if (begin < 0 || begin > text.length - 1)
            throw new StringIndexOutOfBoundsException("判斷開始匹配的位置錯誤: begin=" + begin);

        //標識主串text中的位置
        int i = begin;
        //標識子串p中的位置
        int j = 0;

        //主串的長度
        int textLen = text.length;
        //子串長度
        int pLen = p.length;

        while (i < textLen && j < pLen) {
            //匹配字元
            //如果匹配,則繼續下一個字元
            if (text.charAt(i) == p.charAt(j)) {
                ++i;
                ++j;
            } else {
                //如果匹配不成功,則退回上次匹配首位的下一位
                i = i - j + 1;
                j = 0;
            }
        }

        //如果匹配成功,返回子串序號
        if (j >= pLen) {
            return i - pLen;
        }
        return -1;
    }
複製程式碼
  • 時間複製度分析

二、KMP(Knuth-Morris-Pratt)模式匹配演算法

  1. 示意圖(圖來自)

    KMP.gif

  2. 閱讀文章

  1. 說明
  • Brute-Force演算法無論在哪裡失敗,每次都從成功匹配的下一個位置開始從新匹配,非常浪費時間, 而KMP演算法減少了不必要的回溯,提升了效率。
  • 那麼現在的問題是,如何利用上次匹配失敗的資訊,推斷出下一次開始匹配的位置?
  • 可以針對搜尋詞,算出一張《部分匹配表》(Partial Match Table)
  1. 針對搜尋詞: ABCDABD計算部分匹配表
  • 相關公式 - 1. 對應的部分匹配值 = 字首字元 和 字尾字元 的 最長共有元素的長度 - 2. 匹配失敗移動的距離 = 已匹配的字元數 - 對應的部分匹配值

  • 最長共有元素的長度(對於:ABCDABD)

已匹配字元 字首 字尾 共有長度
A NULL NULL 0
AB [A] [B] 0
ABC [A,AB] [BC,C] 0
ABCD [A,AB,ABC] [BCD,CD,D] 0
ABCDA [A,AB,ABC,ABCD] [BCDA,CDA,DA,A] 1 {A}
ABCDAB [A,AB,ABC,ABCD,ABCDA] [BCDAB,CDAB,DAB,AB,B] 2 {AB}
ABCDABD [A,AB,ABC,ABCD,
ABCDA,ABCDAB]
[BCDABD,CDABD,DABD,ABD,BD,D] 0
  • 匹配表
搜尋詞 A B C D A B D
部分匹配值(Match Value) 0 0 0 0 1 2 0
移動距離(Move distance) 1 2 3 4 4 4 7
  1. 部分匹配表的程式碼實現
  • 程式碼實現
private int[] getNext(SeqString p) {
        //匹配串的長度
        int patternLength = p.length;
        //匹配表;用於匹配過程中,跳過不需要再進行匹配的字元
        int partial_match[] = new int[patternLength];
        //部分匹配表中的第一個賦值為0,
        //因為只有一個已匹配字元,它沒有字首和字尾
        partial_match[0] = 0;
        //前字尾共有元素的長度(即字首字元的最後一個下標)
        int length = 0;
        //已匹配字元最後的下標(字尾字元的最後一個下標)
        int currentIndex = 1;

        while (currentIndex < patternLength) {
            if (p.charAt(currentIndex) == p.charAt(length)) {
                //發現匹配
                //共有長度加一
                length = length + 1;
                //記錄跳過字元數
                partial_match[currentIndex] = length;
                currentIndex = currentIndex + 1;
            } else {
                //沒有匹配
                if (length != 0) {
                    //以AAACAAAA為例子 , 個人理解
                    //假設當前匹配的字串為 AAAC , 字首為AAA,AA,A  字尾為 AAC,AC,C
                    //則length = 2 (是當串為AAA時得到的最長前字尾公共字元長度), currentIndex = 3, 所以字首AAA != AAC
                    //那麼length = partial_match[1] = 1, AA != AC
                    //length = partial_match[0] = 0, A!=C
                    length = partial_match[length - 1];
                } else {
                    //length ==0 ,表示串AAAC沒有最長前字尾公共字元
                    //賦值為0
                    partial_match[currentIndex] = 0;
                    //繼續匹配下一個串 AAACA
                    currentIndex = currentIndex + 1;
                }
            }
        }
        return partial_match;
    }
複製程式碼
  1. KMP演算法實現
private int index_KMP(SeqString text, SeqString p) {

        int textLength = text.length;
        int patternLength = p.length;

        //計算部分匹配表
        int partial_match[] = getNext(p);

        int currentIndexText = 0;
        int currentIndexPattern = 0;

        while (currentIndexText < textLength && currentIndexPattern < patternLength) {
            if (text.charAt(currentIndexText) == p.charAt(currentIndexPattern)) {
                //匹配
                //轉到下一個字元
                currentIndexPattern = currentIndexPattern + 1;
                currentIndexText = currentIndexText + 1;
            } else {
                if (currentIndexPattern != 0) {
                    // if no match and currentIndexPattern is not zero we will
                    // fallback to values in partial match table
                    // for match of largest common proper suffix and prefix
                    // till currentIndexPattern-1
                    currentIndexPattern = partial_match[currentIndexPattern - 1];
                } else {
                    // if currentIndexPattern is zero
                    // we increment currentIndexText for fresh match
                    currentIndexText = currentIndexText + 1;
                }
            }
        }
        //判斷已匹配串的下標currentIndexPattern 是否大於 模式串的長度
        if (currentIndexPattern >= patternLength) {
            //是,則返回匹配模式串的開始位置
            return currentIndexText - patternLength;
        }
        return -1;
    }
複製程式碼
  1. SeqString 完整程式碼

二、陣列

1. 概念
  1. 陣列: 是n(n>=1)個具有相同型別的資料元素a0,a1,a2,a3,...,an-1構成的有限序列
  2. 一維陣列:可以看成一個順序儲存結構的線性表
  3. 二維陣列(矩陣):其資料元素為一維陣列的線性表

4.11二維陣列的矩陣表示.png

相關文章