] > Linear List

第 7 回 線形リスト

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

7-1. 線形リストの操作

線形リストとは図のような特殊な二分木でした。

線形リスト

値は葉にだけ入れられ、左向きの枝には必ず葉が付きます。そして、右の枝に 付く葉には nil という値の葉が付きます。 このような構造にしておくと「x 番目の値」を取り出すのはそれほど容易では ありませんが、次の値を取り出すのは容易です。

データの集まりに対して順番に要素を取り出して処理することは情報処理の基 本です。 そのため、そのような操作を抽象化して扱うべきです。 要素を順番に取り出すための仕組みをイテレータ (Iterator 反復子) と呼びます。 これは要素を順々に指していくのでポインタのようなものになります。 プログラミング用語として「参照」「イテレータ」「ポインタ」は次 のような違いがあります。

参照
特定のオブジェクトを指すもの。あるいは格納先のメモリの番地。 Java 言語のオブジェクト変数には参照が入っている。 参照に対して許されている演算は、代入と等号とオブジェクトのコピー程度。
イテレータ(反復子 Iterator)
データの集まりに含まれる要素に対する参照で、単なる要素の参照の他、 「次の参照を求める」演算や、「データの終了」を調べることが許されている。 for 文や while 文とともに利用することで、データすべてに対する処理を行 うことができる。
ポインタ
一般的なメモリ番地を扱う。当然参照やイテレータの機能も実現できる。 その他に加減乗除などの演算は整数型と同様に許されている。 C 言語ではポインタを使って様々なことを実現するが、なんでもできる点で、 抽象度は低く、トラブルを招きやすい

今回はこのイテレータという概念を導入して、線形リストの操作を考えたいと 思います。

さて、線形リストについて次のような操作を行うことを考えます。

  1. リストの初期化
  2. 先頭の要素を指すイテレータを得る
  3. 最後の要素を指すイテレータを得る
  4. 要素を最後に足す
  5. 要素を先頭に足す
  6. リストが空かどうか調べる
  7. リストのサイズを調べる
  8. リスト同士の結合
  9. イテレータの指す値
  10. イテレータを隣の要素に
  11. イテレータの指す場所へ要素を挿入
  12. イテレータの指す場所の要素を削除

C++ での実装

C++ ではすでに線形リストもそのイテレータも STL で提供されています。 したがって、上記の処理は特別なプログラムを組まなくても次のようにすれば 可能になります。以下はリストの中に文字列を入れる例です。

リストの初期化

#include <string>
#include <list>

std::list<std::string> aList;
先頭の要素を指すイテレータを得る

std::list<std::string>::iterator anIterator;
anIterator = aList.begin();
最後の要素の(次の)イテレータを得る

std::list<std::string>::iterator anIterator;
anIterator = aList.end();
要素を最後に足す

aList.insert(最後を指すイテレータ,要素);
要素を先頭に足す

aList.insert(先頭を指すイテレータ,要素);
リストが空かどうか調べる

aList.empty()
リストのサイズを調べる

aList.size()
リスト同士の結合
イテレータの指す要素の値

*anIterator;
イテレータを隣の要素に

anIterator++;
イテレータの指す場所へ要素を挿入

aList.insert(anIterator,要素);
イテレータの指す場所の要素を削除

aList.erase(anIterator);

例7-1


#include <string>
#include <iostream>
#include <list>
using std::cout;
using std::endl;
using std::string;
using std::list;
int main(){
  list<string> aList;
  list<string>::iterator anIterator;
  aList.insert(aList.end(),"abc");
  aList.insert(aList.end(),"def");
  aList.insert(aList.begin(),"ghi");
  for(anIterator = aList.begin(); anIterator != aList.end(); anIterator++){
    cout << *anIterator << endl;
  }
  cout << "size = " << anList.size() << endl;
  aList.sort();
  while(!aList.empty()){
    anIterator=aList.begin();
    cout << *anIterator << endl;
    aList.erase(anIterator);
  }
  return 0;
}

例7-2

上の例では数の並び替えでは小さい順でしかできません。 しかし、様々な順序で並び替えたい場合もあります。 ここでは、任意の順番を指定して並び替えを行う例を示します。

整数を二進数に直した時、 1 となる桁の少ない順に数を並べることを考えま しょう。 例えば、 0 から 20 までだと次のようになります。

十進数二進数1 の数
000
111
2101
3112
41001
51012
61102
71113
810001
910012
1010102
1110113
1211002
1311013
1411103
1511114
16100001
17100012
18100102
19100113
20101002

したがって、 0 から 20 を並べると次のようになります。

0 個1 個2 個3 個4 個
0, 1, 2, 4, 8, 16, 3, 5, 6, 9, 10, 12, 17, 18, 20, 7, 11, 13, 14, 15

これを C++ で作るには、まずビットの数を計算する関数を書きます。

#include <iostream>
using std::cout;
using std::endl;
int numOfBits(int n){
  int k=0;
  while(n>0){
    k+=n%2;
    n/=2;
  }
  return k;
}
int main(){
  for(int i=0; i<=20; i++){
    cout << i << "  " << numOfBits(i) << endl;
  }
  return 0;
}

数を x, y とした時、 1 の数が x より y が大きい 時だけ true, そうでない時 false が返すような () オペレータをメソッドと してもつクラスを作ります。


#include <iostream>
using std::cout;
using std::endl;
int numOfBits(int n){
  int k=0;
  while(n>0){
    k+=n%2;
    n/=2;
  }
  return k;
}
class CompBit {
public:
  bool operator()(const int& x, const int& y){
    return numOfBits(x)<numOfBits(y);
  }
};
int main(){
  CompBit c;
  for(int i=0; i<=20; i++){
    cout << i ;
    for(int j=0; j<=20; j++){
      cout << "  " << c(i,j);
    }
    cout << endl;
  }
  return 0;
}

この CompBit クラスを利用して数を並べ替えます。 この時、functional をインクルードして CompBit を std::binary_function<int,int,bool> のサブク ラスにしておきます。


#include <iostream>
#include <list>
#include <functional>
using std::cout;
using std::endl;
using std::list;
int numOfBits(int n){
  int k=0;
  while(n>0){
    k+=n%2;
    n/=2;
  }
  return k;
}
class CompBit : public std::binary_function<int,int,bool> {
public:
  bool operator()(const int& x, const int& y){
    return numOfBits(x)<numOfBits(y);
  }
};
int main(){
  CompBit c;
  list<int> aList;
  for(int i=0; i<=20; i++){
    aList.insert(aList.end(),i);
  }
  for(list<int>::iterator anIterator = aList.begin();
  anIterator!= aList.end(); aIterator++){
    cout << *aIterator << " ";
  }
  cout << endl << "---------------------------------------" << endl;
  aList.sort(c);
  for(list<int>::iterator anIterator= aList.begin();
   anIterator != aList.end(); anIterator++){
    cout << *anIterator << " ";
  }
  cout << endl;
  return 0;
}
注釈

なおこの比較専用のクラスをオブジェクトに対して作る場合、 private メン バへのアクセス権が欲しくなる場合があります。 このような場合、そのオブジェクトクラスに operator() を実装してしまう考 え方もありますが、対象に応じて実装方法が異なるのは不自然です。

このような場合、比較専用クラスに private メンバへのアクセスを特別に認 めてしまえば解決します。 そのため、元のオブジェクトのクラスに friend class でフレンド指定をしま す。


#include <functional>
class Example {
  int x;
  int y;
public:
  void set(int _x, int _y){x=_x; y=_y;}
  friend class CompExample;
};
class CompExample : public std::binary_function<Example&,Example&,bool> {
public:
  bool operator()(const Example& a, const Example& b){return a.y<b.y;}
};

Java での実装

Java では線形リストは java.util.LinkedList で提供されています。 また、 java.util.ListIterator をインプリメントしています。 なお、 C++ では Iterator はポインタを改造して作られましたが、 Java で は Iterator はクラス(interface)として実装されます。

リストの初期化

import java.util.*;

LinkedList aList = new LinkedList();
先頭の要素を指すイテレータを得る

ListIterator anIterator = aList.listIterator();
最後の要素の(次の)イテレータを得る
一命令では存在しない。
要素を最後に足す

aList.addLast(要素);
要素を先頭に足す

aList.addFirst(要素);
リストが空かどうか調べる

aList.isEmpty()
リストのサイズを調べる

aList.size()
リスト同士の結合

aList.addAll(anotherList);
イテレータの指す要素の値を得て、イテレータを隣の要素に

Object anObject = anIterator.next();
イテレータの指す場所へ要素を挿入

anIterator.add(要素);
next() をする前のイテレータの指す場所の要素を削除

anIterator.remove();

例7-3


import java.util.*;
class TestList {
    public static void main(String arg[]){
	LinkedList aList = new LinkedList();
	aList.addLast("abc");
	aList.addLast("def");
	aList.addFirst("ghi");
	ListIterator anIterator =aList.listIterator();
	while(anIterator.hasNext()){
	    String s = (String) anIterator.next();
	    System.out.println(s);
	}
	System.out.println("size = " + aList.size());
	java.util.Collections.sort(aList);
	anIterator = aList.listIterator();
	while(!aList.isEmpty()){
	    String s = (String) anIterator.next();
	    System.out.println(s);
	    anIterator.remove();
	}
    }
}

例7-4

ここでも例7-2同様に、 Java において、任意の順序を指定して並び替えを行 うことを考えましょう。 そのため、 Java ではまず、 1 の数を計算する関数を持つクラスを作ります。


class CompBit {
    static int numOfBits(int n){
	int k=0;
	while(n>0){
	    k+=n%2;
	    n/=2;
	}
	return k;
    }
    public static void main(String[] args){
	for(int i=0; i<=20; i++){
	    System.out.println(i+"  "+numOfBits(i));
	}
    }
}

そして、java.util.Collections.sort() を使うため、java.util.Comparator インタフェースをインプリメントします。 Comparator インタフェースの定義は以下の通りです。


public interface Comparator {
  public int compare(Object o1, Object o2);
  public boolean equals(Object obj);
}

したがって作成している CompBit クラスに Comparator インタフェースをイ ンプリメントするには compare メソッドと equals メソッドを実装します。


class CompBit implements java.util.Comparator {
    static int numOfBits(int n){
	int k=0;
	while(n>0){
	    k+=n%2;
	    n/=2;
	}
	return k;
    }
    public int compare(Object o1, Object o2){
	int r1=CompBit.numOfBits(((Integer)o1).intValue());
	int r2=CompBit.numOfBits(((Integer)o2).intValue());
	return (r1<r2)?-1:(r1==r2)?0:1;
    }
    public boolean equals(Object obj){
	return false;
    }
    public static void main(String[] args){
	CompBit c=new CompBit();
	for(int i=0; i<=20; i++){
	    System.out.print(i);
	    Integer anInteger=new Integer(i);
	    for(int j=0; j<=20; j++){
		System.out.print(" "+c.compare(anInteger, new Integer(j)));
	    }
	    System.out.println();
	}
    }
}

そして、線形リストに対して java.util.Collections.sort() で整列します。


class CompBit implements java.util.Comparator {
    static int numOfBits(int n){
	int k=0;
	while(n>0){
	    k+=n%2;
	    n/=2;
	}
	return k;
    }
    public int compare(Object o1, Object o2){
	int r1=CompBit.numOfBits(((Integer)o1).intValue());
	int r2=CompBit.numOfBits(((Integer)o2).intValue());
	return (r1<r2)?-1:(r1==r2)?0:1;
    }
    public boolean equals(Object obj){
	return false;
    }
    public static void main(String[] args){
	CompBit aComparator = new CompBit();
	java.util.LinkedList aList = new java.util.LinkedList();
	for(int i=0; i<=20; i++){
	    aList.addLast(new Integer(i));
	}
	for(java.util.Iterator anIterator = aList.iterator();
	    anIterator.hasNext();){
	    System.out.print(anIterator.next()+" ");
	}
	System.out.println();
	System.out.println("---------------------------------------");
	java.util.Collections.sort(aList, aComparator);
	for(java.util.Iterator anIterator = aList.iterator();
	    anIterator.hasNext();){
	    System.out.print(aIterator.next()+" ");
	}
	System.out.println();
    }
}

Smalltalk での実装

Smalltalk では線形リスト的なものは Collections-Sequenceable カテゴリに ある OrderedCollection で提供されています。 このクラス自体、 do: メソッドなど要素毎の繰返しメソッドを持っているた め、イテレータを実装することはプログラミングスタイルに合いません。

リストの初期化

aList ← OrederedCollection new.
先頭の要素を指すイテレータを得る
最後の要素の(次の)イテレータを得る
イテレータという概念自体がプログラミングスタイルに合わない。 リストに対して要素毎に処理させるメッセージを使う。 (以下 each は単に各要素を指す変数名であり別の名前でも良い)
単純に各要素毎に処理する

aList do: [:each | each に対する処理].
各要素毎に処理したもので新しいリストを作る

newList ← aList collect: [:each | each に対する処理].
各要素毎に処理し、結果がtrueなものだけでリストを作る

newList ← aList select: [:each | each に対する処理].
各要素毎に処理し、結果がfalseなものだけでリストを作る

newList ← aList reject: [:each | each に対する処理].
そのほか、集計などに使う inject: などさまざまなメソッドが定義され ています
要素を最後に足す

aList addLast: 要素.
要素を先頭に足す

aList addFirst: 要素.
リストが空かどうか調べる

aList isEmpty
リストのサイズを調べる

aList size
リスト同士の結合
,(カンマ)演算子で二つのリストをつなぐと、連結した一つのリストを返 します。
ランダムアクセス
インデックスを指定するメソッドには at:put: at: removeAt: などがあ ります。
並べ換え
OrderedCollection のサブクラスとして SortedCollection があります。 これは各 Collection のインスタンスに asSortedCollection メソッドを送る と得られるもので、値が整列されています。 このクラスは MappedCollection とは違い、要素の挿入に対して順序を保証す るものではありません。 しかし、得られたインスタンスに対しては do: により順番に値にアクセスで きます。

プログラム例


testList: aStream
  | aCollection aSorted |
  aCollection←OrderedCollection new.
  aCollection addLast: 'abc';
              addLast: 'def';
              addFirst: 'ghi'.
  aStream cr.
  aCollection do: [:each | each printOn: aStream.].
  aStream cr; nextPutAll: aCollection size asString.
  aSorted ← aCollection asSortedCollection.
  aStream cr.
  [aSorted notEmpty] whileTrue: [ aSorted removeFirst printOn: aStream].
  aStream endEntry.

例7-5

ここでも同様に二進数における 1 の数で並び替えます。 Smalltalk では数はオブジェクトですから、二進数における 1 の数を求める には、既存の Integer クラスのメソッドに numberOfBits を書きます。


numberOfBit
| num returnValue |
	num ← self.
	returnValue ← 0.
	[ num > 0 ] whileTrue: [ returnValue ← returnValue + (num \\\ 2 ).
							 num ← num // 2.].
	 ↑returnValue.
テスト例

( 0 to: 20) collect: [ :each | each numberOfBit ].


 #(0 1 1 2 1 2 2 3 1 2 2 3 2 3 3 4 1 2 2 3 2)


これに対して 0 から 20 を並べるには次のようにする。


(0 to: 20) sortBy: [:a :b | (a numberOfBit > b numberOfBit) not].

このように Smalltalk はまとまった手続きがブロックオブジェクトとして記 述できたり、 0 から 20 までのリストが 0 to: 20と書けたり するので簡潔に記述できる。

C 言語での実装

C++ や Java では線形リストがあらかじめ用意されていましたが、 C 言語に はありません。そこで、ポインタや構造体を使って実現する必要があります。

線形リストでは左の枝には必ず値を持つ頂点が付くので、左の枝を作る代わり に値を直接入れることにします。 右の枝はそのまま別の頂点を指すことにしますので、頂点を作る構造体は、左 の枝を意味する場所には値を入れ、右の枝を表す場所には別の頂点へのポイン タを入れることにします。 線形リストの頂点の構造体を定義すると次のようになります。


typedef struct lst {
  char *item;
  struct lst  *next;
} LIST;

このようにして作った構造体をメモリ上に作り、next フィールドに次の頂点 の番地を入れて連結します。 ところで、リストの最後の nil を含んだ葉も同じ頂点である必要があります。 そこで、 next フィールドに NULL が入っていることとして表現することにし ます。 すると、空のリストは NULL が入った頂点だけで表現することになります。 一方、プログラム内でリストを扱うには、先頭の頂点へのポインタを持ちます。 したがって、プログラムで空のリストを作るには、次のようにします。


LIST *aNode = (LIST *)malloc(sizeof(LIST));
aNode->next=NULL;

先頭への要素の追加

線形リストの先頭に文字列の要素を付け足す関数 addfirst(LIST *firstNode, char *str) は次のよう処理を行います。



まず、C 言語は値呼出しなので、関数の引数に含まれている先頭の 頂点へのポインタ firstNode は変更できません。 これは先頭の頂点のアドレスは変更できないことを意味します。 ですから、新たな要素 *str は既存の頂点へ代入されることになります。 したがって、付け足すべき新たな頂点の領域を確保したら、この領域は既存の 現在の先頭の要素が入ることになります。 つまり、新しい頂点の領域を確保したらその頂点は二番目の頂点にな るようにします。


新しい頂点の next フィールドは三番目、つまり付け足す前では二番目の頂点 を指すようにします。これは付け足す前では先頭の next フィールドに入って いたアドレスになります。


したがって処理をまとめると次のようになります。

  1. 新しい頂点の領域を作る
  2. 先頭の頂点の内容(要素、次の頂点へのポインタ)を新しい頂点にコピーす る。
  3. 先頭の頂点の要素には付け足すべき要素を代入し、次の頂点へのポインタ には新しく確保した頂点の領域のアドレスを入れる。

void addfirst(LIST *firstNode, char *str){
   LIST *newNode=(LIST *)malloc(sizeof(LIST));
   newNode->item = firstNode->item;
   newNode->next = firstNode->next;
   firstNode->item = str;
   firstNode->next = newNode;
}

最後への要素の追加

一方、リストの最後に文字配列の要素を付け足す関数 addend(LIST *firstNode, char *str) は、nil 頂点を探して、その頂点に新しい要素を入れ、新たに作っ た nil 頂点を指すようにします。


void addend(LIST *firstNode, char *str){
  LIST *newNode=(LIST *)malloc(sizeof(LIST));
  LIST *p=firstNode;
  while(p->next != NULL){
    p = p->next;
  }
  p->item = str;
  p->next = newNode;
  newNode->next = NULL;
}

最初に付け加えるのと違い、リストすべての要素を見なければならないので、 計算時間が余計にかかっていることに注意して下さい。

指している頂点の削除

リストの各要素に対して、単純に指している頂点を削除すると、その頂点を指 す手前の頂点が次の頂点を指すようにできません。 そのため、特定の要素を消す場合、他の接続関係を調整してやる必要がありま す。 また、先頭の要素を連続して消したい場合を考えると、リストの先頭を指すデー タは消えても、領域が消えては次の先頭を探すことができません。 そのため、先頭の領域は消さず、その領域に次のデータが入っているようにす る必要があります。 したがって、領域としてはその次の頂点の領域を消すことにし、消 す前に次の頂点の持つ情報を現在指している領域にコピーすることとします。

  1. 消すべき領域である、次の頂点のアドレスを記憶する
  2. 現在の頂点の領域に、次の頂点の要素とポインタをコピーする
  3. 消すべき領域を消す

void removenode(LIST *aNode){
  LIST *deleteNode = aNode->next;
  aNode->item = deleteNode->item;
  aNode->next = deleteNode->next;
  free(deleteNode);
}

サンプルコード


#include <stdio.h>
#include <stdlib.h>
typedef struct lst {
  char *item;
  struct lst  *next;
} LIST;
void addend(LIST *pointer, char *i){
  LIST *newnode=(LIST *)malloc(sizeof(LIST));
  newnode->next=NULL;
  while(pointer->next != NULL){
    pointer = pointer->next;
  }
  pointer->next=newnode;
  pointer->item=i;
}
void addfirst(LIST *pointer, char *i){
  LIST *newnode=(LIST *)malloc(sizeof(LIST));
  newnode->next=pointer->next;
  newnode->item=pointer->item;
  pointer->next=newnode;
  pointer->item=i;
}
int empty(LIST *pointer){
  return pointer->next == NULL;
}
int size(LIST *pointer){
  LIST *p=pointer;
  int i=0;
  while(p->next != NULL){
    p=p->next;
    i++;
  }
  return i;
}
void removeitem(LIST *pointer){
  LIST *d=pointer->next;
  pointer->item=d->item;
  pointer->next=d->next;
  /* free((pointer->next)->item) */
  free(d);
}

main(){
  LIST *firstNode=(LIST *)malloc(sizeof(LIST));
  LIST *p;
  firstNode->next=NULL;
  addend(firstNode,"abc");
  addend(firstNode,"def");
  addfirst(firstNode,"ghi");
  p=firstNode;
  while(p->next!=NULL){
    printf("%s\n",p->item);
    p=p->next;
  }
  printf("size = %d\n",size(firstNode));
  while(!empty(firstNode)){
    p=firstNode;
    while(p->next!=NULL){
      printf("%s ",p->item);
      p=p->next;
    }
    printf("\n");
    removeitem(firstNode);
  }
  
}

7-2. 線形リストと配列

線形リストも配列も複数のものを格納することができますが、それぞれにおい て処理の効率が変わります。 従って、用途に応じて選択する必要があります。

配列はメモリの連続した領域を確保し、 n 番目の要素へのアクセスを許しま す。 n 番目の要素へのアクセスは既に紹介したように、 (0 番目の要素のアドレス ) + n (要素のサイズ) でアドレスを計算します。 従って、時間計算量は n を計算する手間になります。 これは n の桁数に比例した時間かかります。 要素数が最大 N だとすると、全体の時間計算量は O( log N ) になります。 一方、特定の位置(先頭など)に要素を挿入したり、削除したりするには全部の 要素をずらす必要があります。 一つの要素をずらすのに定数時間で済んでも、最大で全要素数 N に比例する 時間だけかかります。 従って、全体の時間計算量は O( N ) になります。

線形リストでは n 番目の要素にアクセスするには、基本的には先頭から順に 見ていくしかありません。 従って、時間計算量は O( N ) になります。 しかし、一方、特定の位置に要素を挿入したり、削除したりするには、メモリ の適当な位置に頂点を確保し、リンクをつなげるだけなので、 O( 1 ) の時間計算量で済みます。

以上のように要素へのランダムアクセスが必要な時は配列、要素の追加、削除 が頻繁な時は線形リストが有利なことが分かります。

ランダムアクセス要素の追加、削除
配列 O( log N ) O( N )
線形リスト O( N ) O( 1 )

7-3. 双方向リスト

双方向リストとは、次の頂点へのポインタと前の頂点へのポインタの両方を保 持するものです。 特定の頂点に注目して処理する場合に便利です。 エディタなど、特定のに注目した処理をするのに用いられます。 実は C++ の list, Java の LinkedList, Smalltalk の OrderedList のどれも双方向リストです。 C++ では Iterator に対して -- 演算が可能です。 一方、Java では java.util.ListIterator には previous() メソッドが用意 されています。 Smalltalk ではイテレータを使いませんが、ランダムアクセスとして、整数値 をインデックスとして値を取り出す at: や、逆に指定したインデックスに値 を入れる at:put: などのメソッドが定義されています。

演習7-1

双方向リストを使って less を作りなさい。 less とは more を拡張したコマンドで、次の動作をします。

  1. コマンドラインでファイル名を指定します。
  2. 先頭の 24 行を表示します。
  3. 空白か d の入力で次の 24 行を表示します。
  4. u の入力で前の 24 行を表示します。
  5. q の入力で終了します。
  6. < で先頭へ、 > で最後(から 24 行前)へ注目行を移動します。

コマンドラインでの指定法

main 関数を int main(int argc, char*[] argv) で指定すると、コマンドライ ンで文字列をプログラムに渡せるようになります。 コマンドラインになにも指定しないと、 argc が 1 になり、第二引数の char *argv[] の argv[0] に起動コマンド名が入ります。 起動時にコマンド(.\a.exe) の後ろに文字列を空白で区切ると、それぞれが argv[] の配列に入ります。

例7-6

#include <stdio.h>
int main(int argc, char * argv[]){
  int i;
  if(argc==1){
    printf("Usage: %s filename ...\n",argv[0]);
    return 2;
  }else{
    printf("起動コマンド %s\n",argv[0]);
    for(i=1;i<argc;i++){
      printf("引数 %d = %s\n",i,argv[i]);
    }
  }
  return 0;
}

付録 C 言語での双方向リストの実装


#include <stdio.h>
#include <stdlib.h>
typedef struct blst {
  char *item;
  struct blst  *next;
  struct blst  *previous;
} BLIST;
void add(BLIST *base, BLIST *pointer, char *i){
  BLIST *newnode=(BLIST *)malloc(sizeof(BLIST));
  newnode->item = i;
  newnode->next = pointer->next;
  if(pointer->next == NULL){
    newnode->previous = base->previous;
    base->previous = newnode;
  }else{
    newnode->previous = (pointer->next)->previous;
    (pointer->next)->previous = newnode;
  }
  pointer->next = newnode;
}

void printlist(BLIST *base){
  BLIST *p=base;
  while((p=p->next)!=NULL){
    printf("%s ",p->item);
  }
  printf("\n");
}
void printreverse(BLIST *base){
  BLIST *p=base;
  while((p=p->previous)!=NULL){
    printf("%s ",p->item);
  }
  printf("\n");
}
int empty(BLIST *base){
  return base->next == NULL;
}
int size(BLIST *base){
  BLIST *p=base;
  int i=0;
  while((p=p->next) != NULL){
    i++;
  }
  return i;
}
int removenode(BLIST *base, BLIST *pointer){
  BLIST *p,*n;
  if(base==pointer){
    return -1;
  }else{
    p = pointer->previous;
    n = pointer->next;
    if(p==NULL){
      base->next = n;
    }else{
      p->next = n;
    }
    if(n==NULL){
      base->previous = p;
    }else{
      n->previous = p;
    }
    /* free(pointer->item) */
    free(pointer);
 }
}
    
main(){
  BLIST *l=(BLIST *)malloc(sizeof(BLIST));
  BLIST *p;
  l->next=l->previous=NULL;
  add(l,l,"abc");
  printlist(l); printreverse(l);  printf("size = %d\n",size(l));
  add(l,l,"def");
  printlist(l);  printreverse(l);  printf("size = %d\n",size(l));
  add(l,l->previous,"ghi");
  printlist(l);  printreverse(l);  printf("size = %d\n",size(l));
  add(l,l->previous,"jkl");
  printlist(l);  printreverse(l);  printf("size = %d\n",size(l));
  add(l,l->next->next->next,"mno");
  printlist(l);  printreverse(l);  printf("size = %d\n",size(l));
  while(!empty(l)){
    printlist(l);  printreverse(l);  printf("size = %d\n",size(l));
    removenode(l, l->next);
  }
}

坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科