DS冲刺3

5 篇文章 0 订阅
3 篇文章 0 订阅

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. 用栈求中缀表达式值:
    • 原则:
      • 创建两个栈,栈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++;
      }   

      中缀转前缀&&后缀转前缀(较难),有时间写一写;


⚫ 方程求根

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值