CLRSの「Introduction to Algorithm」を読んでいます。第2章では、著者は「ループ不変量」に言及しています。ループインバリアントとは何ですか?
CLRSの「Introduction to Algorithm」を読んでいます。第2章では、著者は「ループ不変量」に言及しています。ループインバリアントとは何ですか?
回答:
簡単に言うと、ループ不変量とは、ループのすべての反復で成立する述語(条件)です。たとえばfor
、次のような単純なループを見てみましょう。
int j = 9;
for(int i=0; i<10; i++)
j--;
この例では、(すべての反復で)trueですi + j == 9
。また、真の弱い不変条件は、
i >= 0 && i <= 10
です。
私はこの非常に単純な定義が好きです:(ソース)
ループ不変量は、[プログラム変数の中で]条件であり、ループの各反復の直前と直後に必ずtrueになります。(これは、反復の途中でその真実または虚偽について何も言わないことに注意してください。)
それ自体では、ループ不変量はあまり機能しません。ただし、適切な不変条件が与えられれば、アルゴリズムの正確性を証明するために使用できます。CLRSの簡単な例は、おそらくソートに関係しています。たとえば、ループ不変を次のようにすると、ループの開始時に、i
この配列の最初のエントリがソートされます。これが実際にループ不変であることを証明できる場合(つまり、ループのすべての反復の前後で保持される場合)、これを使用して、ソートアルゴリズムの正確さを証明できます。ループの終了時でも、ループ不変は満たされます。 、そしてカウンターi
は配列の長さです。したがって、最初のi
エントリがソートされるとは、配列全体がソートされることを意味します。
さらに簡単な例:ループ不変式、正当性、プログラム導出。
ループ不変式を理解する方法は、プログラムについて推論するための体系的で正式なツールとしてです。真であることを証明することに焦点を当てた単一のステートメントを作成し、それをループ不変と呼びます。これでロジックが整理されます。アルゴリズムの正確性について非公式に議論することもできますが、ループ不変式を使用すると、慎重に考える必要があり、推論が気密になります。
ループや不変式を処理するときに、多くの人がすぐに気付かないことが1つあります。ループ不変条件と条件付きループ(ループの終了を制御する条件)の間で混乱します。
人々が指摘するように、ループ不変量は真でなければなりません
(ただし、ループの本体で一時的にfalseになる場合があります)。 一方、ループ 終了後、ループ条件は falseでなければなりません。そうでない場合、ループは決して終了しません。
したがって、ループ不変条件とループ条件は異なる条件でなければなりません。
複雑なループ不変の良い例は、二分探索です。
bsearch(type A[], type a) {
start = 1, end = length(A)
while ( start <= end ) {
mid = floor(start + end / 2)
if ( A[mid] == a ) return mid
if ( A[mid] > a ) end = mid - 1
if ( A[mid] < a ) start = mid + 1
}
return -1
}
したがって、ループの条件は非常に単純明快です。開始>終了すると、ループが終了します。しかし、なぜループが正しいのですか?それが正しいことを証明するループ不変量は何ですか?
不変式は論理ステートメントです。
if ( A[mid] == a ) then ( start <= mid <= end )
このステートメントは論理的なトートロジーです。これは、証明しようとしている特定のループ/アルゴリズムのコンテキストでは常に真です。また、終了後のループの正確さに関する有用な情報を提供します。
配列内の要素が見つかったために戻った場合、ステートメントは明らかに真です。A[mid] == a
それa
は、配列内にある場合、mid
開始と終了の間でなければならないためです。そして、ループ終了した場合ので、start > end
そのようなことを何も多数あり得ないstart <= mid
と mid <= end
、したがって、我々は文があることを知っているA[mid] == a
偽でなければなりません。ただし、結果として、全体的な論理ステートメントは依然としてnullの意味で真です。(論理的には、if(false)then(something)は常にtrueです。)
では、ループが終了したときに、ループ条件が必ずfalseになるということについてはどうでしょうか。要素が配列内で見つかると、ループが終了するとループ条件が真になります!?実際にはそうではありません。暗黙のループ条件は実際にはそうですwhile ( A[mid] != a && start <= end )
が、最初の部分が暗黙であるため、実際のテストを短くしています。この条件は、ループの終了方法に関係なく、ループの後は明らかに偽です。
a
ですA
。非公式には、「キーa
が配列に存在する場合、それはその間start
で発生する必要がありますend
」です。場合は、その次のA[start..end]
空である、すなわち、a
Aに存在しない
以前の回答では、ループ不変式を非常に良い方法で定義しています。
以下は、CLRSの作成者がループ不変式を使用して挿入ソートの正確さを証明した方法です。
挿入ソートアルゴリズム(Bookで指定):
INSERTION-SORT(A)
for j ← 2 to length[A]
do key ← A[j]
// Insert A[j] into the sorted sequence A[1..j-1].
i ← j - 1
while i > 0 and A[i] > key
do A[i + 1] ← A[i]
i ← i - 1
A[i + 1] ← key
この場合、ループ不変: サブ配列[1からj-1]は常にソートされます。
これをチェックして、アルゴリズムが正しいことを証明しましょう。
初期化:最初の反復の前にj = 2。したがって、サブアレイ[1:1]はテストされるアレイです。要素が1つしかないため、ソートされます。したがって、不変条件は満たされます。
メンテナンス:これは、各反復後に不変条件をチェックすることで簡単に確認できます。この場合は満足です。
終了:これは、アルゴリズムの正確性を証明するステップです。
ループが終了すると、j = n + 1の値になります。ここでもループ不変条件が満たされています。つまり、Sub-array [1 to n]をソートする必要があります。
これは、アルゴリズムで実行したいことです。したがって、アルゴリズムは正しいです。
すべての良い答えに加えて、Jeff EdmondsによるHow to Think About Algorithmsの優れた例 は、コンセプトを非常によく説明できると思います。
例1.2.1「Find-Max 2本指アルゴリズム」
1)仕様:入力インスタンスは、要素のリストL(1..n)で構成されます。出力は、L(i)が最大値を持つようなインデックスiで構成されます。この同じ値のエントリが複数ある場合は、それらのいずれか1つが返されます。
2)基本的な手順:2本指の方法を決定します。右の指がリストを実行します。
3)進行状況の測定:進行状況の測定は、右指がリストに沿ってどれだけ離れているかです。
4)ループ不変:ループ不変は、左指が右指がこれまでに遭遇した最大のエントリの1つを指していることを示します。
5)主な手順:反復ごとに、右指をリストの1つのエントリの下に移動します。右の指が左の指のエントリよりも大きいエントリを指している場合は、左の指を右の指と同じ位置に移動します。
6)進行する:右の指が1つのエントリを移動するため、進行します。
7)ループ不変量の維持:ループ不変量は次のように維持されています。各ステップで、新しい左の指の要素はMax(古い左の指の要素、新しい要素)です。ループ不変により、これはMax(Max(shorter list)、new element)です。数学的には、これはMax(長いリスト)です。
8)ループ不変式の確立:最初に、両方の指を最初の要素に向けてループ不変式を確立します。
9)終了条件:右指がリストの移動を終了すると、終了です。
10)終了:最終的に、問題は次のように解決されます。終了条件では、右の指がすべてのエントリに遭遇しました。ループ不変により、左指はこれらの最大値を指します。このエントリを返します。
11)終了および実行時間:必要な時間は、リストの長さの定数倍です。
12)特殊なケース:同じ値のエントリが複数ある場合、またはn = 0またはn = 1の場合にどうなるかを確認します。
13)コーディングと実装の詳細:...
14)正式な証明:アルゴリズムの正確さは、上記の手順に従います。
ループの不変式は、各反復の開始時とループの終了時に真でなければならない変数間の重要な関係を表すアサーションと見なされると、反復アルゴリズムの設計に役立ちます。これが当てはまる場合、計算は有効性に向かっています。falseの場合、アルゴリズムは失敗しています。
ループ不変プロパティは、ループ実行のすべてのステップ(つまり、ループ、whileループなど)を保持する条件です。
これは、ループ不変式の証明に不可欠であり、実行の各ステップでこのループ不変式のプロパティが保持されている場合に、アルゴリズムが正しく実行されることを示すことができます。
アルゴリズムが正しいためには、ループ不変式は次の条件を満たしている必要があります。
初期化(初め)
メンテナンス(後の各ステップ)
終了(終了時)
これは、一連のものを評価するために使用されますが、最も良い例は、重み付きグラフトラバーサルの貪欲なアルゴリズムです。貪欲なアルゴリズムが最適なソリューション(グラフ全体のパス)を生成するには、可能な限り最小の重みのパスにあるすべてのノードに接続する必要があります。
したがって、ループ不変のプロパティは、経路の重みが最小であることです。始まる、私たちはすべてのエッジを追加していないので、このプロパティには、(それが、この場合には、偽ではない)が真です。で、各ステップは、我々はそうもう一度、我々は最低重量パスを取っている、最低重量エッジ(貪欲ステップ)に従います。終わり、我々は最低の加重パスを発見したので、私たちの財産も同様です。
アルゴリズムがこれを行わない場合、最適ではないことを証明できます。
ループで何が起こっているかを追跡することは困難です。目的の動作を達成せずに終了または終了しないループは、コンピュータープログラミングの一般的な問題です。ループ不変式が役立ちます。ループ不変式は、プログラム内の変数間の関係についての正式なステートメントであり、ループが実行される直前に不変であり(不変条件を確立)、ループを通過するたびに(不変条件を維持し)ループの最下部で再び真になります。 )。以下は、コードでループ不変式を使用する一般的なパターンです。
... //ループ不変式はここで真でなければならない
(TEST CONDITION){
//ループの先頭
...
// ループの最下部
//ここでループ不変式はtrueでなければなりません
}
//終了+ループ不変式=目標
...
ループの上部と下部の間で、ループの目標に到達するための前進が行われていると考えられます。これは不変式を乱す(falseにする)場合があります。ループ不変量のポイントは、ループ本体を毎回繰り返す前に不変量が復元されるという約束です。これには2つの利点があります。
作業は、複雑でデータに依存する方法で次のパスに繰り越されることはありません。各パスは他のループとは独立してループを通過します。不変条件はパスを結合して作業全体に結合します。ループが機能する理由は、ループを通過するたびにループ不変式が復元されるという理由に限定されます。これにより、ループの複雑な全体的な動作が小さな単純なステップに分割されます。各ステップは個別に考えることができます。ループのテスト条件は不変条件の一部ではありません。それがループを終了させるものです。ループが終了する理由と、ループが終了したときにループが目的を達成する理由の2つを個別に検討します。ループが終了するたびに、ループを通過するたびにループが終了します。これを保証することはしばしば簡単です:例えば 一定の上限に達するまで、カウンター変数を1つずつ増やします。場合によっては、終了の理由がより難しいことがあります。
ループの不変条件は、終了の条件が達成され、不変条件がtrueの場合に目標が達成されるように作成する必要があります。
インバリアント+終了=>目標
終了以外のすべての目標達成を捕捉する単純で関連性のあるインバリアントを作成するには、練習が必要です。数学的な記号を使用してループ不変条件を表現するのが最善ですが、これが過度に複雑な状況につながる場合は、明確な散文と常識に依存します。
コメント権限がありません。
@Tomas Petricekあなたが言ったように
また、trueである弱い不変条件は、i> = 0 && i <10です(これは継続条件であるためです)。 "
どのようにループ不変ですか?
[1]を理解している限り、私は間違っていないといいのですが、ループの不変式はループの開始(初期化)でtrueになり、各反復(メンテナンス)の前後でtrueになり、その後もtrueになりますループの終了(Termination)。しかし、最後の反復の後、iは10になります。したがって、条件i> = 0 && i <10はfalseになり、ループを終了します。ループ不変式の3番目のプロパティ(終了)に違反しています。
[1] http://www.win.tue.nl/~kbuchin/teaching/JBP030/notebooks/loop-invariants.html
ループ不変式は、などの数式(x=y+1)
です。その例では、ループ内の2つの変数x
をy
表します。コードの実行を通して、これらの変数の変更の挙動を考えると、すべての可能性をテストすることはほぼ不可能であるx
とy
の値を、彼らはすべてのバグを生み出すかどうかを確認します。x
整数としましょう。整数は、メモリ内に32ビットのスペースを保持できます。その数を超えると、バッファオーバーフローが発生します。したがって、コードの実行全体を通して、そのスペースを超えないようにする必要があります。そのためには、変数間の関係を示す一般的な式を理解する必要があります。結局のところ、プログラムの動作を理解しようとするだけです。