SuperKey4- 单链表
⚫ 单链表查找+插入和删除问题【超级基础】
⚫ 单链表合并问题【超级重点】
⚫ 单链表逆置【高频考点】
⚫ 链表排序+去重
⚫ 链表交集问题【递增有序链表】【新型考点】【若无序,先排序,再交集】灵活一点
⚫ 单链表最大值问题【递归】
⚫ 循环链表【约瑟夫问题】【超级重点】-【数组||链表实现】-见“经典问题”
⚫ 之前的手写代码,再过一遍
1.单链表的结点类型定义
typedef struct LNode
{ int data;
struct LNode*next;
}LNode,*LinkList;
2.单链表头插法
算法思想:1.创建头结点指向空结点;2.循环创建结点p,
p->next指针指向L->next;L->next=p->next.
LinkList ListInsertHead(LinkList &L)
//LinkList:意味着最终返回LinkList链表; Linklist &L:引用型,意味着:
传入的L在函数中发生任何变化都可以传出来
{
int a;
LNode *L=(LNode*)malloc(sizeof(LNode));
L->next=NULL;
while(scanf("%d",&a)){
if(a==9999) break;
LNode *p=(LNode*)malloc(sizeof(LNode));
p->data=a;
p->next=L->next;
L->next=p;
}
}
3.单链表尾插法
算法思想:1.创建头结点head;2.循环创建结点q并赋值;
3.定义工作指针p指向head并循环创造结点q,p->next=q;p=q;
LinkList ListInserRear(LinkList &L)
{
LNode* L=(LNode*)malloc(sizeof(LNode));
LNode* p=L;
int a;
while(scanf("%d",&a)){
if(a==9999) break;
LNode* q=(LNode*)malloc(sizeof(LNode));
q->data=a;
p->next=q;
p=q;
}
p->next=NULL;
}
4.初始化一个带头结点的单链表
算法思想:1.创建一个头结点并判断是否成功;2.头结点L-next=NLL;
bool InitList(LinkList &L) //因为可能存在空间不足分配失败的可能,
所以:返回bool类型变量
{
L=(LNode*)malloc(sizeof(LNode));
if(L==NULL) return false; //空间不足,分配失败
L->next=NULL; //初始化单链表头结点
return true;
}
5.判断带头结点单链表是否为空
算法思想:如果头结点L->next==NLL为真,则:为空;否则,不为空.
bool IsEmpty(LinkList &L) //这里的L可以不带&L,但是带上也不错;以防万一,可以随时一直带着
{ if(L->next==NULL)
return true;
else
return false;
}
6.按序号查找结点值
算法思想:1.定义i=1且p=L->next;2.判断i是否在正常方位内,是否等于0;
3.设置循环退出条件,并逐个遍历单链表
LNode * GetElem(LinkList &L,int i)
{
LNode* p=L->next;
if(i<0) return NULL; //判断特殊条件
if(i==0) return L;
while(p && j<i){ //注意:此处应该注意到p可能在j还未遇到i之前,
就已经指向空结点的情况;
p=p->next;
j++;
}
return p;
}
7.按值查找链表结点
算法思想:1.传入单链表和目标值;2.在工作指针p不空且p->data!=key的情况下,循环
LNode * GetKey(LinkList &L,int key)
{
LNode* p=L->next; //定义工作指针p
while(p && p->data!=key){
p=p->next;
}
return p;
}
8.递归算法删除不带头结点的单链表L中所有值为x的结点
算法思想:1.判断第一个结点是否为空;(本质:作为递归的一个出口之一)
2.判断当前结点值!=目标值,递归判断下一个结点,并设置出口;
3.设置结点指针指向递归返回的结点,并删除;4.递归判断后续结点值是否等于目标值;
void DelPoint(LinkList &L,int key) //涉及删除,添加,递归,一定都带上&;
{ if(L==NULL) return; //作为递归的出口之一
if(L->data!=key){ //若当前结点!=key,则递归判断L->next;
DelPoint(L->next,key);
return; //作为递归的出口之一
}
LNode *p=L; //难点:上述递归最后一次进入主函数,在if(L->data!=key)处,
不满足条件,则直接来到此处;此时接收到的结点实际上跨过了很多结点或者仅是邻接点
L=L->next;
delete p;
DelPoint(L,key); //寻找下一个key结点,接下来继续递归判断后续结点值是否等于key
}
9.递归算法删除带头结点的单链表L中所有值为x的结点
void DelPoint(LinkList &L,int key)
{ if(L->next==NULL) return;
if(L->next->data!=key){
DelPoint(L->next->next,key);
return;
}
LNode* p;
p=L->next->next;
L=p->next;
delete p;
DeletePoint(L,key);
}
10.设计一个非递归算法,删除带头结点的单链表L中所有值为x的结点
算法思想:1.第一个结点为空,则直接退出;2.定义pre,p一前一后的指针,
开始遍历单链表,同时判断p->data是否等于目标值key;相同,则删除;否则,继续遍历;
void DelPoint(LinkList &L,int key)
{ if(L==NULL) return NULL;
LNode* pre=L;
LNode* p=L->next;
while(p){ //关键点:最好是在p不为空的循环条件下,
判断p->data!=key,是否为真,并进一步确定
if(p->data!=key){
pre=p;
p=p->next;
}
else{
pre->next=p->next;
delete p;
p=pre->next;
}
}
11.反向输出带头结点的单链表L中每个结点的值
void R_Printf(LinkLNode &L)
{
if(L->nxet) R_Printf(L->next);//此行看的懂,请喝胡辣汤
printf("%d",L->data);
}
void R_Printf(LinkLNode &L)
{
if(L->nxet){
R_Printf(L->next);
}
printf("%d",L->data);
}
12.删除带头结点单链表L中最小结点值
void Delete_Min(LinkList &L)
{
if(L==NULL) return; //1.头结点,判空
LNode*pre,*premin; //2.定义4个必须的结点指针
LNode*p,*pmin; //p:遍历单链表寻找最小结点,同时辅助定位pmin的位置;
pre=L;premin=L; //pmin:指向当下最小结点值的指针
p=L->next;pre=L->next; //pre:确定当下的pmin时,辅助确定premin的位置;
pmin:辅助最后删除最小结点值
while(p){ //3.p不为空的情况下,遍历单链表的同时,比较大小,看情况交换;
if(p->data<pmin->data){
pmin=p;
premin=pre;
}
pre=p;
p=p->next;
}
premin->next=pmin->next; //4.删除最小值结点
free(pmin);
}
13.就地逆置(辅助空间复杂度O(1))带头结点单链表
方法一:指针反向
算法思想:指针反向
LinkList Reverse(LinkList &L) //函数返回LinkList;&L:传入的L发生的任何变化都会传出来
{
LNode *pre,*p=L->next,*r=p->next; //1.定义结点指针,并创造指针反向的条件
p->next=NULL;
while(r){ //2.在r指针所指结点不为空的条件下,指针反向
pre=p;
p=r;
r=r->next;
p->next=pre;
}
L->next=p; //3.头结点指向指针反向后的第一个结点
}
方法二:头插法
Linklist Reverse(Linklist &L) //Linklist:强调返回一个单链表 &l:建议永远带&符号
{
LNode* p,* r; p=L->next;
L->next=NULL;
while(p!=NUll){
r=p->next;
p->next=L->next;
L->next=p;
p=r;
}
return L;
}
14.带头结点的单链表,使元素递增有序
Linklist sort(Linklist &L)
{
LNode *p=L->next,*pre,*r=p->next;
p->next=NULL;p=r;
while(p!=NULL){
r=p->next;
pre=L;
while(pre->next!=NULL && pre->next->data<p->data){
pre=pre->next;}
p->next=pre->next;
pre->next=p;
p=r;
}
return L;
}
15.带头结点单链表,删除min和max之间的元素,元素结点顺序排列
//算法思想:构建指针*pre,*p,分别作为辅助删除指针和待比较指针,遍历单链表,逐个判断
Linklist RangeDelete(Linklist &L, int min,int max)
{
LNode *pre=L,*p=L->next;
while(p!=NULL){
if(p->data<max && p->data>min){
pre->next=p->next;
free(p);p=pre->next;
}
else{
pre=p;p=p->next;
}
}
return L;
}
16.两个单链表,找出2个单链表公共结点
算法思想:
情况一:2个单链表没有公共结点,则单链表平行;
情况二:根据单链表自身特点,若有公共结点,则之后2个单链表呈“Y”型;
首先,求得两表之差;
其次,通过指针逐个遍历较长单链表与较短单链表长度一致时;逐个开始比较,
并返回第一个相同的结点,否则平行;
Linklist Search_Common(Linklist &L1, Linklist &L2)
{
int len1=Length(L1),len2=Leng(L2),dist;
Linklist longlist,shorlist;
if(len1>len2){
dist=len1-len2;
longlist=L1->next; shortlist=L2->next;
}
else{
dist=len2-len1;
longlist=l2->next; shortlist=l1->next;
}
while(dist--){
longlist=longlist->next;
}
while(longlist--){
if(longlist->data==shortlist->data){
return longlist;
}
else{
longlist=longlist->next;
shortlist=shortlist->next;
}
}
return;
}
17.带表头结点单链表,结点结构(data,next),head为头指针;递增有序(意味着:排序)输出结点,并释放空间;
算法思路:O(n^2)级单链表内部排序,设指针minpre和指针p,
同时循环遍历单链表,每循环遍历一次输出并释放一个最小结点;
void sortout(Linklist &L)
{
LNode*minp=L,*p=L->next;
while(L->next!=NULL){ //循环到仅剩头结点
while(p->next!=NULL){ //至少剩余2个数据结点进行比较
if(minp->next->data>p->next->data){
minp=p;}
p=p->next;
}
printf("%d",minp->next->data);
LNode *u;
u=minp->next;
minp=u->next;
free(u); //释放最小结点值
}
free(L);
}
18.带头结点单链表分解为A(含原链表奇数元素)和B(含原链表偶数元素)的相对顺序不变(意味着:尾插法)的两个单链表
算法思想:(1)定义变量:i;i%2:判断奇偶性;
(2)创建空表B,并设置尾指针ra和rb,用于尾指针接收结点;
(3)设置工作结点p,遍历单链表,同时置空链表A,用于接收新结点元素
Linklist dis_creat(Linklist &A)
{
int i=0;Linklist B=(Linklist)malloc(sizeof(LNode));
B->next=NULL;
LNode *ra=A,*rb=B,*p=A->next; A->next=NULL;
while(p!=NULL){
i++;
if(i%2==0){
rb->next=p;
rb=p;}
else{
ra->next=p;
ra=p;}
p=p->next;
}
ra->next=NULL;
rb->next=NULL;
return B;
}
19.带头结点单链表 C={a1,b1,a2,b2,...,an,bn},设计就地算法,拆分为A={a1,a2,...,an},B={bn,...b2,b1}
Linklist DisCreat(Linklist &A)
{
Linklist B=(Linklist)malloc(sizeof(LNode)); //创建空结点
B->next=NULL;
LNode *p=A->next; //工作指针准备
LNode *q=A;
LNode *ra=A;
while(p!=NULL){ //在指针p不空时,继续循环
ra->next=p;
ra=p;
p=p->next;
q=p->next;
p->next=B->next;
B->next=p;
p=q;
}
ra->next=NULL;
return B;
}
20.递增有序单链表,去除相同元素,使单链表元素不再重复
算法思想:定义指针p,在p->next!=NULL的情况下,一边遍历,一边比较p->data和p->next->data,
并进行处理
注:若单链表不是递增有序,咋办?排序啊!
void Del_same(Linklist &L)
{
LNode *p=L->next,*q;
if(p==NULL) return;
while(p->next!=NULL){
q=p->next;
if(p->data==q->data){
p->next=q->next;
free(q);
}
else{
p=p->next;
}
}
}
21.两个递增排列单链表,归并为一个按元素值递减的单链表;并要求利用原来的两个单链表结点存放归并后的单链表
算法思想:每一次仅比较第一个元素的大小;因为递减,所以利用头插法;因为利用原来结点
,所以:不能复制;
void MergeList(Linklist &La,Linklist &Lb)
{
LNode*r=NULL;
LNode*ra=La->next,*rb=Lb->next;
La->next=NULL;
while(ra!=NULL && rb!=NULL){
if(ra->data<rb->data){
r=ra->next;
ra->next=La->next;
La->next=ra;
ra=r;
}
else{
r=rb->next;
rb->next=Lb->next;
Lb->next=rb;
rb=r;
}
}
if(ra) rb=ra;
while(rb){
r=rb->next;
rb->next=La->next;
La->next=rb;
rb=r;
}
free(b);
}
22.将两个递增有序单链表合并为一个递增单链表;要求使用原有结点存储空间,且表中不允许有重复数据;
算法思想:因为合并后依旧是递增,所以:尾插法;因为使用原有结点空间,
所以:不能复制;因为不允许重复,同时为了简洁,所以:统一保留La链表数据.
void MergeList(Linklist &La, Linklist &Lb)
{
LNode *p,*q=La;
LNode *ra=La->next,*rb=Lb->next;
while(ra&&rb){
if(ra->data<rb->data){
q->next=ra;
q=ra;
ra=ra->next;
}
else if(ra->data>rb->data){
q->next=rb;
q=rb;
rb=rb->next;
}
else{
q->next=ra; q=ra;ra=ra->next;
p=rb->next;rb=p;free(p);
}
}
q->next=ra?ra:rb;
free(Lb);
}
23.将两个非递减有序单链表合并为一个非递增的有序单链表;要求使用原有结点存储空间,且表中不允许有重复数据。
算法思想:因为:逆序;所以:头插法;因为:使用原有结点空间;所以:不复制;
因为:不重复;所以:仅保留La;
void MergeList(Linklist &La,Linklist&Lb,Linklist&Lc)
{
LNode *pa=La->next,*pb=Lb->next;LNode *p,*q;
Lc=La; Lc->next=NULL;
while(pa || pb){
if(!pa){
q=pa;pa=pa->next;
}
else if(!pb){
q=pb;pb=pb->next;
}
else if(pa->data<=pb->data){
q=pa;pa=pa->nexxt;
}
else{
q=pb;pb=pb->next;
}
q->next=Lc->next;
Lc->next=q;
}
free(Lb);
}
24.两个带头结点单链表,递增有序;在不破坏AB结点的情况下,以A和B公共元素中产生单链表C
算法思想:定义两个指针p和q分别从AB单链表 开始遍历,若元素值相等,
则取A中值域赋给新创结点;且指针P和q同时后移;若元素值不相等,较小一方,指针后移。
void Common(Linklist &A,Linklist &B)
{
Linklist C=(Linklist)malloc(sizeof(LNode));
LNode *p=A->next;LNode *q=B->next;LNode *r=c;
while(p&&q){
if(p->data<q->data) p=p->next;
else if(p->data>q->data) q=q->next;
else{
LNode *S=(LNode*)malloc(sizeof(LNode));
S->data=p->data;
r->next=s;r=s;
p=p->next;
q=q->next;
}
}
r->next=NULL;
}
25.两个递增排列的单链表AB分别表示两个集合,设计算法求AB的交集,并存放于A链表中
算法思路:设两个指针分别指向A,B;逐个比较各个元素大小,
若元素值不同,较小一方指针右移;若元素指向相同,则保留A中元素,释放B中元素;
Linklist Union(Linklist &A,Linklist &B)
{
LNode *p=A->next,*q=B->next,*s;
Linklist C=(Linklist)malloc(sizeof(LNode));
C=A;s=C; LNode*u=NULL;
while(p && q){
if(p->data<q->data){
u=p;
p=p->next;
free(u);
}
else if(q->data<p->data){
u=p;
q=q->next;
free(u);
}
else{
s->next=p;s=p;p=p->next;
u=q;q=q->next;
free(u);
}
}
while(p){
u=p;p=p->next;free(u);
}
while(q){
u=q;q=q->next;free(u);
}
s->next=NULL; free(B); return A; //一定记得释放
}
26.两个整数序列A和B分别存入两个单链表,判断序列B是否是A的子序列
算法思想:定义两个指针分别指向A和B,逐个遍历比较;若想相同,
则:指针同时右移;若不同,则A中指针回到上次开始的位置的后继位;
B中指针从开始进行遍历;若B遍历完,则成功;若A遍历完,B还未完,失败;
char sub_ju(Linklist &A,Linklist &B)
{
LNode *p=A, *q=B,*pre;
pre=p;
while(p&&q){
if(p->data==q->data){
p=p->next;
q=q->next;
}
else{
p=pre->next;
pre=p;
q=B;
}
}
if(q==NULL) return True;
else return false;
}
27.判断带头结点的循环双链表是否对称
int Symmetry(DLinklist &L)
{
DLNOde *p=L->next, *q=L->prior;
while(p!=q && q->next!=p){
if(p->data==q->data){
p=p->next;
q=q->next;
}
else return 0;
return 1;
}
28.将两个带头结点指针h1和h2的循环单链表,令h2链接到链表h1之后,仍保持循环
Linklist Connect(Linklist &h1,Linklist &h2)
{
LNode *p=h1,*q=h2;
while(p->next!=h1) p=p->next;
while(q->next!=h2) q=q->next;
p->next=h2;
q->next=h1;
return h1;
}
29.带头结点的循环单链表,结点为正整数,反复遍历并输出释放最小值结点,直至为空
void outMin(Linklist &L)
{
LNode *min=L,*p=L;
LNode *u=NULL;
whiel(L){
while(p->next!=L){
p=p->next;
if(min->next->data>p->next->data){
min=p;}
u=min->next;
min=u->next;
free(u);}}
free(L);
}
30.If i have time,write down the old question afer 21 on page 42 of WangDao.
经典问题:
1.约瑟夫环问题
(1)用循环链表实现
算法思想:1.定义所需变量及链表结构体变量,并构建头结点;
2.在死循环下:输入n,m,设置结束出口;并利用一组循环建立循环链表;
3.在循环条件下,逐个遍历并利用i计数,若i!=m则一直遍历,
直至:i==m,删除链表结点,继续计数.
//定义链表结构体
typedef struct LNode{
int data;
struct LNode *next;
}LNode;
//主函数
void Joseph()
{
//1.定义所需变量及链表结构体变量,并构建头结点
int n,m; //定义n个猴子,数数到m退出一个
int i;
int answer[100]; //保存每次的答案,最后输出
int count=0; //用来控制题目答案的下标,也就是:
整个一轮循环产生一个猴王结点,count记录猴王结点所存储的位置
LNode *head,*tail,*p,*q;
head=(LNode*)malloc(sizeof(LNode));
head->data=-1;//忘记添加
head->next=NULL;
//2.一个整轮的循环,产生一个猴王结点:输入n,m,设置结束出口;
并利用一组循环建立循环链表;
while(1){
scanf("%d %d",&n,&m); //输入n,m,设置结束出口
if(n==0 || m==0){
free(head);
break;}
else{ //尾插法创建循环链表
tail=head; //强调:tail=head就是放在此处,不是放在下边的循环中;
for(i=0;i<n;++i){
p=(LNode*)malloc(sizeof(LNode));
p->data=i+1;
tail->next=p;
p->next=head->next; /
/精髓:保证链表最后一个结点的尾指针指向首元结点形成循环链表
tail=p;}
//3.在循环条件下:利用i计数,若i!=m则一直计数遍历;若i==m为真,
则删除链表结点;进行下一轮循环;
p=head->next;
q=tail;
i=1;//自己知道忘了,但一时缓不过神来,哦!原来在这里添加啊!哈哈哈!
while(p!=q){ //p,q总是一前一后,一旦相遇,说明:只剩下一个结点了!
if(i==m){
q->next=p->next; //当前结点删除
free(p);
//answer[i]=p->data; //不是在这里记录值哦!这只是第n次i==m,
还不是猴王结点;
p=q->next;
i=1;}
else{ //p总在q前,逐个往前移动
q=q->next;
p=p->next;
i++;} //报数加1
}
//head->next=p; //最后,p和q会指向同一个结点,head的指针会指向随意一个地方;
如果题目需要的话,可以把此句带上
answer[count] = p->data; //answer数组记录整个一轮产生的猴王结点值
count++; //记录下一个整轮产生猴王结点值所存放的answer数组存放的地方
free(p); //每一个malloc()对应一个free()这是一个好习惯;
head->next = NULL; //这是一个好习惯;
}
}
//4.输出answer数组中记录的猴王
for(i=0;i<count;i++){
printf("%d\n",answer[i]);}
free(head);
return 0;
}
(2)用数组标志位实现
约瑟夫环问题-数组标志位实现-算法思想:1.定义基本变量;2.设置循环出口,n || m 中任一==0,
则退出循环;3.初始化monkey[]数组;//4.在猴子数number>1的条件下,循环遍历;
//5.count==m,意味着:计数记到m,猴子出局;否则,依次继续遍历判断;
//6.输出最终选出的猴子
int Joseph()
{
//1.基本变量的定义
int n,m; //n:猴子总数;m:数数数到m,离开一个猴子;
int i,pos; //工作变量
int count=1,number; //count:当前报数;number:剩余猴子数
//2.设置循环出口,n || m 中任一==0,则退出循环;
while(scanf("%d%d",&n,&m)){
if(n==0 || m==0){
return 0;
break;}
//3.初始化monkey[]数组
number=n;
int monkey[301]={0}; //对数组初始化赋值,最终可以输出最终找到的猴子的序号
for(i=0;i<n;i++){
monkey[i]=i+1;
}
//4.在猴子数number>1的条件下,循环遍历:
while(number>1){
if(monkey[pos]>0){ //关键点:monkey[pos]>0,意味着:该位置猴子还在
if(count==m){ //5.count==m,意味着:计数记到m,猴子出局;
否则,依次继续遍历判断
monkey[pos]=0;
number--;
count=1;
pos=(pos+1)%n;} //关键点:本条语句,是数组循环的关键
else{
count++;
pos=(pos+1)%n;}
}
else{ //monkey[pos]!>0,意味着:该位置猴子已经出局
pos=(pos+1)%n;}
}
for(i=0;i<n;i++){ //6.输出最终选出的猴子
if(monkey[i]>0)
printf("%d\n",monkey[i]);
}
return 0;
}
(3)数组链接方式实现
约瑟夫环问题-数组链接方式实现-算法思想:
2.KMP算法问题
(1)字符串匹配-BF算法
表示串的长度的3种方案:
(1)用一个变量来表示串的实际长度;
(2)在串的结尾处存放一个在串中不会出现的一个特殊字符,作为串的结束;
(3)用数组的0号位存储串的长度,从1号位开始存放串值;
模式匹配:在给定主串中,寻找模式串的过程,就是:模式匹配;
如果找到,返回模式串在主串中的第一个位置;否则,返回-1;
模式匹配的特点:模式匹配一般问题规模很大,所以:注意时间效率;
BF算法-暴力算法:回溯公式:主串:i=i-j+1;模式串:j=0;
算法思想:(1)初始化主串S和模式串T下标:i,j;
(2)循环直到主串S和模式串T的所有字符均未比较完:如果S[i]==T[j],继续下一位;
否则,i,j分别回溯:i=i-j+1,j=0;(3)判断T中所有字符是否全部比较完,则匹配成功,
返回匹配的起始下标i-j;否则,匹配失败;
bool BF(int s[],int n,int t[],int m)
{ int i=0,j=0; //1.初始化s,t的下标;
while(s[i]&&t[j]){ //2.在s和t都没结束前,循环一下内容:
if(s[i]==t[j]){ //2.1如果相等,比较下一个
i++;j++
}
else{ //2.2不相等,回溯:i=i-j+1,j=0;
i=i-j+1;
j=0;
}
}
if(t[j]=='\0') return (i-j); //3.while循环结束,要么t提前结束,
要么s提前结束;前者返回i-j;后者返回false;
else return false;
}
本算法的关键:记住它的算法思路及回溯公式;
BF算法作为一种暴力算法,存在太多无效的回溯,每次仅仅移动一个单位距离;在此基础上,提出KMP算法。
(2)模式匹配-KMP算法
Algorithm_Thought:主串及其标记变量i可以不回溯,
模式串每次移动位数=模式串已匹配的字符数-失配位置前的最长已匹配前缀字符数 ,
到新起点k,并且k仅与模式串有关。【j动i不动】
Questions:
(1)如何由当前部分匹配的结果确定模式向右滑动的新比较起点k呢?
(2)模式应该向右滑动多远才是最高效的?
伪代码描述:
(1)在串S和串T中分别设比较的起始下标i和j;
(2)循环直到S或T任意一个所有字符全部比较完毕为止:
2.1如果S[i]==T[i],为真;则i,j均右移一位;
2.2否则,i不动,j=next[j];
2.3如果j=-1,则将i,j均向右移动一位,准备下一趟比较;
(3)如果T中所有字符均比较完毕,则返回模式串T最终匹配的首位元素序号:i-模式串长度;
否则,返回-1;
int KMP(char s[],char t[])
{
int i=0,j=0; //1.初始化
slen=strlen(s);
tlen=strlen(t);
while(i<slen && j<tlen){ //2.此处和BF的区别在于:i可能存在-1,
所以不用数组作为判断条件
if(s[i]==t[j] || j==-1){ //3.字符相同条件下,同时右移一位;j==-1,为真,
则表明当前主串和模式串字符不同,所以:两者同时进行下一位比较;
j++;i++}
else
j=next[j]; //4.如果当前字符不匹配,j=next[j];i保持不动;
}
if(j==tlen) return (i-j); //5.如果j遍历完模式串t,匹配成功,
返回模式串首元素位置:i-tlen=i-j;
else return -1;
}
(3)KMP算法-next数组
KMP算法-next数组:天才级的代码
void GetNext(char *T,char *next) //获得next数组的值
{
//1.初始化并对特殊情况进行处理;注:此处的i,j和上边的i,j含义完全不同;j,i分别表示:
已遍历字符串中最大前缀和最大后缀的最后一位元素的位置
int i=0,j=-1; //j:前缀;i:后缀;
next[0]=-1;
//2.在i还未遍历完T时,我们进行以下循环:
while(i<T.length){
if(j==-1 || T[i]==T[j]){ //当最大前缀和最大后缀最后一位元素值相同时,
i,j同时后移一位,并对next[i]=j进行记录;或者当满足j==-1的情况下,也进行这样的操作,
其实,是对next[]的0号位进行赋值;
j++;
i++;
next[i]=j;
else{
j=next[j];}
}
}
SuperKey5- 串【KMP搞定+以下几个经典函数搞定后;拿真题练练手】
⚫ 顺序存储-最长字串&位置【高频考点】
⚫ 整数转化字符串【递归实现】【高频考点】+atoi 函数
⚫ KMP 算法-判断 n 是否是 m 的字串【超级重点】-又见“经典算法”
⚫ 字符串逆序【递归】
⚫ 几个字符串处理的经典函数【超级重点&超级基础】
strlen函数-字符串求出长度
int strlen(const char*s)
{
int idx=0;
while(s[idx]!='\0'){
idx++;
}
return idx;
}
strcmp函数-字符串比较(直接比较的对象是:两个数组变量的地址)
int strcmp(const char*s1,const char*s2)
{
while(*s1==*s2 && *s1!='\0'){
++s1;++s2;
}
return (*s1-*s2);
}
strncmp函数-实现a所指字符串与b所指字符串的最多前count个字符的比较
#include <stdio.h>
int mycmp(const char *a,const char *b,int count);
/*实现函数strncmp:代码来自MOOC翁凯老师的进阶课;*/
int main()
{
char a[]="abnq ";
char b[]="aBnc";
int count=3;
printf("%d\n",mycmp(a,b,count));
return 0;
}
int myncmp(const char *a,const char *b,int count)
{
//while(*a==*b || *a!=0) //听课后自己写,出现的错误;
/*在前者的基础上,并且我们规定的前count项不为0时,地址继续后移*/
while(count--&&*a==*b && *a!='\0')
{
a++;
b++;
}
return *a-*b;
}
strcpy函数-后者参数拷贝到前者中
char * strcpy(char* dst, const char*src) //dst:目的 src:源
{
char* ret=dst;
while(*src){
*dst++=*src++;
}
*dst='\0';
return rst;
}
strncpy函数-后者参数拷贝到前者中
#include <stdio.h>
char* strncpy(char *a,const char *b,int count);
//注意此处是:char *strcpy(),而不是char strcpy();
/*实现strncpy()*/
int main()
{
char a[]=""; //疑问:用char *a="";就出现错误,
可能是定义的时候不可以用指针吧,也许,待确定;
char b[]="abcdefg123";
int count=4;
printf("%s\n",strcpy(a,b,count));
//输出字符串用%s,不可以是%d和%c;
return 0;
}
char* strncpy(char *a,const char *b,int count)
{
char *ret=a; //最后,地址a已经加加加到不知道哪里了,
所以,先用ret作为一个指针,记录一下;以便最后返回;
/*#######################################
while(*b!='\0') //核心版本一
{
*a=*b;
a++;
b++;
}
while(*b) //核心版本二
{
*a++ = *b++;
}
########################################*/
while((count--) && (*a++ = *b++)); //核心版本三
此处应该注意优先级【需要巩固】
*a='\0'; //因为循环终止条件是:*b!='\0';,
所以,*a中没有"\0';,所以,补上;
return ret;
}
strcat函数-后者拷贝到前者的后边,结成一个长的字符串
#include <stdio.h>
#include <string.h>
char *strcat(char *a,const char *b);
/*实现函数strcat:实现两个字符串的连接*/
int main()
{
char a[100]="abcdef";
char b[100]="1234s";
char *c=strcat(a,b);
printf("%s--%d\n",c,strlen(c));
return 0;
}
char *strcat(char *a,const char *b)
{
//如果目标的操作或者源为空,直接返回;
while(a==NULL && b==NULL)
{
return NULL;
}
char *address=a; //因为后续,地址a会不断变化;返回的就不是最初的地址,
所以先用指针address记录一下
while(*a) //可见简单地写作:while(*a) ;目的是:将指针a移动到末尾;
{
++a;
}
while((*a++)=(*b++)); // 将b指向的内容赋值给a指向的内容,
并让指针a,b分别每次后移一位;
*a='\0';
return address;
}
strncat函数-将str所指字符串的前n个字符添加到strDes所指的字符串结尾处
char *strncat(char *strDes,const char*strSrc,int count)
{
if(strDes==NULL || strSrc==NULL){
return NULL; //如果目的操作或者源为空,那么就直接返回
char*address=strDes;
while(*strDes!='\0'){
++strDes;
while(count-- && *strSrc!='\0')
*strDes ++= *strSrc ++;
*strDes='\0';
return address;
}
itoa函数-实现将整数向字符串转化
void itoa(int n,char s[])
{
int i,j,sign;
if((sign=n)<0) //记录符号
n=-n;
i=0;
do{
s[i++]=n%10+'0'; //取下一个数字
}
while((n/=10)>0); //删除该数字
if(sign<0)
s[i++]='-';
s[i]='\0';
for(j=i;j>=0;j--){
printf("%c",s[j]);
}
}
⚫ 最大子对称字符串长度问题(回文数)+字符串单词出现频率统计问题【今年新型问题】
SuperKey6- 队列&栈 栈 【 低频】
⚫ 2 个栈模拟队列
⚫ 循环链队模拟队的队的初始化&出队&入队+30讲专题18
⚫ 逆序循环队列所有元素
SuperKey7- 重要 专题&C 语言
⚫ 120 题抽典型问题&举一反三问题解决---直接看120题,时间太紧,不再总结
⚫ 排列组合问题--看30讲
⚫ GIS 经典算法问题-看30讲
⚫ 最大公约&最小公倍
⚫ 重要结构体定义--看自己手写的代码
⚫ 前中后缀表达式转化与计算
用栈实现表达式求值
小结:“用栈求中缀表达式值”较复杂【已基本搞定】,只要此处搞定,“用栈求前缀/后缀表达式值”,就不是问题;“用栈求前缀表达式值”和“用栈求后缀表达式”部分细节相反,代码描述基本相同。
- “用栈求中缀表达式值”,需要考虑:算子,运算符,括号,问题;
- “用栈求后缀/前缀表达式值”,需要考虑:算子,运算符,不需要考虑括号问题;
- 所以:相比较而言,对于计算机而言,“用栈求后缀/前缀表达式值”,更加高效;
- 用栈求中缀表达式值:
- 原则:
- 创建两个栈,栈1和栈2;从左到右,依次遍历中缀表达式元素;
- 遇见算子,入栈1,遇见左括号,入栈2;遇见运算符,待入栈2;
- 如果栈2空,或者栈2顶部为左括号,运算符直接入栈2;
- 如果栈2不空,并且栈2顶部不是左括号【那就是运算符或者右括号】:
- 遍历到的运算符优先级,大于栈2栈顶运算符优先级,入栈;否则,栈2栈顶运算符,依次出栈,直至遍历到的运算符优先级大于栈2中栈顶的优先级;栈2中每出栈一个运算符,
- 对应栈1中,依次出栈2个算子,自右向左排列,并同时利用刚出栈的运算符,进行计算,计算结果入栈1;
- 遍历到左括号,入栈2;遍历到右括号,则栈2中,至左括号之间的运算符,依次出栈,同时,对应栈1中的算子依次出栈,进行上述运算操作
- 最终,中缀表达式遍历完之后,如果栈2中还有运算符,则依次出栈,对应栈1中的算子也依次出栈,做如上的运算,并将最终结果压入栈1中,结束。
- 代码实现:
-
getPriority函数:传入当前所扫描到的表达式字符,与栈2顶字符优先级相比较,若优先级大,则返回1;否则返回0;
1.辅助函数1-判断操作符优先级 int getPriority(char p) { if(p=='+' || p=='-') //注意:此处是==,而不是=;此处是'',而不是“”; return 0; else return 1; }
-
calSub函数:栈2出栈1个运算符,栈1出栈2个算子,进行计算;若不符合数学计算准则,返回0;否则,返回正常计算结果;
2.辅助函数2-栈2出栈1个运算符,栈1出栈2个算子,进行计算,返回计算结果 int calSub(float pand1,char p,float pand2,float & result) //float & result:算子可能为小数,result需要返回值,所以用引用型; //栈2输出一个算子,栈1输出2个运算符,进行计算,结果输出; { if(p=='+') result=pand1+pand2; if(p=='-') result=pand1-pand2; if(p=='*') result=pand1*pand2; if(p=='/') //保证分母不为零; { if(fabs(pand2)<MIN) return 0; //注:此处是fabs(),而不是abs(); MIN:一个无线接近于0的整数的宏 else result=pand1/pand2; } return result; }
-
calStackTopTwo函数:传入栈s1和栈s2,标记top1,top2,分别弹出2个算子和1个运算符,利用calSub进行计算,并返回计算结果;
3.辅助函数3-在calSub函数的基础上,返回相关提示并将计算结果压入栈1; float calStackTopTwo(float s1[],char s2[],int &top1,int &top2) { float pand1,pand2,result; char p; int flag; pand2=s1[top1--]; pand1=s1[top1--]; p=s2[top2--]; flag=calSub(pand1,p,pand2,result); if(flag==0) { puts("ERROR!"); return 0; } s1[++top1]=result; return flag; }
总的来说:就是利用 getPriority函数判断运算符与栈顶元素优先级的大小,然后利用calStackTopTwo函数调用calSub函数计算结果,并返回,我们只调用calStackToTwo函数即可,calSub函数不需要我们调用;
-
4.主函数-用栈求中缀表达式的值 float calInfix(char exp[]) //用栈求中缀表达式值的函数 { float s1[maxsize];int top1=-1; //第一大部分:创建两个栈,并指明其指针; char s2[maxsize];int top2=-1; int i=0; while(exp[i]!='\0') //第二大部分:遍历calInfix函数传入的“中缀表达式 “对应的字符串数组,直到最终"\0"结束; { if('0'<=exp[i] && exp[i]<='9') //1.遍历到0-9对应的字符 ,将字符对应的数值,压入栈1;遍历索引,后移一位; { s1[++top1]=exp[i]-'0'; ++i; } else if(exp[i]=='(') //2.遇到左括号,压入栈2;遍历索引,后移一位; { s2[++top2]='('; ++i; } else if(exp[i]=='+' || //3.遇见"+-*/",第一次调用getPriority函数,根据判断结果,决定下一步: exp[i]=='-' || exp[i]=='*' || exp[i]=='/') { if(top2==-1 || //如果“栈2空 ”或者“遇见左括号 ”或者“遍历到字符优先级大于栈2顶部运算符优先级 ”, 入栈2,;遍历索引,后移一位; s2[top]=='(' || getPriority(exp[i])>getPriority(s2[top2])) { s2[++top2]==exp[i]; ++i; } else //否则,调用calStackTopTwo函数和calSub函数,栈2出1对应栈1出2, 进行相关计算,压入栈1,并返回相关结果; { int flag=float calStackTopTwo(s1,s2,top1,top2); if(flag==0) return 0; } } else if(exp[i]==')') //4.遇见右括号, 调用calStackTopTwo函数和calSub函数,栈2出1对应栈1出2,进行相关计算, 压入栈1,并返回相关结果;直至遇见左括号; { while(s2[top2]!='(') { int flag=float calStackTopTwo(s1,s2,top1,top2); if(flag==0) return 0; } --top2; ++i; } } while(top2!=-1) //第三大部分: 遍历最终的栈2, 用calStackTopTwo函数和calSub函数,栈2出1对应栈1出2,进行相关计算,压入栈1, 并返回相关结果;直至栈空为止; { int flag=float calStackTopTwo(s1,s2,top1,top2); if(flag==0) return 0; } return s1[top1]; //最终,返回栈1最后的计算结果,即中缀表达式的值; }
- 原则:
- 2.用栈求后缀表达式值:
- 原则:
- 从左至右,开始扫描后缀表达式;
- 遇到算子,压入栈中;遇到运算符,从栈中依次弹出2个算子【自右至左排列】和运算符进行运算,计算结果压入栈中;直至表达式扫描完毕;
- 代码:
-
float calPostfix(char exp[]) { float s[maxsize];int top=-1; //1.初始化栈 int i=0; whhile(exp[i]!='\0') //2.遍历表达式直到遇见'\0' { if('0'<=exp[i] && exp[i]<='9') //2.1如果是算子,直接入栈 { s[++top]=exp[i]; } /* 直接可以用else,代替此部分,因为:后缀表达式求值,只有2种情况; else if(exp[i]=='+' || exp[i]=='-' || exp[i]=='*' || exp[i]=='/' ) */ else //2.2如果不是算子,从栈中弹出2个运算符,调用calSub函数并返回结果 { float pand1,pand2,result; char p; int flag; pand2 =s[top--]; pand1 =s[top--]; p=exp[i]; flag=calSub(pand1,p,pand2,result); if(flag==0) //2.3如果返回结果是非0,则将计算结果入栈 { printf("None"); break; } s1[++top]=result; } ++i; } return s[top]; //3.循环计算完毕,输出栈顶值 }
-
- 原则:
- 3.用栈求前缀表达式:
- 原则:
- 从右至左,开始扫描前缀表达式;
- 遇到算子,压入栈中;遇到运算符,从栈中依次弹出2个算子【自左至右排列】和运算符进行运算,计算结果压入栈中;直至表达式扫描完毕;
- 代码:【和“用栈求后缀表达式”异曲同工,部分细节相反,即可;】
-
float calPostfix(char exp[],int len) //相比较于后缀表达式求值,不同点; { float s[maxsize];int top=-1; //1.初始化 for (int i=len-1;i>=0;--i) //2.【相比较于后缀表达式求值,不同点】 从右至左扫描表达式;如果遇见算子,入栈;遇见运算符,出栈2个算子, 调用calSub函数进行计算 { if('0'<=exp[i] && exp[i]<='9') { s[++top]=exp[i]; } /* 直接可以用else,代替此部分,因为:后缀表达式求值,只有2种情况; else if(exp[i]=='+' || exp[i]=='-' || exp[i]=='*' || exp[i]=='/' ) */ else { float pand1,pand2,result; char p; int flag; pand1 =s[top--]; //相比较于后缀表达式求值,不同点:出栈算子, 从左至右,排列; pand2 =s[top--]; p=exp[i]; flag=calSub(pand1,p,pand2,result); //flag返回是否计算成功的信号; result通过&返回具体的计算结果并压入栈中; if(flag==0) { printf("None"); break; } s1[++top]=result; } } return s[top]; //3.循环计算完毕,弹出栈顶唯一的元素值 }
-
- 原则:
- 4.编写函数将中缀表达式转化为后缀表达式
- 算法步骤:
- 从左到右读取中缀表达式的每个字符;
- 如果读到的字符为操作数,则直接输出到后缀表达式中;
- 如果遇到‘)’,则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止;注意:左括号只弹出,不输出到后缀表达式中;
- 遇到左括号,入栈;
- 如果遇到操作符,当栈为空直接进栈;如果不为空,判断栈顶元素操作符优先级是否比当前操作符小,小于的话,当前操作符直接进栈;不小的话,栈顶元素出栈输出,直到栈顶元素操作符优先级比当前操作符小,再进栈;
- 如果我们读到了输入的末尾,则将栈中所有元素依次弹出输出到后缀表达式中;
- 干代码:
-
//中缀转后缀表达式函数,输入为中缀表达式以及存储后缀表达式的数组 void get_suffix(char *str,char *result) { char stack[100]; int top=-1,k=0,i=0; while(str[i]!='\0'){ //情况1: if(str[i]=='+' || str[i]=='-')//如果str[i]是加号或减号, 则先弹出栈顶直到栈已空或栈顶元素为左括号,再将str[i]压入栈 { if(top==-1){ stack[++top]=str[i]; } else{ while(stack[top]=='+' || stack[top]=='-' || stack[top]=='*' || stack[top]=='/'){ result[k++]=stack[top--]; } stack[++top]=str[i]; } } //情况2:如果str[i]是乘号或者除号,则只有栈顶也是乘除号时,才需要弹出 else if(str[i]=='*' || str[i]=='/'){ if(top==-1)//若栈已空,栈顶指针为空,找不到其元素,故需单独讨论 {stack[++top]=str[i];} else{ while(stack[top]=='*' || stack[top]=='/'){ result[k++]=stack[top--]]; } stack[++top]=str[i]; } } //情况3: else if(str[i]=='(')//如果是左括号,则直接入栈 { stack[++top]=str[i]; } //情况4: else if(str[i]==')') //如果str[i]是右括号, 则打印并弹出栈中第一个左括号前的所有操作符,最后将此括号直接弹出 { while(stack[top]!='('){ result[k++]=stack[top--]; } stack[top--]; } //情况5: else{ //如果str[i]不是操作符,则直接打印 result[k++]=str[i]; } i++; }
中缀转前缀&&后缀转前缀(较难),有时间写一写;
- 算法步骤:
⚫ 方程求根