データ構造 - キューの基本概念と応用

スタックとキューは 2 つの重要な線形構造です。データ構造の観点から見ると、スタックとキューも線形テーブルです。特徴は、スタックとキューの基本的な操作が線形テーブル操作のサブセットであることです。これらは、操作が制限された線形テーブルであるため、制限付きテーブルと呼ぶことができます。データの構造。

しかし一方で、データ型の観点から見ると、スタックとキューは線形テーブルとはまったく異なる 2 つの重要な抽象データ型です。また、スタックとキューはその特徴からさまざまなソフトウェアシステムで広く使用されており、それらを学ぶことは私たちにとって非常に役立ちます。したがって、この記事では、スタックとキューの定義、表現、実装について説明することに加えて、アプリケーションの例もいくつか示します。

前回の記事ではスタックの概念について説明しましたが、今回は引き続きキューについて説明します。

1. キューの基本概念

キューの定義: キューは略してチームと呼ばれ、操作が制限された線形テーブルでもあり、テーブルの一方の端では挿入、もう一方の端では削除のみが許可されるように制限されています。挿入できる端をテール(Rear)、削除できる端をフロント(Front)と呼びます。

リソース割り当てマップ

キューの特性: キューの特性は、先入れ先出し (FIFO) として要約できます。

行列は私たちの生活の中でより一般的です。たとえば、私たちは物を買うために列に並び、12306 番の切符購入の列を待ちます。電車がトンネルに入るとき、各車両はチームの要素であり、最初の車両がチームの一員です。トンネルに入るのは常に最初にトンネルを出る人です。

リソース割り当てマップ

2. キューの記憶構造

キューには、シーケンシャル キューとチェーン キューという 2 つの主なストレージ構造があります。シーケンシャル キューの定義は次のとおりであり、シーケンシャル キューの中心となるのは連続した記憶領域であり、ここでは配列を使用して実装されていることがわかります。

typedef struct {
    
    
  //用于存放队列中的元素
  int data[maxSize];
  //队首指针
  int front;
  //队尾指针
  int rear;
} SqQueue;		

チェーンチームは次のように定義されており、チェーンチームはスタックの保存にリンクリストを使用し、末尾挿入方式を使用してチームを作成していることがわかります。チェーン チームの定義はさらに複雑で、チーム ノードとチェーン チームの 2 つの部分に分かれています。

//队结点
typedef struct QNode {
    
    
  //数据域
  int data;
  //指针域
  struct QNode * next;
} QNode;

//链队类型
typedef struct {
    
    
  //队头指针
  struct QNode * front;
  //队尾指针
  struct QNode * rear;
} LiQueue;
リソース割り当てマップ

キューについても 2 つのストレージ構造があり、一般的にはシーケンシャル キューを選択します。

注:場所によっては、キューを定義するときに、ストレージの都合上、ヘッド ノードがチェーン チームに追加され、ヘッド ポインターがヘッド ノードを指すようになりますこのとき、空きキューの判定条件は、先頭ポインタと末尾ポインタが両方とも先頭ノードを指していることとなる(先頭のポイントは変わらないため、末尾ポインタは先頭ノードを指している)。ストレージ構造を次の図に示します。

リソース割り当てマップ

3. シーケンシャルキューの定義と基本操作

3.1 循環キュー

シーケンシャルスタックと同様に、キューのシーケンシャルストレージ構造では、連続したアドレスを持つストレージユニットのグループを使用してキューの先頭からキューの末尾まで要素をシーケンシャルに格納することに加えて、前後に2つのポインタを使用します。キューの先頭の要素とキューの末尾の要素の位置を示すために、 を付ける必要があります。C 言語での記述の便宜上、ここで同意します: 空のキューを初期化するときは、front=rear=0 とし、新しいキューの末尾要素が挿入されるたびに、「末尾ポインタは 1 ずつインクリメントされます」; キューの先頭要素が挿入されるたびに、が削除されると、「先頭ポインタが 1 ずつインクリメントされます」。したがって、空ではないキューでは、次の図に示すように、先頭ポインタは常にキューの先頭要素を指し、末尾ポインタは常にキューの
末尾要素の次の位置を指します。

リソース割り当てマップ

現在キューに割り当てられている最大スペースが 6 であると仮定すると、キューが上記 (d) の状態にある場合、キューの最後に新しい要素を挿入することはできません。そうでない場合は、境界外の例外が発生します。配列が範囲外のため。ただし、現時点では、キューの実際の利用可能なスペースがいっぱいではないため、シーケンシャル スタックのようにストレージを再配分して配列スペースを拡張することは適切ではありません。より独創的な方法は、次の図に示すように、順次キューを円形の空間として作成することです。これは、循環キューと呼ばれます

リソース割り当てマップ

配列がリングに「回転」し、後部と前部がリングに沿って歩かせることを想像してください。そうすれば、2 人が配列の端に到達して、それ以上下ることができなくなるという状況は決して発生しません。これは循環です。列。循環キューは改良された順次キューであり、要素の入口と出口は次の図に示されています。

リソース割り当てマップ

上の図では、キューの進行状況とキューの変化は次のようになります。

①空いたチームから要素を2つ入力します。この時、前は0、後は2を指します。

② 4つの要素を入力し、3つの要素を残す このとき、前は3を指し、後は6を指します。

③ 2 つの要素がキューに入り、4 つの要素がキューから出る このとき、前は 7 を指し、後は 0 を指します。

①から③の変更過程を見ると、要素の出入り後、後ろと前が配列の最後に達しても(図の③)、要素は継続できることがわかります。 2 つのポインタが一致していないため、キューに入るには、配列の添字を増加させながら直線的に歩くのではなく、リングに沿って歩き、配列の終わりに達すると自動的に配列の開始位置に戻ります。

上記の分析から、通常の逐次キューにも欠陥があることがわかりますので、実際に使用する場合は循環キューを使用します (後で説明する逐次キューは主に循環キューです)。キューが空であるか満杯であるかを確実に判断するために、循環キューは記憶スペースを犠牲にする必要がある。

3.2 循環キューの要素

循環キュー qu には 4 つの要素もあり、これらは 2 つの特別な状態 (満杯)、2 つの操作 (キューに入るときと出るとき) です

キューの空ステータス: qu.rear == qu.front

キューフルステータス: (qu.rear+1)%maxSize == qu.front

エンキュー操作:qu.data[qu.rear]=x; qu.rear = (qu.rear+1)%maxSize;

デキュー操作:x = qu.data[qu.front]; qu.front = (qu.front+1)%maxSize;

注: 上記のコードは、最初にデータを操作し、キューに出入りするときにポインタを移動するものです。他の順序がある可能性もありますが、それらは本質的に同じです。

3.3 循環キューの基本動作

キューを初期化する

void initQueue(SqQueue *qu){
    
    
  qu->front = qu->rear = 0;
}

チームが空かどうかを判断する

//如果为空,返回1,否则返回0
int isEmpty(SqQueue qu){
    
    
    if(qu.rear == qu.front){
    
    
        return 1;
    }
    return 0;
}

キュー操作

int enQueue(SqQueue *qu, int x){
    
    
    if((qu->rear+1)%maxSize == qu->front){
    
    
        printf("队满,无法入队!");
        return 0;
    }
    qu->data[qu->rear] = x;
    qu->rear = (qu->rear+1) % maxSize;
    return 1;
}

デキュー操作

int deQueue(SqQueue *qu, int *x){
    
    
    if(qu->rear == qu->front){
    
    
        printf("队空,无法出队!");
        return 0;
    }
    *x = qu->data[qu->front];
    qu->front = (qu->front+1) % maxSize;
    return 1;
}

試験では、チームは通常、他の問題を解決するための補助的な構造として使用されるため、一般に、チームの定義と運用は次のように非常に簡単に記述できます。

//两句话定义一个队
int queue[maxSize];
int front = rear = 0;
//入队
queue[rear] = x;
rear = (rear + 1) % maxSize;

//出队
x = queue[front];
front = (fron + 1) % maxSize;

4. チェーンチームの定義と基本的な運用

4.1 チェーンチームの要素

チェーン チーム lqu の場合、4 つの要素もあります。これらは 2 つの特別な状態、つまりチームが空であることチームが満員であること、2 つの操作:チームに入るチームから抜けることです

空のキュー: lqu->rear == NULL または lqu->front == NULL、スタックが空であることを示します。

キューがいっぱい: 通常の状況では、キューがいっぱいになることはありません (メモリが不足していて新しいノードを申請できない場合を除く)。

エンキュー操作:要素が配置されているノードをポインタpで指し、末尾挿入方式でノードを挿入します。

//尾插法
lqu->rear->next = p;
lqu->rear = p;

デキュー操作: ポップされた要素は x に保存されます

//p指向出栈结点
p = lqu->front;
x = p->data;
//队首指针指向下一个结点
lqu->front = p->next;
free(p);

エンキューおよびデキュー操作の動的概略図は次のとおりです。

リソース割り当てマップ

4.2 チェーンチームの基本動作

キューを初期化する

void InitLinkedQueue(LiQueue *lqu){
    
    
    lqu = (LiQueue *) malloc(sizeof(LiQueue));
    lqu -> front =NULL;
    lqu -> rear = NULL;
}

チームが空かどうかを判断する

int isLinkedEmpty(LiQueue lqu){
    
    
    if(lqu.rear == NULL || lqu.rear == NULL){
    
    
        return 1;
    }
    return 0;
}

エンキュー操作

void enLinkedQueue(LiQueue *lqu, int x){
    
    
    //创建一个节点p
    QNode *p = (QNode *) malloc(sizeof(QNode));
    p->data = x;
    p->next = NULL;
    
    //需要判断队列是否为空,如果为空,需要同时修改front指针的值
    if(lqu->rear == NULL){
    
    
        lqu->rear = lqu ->front = p;
    } else{
    
    
        lqu->rear->next = p;
        lqu->rear = p;
    }
}

デキュー操作

int deLinkedQueue(LiQueue *lqu, int *x){
    
    
    if(lqu->rear == NULL){
    
    
        printf("队空,无法出队!");
        return 0;
    }
    QNode *p = lqu->front;
    //如果队中只有一个结点,需要同时修改rear指针的值
    if(lqu->front == lqu->rear){
    
    
        lqu->rear = lqu ->front = NULL;
    } else{
    
    
        lqu->front = p->next;
    }
    *x = p->data;
    free(p);
    return 1;
}

メイン機能のテストコード

int main(int argc, const char * argv[]) {
    
    
    // insert code here...
    LiQueue lqu;
  	int x;
    InitLinkedQueue(&lqu);
    printf("st.top:%d\n", lqu.front);
    printf("Queue is Empty:%d\n", isLinkedEmpty(lqu));
    enLinkedQueue(&lqu, 1);
    enLinkedQueue(&lqu, 2);
    enLinkedQueue(&lqu, 3);
    printf("Queue is Empty:%d\n", isLinkedEmpty(lqu));
    deLinkedQueue(&lqu, &x);
    printf("出队的元素为:%d\n", x);
    deLinkedQueue(&lqu, &x);
    printf("出队的元素为:%d\n", x);
    deLinkedQueue(&lqu, &x);
    printf("出队的元素为:%d\n", x);
}

5. 両端キュー

両端キューは、両端で挿入および削除操作を実行できる線形リストです。両端キューは、スタックの下部で接続された 2 つのスタックと考えることができます。共有スタックとは異なり、2 つのスタックの最上位ポインタは両端まで拡張されます。両端キューでは両端で要素の挿入と削除ができるため、両端のキューの両端の要素をそれぞれ指す 2 つのポインタ end1 と end2 を設定する必要があります。

リソース割り当てマップ

下図の左側に示すように、一方の端では挿入と削除が可能で、もう一方の端では削除のみが可能な両端キューを入力制限両端キューと呼びます。このキューは、次の図の右側に示すように、出力制限両端キューと呼ばれます。

リソース割り当てマップ

両端キューの基本的な操作については、次の質問を参照してください。

例: キューでは、キューの両端でエンキュー操作が許可されますが、一方の端でのみデキュー操作が許可されます。要素 a、b、c、d、e が順番にこのキューに入り、デキュー操作を実行すると、のデキュー シーケンスは ( ) です。
A. b、a、c、d、e B. d、b、a、c、e
C. d、b、c、a、e D. e、c、b、a、d

答え:C.

6. まとめ

スタックの概念と操作は比較的単純ですが、バイナリ ツリーのレベル トラバースなど、他の問題の解決における補助構造として現れることがよくあります。

キューは非常に広く使用されているデータ構造であり、将来のプログラミングでは使用頻度が非常に高くなります。チームの特徴をよく理解し、具体的な問題解決に活かしてください。

スタックとキューに関する練習問題については、この記事を参照してください

参考:

1.データ構造における C 言語プログラミングの基礎

2.データ構造 大学院入試概要

3. データ構造ハイスコアメモ

4. Kinglyのデータ構造

5. データ構造 – C 言語バージョン、Yan Weimin


再び境界線を下回ったのでこの記事は終了です この記事の内容はすべてブロガーが大学院受験資料や独自の理解に基づいて整理したものです あくまで参考としてください ご不明な点がございましたら、コメント欄にメッセージを残していただけますので、間違いがあれば批判して修正してください。

このコラムはデータ構造の知識に関するものです。気に入っていただけましたら、引き続き注目していただけます。この記事が役に立った場合は、いいね、コメント、注目してください。

ご質問がある場合は、コメント欄にメッセージを残してください。

おすすめ

転載: blog.csdn.net/qq_34666857/article/details/121455479