主要知識點學習
- 串的基本概念及其抽象資料型別描述
- 串的儲存結構
- 串的基本操作實現
- 陣列的定義、操作和儲存結構
- 矩陣的壓縮儲存
一、 串
字串(串): 是由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. 順序串及其實現
- 儲存結構示意圖
- 求子串操作
/**
* 擷取子串
*
* @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);
}
}
複製程式碼
- 插入操作
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;
}
複製程式碼
- 刪除操作
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;
}
複製程式碼
- 模式匹配操作
一、Brute-Force模式匹配演算法
- 操作過程示意圖(網上搜尋所得)
- 程式碼實現
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)模式匹配演算法
-
示意圖(圖來自)
-
閱讀文章
- 說明
- Brute-Force演算法無論在哪裡失敗,每次都從成功匹配的下一個位置開始從新匹配,非常浪費時間, 而KMP演算法減少了不必要的回溯,提升了效率。
- 那麼現在的問題是,如何利用上次匹配失敗的資訊,推斷出下一次開始匹配的位置?
- 可以針對搜尋詞,算出一張《部分匹配表》(Partial Match Table)
- 針對搜尋詞: 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 |
- 部分匹配表的程式碼實現
- 程式碼實現
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;
}
複製程式碼
- 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. 概念
- 陣列: 是n(n>=1)個具有相同型別的資料元素a0,a1,a2,a3,...,an-1構成的有限序列
- 一維陣列:可以看成一個順序儲存結構的線性表
- 二維陣列(矩陣):其資料元素為一維陣列的線性表