deque(両端キュー)
双方向循環リストを使うと、(1)先頭にデータを挿入(addFirst/unshift)、(2)先頭のデータを取り出す(removeFirst/shift)、(3)末尾にデータを追加(addLast/push)、(4)末尾のデータを取り出す(removeLast/pop)、といった処理が簡単に記述できる。この4つの処理を使うと、単純リスト構造で説明した、待ち行列(queue)やスタック(stack) が実現できる。この特徴を持つデータ構造は、先頭・末尾の両端を持つ待ち行列ということで、deque (double ended queue) とも呼ばれる。
先頭への挿入/削除

末尾への挿入削除

import java.util.*;
class BDListNode {
BDListNode prev ;
int data ;
BDListNode next ;
BDListNode( BDListNode p , int d , BDListNode n ) {
this.prev = p ;
this.data = d ;
this.next = n ;
}
BDListNode() {
this.prev = this ;
this.data = -1 ; // dummy
this.next = this ;
}
}
public class Main {
static void addFirst( BDListNode p , int d ) {
p.next = new BDListNode( p , d , p.next ) ;
p.next.next.prev = p.next ;
}
static int removeFirst( BDListNode p ) {
int ans = p.next.data ;
p.next = p.next.next ;
p.next.prev = p ;
return ans ;
}
static void addLast( BDListNode p , int d ) {
p.prev = new BDListNode( p.prev , d , p ) ;
p.prev.prev.next = p.prev ;
}
static int removeLast( BDListNode p ) {
int ans = p.prev.data ;
p.prev = p.prev.prev ;
p.prev.next = p ;
return ans ;
}
public static void main(String[] args) throws Exception {
BDListNode top = new BDListNode() ;
// 先頭で Stack
addFirst( top , 11 ) ;
addFirst( top , 22 ) ;
System.out.println( removeFirst( top ) ) ;
System.out.println( removeFirst( top ) ) ;
// 末尾で Stack
addLast( top , 33 ) ;
addLast( top , 44 ) ;
System.out.println( removeLast( top ) ) ;
System.out.println( removeLast( top ) ) ;
// Queue として使う
addFirst( top , 55 ) ;
addFirst( top , 66 ) ;
System.out.println( removeLast( top ) ) ;
System.out.println( removeLast( top ) ) ;
}
}
Javaでの LinkedList(Deque) のクラスの使い方
Javaで Deque のような双方向リスト(2重リスト)を使うには、LinkedList<E> を用いる。クラス宣言などで <E> の内側に型Eを指定する機能はジェネリクスと呼ばれる。クラス名に Deque というのもあるけど、LinkedList などの内部の処理を記述するために使われているものなので、ユーザが直接 Deque クラスを使うことはない。
LinkedList でデータ構造を宣言する場合には、LinkedList<E> のように、どのテータ構造を要素にもつ LinkedList なのかを指定する。ただし int,double といったプリミティブ型を型の欄に書くことはできない。こういった場合には Integer,Double といったオブジェクトを記載すること。
また、リストの要素で繰り返しをする場合には、for( 型 item : LinkedList型 ) {…} のように書けば、繰り返しが記述できる。
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
LinkedList<Integer> lst = new LinkedList<Integer>() ;
lst.addFirst( 11 ) ; // lst の先頭に11を挿入
lst.addFirst( 22 ) ; // lst の先頭に22を挿入
for( Integer item : lst ) {
System.out.println( item ) ;
}
lst.addLast( 33 ) ; // lst の末尾に33を追加
// 全要素の繰り返し
for( Integer item : lst ) {
System.out.println( item ) ;
}
// 先頭を取り出す
while( !lst.isEmpty() ) { // isEmpty() : lst の要素が空か判定する
System.out.println( lst.removeFirst() ) ; // lstの先頭要素を取り出す
}
}
}
LinkedList クラスは双方向リストだけど、単純リスト専用のクラスは無い。このため、これまでの授業のように自分でクラスを宣言して使う必要がある。
ArrayList クラス
これと同じように自由な配列サイズのデータクラスとして、ArrayListクラスも存在する。以下の例は、ArrayList<Integer> の使用例。
ArrayList はジェネリクスを使った、要素数を増やすことができる配列である。
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
ArrayList<Integer> lst = new ArrayList<Integer>() ;
lst.add( 11 ) ;
lst.add( 22 ) ;
for( Integer item : lst ) {
System.out.println( item ) ;
}
lst.add( 33 ) ;
for( Integer item : lst ) {
System.out.println( item ) ;
}
for( int x = 0 ; x < lst.size() ; x++ ) {
System.out.println( lst.get( x ) ) ;
}
}
}
型推論
上記のプログラムで、LinkedList<Integer> lst = new LinkedList<Integer>() ; で宣言しているが、初期化の右辺と左辺で型を揃える必要があるが、複雑な型だとプログラムが煩雑になる。このため、最近の Java には型推論の機能があるため、下の様に型を省略して記述できる。
LinkedList<Integer> lst = new LinkedList<Integer>() ; // 型推論なし
LinkedList<Integer> lst = new LinkedList<>() ; // 後半のIntegerは明らか
var lst = new LinkedList<Integer>() ; // lst の型は new LinkedList<Integer> で明らか
for( Integer item : lst ) { ... } // 型推論なし
for( var item : lst ) { ... } // 型推論でIntegerを省略
LinkedListとArrayListの違い
LinkedList や ArrayList は同じ使い方ができるようにadd(int,E),get(int)クラスメソッド等が定義されている。しかし、LinkedList は双方向リスト、ArrayList は配列を使って実装されているので、同じ名前のメソッドでもそのメソッドの処理時間には、以下のような特徴がある。
| 処理時間のオーダ | LinkedList | ArrayList | メソッドの機能 |
|---|---|---|---|
| addFirst(E e) / add(E e) |
O(1) | O(1) | リストの末尾に要素を追加 |
| add(int index, E e) | 指定した場所探しにO(N) その場所に挿入O(1) |
指定した場所探しにO(1) その場所に挿入O(N) |
リストの指定したインデックスに要素を追加 |
| addFirst(E e) / add(0, E e) | 先頭に挿入O(1) | 先頭に挿入O(N) | リストの先頭に要素を追加 |
| E get(int index) | O(N) | O(1) | リストのインデックス番目を参照 |
理解確認
- ジェネリックス ArrayList, LinkedList を使って逆順のリストを作る、最小値を探すプログラムを作成せよ。
LinkedListで逆順,最小値を返す練習問題 - 型推論とは何か?