链队列

言归正传,今天我们讲讲链队列,头文件那些上次讲过了,差不多,我就不再赘述了。

我们先讲讲队列的特性:先进先出

(这里说一下,此图取自Leetcode)

 

在 FIFO 数据结构中,将首先处理添加到队列中的第一个元素。

如上图所示,队列是典型的 FIFO 数据结构。插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾。 删除(delete)操作也被称为出队(dequeue)。 你只能移除第一个元素。

为了实现插入和删除操作,我们需要用到一个数组来存储元素和指针移到,另外还需要两个指针front和rear来表示队列的头部和尾部(所以其实我们可以把它当成循环队列来理解。)

那么目标明确了,就先定义结构体了。

typedef struct node
{
  elemtype data;                             //元素
  struct node *next;                         //移到的指针设置
}QNode,*queuenode;                          //QNode是数组,后面那个就是数组的指针形式了,可以这么理解的,名称自己随意了,最好是能表达意思的那种
typedef struct
{
  queuenode front, rear;                  //这里的定义其实是多变的,但是归根结底还是要把这两个设置为指针型。
}linkqueue;                                           //这里的定义其实也是多变的,你可以定义为指针型,都OK的,但是数组简单一点,就这样了。

1,插入(也就是入队)

这里补充一下,为什么今天没有定义函数了呢,因为队列的定义相对简单,直接在主函数里实现就好了。(但还是要尽可能的保持主函数的简洁性)

再来剖析一下入队操作:1,基础的分配空间;2,插入必备的赋值,和地址转换

 

status enqueue(linkqueue *Q, elemtype e){          //可能有同学会问,为什么我的是*Q,因为我上面的第二个数组我只设置了数组类型名,我想用地址来做,所以就加*号了,这里看大家习惯了。除此之外,我这里讲一下,不知道讲过没有,还是讲一下,数  组的指针的引用区别:数组是加“.” ,而指针则是用“->” 。
  queuenode p;
  p=(queuenode) malloc (sizeof (QNode));
  if(!p) exit (ERROR);
  p->data = e;
  p->next = NULL;                                 //大家还没有忘记我们上面放的那个图吧,队列是先进先出,所以它的插入都是插在后面,也就是说插进来的那个元素的next始终指向NULL。
  Q->rear->next = p;                              //这里有没有那个小伙伴有问题的啊,好,没有,下一个
  Q->rear = p;                                    //开玩笑开玩笑,为什么这里会用rear呢,按理来说,插入不是应该要头结点的next吗?大家再好好想一想我们一开始的那个图,队列是先进先出,也就是说只有队尾是在移动的,是能够用了插入的,你一直用front那岂不是每次插入的都是同一个位置,是吧,当然,你可以再中间断开,但那样麻烦的十我是不会做的。
  return OK;                                      //想必有小伙伴已经发现了一个问题了,嘿嘿,我先不说
}

 

2,删除(也就是出队)

剖析剖析,删除之基本操作:1,弹出去一个元素,其他的补上去,呸,我呸,知不知道那样的时间复杂度为O(n),队列队列,一开始设置那个头结点用来干什么的,好看的吗(当然不包括上面发现问题的同学们的那个东西),这时我们就可以通过移动头结点来进行删除操作了,这样的时间复杂度就少了太多了,2,切记要free空间,虽然系统自己会清理掉,但写出来就是加分项哦

 

status dequeue(linkqueue *Q, elemtype *e){               //Q我不管你们怎么定义了,但是e一定要用指针,其实要作出改变的对象应该都要用*的,也就是指了(好习惯)
  queuenode p;
  if(Q->front == Q->rear) return ERROR;                 //好了好了,现在我们来说说上面发现的那个问题,我一开始是不是说了这是一个循环队列,循环循 环,那是不是总有一天front会跟rear碰上,这就是一个目前为止最能体现出插人函数分配空间的用处的了,因为每次插入,我们都会给它分配空间,所以它是可以无限插入直到电脑不行了的。
                                                         //那碰上了,怎么办。碰上了就代表到头了,也就是循环一遍了,那删除一开始,还没开始动,就头了,那肯定就是一个空队列了,就要退出了。
  p = Q->front->next;                                   //移移移移动头结点
  *e = p->data;
  Q->front->next = p->next;                              //这里是必须要指一下next的哦,大家还记得链表吗,头结点位于第一个元素前面一点点
  if(Q->rear ==p)Q->rear = Q->front;                     //这里是什么意思,就是代表着队列里只有一个元素
  free(p);
  return OK;
}

 

好了好了,插入和删除就到这里结束了,再给大家讲一下print函数吧

剖析again,怎么输出呢,有很多方法:1,从头结点开始遍历,2,我们一开始不是有定义一个数组吗,可以通过数组输出,3,因为是循环队列,从队尾开始也是OK的。(这里讲讲第一种,比较常用的)

 

void printf_Q(linkqueue Q){
  int i;
  Q.front=Q.front->next;                                 //注意一开始必须要指一下next,原因上面讲过了
  if(Q.front==Q.rear) return ERROR;                      //一样的,空就退出
  for(i=0;;i++){
    printf("%d ",Q.front->data);                        //输出就很简单了,因为结点也是定义的数组指针,所以直接值向data就好了
    if(Q.front!=Q.rear)                                 //直到他们碰上,就代表已经循环了一遍了
      Q.front=Q.front->next;
    else
      break;
  }
}

 

老师还有布置一个返回长度的函数吧,那个跟print其实差不多,就不赘述了。

附:主函数:

void main()
{
  QNode Q0; linkqueue Q; elemtype x;
  Q0.next=NULL;
  Q.front=&Q0; Q.rear=&Q0;
  enqueue(&Q,3); enqueue(&Q,55); enqueue(&Q,2); enqueue(&Q,9); enqueue(&Q,7);
  enqueue(&Q,32); enqueue(&Q,66);
  printf("长度为:%d, 元素为:", length_Q(Q));printf_Q(Q);
  dequeue(&Q,&x); dequeue(&Q,&x);
  printf("\n长度为:%d, 元素为:", length_Q(Q));printf_Q(Q);
  enqueue(&Q,77);
  printf("\n长度为:%d, 元素为:", length_Q(Q));printf_Q(Q);
}

大家,下次再见吧(再说)!!!

 

posted @ 2019-10-28 20:40  随...风  阅读(547)  评论(0编辑  收藏  举报