イベントアドレス: CSDN 21 日間学習チャレンジ
Leetcode622 - 循環キューの設計
トピックの説明
循環キューの実装を設計します。循環キューは線形データ構造であり、その動作は FIFO (先入れ先出し) 原理に基づいており、キューの最後尾がキューの先頭の後に接続されてループを形成します。「リングバッファ」とも呼ばれます。
循環キューの利点の 1 つは、このキューで以前に使用されていたスペースを利用できることです。通常のキューでは、キューがいっぱいになると、キューの先頭にまだ空きがある場合でも、次の要素を挿入できません。しかし、循環キューを使用すると、このスペースを新しい値の保存に使用できます。
リンク: https://leetcode.cn/problems/design-circular-queue
実装では次の操作をサポートする必要があります。
MyCircularQueue(k): コンストラクター。キューの長さを k に設定します。
フロント: キューの先頭から要素を取得します。キューが空の場合は -1 を返します。
後部: キューの最後にある要素を取得します。キューが空の場合は -1 を返します。
enQueue(value): 要素を循環キューに挿入します。挿入が成功した場合は true を返します。
deQueue(): 循環キューから要素を削除します。削除が成功した場合は true を返します。
isEmpty(): 循環キューが空かどうかを確認します。
isFull(): 循環キューがいっぱいかどうかを確認します。
例:
MyCircularQueuecircularQueue = new MyCircularQueue(3); // 長さを 3 に設定
circularQueue.enQueue(1); // true を返します
circularQueue.enQueue(2); // true を返します
circularQueue.enQueue(3); // true を返します
circularQueue . enQueue(4); // false を返します、キューはいっぱいです
circularQueue.Rear(); // 3 を返します
circularQueue.isFull(); // true を返します
circularQueue.deQueue(); // true を返します
circularQueue.enQueue(4) ); // true を返します
circularQueue.Rear(); // 4 を返します
ヒント:
- すべての値の範囲は 0 ~ 1000 です。
- オペランドの範囲は 1 ~ 1000 です。
- 組み込みのキュー ライブラリは使用しないでください。
コアコードパターン
typedef struct {
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
}
int myCircularQueueFront(MyCircularQueue* obj) {
}
int myCircularQueueRear(MyCircularQueue* obj) {
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
}
void myCircularQueueFree(MyCircularQueue* obj) {
}
/**
* Your MyCircularQueue struct will be instantiated and called as such:
* MyCircularQueue* obj = myCircularQueueCreate(k);
* bool param_1 = myCircularQueueEnQueue(obj, value);
* bool param_2 = myCircularQueueDeQueue(obj);
* int param_3 = myCircularQueueFront(obj);
* int param_4 = myCircularQueueRear(obj);
* bool param_5 = myCircularQueueIsEmpty(obj);
* bool param_6 = myCircularQueueIsFull(obj);
* myCircularQueueFree(obj);
*/
アイデア分析とコード実装(C言語)
単一リンク リストの使用はあまり適切ではありません。たとえば、キューの最後にある要素を取得したい場合は、再度それを走査する必要がありますが、キューの最後に添字 -1 を直接使用できます。また、単一リンクリストの作成はシーケンステーブルよりも面倒で、1つずつ使用する必要があり、ノードが作成され、すべてがリンクされます。動的シーケンス テーブルを使用することをお勧めします。
循環キュー内の要素の挿入または削除によってスペースは変更されず、上書き戦略が採用されます。実際、この質問の鍵は、空か満杯かを正確に判断することです。ここでは、後部がキューの最後にある要素の次の位置を指すようにすると、問題が発生します。この 2 つを区別することはできません。空と満杯の状態。なぜ?
図に示すように、キューの先頭ポインタとキューの末尾ポインタは両方とも、空の状態でも満杯の状態でも同じ位置を指します。
解決策:
- カウントするサイズを増やす
- スペースを追加し、スペースがいっぱいになったら必ずスペースを残します。たとえば、循環キューに 4 つの要素がある場合、5 つのスペースが空きます。
後部の次の位置は前部であり、これが満杯状態であり、前部と後部が同じ位置を指している、これが空の状態である。ただし、これは論理構造の結論であり、図に示すように、論理構造のキューはいっぱいですが、循環キューを実現するために動的シーケンス テーブルを使用しますが、物理構造は別のものであることに注意してください。私たちの操作は物理学、構造に基づいていなければなりません。
対処方法に注意したいのは、図のように、後ろの添え字+1%N、Nをスペースの総数(余分なスペースも含む)とし、その結果が前と等しいかどうかで判断することです。添字。
構造体の宣言とキューの作成
循環リストは、動的シーケンス テーブルを使用して実装され、キューの先頭と末尾の添字と、動的シーケンス テーブルによって開かれるスペースの合計数 (もう 1 つ) を表す N を設定します。
動的シーケンス テーブルと同様に、ヒープ上のスペースを適用するキューを作成し、指定された k を超えるスペースを開き、初期化の完了後にキュー ポインターを返します。
typedef struct {
int* arr;
int front;
int rear;
int N;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->arr = (int*)malloc((k+1)*sizeof(int));
obj->front = obj->rear = 0;
obj->N = k + 1;
return obj;
}
前
と後ろが等しい場合はキューが空であることを意味し、判定がいっぱいである場合は、上記の 2 つの注意すべき状況があります。
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->rear == obj->front;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return ((obj->rear+1) % obj->N) == obj->front;
}
要素キュー
が満杯の場合は、要素の挿入は許可されず、 false が返されます。満杯でない場合は、要素は直接後方に配置されます。オブジェクトを待つことを忘れないでください。 >N. これは、後方が空間の端に到達したら添字0の位置を返せるように制御し、ループを実現するためです。
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
return false;
obj->arr[obj->rear] = value;
++obj->rear;
obj->rear %= obj->N;
return true;
}
要素がキューの外にある場合は
、キューの外に失敗し false が返されます。要素が空でない場合は、先頭が 1 ビット前に移動します。obj->N を待つことを忘れないように注意してください。これは、後方がスペースの端に到達したら添え字0の位置に戻れるように制御し、ループを実現するためである。
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return false;
++obj->front;
obj->front %= obj->N;
return true;
}
まずキューのヘッド要素を取得して
キューが空かどうかを確認し、空の場合は -1 を返し、空でない場合はキューのヘッド要素を直接返します。
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->arr[obj->front];
}
キューの最後尾の要素を取得するには、
まずキューが空かどうかを確認します。空の場合は -1 を返します。空でない場合は、後部が 0 である場合と後部が 0 ではない場合の 2 つの状況に注意する必要があります。 0.
次の 2 つの書き方があります。
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
else if(obj->rear == 0)
return obj->arr[obj->N - 1];
else
return obj->arr[obj->rear - 1];
}
queue を破棄して解放すること
で動的に作成される構造が 2 つあり、1 つはキュー構造、もう 1 つは動的配列です。最初に配列を解放し、次に構造を解放する必要があります。
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->arr);
obj->arr = NULL;
obj->front = obj->rear = obj->N = 0;
free(obj);
}