第一个博客 发个弄了2天的C语言数据结构字符串吧
首先
这里只发KMP算法改过来的,BF算法直接去掉getnext函数然后修改Index函数就好。
KMP算法参考了楠先生发的kmp算法,然后根据其代码逻辑编辑成自己能用的(点击跳转至楠先生的kmp算法解析原文连接)。然后BF算法则是直接参考课本里的。
其次
说说这次将这个作业敲出来的感受,虽然这不是做项目,也不是参加比赛,只是简简单单的敲一个结构体。但是这是第一次自己根据要求敲出各个方法,除了这个KMP算法没法做到不参考其他人的,其余的内容基本都由自己思索,然后实现功能。特别是在敲完所有函数功能后,第一次编译时,报错只是一些由于马虎或者太疲劳导致的缺少符号或者变量名敲错,而大体语法无错,思路也基本正确,所以当时编译后是挺欣慰的。不过我说的两天并不是真正意义上简简单单的两天,而是几乎花了48个小时做出来。然而其中估计有30个小时是花在了将楠先生的KMP算法移植到我的代码中去。期间,变量之间的关系实在是绕脑。而噩梦来了。搬运过来不适用,之间做出的N次修改就已经挺考验耐心了。而后来多测试几种不同组合的字符串,发现移植并不是很完美。有一些例如 abaccabc的字符串,会出逻辑的小错误,也正是这个小错误花费了我一天的时间,一直想不出解决方法,或者说一直不能知道具体的错误原因。昨天再次尝试,将一切的变量的运算过程都给列出来才知道,然后又思考了许久,才琢磨出解决的方法。顿时,感觉到一种成就感,也许这只是一个十分简单的作业,但于我自己而言,算是在我代码生涯上一个入门的标志点吧,根据功能需要敲出代码,而不是通过直接搬运或者参考其他。
一开始敲的时候,根据了楠先生里面的kmp代码的getnext 写出子串的next,然后在按照此类思想,新建一个Str结构体变量Str *Next,将其各个结点用指针域next连接,而prior指向对应的子串元素。在运行过程同时给予子串和Next串指针,并令子串指针提前Next前一个节点,移动时同时后移,当某个结点不相匹配时,将子串指针返回至Next串指针的prior域。
先给出Str结构体的声明定义:
typedef char DataType;
typedef struct Char
{
DataType data;
struct Char *prior, *next;
}Char;
typedef struct Str
{
struct Char *C;
}Str;
首次敲出来后代码大致如下:
// 用于链式结构的kmp算法
Str *GetNext(Str *T) //方法①:将T赋值给字符数组 其余代码照原 方法② 找到取代利用next[]记录位置的方法
{
Str *Next, Next1; //记录: 将匹配到的结点 的下一结点 通过next连接起来, 再取的该结点,即存储结点的前驱。
StrInit(&Next1);//结点数为T的结点数-1
Next = &Next1;
Char *p, *q, *kp, *kq;
p = T->C;
q = p->next;
kp = Next->C;
kp->prior = T->C;
while(q != NULL)
{
if(p == T->C || p->data == q->data)
{
p = p->next;
q = q->next;
Char *s = (Char *)malloc(sizeof(Char));
s->prior = p;
s->next = NULL;
kp->next = s;
kp = kp->next;
}
else
{
if(p->prior != NULL)
p = p->prior;
}
}
return Next;
}
int StrIndex(Str *S, Str *T) //基于KMP算法的子串查找
{
if(StrLength(S)<StrLength(T))
{
printf("主串长度小于子串长度!");
return 0;
}
Str *Next;
Next = GetNext(T);
Char *p, *q, *Np, *Nq;
int count = 1, i = 0, k = 0;
p = S->C;
q = T->C;
Np = Next->C;
Nq = Next->C;
while(q != NULL)
{
if(q == T->C || p->data == q->data)
{
p = p->next;
q = q->next;
if(i++)
{
Nq = Np;
if(Np->next != NULL)
Np = Np->next;
}
k++;
}
else
{
q = Np->prior;
if(Np != Nq)
Np = Nq;
if(q == T->C->next && p->data != q->data)
{
Np = Next->C;
q = T->C;
i = 0;
}
count = k; //其实一开始并不知道如何将匹配到的字符串位置传达给count,后来想到当主串和子串字符不相符时传达一次主串指针的位置,这样在之后匹配到子串的时候不进行传达就能得到子串所在位置的前一个位置。
}
if(p == NULL && q != NULL)
return 0;
}
printf("%d \n\n",count);
return count;
}
问题发现及修正:
然而在我敲完这些代码已经花了好多小时,好多根头发了,所以在运行后出现正确结果时,我开始沾沾自喜。然而在我换了子串内容后发现结果出现异常了。就是显示位置不正确。
思考了好久,重复运算了好多次,一开始一直不注意i,k,count之间的变化。特别是k和count的变化,i主要是用于保证子串指针指向其首个元素时,Next指针指向Next->C,也就是子串指针领先Next一个位置。
既然出现了bug或者说问题,那就要尽可能去解决。然而我又折腾了几个小时,无果放弃。
然而在昨天,突然兴起,于是很细致很佛系的慢慢思考程序的运作,并且画出i,k, count的变化过程,于是发现了这样一个问题。就如abaaackabacad中查找aca,会出现结果出错。在随后的运算分析过程中,我将所有变量的变化都列出来,才得以发现问题所在。就是在将子串上的指针进行回溯时,若是回溯后跟主串上指针的data域相等,结果k传给count的值就是当前主串元素的位置,这样就导致跟原本预想的不一样(在不相符时将k的值传给count,然后进行下一组匹配,也就是count会是子串位置的前一个)。所以这个时候借助 lastp1 和 lastp2 分别来保存最后一次不相符的时候,主串指针所在位置,以及子串指针回溯后与主串指针数据相符时主串指针的位置。在出了while循环后,若两指针相等,则count- -。
计算过程画的图:
下面是修改后的代码:(主要StrIndex函数)
Str *GetNext(Str *T) //方法①:将T赋值给字符数组 其余代码照原 方法② 找到取代利用next[]记录位置的方法
{
Str *Next, Next1; //记录: 将匹配到的结点 的下一结点 通过next连接起来, 再取的该结点,即存储结点的前驱。
StrInit(&Next1);//结点数为T的结点数-1
Next = &Next1;
Char *p, *q, *kp, *kq;
p = T->C;
q = p->next;
kp = Next->C;
kp->prior = T->C;
while(q != NULL)
{
if(p == T->C || p->data == q->data)
{
p = p->next;
q = q->next;
Char *s = (Char *)malloc(sizeof(Char));
s->prior = p;
s->next = NULL;
kp->next = s;
kp = kp->next;
}
else
{
if(p->prior != NULL)
p = p->prior;
}
}
return Next;
}
int StrIndex(Str *S, Str *T) //基于KMP算法的子串查找
{
if(StrLength(S)<StrLength(T))
{
printf("主串长度小于子串长度!");
return 0;
}
Str *Next;
Next = GetNext(T);
Char *p, *q, *Np, *Nq, *lastp1, *lastp2; //lastp1 p2,p1 用于记录最后一次不同的主串结点, p2 用于记录最后一次因不同而跳语句跳到相同的结点。
int count = 1, i = 0, k = 0, op = 0;;
p = S->C;
q = T->C;
lastp1 = p;
lastp2 = p->next;
Np = Next->C;
Nq = Next->C;
while(q != NULL)
{
if(q == T->C || p->data == q->data)
{
p = p->next;
q = q->next;
if(i++)
{
Nq = Np;
if(Np->next != NULL)
Np = Np->next;
}
k++;
}
else
{
q = Np->prior;
if(Np != Nq)
Np = Nq;
lastp1 = p;
if(q == T->C->next && p->data != q->data)
{
Np = Nq = Next->C;
q = T->C;
i = 0;
}
if(p->data == q->data)
lastp2 = p;
count = k;
}
if(p == NULL && q != NULL)
return 0;
}
if(lastp1 == lastp2)
count--;
return count;
}
最后是完整的代码:
/*字符串*/
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
typedef char DataType;
typedef struct Char
{
DataType data;
struct Char *prior, *next;
}Char;
typedef struct Str
{
struct Char *C;
}Str;
void StrInit(Str *S);
void StrCreate(Str *S, char* CH, int len);
Str StrAssign(Str *S, Str *T);
int StrLength(Str *S);
Str StrCat (Str *S, Str *T);
Str StrSub (Str *S, int i, int len);
int StrCmp (Str *S, Str *T);
Str *GetNext(Str *T);
int StrIndex(Str *S, Str *T);
Str StrInsert(Str *S, Str *T, int i);
Str StrDelete (Str *S, int i, int len);
int StrPrint(Str *S);
int main()
{
Str S, T;
char A[] = "ababccca", B[] = "abc";
int i, lenA, lenB;
lenA = sizeof(A)-1;
lenB = sizeof(B)-1;
StrInit(&S);
StrInit(&T);
StrCreate(&S, A, lenA);
if(StrPrint(&S))
printf("打印完毕!\n");
else
printf("字符串为空!\n");
printf("Length of S: %d\n",StrLength(&S));
printf("Length of T: %d\n",StrLength(&T));
StrCreate(&T, B, lenB);
S = StrAssign(&S, &T);
if(StrPrint(&S))
printf("打印完毕!\n");
else
printf("字符串为空!\n");
printf("Length of S: %d\n",StrLength(&S));
printf("Length of T: %d\n",StrLength(&T));
StrCreate(&S, A, lenA);
if(StrPrint(&S))
printf("打印完毕!\n");
else
printf("字符串为空!\n");
printf("Length of S: %d\n",StrLength(&S));
printf("Length of T: %d\n",StrLength(&T));
S = StrCat(&S,&T);
if(StrPrint(&S))
printf("打印完毕!\n");
else
printf("字符串为空!\n");
printf("Length of S: %d\n",StrLength(&S));
printf("Length of T: %d\n",StrLength(&T));
T = StrSub(&S, 2, 11);
printf("从主串S第二个字符开始截取到结束后的子串T:\n");
if(StrPrint(&T))
printf("打印完毕!\n");
else
printf("字符串为空!\n");
printf("Length of S: %d\n",StrLength(&S));
printf("Length of T: %d\n",StrLength(&T));
printf("主串S与子串T比较:\n");
if(StrCmp(&S, &T) == -1)
printf("S < T ! \n");
else if(StrCmp(&S, &T))
printf("S > T ! \n");
else
printf("S = T ! \n");
T = StrSub(&S, 9, 3);
printf("截取主串S后三个字符后的子串T\n");
if(StrPrint(&T))
printf("打印完毕!\n");
else
printf("字符串为空!\n");
if(i = StrIndex(&S, &T))
printf("子串T在主串S中首次出现的位置为: %d\n", i+1);
else
printf("主串S中无子串T\n");
S = StrInsert(&S, &T, i);
printf("主串S在第 3 个位置插入子串T后的数据为:\n");
if(StrPrint(&S))
printf("打印完毕!\n");
else
printf("字符串为空!\n");
S = StrDelete(&S, 8, 3);
printf("主串S第 8 个位置往后删除 3 个数据后为:\n");
if(StrPrint(&S))
printf("打印完毕!\n");
else
printf("字符串为空!\n");
return 0;
}
void StrInit(Str *S) //字符串头初始化
{
Char *SC = (Char *)malloc(sizeof(Char));
SC->next = SC->prior = NULL;
S->C = SC;
}
void StrCreate(Str *S, char* CH, int len) //生成字符串
{
if(len == 0)
{
printf("error! the length is 0!");
return;
}
int count = 0;
Char *p = NULL, *q = NULL;
p = S->C->next;
while(p != NULL)
{
q = p;
p = p->next;
free(q);
}
p = S->C;
while(count < len)
{
Char *s = (Char *)malloc(sizeof(Char));
s->data = CH[count++];
p->next = s;
s->next = NULL;
s->prior = p;
p = p->next;
}
}
Str StrAssign(Str *S, Str *T) // make S to T.
{
Char *p, *p2, *q;
p = S->C;
p2 = p->next;
q = T->C->next;
while(q != NULL)
{
Char *s = (Char *)malloc(sizeof(Char));
s->data = q->data;
s->next = NULL;
s->prior = p;
p->next = s;
p = p->next;
q = q->next;
}
while(p2 != NULL)
{
p = p2;
p2 = p2->next;
free(p);
}
return *S;
}
int StrLength(Str *S) //get length of String.
{
int i = 0;
Char *p = NULL;
p = S->C; //point at the first data area.
while(p->next != NULL)
{
p = p->next;
i++;
}
return i;
}
Str StrCat (Str *S, Str *T) //catch T to S;
{
Char *p = NULL, *q = NULL;
p = S->C;
q = T->C;
while(p->next != NULL)
p = p->next;
p->next = q->next;
if(q->next != NULL)
q->next->prior = p;
return *S;
}
Str StrSub (Str *S, int i, int len)
{
int count = 0;
Str ST, *T;
StrInit(&ST);
Char *p = NULL, *q = NULL;
T = &ST;
p = S->C;
q = T->C;
while(count < i && p->next != NULL)
{
count++;
p = p->next;
}
count = 0;
while(count < len && p != NULL)
{
count++;
Char *s = (Char *)malloc(sizeof(Char));
s->data = p->data;
s->next = NULL;
s->prior = q;
q->next = s;
q = q->next;
p = p->next;
}
return ST;
}
int StrCmp (Str *S, Str *T)
{
Char *p = NULL, *q = NULL;
p = S->C->next;
q = T->C->next;
while(p != NULL && q != NULL)
{
if(p->data > q->data) return 1;
else if (p->data < q->data) return -1;
p = p->next;
q = q->next;
}
if(p != NULL && q == NULL) return 1;
else if(p == NULL && q != NULL) return -1;
return 0;
}
Str *GetNext(Str *T) //方法①:将T赋值给字符数组 其余代码照原 方法② 找到取代利用next[]记录位置的方法
{
Str *Next, Next1; //记录: 将匹配到的结点 的下一结点 通过next连接起来, 再取的该结点,即存储结点的前驱。
StrInit(&Next1);//结点数为T的结点数-1
Next = &Next1;
Char *p, *q, *kp, *kq;
p = T->C;
q = p->next;
kp = Next->C;
kp->prior = T->C;
while(q != NULL)
{
if(p == T->C || p->data == q->data)
{
p = p->next;
q = q->next;
Char *s = (Char *)malloc(sizeof(Char));
s->prior = p;
s->next = NULL;
kp->next = s;
kp = kp->next;
}
else
{
if(p->prior != NULL)
p = p->prior;
}
}
return Next;
}
int StrIndex(Str *S, Str *T) //基于KMP算法的子串查找
{
if(StrLength(S)<StrLength(T))
{
printf("主串长度小于子串长度!");
return 0;
}
Str *Next;
Next = GetNext(T);
Char *p, *q, *Np, *Nq, *lastp1, *lastp2; //lastp1 p2,p1 用于记录最后一次不同的主串结点, p2 用于记录最后一次因不同而跳语句跳到相同的结点。
int count = 1, i = 0, k = 0, op = 0;;
p = S->C;
q = T->C;
lastp1 = p;
lastp2 = p->next;
Np = Next->C;
Nq = Next->C;
while(q != NULL)
{
if(q == T->C || p->data == q->data)
{
p = p->next;
q = q->next;
if(i++)
{
Nq = Np;
if(Np->next != NULL)
Np = Np->next;
}
k++;
}
else
{
q = Np->prior;
if(Np != Nq)
Np = Nq;
lastp1 = p;
if(q == T->C->next && p->data != q->data)
{
Np = Nq = Next->C;
q = T->C;
i = 0;
}
if(p->data == q->data)
lastp2 = p;
count = k;
}
if(p == NULL && q != NULL)
return 0;
}
if(lastp1 == lastp2)
count--;
return count;
}
Str StrInsert(Str *S, Str *T, int i)
{
if(StrLength(S) < i)
{
printf("下溢错误!\n");
return *S;
}
Char *ps, *qs, *pt, *qt;
int count = 0;
ps = S->C;
pt = T->C->next;
while(count < i && ps != NULL)
{
count++;
ps = ps->next;
}
qs = ps->next;
while(pt != NULL)
{
Char *s = (Char *)malloc(sizeof(Char));
s->data = pt->data;
s->next = NULL;
s->prior = ps;
ps->next = s;
ps = ps->next;
pt = pt->next;
}
ps->next = qs;
if(qs != NULL)
qs->prior = ps;
return *S;
}
Str StrDelete (Str *S, int i, int len)
{
if(StrLength(S)<i)
{
printf("下溢错误! \n");
return *S;
}
Char *p, *q;
int count = 0;
p = S->C;
while(count < i && p != NULL)
{
p = p->next;
count++;
}
q = p;
count = 1;
while(count < len && p != NULL)
{
p = p->next;
count++;
}
if(p->next != NULL)
p->next->prior = q->prior;
q->prior->next = p->next;
q->prior = NULL;
p->next = NULL;
while(q != NULL)
{
p = q;
q = q->next;
free(p);
}
return *S;
}
int StrPrint(Str *S)
{
Char *p;
p = S->C->next;
if(p == NULL)
return 0;
while(p != NULL)
{
printf(" %c \n",p->data);
p = p->next;
}
return 1;
}
首次发博,也算是给自己一个纪念帖。
各位大佬见笑了,希望能帮小弟指出代码的错误与不足之处,十分感谢!