主要知识点学习

  • 串的基本概念及其抽象数据类型描述
  • 串的存储结构
  • 串的基本操作实现
  • 数组的定义、操作和存储结构
  • 矩阵的压缩存储

一、 串

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

1. 串的抽象数据类型

  1. public interface IString {
  2. void clear();//置空
  3. boolean isEmpty();//判空
  4. int length();//长度
  5. char charAt(int index);//获取指定下标的字符
  6. IString substring(int begin,int end);//截取子串
  7. IString insert(int offset,IString str);//插入
  8. IString delete(int begin,int end);//删除
  9. IString concat(IString str);//串连接
  10. int compareTo(IString str) ;//比较
  11. int indexOf(IString str,int begin);//子串定位
  12. }

2. 顺序串及其实现

  1. 存储结构示意图

数据结构基础学习之(串与数组) - 图1

  1. 求子串操作
  1. /**
  2. * 截取子串
  3. *
  4. * @param begin 开始索引
  5. * @param end 结束索引
  6. * @return
  7. */
  8. @Override
  9. public IString substring(int begin, int end) {
  10. //1 判断开始截取位置是否合法
  11. if (begin < 0)
  12. throw new StringIndexOutOfBoundsException("起始位置不能小于0");
  13. //2 判断结束截取位置是否合法
  14. if (end > this.length)
  15. throw new StringIndexOutOfBoundsException("结束位置不能大于串的当前长度: end:" + end + " length:" + length);
  16. //3. 开始位置不能大于结束位置
  17. if (begin > end)
  18. throw new StringIndexOutOfBoundsException("开始位置不能大于结束位置");
  19. if (begin == 0 && end == this.length) {
  20. return new SeqString(this);
  21. } else {
  22. //创建截取的字符数组
  23. char[] buffer = new char[end - begin];
  24. //复制字符
  25. for (int i = begin; i < end; i++) {
  26. buffer[i] = this.values[i];
  27. }
  28. return new SeqString(buffer);
  29. }
  30. }
  1. 插入操作
  1. public IString insert(int offset, IString str) {
  2. //判断偏移量是否合法
  3. if (offset < 0 || offset > this.length)
  4. throw new StringIndexOutOfBoundsException("插入位置不合法!");
  5. //获取插入串的长度
  6. int len = str.length();
  7. //计算所需的长度
  8. int newCount = this.length + len;
  9. //如果所需的长度大于串数组的容量
  10. if (newCount > this.values.length) {
  11. //扩充容量
  12. allocate(newCount);
  13. }
  14. //为插入的串腾出位置(往后移动len个位置)
  15. for (int i = this.length - 1; i >= 0; i--) {
  16. this.values[len + i] = this.values[i];
  17. }
  18. //复制
  19. for (int i = 0; i < len; i++) {
  20. this.values[offset + i] = str.charAt(i);
  21. }
  22. this.length = newCount;
  23. return this;
  24. }
  1. 删除操作
  1. public IString delete(int begin, int end) {
  2. //1 判断开始截取位置是否合法
  3. if (begin < 0)
  4. throw new StringIndexOutOfBoundsException("起始位置不能小于0");
  5. //2 判断结束截取位置是否合法
  6. if (end > this.length)
  7. throw new StringIndexOutOfBoundsException("结束位置不能大于串的当前长度: end:" + end + " length:" + length);
  8. //3. 开始位置不能大于结束位置
  9. if (begin > end)
  10. throw new StringIndexOutOfBoundsException("开始位置不能大于结束位置");
  11. for (int i = 0; i < this.length - end; i++) {
  12. this.values[begin + i] = this.values[end + i];
  13. }
  14. this.length = this.length - (end - begin);
  15. return this;
  16. }
  1. 模式匹配操作

一、Brute-Force模式匹配算法

  • 操作过程示意图(网上搜索所得)

数据结构基础学习之(串与数组) - 图2

  • 代码实现
  1. public int indexOf_BF(SeqString text, SeqString p, int begin) {
  2. //判断开始匹配的位置是否合法
  3. if (begin < 0 || begin > text.length - 1)
  4. throw new StringIndexOutOfBoundsException("判断开始匹配的位置错误: begin=" + begin);
  5. //标识主串text中的位置
  6. int i = begin;
  7. //标识子串p中的位置
  8. int j = 0;
  9. //主串的长度
  10. int textLen = text.length;
  11. //子串长度
  12. int pLen = p.length;
  13. while (i < textLen && j < pLen) {
  14. //匹配字符
  15. //如果匹配,则继续下一个字符
  16. if (text.charAt(i) == p.charAt(j)) {
  17. ++i;
  18. ++j;
  19. } else {
  20. //如果匹配不成功,则退回上次匹配首位的下一位
  21. i = i - j + 1;
  22. j = 0;
  23. }
  24. }
  25. //如果匹配成功,返回子串序号
  26. if (j >= pLen) {
  27. return i - pLen;
  28. }
  29. return -1;
  30. }
  • 时间复制度分析

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

  1. 示意图(图来自)
    数据结构基础学习之(串与数组) - 图3

  2. 阅读文章

  1. 说明
  • Brute-Force算法无论在哪里失败,每次都从成功匹配的下一个位置开始从新匹配,非常浪费时间, 而KMP算法减少了不必要的回溯,提升了效率。
  • 那么现在的问题是,如何利用上次匹配失败的信息,推断出下一次开始匹配的位置?
  • 可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)
  1. 针对搜索词: ABCDABD计算部分匹配表
  • 相关公式
    1. 对应的部分匹配值 = 前缀字符 和 后缀字符 的 最长共有元素的长度
    1. 匹配失败移动的距离 = 已匹配的字符数 - 对应的部分匹配值
  • 最长共有元素的长度(对于: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. 部分匹配表的代码实现
  • 代码实现
  1. private int[] getNext(SeqString p) {
  2. //匹配串的长度
  3. int patternLength = p.length;
  4. //匹配表;用于匹配过程中,跳过不需要再进行匹配的字符
  5. int partial_match[] = new int[patternLength];
  6. //部分匹配表中的第一个赋值为0,
  7. //因为只有一个已匹配字符,它没有前缀和后缀
  8. partial_match[0] = 0;
  9. //前后缀共有元素的长度(即前缀字符的最后一个下标)
  10. int length = 0;
  11. //已匹配字符最后的下标(后缀字符的最后一个下标)
  12. int currentIndex = 1;
  13. while (currentIndex < patternLength) {
  14. if (p.charAt(currentIndex) == p.charAt(length)) {
  15. //发现匹配
  16. //共有长度加一
  17. length = length + 1;
  18. //记录跳过字符数
  19. partial_match[currentIndex] = length;
  20. currentIndex = currentIndex + 1;
  21. } else {
  22. //没有匹配
  23. if (length != 0) {
  24. //以AAACAAAA为例子 , 个人理解
  25. //假设当前匹配的字符串为 AAAC , 前缀为AAA,AA,A 后缀为 AAC,AC,C
  26. //则length = 2 (是当串为AAA时得到的最长前后缀公共字符长度), currentIndex = 3, 所以前缀AAA != AAC
  27. //那么length = partial_match[1] = 1, AA != AC
  28. //length = partial_match[0] = 0, A!=C
  29. length = partial_match[length - 1];
  30. } else {
  31. //length ==0 ,表示串AAAC没有最长前后缀公共字符
  32. //赋值为0
  33. partial_match[currentIndex] = 0;
  34. //继续匹配下一个串 AAACA
  35. currentIndex = currentIndex + 1;
  36. }
  37. }
  38. }
  39. return partial_match;
  40. }
  1. KMP算法实现
  1. private int index_KMP(SeqString text, SeqString p) {
  2. int textLength = text.length;
  3. int patternLength = p.length;
  4. //计算部分匹配表
  5. int partial_match[] = getNext(p);
  6. int currentIndexText = 0;
  7. int currentIndexPattern = 0;
  8. while (currentIndexText < textLength && currentIndexPattern < patternLength) {
  9. if (text.charAt(currentIndexText) == p.charAt(currentIndexPattern)) {
  10. //匹配
  11. //转到下一个字符
  12. currentIndexPattern = currentIndexPattern + 1;
  13. currentIndexText = currentIndexText + 1;
  14. } else {
  15. if (currentIndexPattern != 0) {
  16. // if no match and currentIndexPattern is not zero we will
  17. // fallback to values in partial match table
  18. // for match of largest common proper suffix and prefix
  19. // till currentIndexPattern-1
  20. currentIndexPattern = partial_match[currentIndexPattern - 1];
  21. } else {
  22. // if currentIndexPattern is zero
  23. // we increment currentIndexText for fresh match
  24. currentIndexText = currentIndexText + 1;
  25. }
  26. }
  27. }
  28. //判断已匹配串的下标currentIndexPattern 是否大于 模式串的长度
  29. if (currentIndexPattern >= patternLength) {
  30. //是,则返回匹配模式串的开始位置
  31. return currentIndexText - patternLength;
  32. }
  33. return -1;
  34. }
  1. SeqString 完整代码

二、数组

1. 概念
  1. 数组: 是n(n>=1)个具有相同类型的数据元素a0,a1,a2,a3,…,an-1构成的有限序列
  2. 一维数组:可以看成一个顺序存储结构的线性表
  3. 二维数组(矩阵):其数据元素为一维数组的线性表

数据结构基础学习之(串与数组) - 图4