blog

データ構造とアルゴリズム - キューとその関連アルゴリズム

キューの基本\nはじめに\n図に示すように\n\n待ち行列の最後尾だけが入り、先頭だけが出ることができる記憶構造。\n逐次キュー\n\nキューのサイズは固定\nキューアウトとキューインの時間複雑度はO...

Feb 18, 2020 · 6 min. read
シェア

キューの基本

はじめに

この通り(イメージ提供:Geek Timeのコラム「データ構造とアルゴリズムの美」)。

キューの末尾のみが入り、キューの先頭のみが出ることができる記憶構造体。

シーケンシャルキュー

  1. 固定キューサイズ
  2. 送信キューと受信キューの時間複雑度はO(1)

連鎖キュー

連鎖リスト方式で実装されたキュー:

  1. キューサイズ無制限
  2. 送信キューと受信キューの時間複雑度はO(1)

一般的なアルゴリズム

サーキュラーキューの実装

循環キューは、キューの先頭と末尾をそれぞれ指すダブルポインタ head と tail によって実装されます。循環キューの実装では、そのキューが空かどうか、キューが判定で一杯かどうかに注意する必要があります。その条件は以下の通りです:

キューは空です: head == tail

キューがいっぱいです: %capacity == head

コードは以下の通り:

class LoopQueue{
 int[] queue = null;
 int capacity = 0;
 int head = 0;
 int tail = 0;
 public LoopQueue(int capacity) {
 this.capacity = capacity;
 queue = new int[capacity];
 }
 public void add(int val){
 if((tail + 1) % capacity == head){
 System.out.println("キューが一杯になった");
 }else {
 System.out.println("値を挿入する+val);
 tail = tail%capacity;
 queue[tail] = val;
 tail++;
 }
 }
 public void remove(){
 if(head%capacity == tail){
 System.out.println("キューは空である");
 }else {
 head = head%capacity;
 int val = queue[head];
 System.out.println("の値を削除する。+val);
 head++;
 }
 }
 }

円形ダブルエンド待ち行列の設計

ダブルエンド待ち行列の実装を設計する。
実装は以下の操作をサポートする必要がある:
MyCircularDeque(k)コンストラクタ,サイズkのダブルエンド待ち行列。
insertFront()両端キューの先頭に要素を追加する。 操作が成功すれば真を返す。
insertLast()ダブルエンド・キューの末尾に要素を追加する。操作が成功すれば真を返す。
deleteFront()ダブルエンド待ち行列の先頭から要素を削除する。 成功すれば真を返す。
deleteLast()両端キューの末尾から要素を取り出す。操作が成功すれば真を返す。
getFront()両端キューの先頭から要素を取得する。両端待ち行列が空の場合は-1を返す。
getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
isEmpty()ダブルエンド・キューが空かどうかをチェックする。
isFull()ダブルエンド待ち行列が満杯かどうかをチェックする
 
MyCircularDeque circularDeque = new MycircularDeque(3); // 容量サイズを3に設定する
circularDeque.insertLast(1);			 // 真を返す
circularDeque.insertLast(2);			 // 真を返す
circularDeque.insertFront(3);			 // 真を返す
circularDeque.insertFront(4);			 // が満杯の場合、falseを返す
circularDeque.getRear(); 				// 2を返す
circularDeque.isFull();				 // 真を返す
circularDeque.deleteLast();			 // 真を返す
circularDeque.insertFront(4);			 // 真を返す
circularDeque.getFront();				// 戻り値4

コードは以下の通り:

class MyCircularDeque {
 class Node{
 Node next;
 int value;
 Node(int value){
 this.value = value;
 }
 }
 Node head;
 Node tail;
 int count = 0;
 int capcity;
 /** Initialize your data structure here. Set the size of the deque to be k. */
 public MyCircularDeque(int k) {
 head = new Node(-1);
 tail = new Node(-1);
 capcity = k;
 }
 
 /** Adds an item at the front of Deque. Return true if the operation is successful. */
 public boolean insertFront(int value) {
 if(count >= capcity)return false;
 Node next = new Node(value);
 if(count == 0){
 head.next = next;
 tail.next = next;
 }else{
 Node p = head.next;
 head.next = next;
 next.next = p;
 }
 count++;
 return true;
 }
 
 /** Adds an item at the rear of Deque. Return true if the operation is successful. */
 public boolean insertLast(int value) {
 if(count >= capcity)return false;
 Node next = new Node(value);
 if(count == 0){
 head.next = next;
 tail.next = next;
 }else{
 Node p = tail.next;
 tail.next = next;
 p.next = next;
 }
 count++;
 return true;
 }
 
 /** Deletes an item from the front of Deque. Return true if the operation is successful. */
 public boolean deleteFront() {
 if(count == 0)return false;
 if(count == 1){
 head.next = null;
 tail.next = null; 
 }else{
 Node p = head.next;
 head.next = p.next;
 p.next = null;
 }
 count--;
 return true;
 }
 
 /** Deletes an item from the rear of Deque. Return true if the operation is successful. */
 public boolean deleteLast() {
 if(count == 0)return false;
 if(count == 1){
 head.next = null;
 tail.next = null; 
 }else{
 Node p = tail.next;
 Node root = head.next;
 while(root!=null&&root.next != p)root = root.next;
 tail.next = root;
 root.next = null;
 }
 count--;
 return true;
 }
 
 /** Get the front item from the deque. */
 public int getFront() {
 if(count == 0)return -1; 
 Node node = head.next;
 return node.value;
 }
 
 /** Get the last item from the deque. */
 public int getRear() {
 if(count == 0)return -1; 
 Node node = tail.next;
 return node.value;
 }
 
 /** Checks whether the circular deque is empty or not. */
 public boolean isEmpty() {
 return count == 0;
 }
 
 /** Checks whether the circular deque is full or not. */
 public boolean isFull() {
 return count == capcity;
 }
}

スライディングウィンドウの最大値

配列 nums とサイズ k のスライディング・ウィンドウが与えられたとき,すべてのスライディング・ウィンドウにおける最大値を求めよ.
 :
 : nums = [1,3,-1,-3,5,3,6,7], とk = 3
 : [3,3,5,5,6,7] 
 : 
 スライディングウィンドウの最大位置
--------------- -----
[1 3 -1] -3 5 3 6 7 3
 1 [3 -1 -3] 5 3 6 7 3
 1 3 [-1 -3 5] 3 6 7 5
 1 3 -1 [-3 5 3] 6 7 5
 1 3 -1 -3 [5 3 6] 7 6
 1 3 -1 -3 5 [3 6 7] 7

アイデアの実現:

ウィンドウをキューと考え、各スライドが発信と着信の操作になります。

方法1:キュー内の最大値を毎回比較して求める場合、キューのサイズがkであるため、時間の複雑さはO(n * k)となります。

方法2:キューヘッド要素のキューからスライディングウィンドウの値は、キューのヘッド要素がキューから出るように、キューにスライディングウィンドウの値は、キューの終わりよりも、キューからキューの終わりを聞かせて、キュー要素の終わりまで周回し続けるキュー要素またはキューが空であるよりも大きいですし、要素を挿入するキューの最後に。これは、キューがソートの逆順の値を満たすことができるようになり、毎回、最大値である先頭の要素を取るだけです。

コードは以下の通り:

class Solution {
 public int[] maxSlidingWindow(int[] nums, int k) {
 if(nums.length == 0)return new int[0]; 
 if(k == 1)return nums;
 int[] res = new int[nums.length - k + 1];
 Deque<Integer> l = new LinkedList<>();
 for(int i = 0;i < k;i++){
 if(l.isEmpty())l.add(nums[i]);
 else{
 while(!l.isEmpty() && nums[i] > l.peekLast()){
 l.pollLast();
 }
 l.add(nums[i]);
 }
 }
 res[0] = l.peekFirst();
 for(int i = k;i < nums.length;i++){
 if(nums[i - k] == l.peekFirst())l.pollFirst();
 while(!l.isEmpty() && nums[i] > l.peekLast()){
 l.pollLast();
 }
 l.add(nums[i]);
 res[i - k + 1] = l.peekFirst();
 }
 return res;
 }
}

必須コードの実装

  • 配列によるシーケンシャル・キューの実装
  • リンクされたテーブルによる連鎖キューの実装
  • サーキュラーキューの実装
  • 円形ダブルエンド待ち行列の設計
  • スライディングウィンドウの最大値
Read next

Pingのワークフロー

まず、pingコマンドは実際に32バイトのランダムな文字列をターゲットホストに送信し、ターゲットホストが同様に文字列を返信した場合、そのホストはオンラインであることを意味します。TCP/IPの5層モデルによると、ICMPプロトコルとIPプロトコルは同じネットワーク層に属します。 通常、上位層のプロトコル...

Feb 18, 2020 · 1 min read