我们大部分时间都是在使用数组来存储串 , 然后进行操作的 , 对于链串我们不经常使用, 在这里我们为了练习的需要 , 实现一下链串的基本操作 , 其实也就是插入,删除, 替换 ,然后指针的交替罢了.
下面开始实操:
目录
● 串赋值 : StrAssign(s,cstr)
● 串复制: StrCopy(s,t)
● 判断串相等 : StrEqual(s,t)
● 求串长 : StrLength(s)
●串连接: Concat(s,t)
●求子串: SubStr(s,i,j)
●输出串 : DispStr(s)
●插入串 : InsStr(s,i,t)
● 删除串中字符 : DelStr(s,i,j)
● 替换子串 : RepStr(s , i, j, t)
我们通常的操作是 在链串里面 ,每个节点存储四个字节的数据, 就是一个字的数据 , 在这个我们方便对数据进行操作 , 每个 节点只放一个字节 , 那一个节点明明可以存储 4 个字节 , 我们只存一个字节 ,那就会空余 3 个字节的空间 , 那也没办法 , 继续操作即可
(c 语言有内存对齐机制 ,所以就算我们定义数据结构的时候, 定义 char 节点, 一样会占用 4 个字节的空间的)
我们定义串的节点
typedef struct snode { char data; struct snode *next; }LiString;
自定义结构体 , 数据和 本节点的后继节点
将一个字符串常量cstr赋值给串s
方法: 尾插法
//传入要插入的链表的指针地址, 字符串常量数组 void StrAssign(LiString *&s,char cstr[]) { //定义遍历计数器 int i; //定义指向尾指针的变量 r ,创建的新节点p LiString *r , *p; s = (LiString *)malloc(sizeof(LiString)); //刚开始尾指针指向 头结点 r = s; //开始遍历赋值 for(i= 0; cstr[i]='\0'; i++) { p = (LiString *)malloc(sizeof(LiString)); //数据赋值 p->data = cstr[i]; //尾指针操作 //尾结点的后继指针指向新节点 r->next = p; //指向之后,新节点成为尾结点,然后 r指向尾结点 r = p; } //尾结点的后继节点置空 r->next = NULL; }
将串 t 复制给串 s
说白了,就是用一个指针指向 t 的元素,然后对 s 数据进行修改
//传入 要被赋值的串的指针地址 和 要复制的串的指针 //注意: 指针地址会修改原串, 指针并不会修改 void StrCopy(LiString *&s , LiString *t) { //定义一个指针 p 指向t的元素,初始指向 t 的头结点后的第一个元素(头结点一般不保存数据,起标识作用) LiString *p = t->next; //创建s的头结点 s = (LiString *)malloc(sizeof(LiString)); // 定义指针 r 指向 s 的尾结点,初始时,串s里面就一个头结点 r = s; //下面将t除了头结点之外的元素全部复制给s while(p!=NULL) //当p指向 t的节点不为空时,就循环跳转复制 { //先为 s 的新节点分配空间 q = (LiString *)malloc(sizeof(LiString)); //为s的节点赋值 q->data = p->data; //新节点已经创建,开始插入到 s 后面 //利用尾插法 //先把尾结点的后继指针指向新节点 r->next = q; //然后把尾指针指向新节点 q r=q; //然后 指向我们复制的串的指针 向后移动,继续复制新元素 p = p->next; } // 当复制完后 , 串s的尾指针指向空 r->next = NULL; }
若两个串 s 与 t 相等返回真(1);否则返回假(0)
//传入两个比较的串 bool StrEqual(LiString *s,LiString *t) { //我们要比较的不是头结点,而是后面的节点 //头结点里面存储的是, 识别串的信息 LiString *p = s->next; LiString *q = t->next; //当两者指向的指针指向空,跳出 ,或者两者所指向的数据区不一样跳出 while( p != NULL && q! = NULL && p->data == q->data) { //开始比较,跳转指针 p = p->next; q = q->next; } //我们要判断跳出的是那两种情况 //如果p 和 q 都指向空的时候 ,我们就说明已经遍历完才跳出,不然的话,就是数据不一致才跳出 if( p == NULL && p == NULL) { return true; } else { return false; } }
// 返回串的串长
//说白了就是遍历一下串而已,加一个计数器//传入要计算的串的长度 int StringLength(LiString *s) { //计数器初始为0 int i = 0; //用一个指针来遍历,初始指向头结点的后继节点 LiStirng *p = s->next; //不为空时,继续遍历 while(p!=NULL) { // 不为空,代表没有遍历到最后,加一即可 i++; //接着遍历下一个 p = p->next; } //为空则跳出 //返回i的值即可 return i; }
将两个串 s 和 t 连接形成新串,返回这个新串
代码如下:
//我们遍历两个串,然后把串的数据赋值给一个新串 //传入两个串的指针 LiString *Concat(LiString *s,LiString *t) { //既然我们要遍历这两个串,就要用指针指向串元素 //定义指针 *p 指向要被串连的串的数据 LiString *p; //插入新串,我们就要创建一个新节点 *q LiStirng *q; //我们要插入到新串的结尾,需要用到尾插法,所以需要定义指针r指向尾指针 LiStirng *r; //下面我们开始创建新串节点 str = (LiString *)malloc(sizeof(LiString)); //尾指针 r 指向新串 str头结点 r = str; //两个串按次序插入到新串 //先将 s 的所有节点复制到 str, p初始指向s头结点的后继节点 p = s->next // P所指向的节点为空时,跳出赋值循环 while(p!=NULL) { //构造新串节点 q = (LiString *)malloc(sizeof(LiString)); //数据区赋值 q->data = p -> data; //每创建一个新节点,其都是str的尾结点,所以后继指针置空 q->next = NULL; //开始把新节点,利用尾插法,插入到新串 str //新串尾结点指向新节点 r->next = q; //尾指针指向新节点 r = q; //指向 串 s 的指针后移 p = p->next; } //当p下一个节点为空时跳出 //下面将t的所有节点复制到str //赋值指针p 指向 t头结点的后继节点 p = t->next; //当p指向的节点不为空时候,一直循环插入 while(p!=NULL) { //同理 q = (LiString *)malloc(sizeof(LiString)); q->data = p->data; r->next = q; r = q; p = p->next; } //最后将新串的尾结点的后继指针指向空 r->next = NULL; //返回新串 return str; }
返回串 s 中从第i个字符开始的,由连续 j 个字符组成的子串
参数不正确时,返回一个空串
(1 <= i <= StrLength(s))
(我们所说的第i个字符是逻辑上所说的位序,从1开始,
所以子串开始的字符那就是从1到字符串长度)我们就是创建一个新串,然后从第i个字符开始复制,然后加一个计数器,然后赋值 j 个即可
我们需要检查输入字符的合法性
//传入要求子串的字符串, 逻辑位序,子串个数 LiString *SubStr(LiString *s,int i,int j) { //先创建一个空子串 //然后非法字符的话才能返回空串 LiString *str; //为新子串分配空间 str = (LiString*)malloc(sizeof(LiString)); //我们已经创建了一个头结点,头结点的后继指针置空 str->next = NULL; //我们既然要往子串后面插入节点,就要定义一个指向串尾的指针 *r LiString r = str; //初始指向头结点 // 我们新建节点 , 需要为新节点分配空间 ,用一个指针指向新节点 LiString *q; //为新节点复制数据的是 串 s ,再来一个指针 p指向 s的节点 LiString *p = s->next; //初始指向s头结点的后继节点,即第一个元素, 头结点不算数据元素 //下面判断非法输入 //如果 i 不在0 到字符串长度之间,或者 从第 i 个元素开始,索要的元素超过串s的长度的话,也返回空串 // j为索要的元素长度 , i-1表示开始索要元素前,前面已经略过几个元素,再加j 个的话,不能超过s的长度 if(i<=0 || i>StrLength(s) || j<0 || i-1+j > StrLength(s)) { return str; //参数非法时, 返回空串 } //通过合法验证,则进行串的复制 //要从哪里开始复制呢? 从第i个元素 //进入循环前 ,p= s->next , p指向头结点的后继节点,即第1个数据元素 // 进入循环后 // 当k = 0时, p指向第2个数据元素 // 当 k= 1时 ,p指向第 3 个数据元素 //当k = 2时 ,p指向第 4个数据元素 //当 k = i-2 时,跳出,此时 p 指向第 i 个数据元素 for(k = 0; k
next; } //元素已经找到,下面开始复制 j 个元素到新子串 for( k = 1; k<=j; k++) { //为新节点创建空间 q = (LiString *)malloc(sizeof(LiString)); //数据赋值 q->data = p->data; //尾插法插入元素 //新元素插入到尾部 r->next = q; //尾指针指向新节点 r = q; // 继续复制元素,我们的p 继续向后移动即可 p = p->next; } //子串尾指针置空 r->next = NULL; //返回子串 return str; }
所谓输出串 ,就是把链串里面的数据逐个输出而已
//传入要输出的串 void DispStr(LiString *s) { //第一个节点是串s头结点的后继节点 LiString *p = s->next; //开始循环遍历串,指导p指向为空 while(p!=NULL) { //开始输出 printf("%c",p->data); //输出之后,指针向后移动 p = p->next; } //为了优美,换行 printf("\n"); }
将串 t 插入到s的第i个字符中,即将t的第一个字符作为s的第i个字符, 并返回新串
参数不正确时,返回一个空串
如下图所示://我们要把t的数据插入到 s 的第i个字符中,就是把t的数据节点,从 s第i个元素 前 逐个插入,然后,再把 s 后面从 第i个被断开 的节点,再插入到后面即可
下面开始代码实操:
//我们先传入要插入和被插入的链串,和插入的位置 LiString *InsStr(LiString *s,int i,LiString *t) { //我们的建议还是不在原串上修改,而是再新建一个新串,然后再逐个复制 //新建一个str串,并分配空间 LiString *str; str = (LiString *)malloc(sizeof(LiString)); //后继指针置空 str->next = NULL; //下面开始验证非法字符 //我们每次都是插入到字符前的,理论上我们可以把字符插入到字符串的任意字符串的间隔, //所以我们也能插入到尾部,那尾部的前面是第几个元素呢? 是第i=StrLength(s)+1 个,所以这时也合法 if(i<1 || i>StrLength(s)+1) { //非法,返归空串 return str; } //接下来,我们需要往新串里面,分三段插入节点 // ①将s的前 i-1 个节点复制到str // ②将t的所有节点复制到str // ③将s从第i个节点往后,全部复制到str //下面开始我们的实操 //我们插入的方法是尾插法, 所以需要一个指针r指向 str尾部,初始指向头结点 LiString *r; r = str; //然后我们创建新节点,需要指针指向 ,定义 q 为新节点指针 LiString *q; //定义 p 为指向需要复制的节点 LiString *p; //开始第一段复制,p指向s的前 i-1 个节点中第一个节点 p = s->next; for(k=1;k<=i-1;k++) { //创建新节点,然后赋值 q = (LiString *)malloc(sizeof(LiString)); q->data = p->data; //开始尾插法,插入到str尾部 //str尾指针r指向 新节点 r->next = q; //尾指针后移 r = q; // 幅值节点,继续向后移动 p = p->next; } //当跳出循环时,p又向后移动了,指向了s的第 i 个元素 //我们下面,能直接利用p 来进行 t的元素赋值吗? // 答案是:不能,我们此时是利用链表来进行指针赋值,p里面还存放着后续我们所需要的信息 //所以,另定义一个 p1 来指向 t的元素 //开始第二段复制,p指向t的第一个节点,复制全部节点 LiString *p1; p1 = t->next; //当p不为空,则一直复制 while(p1!=NULL) { //尾插法同理 q = (LiString *)malloc(sizeof(LiString)); q->data = p1->data; r->next = q; r = q; p1 = p1->next; } //开始第三段复制,p指向 s的第i个节点,直到最后 //我们此时p里面存放的是 s第 i个节点的信息 //下面我们直接开始复制,把元素利用尾插法插入到新串后面 //当p不为空时继续 while(p!=NULL) { //为新节点 q 分配空间 q = (LiString *)malloc(sizeof(LiString)); //数据赋值 q->data = p->data; //尾插法开始 r->next = q; r = q; //p 继续向后移动 p = p->next; } //最后新串尾结点置空即可 r->next = NULL; //返回新串 return str; }
从串s 中删去第 i 个字符开始的长度为 j 的子串 , 并返回剩下的新串,参数不正确时, 返回一个空串 , ( 1 <= i <= StrLength(s))
我们要删除这其中一部分子串, 第 i 个的话 , 需要一个计数器 , 遍历到 第 i-1 个节点, 然后标记, 接着再 用另一个计数器, 查 j 个元素 , 再把 j 个元素之后的元素, 插入到 第 i-1 个元素的后面即可 .
我们此时的操作 , 目的是构造一个新串 , 然后去除我们要删除的子串 ,
思路差不多 , 只不过是有选择的复制罢了.
//传入要删除子串的串指针,删除的元素位序, 删除的元素个数 LiString *DelStr(LiString *s,int i, int j) { //首先第一步,要判断非法字符, 非法返回空串 //先定义新串吧 LiString *str; str = (LiString *)malloc(sizeof(LiString)); //后继指针指控 str->next = NULL; //我们要删除的元素,位序在 1到 串长度之间,然后我们从删除的元素开始, 删除的长度不能超过串长度 //从第i个元素开始 , 说明我们前面需要略过 i-1 个元素 ,然后从第i个元素开始删除 // 我们需要删除j 个元素 , 但是这不能超过串长度 , 所以 // i-1 + j < StrLength(s) if(i<1 || i> StrLength(s) || i-1 + j > StrLength(s)) { //非法返回空串 return str; } // 如果字符合法 ,则开始有选择复制 // 我们当然是利用尾插法, 插入到新串str 后面 // 定义一个尾指针 r , 指向串s的指针 p , 新节点 q LiString *r,*p,*q; //先将s 的前 i-1 个节点复制到 str //p 先指向 s 头结点的后继节点,即 第一个数据节点 p = s->next; //新串尾指针先指向头节点 r = str; int k; for(k=1;k <= i-1 ; k++) { //构造新节点 q = (LiString *)malloc(sizeof(LiString)); //新节点赋值 q->data = p->data; //尾插法实施 r->next = q; r = q; //p向后移动指向下一个节点 p = p->next; } //当p出来的时候 , p指向的是 第 i个节点 //而我们要删除从第i 个节点到 第 i-1+j 个节点 //所以我们需要把 从第(i-1+j + 1 )个节点到最后的节点,复制插入到新串 //先把p指向第 (i-1+j + 1)个节点,即 第 (i+j)个节点 for(k=1; k<=j; k++) { p= p->next; //k=1 时, p指向 第(i+1)个 //k=2 时, p指向 第(i+2)个 //k=j 时, p指向 第(i+j)个 } //跳出的时候, p 指向串s第(i+j)个元素 //下面就开始,把剩余的元素插入到新串尾部 //不为空时,循环插入 while(p!=NULL) { //构建新节点 q = (LiString *)malloc(sizeof(LiString)); //数据复制 q->data = p->data; //尾插法 r->next = q; r = q; //指向下一个节点 p = p->next; } //直到 p 指向空,则跳出,我们把新串尾结点置空即可 r->next = NULL; //返回新串,就是我们所需要的结果 return str; }
在串 s 中,将第 i 个字符开始的 j 个字符构成的子串用串 t 替换 , 并返回产生的新串 , 参数不正确返回一个空串
(1 <= i < =StrLength(s))
我们从第 i 个开始替换 ,就把串 s 的第 i-1个元素指向 t 的第一个元素 ,然后 t 的尾结点的后继指针指向 串 s 的第 i+j 个元素即可
我们 为了不破坏原串 ,所以选择创建新串 ,来进行有选择复制即可
我们要时刻注意,我们覆盖的指针 , 里面存放的信息 ,后续是否需要使用
先复制 串 s 的前 i-1 个元素 ,然后复制 串 t 的元素 ,接着 让指向串s 的指针跳转过 j 个元素, 指向第 i+j 个元素 ,接着复制 , 构造一个符合结果的新串即可.
下面开始代码实操:
//传入要被替换的串,替换的位置 ,替换的个数 ,替换的串 LiString *RepStr(LiString *s,int i, int j, LiString *t) { //首先判断输入是否合法 //不合法返回新串 //返回新串 ,就要先定义新串 LiString *str; str = (LiString *)malloc(sizeof(LiString)); //后继指针置空 str->next = NULL; //参数非法处理 // 要被替换的位置,是 从 1 到 串的长度 , 个数的话,被替换位置前,已经有 i-1 个元素, //长度 i-1 + j < 串元素长度 if(i<1 || i>StrLength(s) || i-1+j > StrLength(s)) { //如果是非法字符,返回空串 return str; } //通过非法检验, 就开始往复制插入字符 //我们要通过尾插法,把要选择复制的元素赋值给新串 //先定义一个尾指针指向新串str,初始指向头结点 LiString *r; r = str; //下面进行四段操作 //① 将s的前 i-1 个节点复制到str //② 让p沿next 跳j个节点 //③ 将 t所有节点复制到str //④ 将p及其后的节点复制到str //我们现在需要定义指向s的指针 *p , 构造新节点*q // 指向 t 的节点 *p1 LiString *p; LiString *q; LiString *p1; //注意: 我们不能让 s 和 t 公用一个指针,因为我们需要记录s的指针,以备后用,把s后续节点插入到串str //第一段 //p先指向s的第一个节点,即 头结点的后继节点 p = s->next; for(k = 1; k <= i-1; k++) { //为新节点分配空间 q = (LiString *)malloc(sizeof(LiString)); //数据赋值 q->data = p->data; //每创建一个节点,其都是str的尾结点,所以后继指针置空 q->next = NULL; //str的尾指针指向新节点q r->next = q; //尾指针后移 r = q; //接着赋值节点后移 p = p->next; } //跳出循环时, p指向第i个节点 //为了后续接着把串s被替换后的节点插入到str,先将p跳过被替换的节点 //我们从第i个节点, 跳过j个节点, 最后一个被替换的节点是 第(i+j-1)个 //那下一个节点第(i+j)个就是需要复制的 for(k=1; k<=j; k++) { p=p->next; //k=1时,p指向s的第i+1个节点 //k=2时,p指向s的第i+2个节点 //k=j时,p指向s的第i+j个节点 } //p的指针已经保存好,接下来进行第二段 //接着进行第二段,把要替换的串,复制给新串 //p1指向新串的第一个数据节点 p1 = t->next; //不为空时,继续 while(p1!=NULL) { //创建新节点 q = (LiString*)malloc(sizeof(LiString)); //数据复制 q->data = p1->data; //新节点后继指针置空 q->next = NULL; //把新节点利用尾插法,插入到str结尾 r->next = q; r = q; //赋值指针 p1继续向后移动 p1 = p1->next; } //开始第三段赋值 //把 p里面存放的第 i+j个元素开始,到串s结束,插入到str //p此时指向第(i+j)个元素 while(p!=NULL) { //构造新节点 q = (LiString*)malloc(sizeof(LiString)); //赋值 q->data = p->data; //新节点后继指针置空 q->next = NULL; //利用尾插法,插入到str结尾 r->next = q; r = q; //赋值节点后移 p = p->next; } //为了保险起见,str后继节点置空 r->next = NULL; //返回新串 str return str; }