目录

1 字符串与数组的关系

1.1 字符串与数组的联系

1.2 字符串与数组的区别

2 实现字符串的链式存储(Java)

3 子串查找的简单实现


1 字符串与数组的关系

1.1 字符串与数组的联系

在初学编程的时候(比如Java),我们都会接触字符串(string)和数组(array)这两种数据类型。虽然从字面上不难理解,字符串就是由字符(character)组成的一串线性序列;即使如此,我们仍然会把字符串类型的变量看作存储单值的变量,把数组类型的变量理解为存储多值的变量。

然后,我们会学习字符串对象的一些常用方法,此时很容易感受到它与数组之间千丝万缕的联系,因为从字符串中提取字符和从数组中提取元素的方法都是以索引为依据的,关于数组通过索引访问数据的原理,在上一篇笔记“重识数组(Array)”中有所阐述,链接贴在下面:

重识数组:https://blog.csdn.net/weixin_45370422/article/details/117395751

我们在下面的Java代码中定义了一个字符串和一个字符类型的数组,并进行了两个操作:①取出单个字符或元素,②切片取出一组连续的字符或元素。

package com.notes.data_structure5;import java.util.Arrays;public class StringDemo {public static void main(String[] args) {String string = "abc";char[] array = {'a','b','c'};// 取出单个字符 或 元素char strFirst = string.charAt(0);char arrFirst = array[0];System.out.println(strFirst); // aSystem.out.println(arrFirst); // a// 切片取出一组连续的字符 或 元素String subStr = string.substring(0,2);char[] subArr = Arrays.copyOfRange(array,0,2);System.out.println(subStr); // abSystem.out.println(subArr); // ab}
}

打印char数组和字符串打印出来的结果居然是一样的。我们打开String的源码,可以看到如下注释内容,指出定义字符串"abc"和定义字符数组{'a','b','c}效果是一样的。因此,在Java中,字符串的底层的实现就是数组。

继续往下滑,会看到字符串的值被定义为一个byte类型的数组,而且被定义为常量,且加上了注解stable,表示“一个字段的所有组件变量最多更改一次值”(A field may be annotated as stable if all of its component variables changes value at most once. ),这就是常说的字符串的不可变性

@Stableprivate final byte[] value;

虽然在Java中字符串和数组都能是基于索引随机访问元素,但是方法的写法还是不一样的。相比而言,Python更狠。Python没有内置对数组的支持,用与之相似的列表(list)为例,字符串和列表的随机访问、切片和拼接的写法完全一样:

# 定义一个 字符串 和 一个元组
my_str = "abc"
my_list = ["a", "b", "c"]# 随机访问
str_first = my_str[0]
list_first = my_list[0]
print(str_first, list_first) # a a# 切片
str_sub = my_str[0:2]
list_sub = my_list[0:2]
print(str_sub, list_sub) # ab ['a', 'b']# 拼接
str_new = my_str + "xyz"
list_new = my_list + ["x", "y", "z"]
print(str_new, list_new) # abcxyz ['a', 'b', 'c', 'x', 'y', 'z']

1.2 字符串与数组的区别

字符串和数组的主要区别不在原理层面,而在应用层面,用索引随机访问单一字符其实没有多少应用价值。我们把数组变量看作存储多值、字符串看作存储单值是符合应用需求的,否则Java和Python不会不约而同地将一种字符串转换数组的方法命名为split,意为“分隔”。以下是Java代码:

package com.notes.data_structure5;import java.util.Arrays;public class StringDemo {public static void main(String[] args) {String string = "Hello world";String[] array = string.split(" ");System.out.println(Arrays.toString(array)); // [Hello, world]}
}

以下是Python代码:

my_str = "Hello world"
my_list = my_str.split(" ")
print(my_list)  # ['Hello', 'world']

对于字符串,我们关注的焦点不是字符与字符串的关系,而是子串主串的关系。换言之,字符串始终被作为一个整体来看待。所谓“子串”,就是一个字符串中某一段连续字符组成的字符串,上面演示的split方法的作用就是根据指定的分隔符将一个字符串切片成若干子串。

另外,字符串的底层实现也不一定非要是数组,和线性表中的栈和队列一样,字符串也有基于数组顺序存储和基于链表的链式存储两种方式。关于栈和队列的两种实现方式,可以参考“数据结构学习笔记”系列之前的文章,链接贴在下面:

栈:https://blog.csdn.net/weixin_45370422/article/details/116889212

队列:https://blog.csdn.net/weixin_45370422/article/details/117376241

在下一章,我们不妨用Java实现一个链式存储的字符串。

2 实现字符串的链式存储(Java)

最简单粗暴的思路无疑是一个字符一个结点,然后把结点用指针连起来,但是这样做很浪费空间,意义也不大,正如上一节提到的,对于字符串我们关注的焦点其实不是字符与串的关系。因此,我们不妨在一个结点中放入一个或多个字符。

基于上面的想法,我们设计这样一个字符串,一个结点放入一个英文单词或一个标点,结尾都是一个空格。我们用这个结构实现Linux创始人Linus Torvalds的名言:Talk is cheap, show me the code.

至于代码层面,我们可以直接参照“数据结构学习笔记”系列博文的第一篇“链表”中的代码(文章链接贴在下面)。为了体现一个结点放入多个字符的思路,将结点中的数据类型设置为字符数组(char[])。先把链表那篇文章的链接贴在下面。

链表:https://blog.csdn.net/weixin_45370422/article/details/116573863

由于我们的这个用Java实现的链式字符串没有任何实用价值,因此仅定义了增加结点(addNode)和打印字符串(printString)两个公共方法:

package com.notes.data_structure5;public class MyLinkedString {private Node headNode; // 定义一个头结点private Node currentNode; // 定义一个当前结点// 定义一个结点类private class Node {// 结点的两个要素:数据和指针private char[] data;private Node next;// 构造方法:Node实例化时给data赋值public Node(char[] data) {this.data = data;}}// 创建头结点private Node addHeadNode() {// 头结点用来模拟头指针,数据设为 空数组headNode = new Node(new char[] {});return headNode;}// 增加结点,相当于 拼接 的过程public void addNode(char[] data) {// 如果头结点不存在,当前结点为头结点,这样就有了头指针if (headNode==null) {// 当前结点是头结点,调用创建头结点的方法currentNode = addHeadNode();}// 创建一个新的结点Node node = new Node(data);// 当前结点的指针指向新结点currentNode.next = node;// 当前结点后移一位,新结点成为当前结点,正式加入链表currentNode = currentNode.next;}  // 遍历打印所有的节点,为显示字符串效果,打印不换行public void printString() {// temp是一个移动的指针,以头指针为起点Node temp = headNode; // 通过指针的移动遍历数组,直到指针指向链尾的nullwhile(temp.next != null) {temp = temp.next;System.out.print(temp.data);}}
}

为了节省篇幅,我们用这个链式字符串实现上面那句名言的前半句:talk is cheap

package com.notes.data_structure5;public class linkedStringDemo {public static void main(String[] args) {MyLinkedString myString = new MyLinkedString();myString.addNode(new char[] {'t','a','l','k',' '});myString.addNode(new char[] {'i','s',' '});myString.addNode(new char[] {'c','h','e','a','p',' '});myString.addNode(new char[] {'.',' '});myString.printString();}}

运行结果如下:

talk is cheap .

3 子串查找的简单实现

子串查找又被称为字符串匹配,在开发过程中,这是很常见的一种需求。子串查找的工作包括三个要素:主串、模式串、子串。所谓字符串匹配,就是从主串中找出与模式串等值的子串

子串查找最简单的实现方式就是暴力(brute force, 简称BF)查找,这种方法简单粗暴。字符串匹配基于主串和模式串的字符间的比对,假设主串字符数为n,模式串字符数为m,目标是判断模式串是否为主串的子串,我们可以这样设计比对的步骤:

将模式串的索引为0的字符依次与主串中索引0到索引(n-m)的字符依次进行比对。如果在主串k索引处比对成功,模式串0索引之后的字符分别与主串k索引之后的字符比对,如果一直比对到模式串的最后的一个字符,返回true;如果中途发生不匹配的情况,模式串从0索引开始,继续与主串索引(k+1)到索引(n-m)的字符依次比对,重复上述过程。

假设我们的匹配目标是判断 Linus 那句名言的后半句“show me your code.”中是否包含字符串“our”,那么过程如下,图示中橙色箭头代表比对成功,灰色箭头代表比对失败,有三条连续的橙色箭头才算匹配成功

代码实现如下:

package com.notes.data_structure5;public class Demo {public static void main(String[] args) {Boolean result = isContain("show me your code.","our");System.out.println(result); // trueBoolean result2 = isContain("show me your code.","oua");System.out.println(result2);  // false}public static Boolean isContain(String mainString, String patternString) {int n = mainString.length();  // 主串的字符数int m = patternString.length();  // 模式串的字符数int k = 0;  // 模式串 首元素 与匹配成功的 主串字符 的 索引int count = 0; // 比对中的连续字符数for(int i=0;i<(n-m+1);i++) {// 如果模式串的第0个字符与当前主串字符匹配if(mainString.charAt(i) == patternString.charAt(0)) {k = i; // 在主串索引k的位置匹配成功,继续匹配后续字符count = 1; // 已经比对上一个字符for(int j=1;j<m;j++) {// 如果后续字符出现不匹配的情况,不再比对if(mainString.charAt(k+j) != patternString.charAt(j)) {break;}count++;}if(count==m) { // 字符全部比中,匹配成功return true;}}}// 匹配失败return false;}
}

数据结构学习笔记(五):重识字符串(String)相关推荐

  1. Go 学习笔记(31)— 字符串 string、字符 rune、字节 byte、UTF-8 和 Unicode 区别以及获取字符串长度

    1. 字符串 string 类型 Go 语言中字符串的内部实现使用 UTF-8 编码,通过 rune 类型,可以方便地对每个 UTF-8 字符进行访问.当然, Go 语言也支持按照传统的 ASCII ...

  2. Java学习笔记(六)--字符串String类

    文章目录 字符串 一.String 类 1.1 声明字符串 1.2 创建字符串 二.连接字符串 2.1 连接多个字符串 2.2 连接其他数据类型 三.获取字符串信息 3.1 获取字符串长度 3.2 字 ...

  3. 数据结构学习笔记(四):重识数组(Array)

    目录 1 数组通过索引访问元素的原理 1.1 内存空间的连续性 1.2 数据类型的同一性 2 数组与链表增删查操作特性的对比 2.1 数组与链表的共性与差异 2.2 数组与链表增删查特性差异的原理 3 ...

  4. Python数据结构学习笔记——队列和双端队列

    目录 一.队列的定义 二.队列 实现步骤分析 三.队列的Python实现代码 四.队列的应用 六人传土豆游戏 五.双端队列的定义 六.双端队列 实现步骤分析 七.双端队列的Python实现代码 八.双 ...

  5. 数据结构学习笔记(王道)

    数据结构学习笔记(王道) PS:本文章部分内容参考自王道考研数据结构笔记 文章目录 数据结构学习笔记(王道) 一.绪论 1.1. 数据结构 1.2. 算法 1.2.1. 算法的基本概念 1.2.2. ...

  6. 数据结构学习笔记(七):哈希表(Hash Table)

    目录 1 哈希表的含义与结构特点 1.1 哈希(Hash)即无序 1.2 从数组看哈希表的结构特点 2 哈希函数(Hash Function)与哈希冲突(Hash Collision) 2.1 哈希函 ...

  7. 数据结构学习笔记(六):二叉树(Binary Tree)

    目录 1 背景知识:树(Tree) 2 何为二叉树(Binray Tree) 2.1 二叉树的概念与结构 2.2 满二叉树与完全二叉树 2.3 二叉树的三种遍历方式 3 二叉树及其遍历的简单实现(Ja ...

  8. Java之多线程学习笔记五 —— 多线程模拟龟兔赛跑

    Java之多线程学习笔记五 -- 多线程模拟龟兔赛跑 参考教程B站狂神https://www.bilibili.com/video/BV1V4411p7EF package pers.ylw.less ...

  9. python函数是一段具有特定功能的语句组_Python学习笔记(五)函数和代码复用

    本文将为您描述Python学习笔记(五)函数和代码复用,具体完成步骤: 函数能提高应用的模块性,和代码的重复利用率.在很多高级语言中,都可以使用函数实现多种功能.在之前的学习中,相信你已经知道Pyth ...

最新文章

  1. C++拾趣——有趣的操作符重载
  2. 记录一下海上风力机机械装置
  3. eclipse没有server选项解决方法
  4. java程序设计颜志军_JSP 自定义标签之一 简单实例
  5. 音高和基频(Pitch and F0)
  6. Hibernate 对象的三种状态
  7. 最好的5个C++ 网站
  8. 3.2. 开始入住实验
  9. atitit.泛型编程总结最佳实践 vO99 java c++ c#.net php
  10. Request header field mytoken is not allowed by Access-Control-Allow-Headers in preflight (请求头设置问题)
  11. 超全汇总,常见的芯片封装大全-道合顺大数据infinigo
  12. 禁忌搜索算法c语言代码,禁忌搜索算法
  13. sketchup生成面域插件_适用于Revit / SketchUp / Rhino / ArchiCAD插件最新版
  14. 齐聚静安,共襄盛举--「2020上海静安国际大数据论坛」成功举行
  15. html网页字体颜色代码大全
  16. gan处理自己的数据集_使用StyleGAN训练自己的数据集.md
  17. HTML(常用标签与超链接)的案例
  18. 推荐一本书《亚马逊网络书店传奇》
  19. Multisim基础 发光二极管 添加元件的位置
  20. 使用STM8S003定时器的PWM功能输出PWM波

热门文章

  1. 四叶草社交平台——十天冲刺(9)
  2. 在 Mac 安装Docker
  3. webpack打包vue文件报错,但是cnpm run dev正常,最后我只想说:是我太笨,还是webpack4.4版本太坑...
  4. lable标签的妙用
  5. 【JZOJ3636】【BOI2012】Mobile(mobile)
  6. Ubuntu系统如何安装软件
  7. node-webkit File Dialog
  8. linux 系统中的文件权限
  9. 16、用VS2005调试编译驱动程序
  10. poj3216 Prime Path(BFS)