いいにおい!「キュー」の霧が一目でわかる20枚の写真

キューの概念

まず、リンクリストについて考えてみましょう。単一リンクリストでは、リンクリストの末尾を挿入し、リンクリストの先頭にあるノードを削除することしかできません。このような限定的なリンクリストをキューと呼びます。

つまり、キューはデータ構造であり、テーブルの一方の端で挿入し、もう一方の端で削除するように制限されています。

下図のように並んでチケットを購入すると、ラインごとに尻尾と相手があり、先に来た人が先にチケットを購入し、後から購入した人が相手から出ます。行の終わりは整列し続けます。

通常、データの一端をデータキューヘッドの最後尾に挿入します。キューへのデータ要素はエンキューと呼ばれ、デキュープロセスはチームと呼ばれます。

以下のようにまとめることができます

キューは線形データ構造であり、このデータ構造は、一端での挿入と他端での削除のみを許可します。これらの両端を除くすべてのデータへの直接アクセスは禁止されており、キューは先入れ先出しデータ構造です。

上の図に示すように、キューは両端が接続された配水管のようなものです。一方の端だけが挿入され、もう一方の端が取り出されます。取り出されたボールは配水管に入れられず、最初に管に入れられたボールが最初に管から取り出されます。

キューストレージ構造を実装するには2つの方法があります。

  • シーケンスキュー:シーケンステーブルに基づいて実装されたキュー構造

  • チェーンキュー:リンクリストに基づいて実装されたキュー構造

2つの違いは、シーケンステーブルとリンクリストの違いのみです。つまり、実際の物理空間では、データセンターに格納されているキューはシーケンシャルキューであり、分散ストレージに格納されているキューはチェーンキューです。

ノードの設計とキューの初期化

キューにはチェーン設計方法のみがあり、それ自体が順次キュー循環キューなどの複数のキュー、および派生優先キューなどに分割されています。ここでは、順次キューの設計を例に取り上げます。

1つはキューのノード設計です。次の図に示すように、2つの構造を設計できます。1つの構造Nodeは、データフィールドと次のポインタを含むノードを表します。

その中で、データはデータを表します。データは、単純なタイプでも複雑な構造でもかまいません。

次のポインタは、次のポインタが次のノードを指し、各ノードが次のポインタを介してリンクされていることを示します。

次に常にキューの最後とキューの先頭を指す 2つのポインターを含む別の構造を追加します。スタックのように見えますか?

図に示すように、私たちの主な操作はこれらの2つのポインターに対してのみ動作します。

その構造設計のコードは次のように表現できます。

//结点定义
typedef struct node{
    int data;
    struct node *next;
}node;
//队列定义,队首指针和队尾指针
typedef struct queue{
    node *front;    //头指针
    node *rear;     //尾指针
}queue;

初期化では、2つのタイプを初期化する必要があります。1つは初期化ノードで、もう1つは初期化キューです。

コードに説明があります。初期化キューは多少異なります。キューが初期化されると、先頭と末尾の2つのノードが指す内容を空に設定する必要があります。これは、空のキューであることを意味します。作成された2つの関数コードは、 :

//初始化结点
node *init_node(){
    node *n=(node*)malloc(sizeof(node));
    if(n==NULL){    //建立失败,退出
        exit(0);
    }
    return n;
}
//初始化队列
queue *init_queue(){
    queue *q=(queue*)malloc(sizeof(queue));
    if(q==NULL){    //建立失败,退出
        exit(0);
    }
    //头尾结点均赋值NULL
    q->front=NULL;  
    q->rear=NULL;
    return q;
}

キューが空かどうかを確認する

キューが空かどうかを判断するのは、キューの先頭が空かどうかを判断することであり、キューが空かどうかを判断するのが一般的な操作ですので、お忘れなく。

コードは次のように表現できます。

//队列判空
int empty(queue *q){
    if(q->front==NULL){
        return 1;   //1--表示真,说明队列非空
    }else{
        return 0;   //0--表示假,说明队列为空
    }
}

または、戻り値を直接使用して簡単な判断を行うと、コードは次のようになります。

int empty(queue *q){
    return q->front==NULL;
}

エンキュー操作

エンキュー操作の変更は次のとおりです。

同じように、プッシュ操作を実行するときは、最初にキューが空かどうかを判断する必要があります。キューが空の場合はヘッドポインターとテールポインター最初のノードに一緒にポイントする必要があります。コードは次のとおりです。

front=n;
rear=n;

写真が示すように:

一意のノードn

キューが空でない場合、テールノードを後ろに移動し、常にnext新しいノードにポインターを移動してキュー形成するだけです写真が示すように:

コードは次のように表現できます。

//入队操作
void push(queue *q,int data){
    node *n =init_node();
    n->data=data;
    n->next=NULL;   //采用尾插入法
    //if(q->rear==NULL){     //使用此方法也可以
    if(empty(q)){
        q->front=n;
        q->rear=n;
    }else{
        q->rear->next=n;    //n成为当前尾结点的下一结点
        q->rear=n;  //让尾指针指向n
    }
}

デキュー操作

デキュー操作の変更は次のとおりです。

pop操作とは、キューが空でない場合の判断のことですが、もちろんここでキューを空にする操作も行わなければなりません。

図に示すように、キューに要素が1つしかない場合、つまりヘッドポインターとテールポインターがすべて同じノードを指している場合NULL、次の図に示すように、ヘッドポインターとテールポインターを直接空にしてこのノードを解放します。

キューが上記の要素が含まれている場合、我々はする必要があるポイントの先頭ポインタキューを現在ヘッドポインタで指さ次の要素に次の図に示すように、現在の要素を解放します

コードは次のように表現できます。

//出队操作
void pop(queue *q){
    node *n=q->front;
    if(empty(q)){
        return ;    //此时队列为空,直接返回函数结束
    }
    if(q->front==q->rear){
        q->front=NULL;  //只有一个元素时直接将两端指向置为空即可
        q->rear=NULL;
        free(n);        //记得归还内存空间
    }else{
        q->front=q->front->next;
        free(n);
    }
}

キュー要素の印刷(トラバース)

キューのすべての要素を出力すると、キュー内の特定のデータをデバッグして確認するのに役立ちます。キューが空でない場合は、ノードのポイントをnext順番に通過して要素を出力するだけで十分です。

そのコードは次のように表すことができます

//打印队列元素
void print_queue(queue *q){
    node *n = init_node();
    n=q->front;
    if(empty(q)){
        return ;    //此时队列为空,直接返回函数结束
    }
    while (n!=NULL){
        printf("%d\t",n->data);
        n=n->next;
    }
    printf("\n");   //记得换行
}

トラバース操作を表現する方法は他にもたくさんあります。たとえば、キュ​​ーにある要素の数を計算するなど、コードは次のとおりです。

int calac(queue *q){
    node *n = init_node();
    n=q->front;
    int cnt=0;    //计数器设计
    if(empty(q)){
        return 0;    //此时队列为空,直接返回函数结束
    }
    while (n!=NULL)
    {
        n=n->next;
        cnt++;
    }
    return cnt;
}

シーケンシャルキューの誤ったオーバーフロー

偽のオーバーフローとは何ですか?質問、オーバーフロー、偽物が発生する可能性があります。

ここでも、シーケンシャルキューの欠点を考慮する必要があります。シーケンシャルキューの場合、その存在はほとんどの設計問題を解決するのに十分ですが、いくつかの欠陥や欠陥があります。

上記の分析から、エンキューおよびデキュー操作はノードのすぐ後ろにあるノードをリンクおよび削除することであることがわかります。この種の操作により、使用スペースがデキューの側に継続的にシフトし、誤ったオーバーフローが発生します。 。

類推してみましょう。最初に次の図を見てください。

順次キューの例

上の図に示すように、順次キューがあり、このキューのサイズは5で、すでに4つの要素が含まれていますdata1,data2,data3,data4

次に、このキューをデキューし、2つの要素をデキューすると、次の図に示すように、キューは次のようになります。

図から見ると問題はないようですが、たとえばエンキュー操作に進むと、それぞれ2つの要素をエンキューdata5data6ます。

この時点で問題が見つかりましたが、テールポインターがキュー操作を実行できる範囲を超えています。

この現象をキューとして使用している記憶領域が一杯ではないのに、キューがオーバーフローする現象、これをフォールスオーバーフローと呼びます以下に示すように:

偽のオーバーフロー

では、この問題を解決するために何ができるでしょうか?これには循環キューの性質が含まれます!

循環キューの概念

現時点で問題が発生する可能性があります。動的キューを学習しているキューは、リンクされたリストで実装されていませんか?

スペースがない場合は、スペースが開かれますが、これでも偽のオーバーフローは発生しますか?

確かに、動的にキューを作成するときは、メモリ空間の後方への適用を継続しているだけです。

前のデキュー操作で前のスペースが解放された場合でも、ポインターは、プログラム用にシステムによって予約されているメモリーの上限に達し、強制的に終了されるまで後方に移動します。

これは非常に頻繁なキュー操作とプログラムには致命的です。現時点では、キューを最適化し、より優れた構造循環キューを使用する必要があります

循環キューは、キューストレージスペースの最後の位置を最初の位置に変えて、以下の図に示すように、キューによって循環的に使用される論理リングスペース形成します。

循環キューは、キューのサイズ範囲です。元のキューに基づいて、キューの後ろがいっぱいである限り、挿入はキューの前から開始され、スペースを再利用する効果が得られます。

循環キューのデザイン思考はリングに似ているため、リング図で表されることがよくありますが、実際には循環キューは実際のリングではなく、単一の線形であることに注意する必要があります。

循環キューの構造設計

ループは列にデータ範囲のサイズを与えるので、連鎖動的作成メソッドを使用する必要はありません。

チェーンストレージを使用する場合は、キューの先頭に戻って挿入操作を実行するタイミングを判断できないため、図に示すようにシミュレーションメソッドを使用します

このうち、dataはデータフィールドを表します。intは、単純なchar、float型などの任意のカスタム型、または複雑な構造型に変更できる型です。

  • maxsize循環キューの最大容量を表します。これは、キューの操作スペース全体を表します。

  • rearキューに入るときに移動するテールポインターを表します。

  • frontチームを離れるときに移動するヘッドポインターを表します。

コードは次のように表現できます。

#define maxsize 10      //表示循环队列的最大容量
  
//循环队列的结构设计
typedef struct cir_queue{
    int data[maxsize];
    int rear;
    int front;
}cir_queue;

循環キューの初期化

循環キューの初期化の中心は、スペースを適用し、フロントポインターとリアポインターの内容を0に、つまり0番目の要素を指すように割り当てることです。ここでは、次の図に示すように、0番目の要素が空であることに注意してください。

コードは次のように表現できます。

//初始化
cir_queue *init(){
    cir_queue *q = (cir_queue*)malloc(sizeof(cir_queue));
    if(q==NULL){
        exit(0);   //申请内存失败,退出程序
    }
    q->front=0;
    q->rear=0;
    return q;
}

エンキュー操作

エンキュー操作の方法はシーケンシャルキューと同じで、直接後ろに移動するだけです。

ただし、判断に注意してください。後部がキュースペースに到達してオンラインになると、最初から動き続けます。

ここでは、remainerメソッドを使用することをお勧めします。つまり、エラーの実行によって全体が直接クラッシュするのを防ぐために、とにかくこのスペースで剰余を操作します。比較的簡潔です。ifステートメントを使用することはお勧めしません。

エンキュー操作

位置を移動するq->rear++ために1を追加する場合、この操作を直接実行できないので、コンピューターが優先度に予期しない結果をもたらすとコンピューターが判断することに注意してください

また、キューがいっぱいかどうかを判断する必要があり、後部ポインタの次の位置が前の位置の場合、循環キューがいっぱいです。

示されているように:

キューがいっぱいです

コードは次のように表現できます。


//入队操作push
void push(cir_queue *q,int data){
    if((q->rear+1)%maxsize==q->front){
        printf("溢出,无法入队\n");
        return;
    }else{
        q->rear=(q->rear+1)%maxsize;
        q->data[q->rear]=data;
    }
}

デキュー操作

シーケンスキューがデキューされる場合は、フロントを1ビット後ろに移動するだけです。

上記の多くの場所で述べたように、キューが空であるかどうかに注意する必要があります。キューが空の場合、デキュー操作は実行できません。

デキュー操作

コードは次のように表現できます。

//出队操作pop
void pop(cir_queue *q){
    if(q->rear==q->front){
        printf("队列为空,无法出队\n");
        return;
    }else{
        q->data[q->front]=0;
        q->front=(q->front+1)%maxsize;
    }
}

トラバース操作

トラバーサル操作では、一時的な変数を使用して前部の位置情報を格納し、iを使用して後部の位置に到達するまで徐々に後方に移動して、トラバーサルの終了を宣言する必要があります。

//遍历队列
void print(cir_queue *q){
    int i=q->front;
    while(i!=q->rear){
        i=(i+1)%maxsize;
        printf("%d\t",q->data[i]);   
    }
    printf("\n");       //记得换行
}

キューに関するまとめ

この文を覚えておいてください:キューは先入れ先出しのデータ構造です。

今日のキューの基本は以上です。次の号でお会いしましょう!

おすすめ

転載: blog.csdn.net/u012846795/article/details/108613463