无中生有之突击NOIP(2)--栈,队列,链表

来源:互联网 发布:学编程入门 编辑:程序博客网 时间:2024/05/06 11:21

1、队列

定义:形如我们排队买票,第一个站队的人第一个买票一样,一个可以控制变量先进先出的结构体里,我们称之为队列。

理解:我们可以想象出一排东西整齐存放于一行里,我们要做的是用一个可以压缩的框框,通过从前到后移动,来决定我们框住的值到底为多少。

实现代码如下:

#include<stdio.h>#include<algorithm>int main(){    int q[102]={0,6,3,1,7,5,8,9,2,4},head,tail;    head=1;    tail=10;//队列初始化    while(head<tail)    {        cout<<q[head];        head++;        q[tail]=q[head];        tail++;        head++;    }    getchar();getchar();    return 0;}

总结:队列属于一种特殊的线性结构,它只允许在队列首部删除,队列尾部插入,称为入队。当队列中没有元素的时候,我们称之为空队列。队列将作为我们今后广度优先搜索(BFS)和队列优化的Bellman-Ford最短路算法的核心数据。

P.S.我们可以将队列定义为一个结构体,即一共两个变量一个数组的结构。
e.g.`
struct queue
{
int data[100];//队列的主体,
int head;//队首
int tail;//队尾
};
//上面是结构体的定义,下面定义一个名为q的结构体
struct queue q;
q.head=1;
q.tail=1;
cin>>q.data[q.tail];

2、栈

定义:栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

理解:例如汉诺塔,我们把123号塔依次放入,若要移出1号塔,首先要移出3号塔,然后是2号。说白了就是一个后进先出的数据结构,我们只能在一端进行操作。可以判断回文,数据如下:

#include<stdio.h>;#include<string.h>;#include<algorithm>;int main(){    char a[101],s[101];    int i,len,mid,next,top;    gets(a);//读入一串字符串    len=strlen(a);//求字符串的长度    mid=len/2-1;//寻找中点    top=0;//栈初始化    for(i=0;i<=mid;i++)        s[++top]=a[i];//依次入栈    //判断字符串长度是奇还是偶,并找出需要进行字符匹配的起始下标    if(len%2==0)        next=mid+1;    else        next=mid+2;    //开始判断匹配    for(i=next;i<=len-1;i++)    {        if(a[i]!=s[top])            break;        top--;    }    if(top==0)//如果top的值为0;则说明栈内的字符都被匹配过了        cout<<"yes";        else cout<<"NO";        getchar():getchar();        return 0;    }

总结使用:大家可以尝试做做此题:

问题描述:

 小哼和小哈在玩一个比较奇怪的扑克游戏——小猫钓鱼。游戏规则:将一副扑克牌平均分成两份,没人拿一分。小哼先拿出手中第一张扑克牌放在桌上,然后小哈也拿出手中第一张扑克牌,并放在小哼刚才打出的扑克牌的上面,两人交替出牌。出牌时,如果某人打出的牌与桌上某张牌的牌面相同,可将两张相同的牌及其中间所夹的牌全部拿走,并依次放到自己手中牌的末尾。当任意一人手中当牌出完时,游戏结束,对方获胜。

分析:

1、这个游戏一共有两种操作:出牌和赢牌。

                                             出牌即出队,赢牌即入队。

2、桌面相当于一个栈,出牌即入栈,赢牌即出栈。

3、通过枚举了解桌面有哪些牌。

思路:

1、定义两个队列q1、q2表示小哼、小哈手中的牌。

2、定义一个栈s表示桌面的牌

3、定义数组book记录桌面上已经出现的牌面

4、游戏开始时q1的队首出队,判断当前时是出牌还是赢牌

 4.1如果是出牌,则入栈 4.2如果是赢牌,则q1的队首到队尾,再将赢得的牌依次入队

5、q2 的队首出队,操作同4

6、第4、5步重复进行直至任意人手中没有牌

7、输出赢的人手中的牌,输出此时桌面上的牌

代码实现如下:

#include <stdio.h>  struct queue{      int  data[1000];      int head;      int hail;  };  struct stack{      int data[10];      int top;  };  int main(int argc, const char * argv[]) {      struct queue q1,q2;      struct stack s;      int book[10]={0}; //用来标记哪些牌已经在桌子上      int i,t;      //初始化队列      q1.head=1;      q1.hail= 1;      q2.head=1;      q2.hail=1;      //初始化栈      s.top=0;      //依次向队列输入6个数      for(i=1;i<=6;i++)      {          scanf("%d",&q1.data[q1.hail]);          q1.hail++;      }      for(i=1;i<=6;i++)      {          scanf("%d",&q2.data[q2.hail]);          q2.hail++;      }      while(q1.head<q1.hail&&q2.head<q2.hail)      {          t=q1.data[q1.head];  //q1打出第一张牌          //判断q1当前打出的牌是否能赢牌          if(book[t]==0)// 表面桌上没有牌面为t的牌          {    //q1此轮不能赢牌              q1.head++;//q1打出一张牌,打出的那张牌出队              s.top++;              s.data[s.top]=t;//q1打出的牌入栈              book[t]=1;//标记已打出的牌t          }          else          {//q1此轮可以赢牌              q1.head++;//q1打出第一张牌              q1.data[q1.hail]=t;//q1打出的牌放到队尾              q1.hail++;              while(s.data[s.top]!=t)//将赢得的牌依次入队              {                  q1.data[q1.hail]=s.data[s.top];                  q1.hail++;                  s.top--;                  book[s.data[s.top]]=0;//将从桌面入队的牌取消标记              }          }          t=q2.data[q2.head];//q2出牌          if(book[t]==0) //q2此轮不能赢牌          {              q2.head++;              s.top++;              s.data[s.top]=q2.data[q2.head];              book[t]=1;          }          else          { //q2此轮能赢牌              q2.data[q2.hail]=q2.head;              q2.head++;              q2.hail++;              while(s.data[s.top]!=t)              {                  q2.data[q2.hail]=s.data[s.top];                  q2.hail++;                  book[s.data[s.top]]=0;                  s.top--;              }          }      }      if(q2.head==q2.hail)      {          printf("q1 win!\n");          printf("q1当前手中的牌是:\n");          for(i=q1.head;i<q1.hail;i++)          {              printf("%d ",q1.data[i]);          }          if(s.top>0)//          {              printf("\n桌上的牌是:\n");              for(i=1;i<=s.top;i++)              {                  printf("%d ",s.data[i]);              }          }          else              printf("\n桌上已经没有牌!\n");      }      else          if(q1.head==q1.hail)          {              printf("q2 win!\n");              printf("q2当前手中的牌是:\n");              for(i=q2.head;i<q2.hail;i++)              {                  printf("%d ",q2.data[i]);              }              if(s.top>0)//              {                  printf("\n桌上的牌是:\n");                  for(i=1;i<=s.top;i++)                  {                      printf("%d ",s.data[i]);                  }              }              else                  printf("\n桌上已经没有牌!\n");          }      getchar();      getchar();      return 0;  }  

总结:是不是发现栈和队列不是那么简单呢??大家可以考虑一下,其实无论是栈还是队列,都是一种自己定义的结构体,完全可以在理解struct之后,自行定义。

3.链表
在储存数的时候,我们通常用数组,但实际上,数组不够灵活而且特别呆板,比如说我们要让一组排好序的数字{2 3 5 8 9 10 18 26 32 }中插入一个6,我们其实只需要插入在5到8之间,但这样我们却需要从8向后所有的数字都依次向后移动一位,而实际上我们只需要用链表就可以完成操作,链表考指针和动态分配内存函数malloc来实现。我们先来回顾一下指针。

    1.定义
int a; int*p;
    第一行我们很熟悉了,就是定义一个整型变量 a。第二行你会发现在 p前面多了一个* 号,这就表示定义了一个整型指针变量 p。即定义一个指针,只需在变量前面加一个*号就OK啦。而至于浮点数的指针,则只需要把int换为double就好。    2.存储    p=&a; &这个符号很熟悉吧, 就是经常在 scanf函数中用到的&。&叫取地址符。这样整型指针p就获得了(存储了)整型变量 a的地址。3.使用用*p来使用,即指针所指向的内存中的值。直接可以理解为*p就是a。4.存储2我们想在程序中存储一个整数10, 除了使用 int a;这种方式在内存中申请一块区域来存储,还有另外一种动态存储方法。malloc(4) ;malloc函数的作用就是从内存中申请分配指定字节大小的内存空间。 上面这行代码就申请了4个字节。如果你不知道 int类型是4个字节的, 还可以使用 sizeof(int)获取 int类型所占用的字节数,如:malloc(sizeof(int) ) ;现在你已经成功地从内存中申请了 4个字节的空间来准备存放一个整数,可是如何来对这个空间进行操作呢?这里我们就需要用一个指针来指向这个空间,即存储这个空间的首地址。int*p;p=(int *)malloc(sizeof(int) ) ;完整代码如下, 注意当在程序中使用 malloc函数时需要用到 stdlib.h头文件。
#include<stdio.h>#include<stdlib.h>int main(){int*p;  //定义一个指针pp=(int*)malloc(sizeof(int) ) ;  //指针p获取动态分配的内存空间地址*p=10;  / /向指针p所指向的内存空间中存入10printf( "%d" , *p) ;  //输出指针p所指向的内存中的值getchar() ;getchar() ; return 0;}

而如果我们想要完成之前的问题,则要放弃前面所讲的,毕竟我们无法确定我们要有多少个整数,即我们需要申请多少的内存。所以在这里我们要申请一个结构体,

 struct node    {     int data;     struct node*next;    };

上面代码中,我们定义了一个叫做 node的结构体类型,这个结构体类型有两个成员。第一个成员是整型 data,用来存储具体的数值;第二个成员是一个指针,用来存储下一个结点的地址。 因为下一个结点的类型也是 struct node, 所以这个指针的类型也必须是 struct node 类型的指针。

然后我们可以建立一个头指针用来储存下一节点的值,当链表没有建立时指针为空,然后创造第一个结点,用指针p指向这个点。在然后分别设置新创建的这个结点的左半部分和右半部分

struct node*head;head= NULL;//头指针初始为空struct node*p;/ /动态申请一个空间, 用来存放一个结点, 并用临时指针p指向这个结点p=(struct node*)malloc(sizeof(struct node) ) ;scanf("%d" ,&a) ;p- >data=a; / /将数据存储到当前结点的data域中p->next=NULL; //设置当前结点的后继指针指向空, 也就是当前结点的下一个结点为空

上面我们发现有一个新的符号->,这叫做结构体指针运算符,也是用来访问结构体内部成员的。因为此处的p是一个指针,所以不能用”.”,只能用->,下面用*next指向空。头指针的作用是方便以后从头便利整个链表

if(head==NULL)head=p; / /如果这是第一个创建的结点, 则将头指针指向这个结点elseq- >next=p; / /如果不是第一个创建的结点, 则将上一个结点的后继指针指向当前结点

完整代码如下:

#include<stdio.h>#include<stdlob.h>#include<algorithm>;//创造一个结构体用来表示链表的节点类型struct node{    int data;    struct node *next;};int main(){    struct node *head,*p,*q,*t;    int i,n,a;    cin>>n;    head = NULL;//头指针为空    for(i=1;i<=n;i++)//循环读入    {        cin>>a;        //动态申请空间,用来存放一个结点,并用临时指针p指向这个结点。        p=(struct node*)malloc(sizeof(struct node));        p->data=a;//存储数据        p->next=NULL;使当前结点的后继结点为空,也就是下一个结点为空。        if(head == NULL)如果是第一个结点,则头指针指向这个结点            head=p;//如果不是第一个创造的结点,则将头指针指向这个结点        else            q->next=p;//否则则让上一个结点的值指向此节点        q=p;//上一个结点指向这个结点,因为p会指向下一个结点    }    //输出所有链表内的数。    t=head;    while(t!=NULL)    {        cout<<t->data;        t=t->next;//继续下一个结点。    }    getchar();getchar();    return 0;}

如果要插入一个值怎么办呢,很简单啊,你只要能找到一个可以比他大的值或者所有都比他小,第一种插入,第二种就直接放在队列尾部喽。

实现代码如下:

#include<stdio.h> #include<stdlib.h>/ /这里创建一个结构体用来表示链表的结点类型struct node{    int data;    struct node*next;};int main( )    {    struct node*head,*p,*q,*t;    int i,n,a;    scanf( "%d" ,&n) ;    head= NULL;//头指针初始为空    for( i=1; i<=n; i++ ) / /循环读入n个数        {        scanf("%d" ,&a) ;        / /动态申请一个空间, 用来存放一个结点, 并用临时指针p指向这个结点        p=(struct node*)malloc(sizeof(struct node) ) ;        p- >data=a; / /将数据存储到当前结点的data域中        p- >next=NULL; / /设置当前结点的后继指针指向空, 也就是当前结点的下一个结点为空if(head==NULL)        head=p; //如果这是第一个创建的结点, 则将头指针指向这个结点        else        q- >nex t=p; / /如果不是第一个创建的结点, 则将上一个结点的后继指针指向当前结点        q=p; / /指针q也指向当前结点    }    scanf( "%d" , &a) ; //读入待插入的数    t=head; / /从链表头部开始遍历    while( t! =NULL) / /当没有到达链表尾部的时候循环        {        if(t->next->data> a) //如果当前结点下一个结点的值大于待插入数,将数插入到中间        {            p=(struct node*)malloc(sizeof(struct node) ) ;//动态申请一个空间,用来存放新增结点            p->data=a;            p- >next=t->next; / /新增结点的后继指针指向当前结点的后继指针所指向的结点            t- >next=p; / /当前结点的后继指针指向新增结点            break; / /插入完毕退出循环        }        t= t- >next; / /继续下一个结点    }/ /输出链表中的所有数    t=head;    while( t! =NULL)    {        printf("%d",t->data) ;        t= t- >next; / /继续下一个结点    }    getchar() ;getchar() ;     return 0;}

4.模拟链表
分析:如果我用尽洪荒之力也看不懂指针,那怎么办呢,简单,看看这个,模拟链表,通过数组来实现链表的套路。

如图data是用来存放数据,right则是存放该数据右侧的数据在data中的位置

如图data是用来存放数据,right则是存放该数据右侧的数据在data中的位置,若right为0,那么右边就没有元素。比如说right[9].
代码实现如下:

#include<stdio.h>#include<algorithm>int main(){    int data[101],right[101];    int i,n,t,len;    cin>>n;    for(i=1;i<=n;i++)        cin>>data[i];    len=n;    for(i=1;i<=n;i++)    {        if(i!=n)            right[i]=i+1;        else            right[i]=0;    }    len++;    cin>>data[len];    t=1;    while(t!=0)    {        if(data[right[t]]>data[len])        {            right[len]=right[t];//将right[len]值指向第一个比他大的值            right[t]=len;//将此值指向插入的值            break;        }        t=right[t];     }    //输出    t=1;    while(t!=0)    {        cout<<data[t];        t=right[t];    }    getchar();    getchar();    return 0;}

P.S.模拟链表也可以实现双向链表和循环链表,大家自行尝试哈

编者口述:有谁知道该如何下冰箱里的馄饨啊,先拿出来化着还是直接扔冰的进去啊求解。

2 0
原创粉丝点击