(c言語)リングバッファのキューの無駄をなくす

状況

リングバッファのキューは先頭に戻す処理時に1つ無駄ができることを知ったので、改善策を考えた。(enqueueのみ)

リングバッファのキューとは

リングバッファのキューは、キューの限界に領域まで達したら、先頭に戻すことでデキューをタイミングよくすれば無限に入るキューが完成する。

これがリングバッファのキューである。

enqueue以外のコードに関しては以下である。

#include <stdio.h>
#include <stdlib.h>

#define SIZE 4

struct queue {
  int head, tail;
  char elem[SIZE];
};
char dequeue(struct queue *q) {
  char val;
  if (q->head == q->tail) {
    printf("queue underflow\n");
    exit(1);
  } else {
    val = q->elem[q->head];
    q->head++;
    if (q->head >= SIZE) {
      q->head = 0;
    }
    return val;
  }
}

int main(void) {
  struct queue Q;
  char e, moji[5];
  int input, i;

  Q.head = 0;
  Q.tail = 0;
  printf("キューに格納されている値\n");
  for (i = 0; i < 10; i++) {
    printf("%i:::%c\n", i, Q.elem[i]);
  }
  while (1) {
    // メニュー表示
    printf("操作を選択:\n");
    printf("  1: 格納\n");
    printf("  2: 取得\n");
    scanf("%d", &input);  // 操作を入力

    switch (input) {
      case 1:  // 格納
        printf("格納する文字を入力してください:");
        scanf("%s", moji);
        enqueue(&Q, moji[0]);
        printf("\n");
        printf("キューに格納されている値\n");
        for (i = Q.head; i <= Q.tail; i++) {
          printf("%i::%c\n", i, Q.elem[i]);
        }
        printf("tailの値::%d\n", Q.tail);
        printf("headの値::%d\n", Q.head);
        break;
      case 2:  //出力
        e = dequeue(&Q);
        printf("%c\n", e);
        printf("キューに格納されている値\n");
        for (i = Q.head; i <= Q.tail; i++) {
          printf("%i::%c\n", i, Q.elem[i]);
        }
        printf("tailの値::%d\n", Q.tail);
        printf("headの値::%d\n", Q.head);
        break;

        break;
    }
  }
  return 0;
}

問題となる例とコード

以下が今回の問題となる部分です。

  • 先頭に値を入れていない。(*1)
  • または、全てが埋まった瞬間にoverflowとなってしまい、デキューの可能性を否定している。(*2)

*1

void enqueue(struct queue *q,char val){
  q->tail++;
  if(q->tail==SIZE){//最後まで行ったら先頭に戻す
    q->tail=0;
  }
  if(q->tail==q->head){//先頭と最後の指す位置が同じならoverflowとする
    printf("queue overflow\n");
  }else{
     q->elem[q->tail] = val;//値の代入
  }
}

*2

void enqueue(struct queue *q,char val){
  q->elem[q->tail] = val;//値の代入
  q->tail++;
  if(q->tail==SIZE){//最後まで行ったら先頭に戻す
    q->tail=0;
  }
  if(q->tail==q->head){//先頭と最後の指す位置が同じならoverflowとする
    printf("queue overflow\n");
  }
}

解決のためのコード

上記の問題を解決するために以下のようなコードにします。

void enqueue(struct queue *q, char val) {
  if ((q->tail) % SIZE == (q->head) % SIZE && (q->tail) != 0) {
    printf("queue overflow\n");
  } else {
    q->elem[(q->tail) % SIZE] = val;
    q->tail++;
  }
  return;
}

処理内容

  1. tailをSIZEで割った値の余りとheadをSIZEで割った値の余りを比較する
  2. 一緒であり、tailの値が0でないoverflowとする
  3. 違うならtailをSIZEで割った値の余り番目に値を代入する

詳細

  • リングバッファで何回も周回してもSIZEで割った際の余りは常に1周目の場所と同じところを指すため、SIZEで割った余りの値を参考にoverflow等を判定している。
  • また、if文の条件式において、&&の後ろは&&の前半部分のみだけだと、一番最初にenqueueをした際にはじかれてしまうので、最初でないことを示すために書いている。

まとめ

  • 今回、リングバッファのキューにおいて無駄があったため解決策を探した。
  • 正直、余りを使った構造で無駄をなくす構造なのでoverflowしていないのにおかしいと思う人はぜひ活用していただきたい。