루프 불변은 무엇입니까?


268

CLRS의 "알고리즘 소개"를 읽고 있습니다. 2 장에서 저자는 "루프 불변량"을 언급합니다. 루프 불변은 무엇입니까?


4
:이 설명에서 꽤 좋은 것 같다 cs.miami.edu/~burt/learning/Math120.1/Notes/LoopInvar.html
톰 Gullen


누군가가 루프 불변의 개념을 기반으로 실제 알고리즘 코딩 문제를 해결하려는 경우 HackerRank 에서이 문제 를 참조하십시오 . 또한 개념을 자세히 설명하기 위해 삽입 정렬 문제를 언급했습니다.
RBT

이론적 이해를 위해 여기 에서 참고 사항을 참조 할 수도 있습니다 .
RBT

답변:


345

간단히 말해서, 루프 불변은 루프의 모든 반복에 대해 유지되는 일부 술어 (조건)입니다. 예를 들어 for다음과 같은 간단한 루프를 살펴 보겠습니다 .

int j = 9;
for(int i=0; i<10; i++)  
  j--;

이 예제에서는 모든 반복에 대해 true입니다 i + j == 9. 또한 약한 불변은 사실이다 i >= 0 && i <= 10.


29
이것은 훌륭한 예입니다. 강사가 루프 불변성을 설명하는 것을 여러 번 들었을 때, 그것은 단순히 '루프 조건'또는 이와 비슷한 것이 었습니다. 귀하의 예는 불변이 훨씬 더 클 수 있음을 보여줍니다.
Brian S

77
루프 불변 값이 루프의 목표가되어야하기 때문에 이것이 좋은 예가 아닙니다. CLRS는이를 사용하여 정렬 알고리즘의 정확성을 높입니다. 삽입 정렬의 경우 루프가 i를 반복한다고 가정하면 각 루프의 끝에서 i 번째 요소까지 배열이 정렬됩니다.
Clash

5
예,이 예는 잘못된 것이 아니라 충분하지 않습니다. 루프 불변은 그 자체가 아니라 목표를 제시해야하기 때문에 @Clash up을 백업합니다.
Jack

7
@Tomas Petricek-루프가 종료되면 i = 10이고 j = -1입니다. 그래서 당신이 제공 한 약한 불변의 예가 정확하지 않을 수 있습니다 (?)
Raja

7
위의 의견에 동의하지만 목표가 여기에 정의되어 있지 않기 때문에이 답변을 찬성했습니다. 적합한 목표를 정의하면 예제가 좋습니다.
Flavius

119

나는이 매우 간단한 정의를 좋아한다 : ( source )

루프 불변은 루프의 각 반복 직전과 직후에 반드시 필요한 [프로그램 변수 중] 조건입니다. (이것은 반복을 통해 진실이나 허위에 대해 아무 말도하지 않습니다.)

루프 자체는 그다지 중요하지 않습니다. 그러나 적절한 불변이 주어지면 알고리즘의 정확성을 입증하는 데 도움이 될 수 있습니다. CLRS의 간단한 예는 아마도 정렬과 관련이 있습니다. 예를 들어, 루프가 시작될 i때이 배열 의 첫 번째 항목이 정렬 된 것처럼 루프가 변하지 않도록하십시오 . 이것이 실제로 루프 불변임을 증명할 수 있다면 (즉, 모든 루프 반복 전후에 유지됨)이를 사용하여 정렬 알고리즘의 정확성을 증명할 수 있습니다. 루프 종료시 루프 불변은 여전히 ​​만족합니다 카운터 i는 배열의 길이입니다. 따라서 첫 번째 i항목이 정렬되면 전체 배열이 정렬됩니다.

더 간단한 예 : 루프 불변량, 정확성 및 프로그램 도출 .

루프 불변을 이해하는 방식은 프로그램에 대해 추론하기위한 체계적이고 공식적인 도구입니다. 우리는 사실을 증명하는 데 중점을 둔 단일 진술을 루프 불변이라고 부릅니다. 이것은 우리의 논리를 구성합니다. 비정형 적으로 일부 알고리즘의 정확성에 대해 비공식적으로 논쟁 할 수는 있지만, 루프 불변량을 사용하면 매우 신중하게 생각하고 추론이 완전해야합니다.


10
"각 반복 직후"에는 루프 종료 방법에 관계없이 루프 종료 후가 포함됩니다.
Robert S. Barnes

이 답변에 대단히 감사합니다! 이 루프에서 변하지 않는 목적은 알고리즘의 정확성을 증명하는 것입니다. 다른 답변은 루프 불변성에 중점을 둡니다!
Neekey

39

루프와 불변을 처리 할 때 많은 사람들이 즉시 깨닫지 못하는 것이 있습니다. 루프 불변과 루프 조건 (루프의 종료를 제어하는 ​​조건) 사이에 혼동됩니다.

사람들이 지적했듯이 루프 불변은 참이어야합니다.

  1. 루프가 시작되기 전에
  2. 루프가 반복 될 때마다
  3. 루프가 종료 된 후

(루프 본문 중에 일시적으로 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거짓이어야합니다. 그러나 결과적으로 전체 논리 문은 여전히 ​​널 의미입니다. (논리에서 (false)이면 (뭔가)는 항상 참입니다.)

이제 루프가 종료 될 때 루프 조건부 조건에 대해 필자가 거짓이라고 말한 것은 무엇입니까? 배열에서 요소가 발견되면 루프가 종료되면 루프 조건이 참입니다!? 내포 된 루프 조건은 실제로는 while ( A[mid] != a && start <= end )아니지만 첫 번째 부분이 내포 된 이후 실제 테스트를 단축 하기 때문에 실제로는 그렇지 않습니다 . 이 조건은 루프가 종료되는 방식에 관계없이 루프 이후에 분명히 거짓입니다.


모든 논리 문이 조건에 관계없이 항상 참일 수 있으므로 논리 문을 루프 불변 값으로 사용하는 것이 이상합니다.
acgtyrant

보장이 없기 때문에 그리 이상한 나는 생각한다 a에 존재한다 A. 비공식적으로는 "키 a가 배열에있는 경우 키 사이 startend포함 되어야 합니다"입니다. 그런 다음 A[start..end]비어 있으면 aA에없는 것입니다.
scanny

33

이전 답변은 루프 불변성을 매우 좋은 방법으로 정의했습니다.

다음은 CLRS 작성자가 루프 불변을 사용 하여 삽입 정렬의 정확성 을 입증 한 방법입니다.

삽입 정렬 알고리즘 (도서 참조) :

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]은 테스트 할 배열입니다. 요소가 하나뿐이므로 정렬됩니다. 따라서 불변은 만족된다.

유지 관리 : 각 반복 후 불변을 확인하여 쉽게 확인할 수 있습니다. 이 경우에는 만족합니다.

종료 : 알고리즘의 정확성을 입증하는 단계입니다.

루프가 종료되면 j = n + 1의 값입니다. 다시 루프 불변 값이 충족됩니다. 이는 하위 배열 [1 ~ n]이 정렬되어야 함을 의미합니다.

이것이 알고리즘으로하고 싶은 일입니다. 따라서 우리의 알고리즘은 정확합니다.


1
동의합니다. 종료 진술은 여기서 매우 중요합니다.
Gaurav Aradhye

18

모든 좋은 답변 외에도 Jeff Edmonds의 알고리즘에 대한 생각 방법 의 좋은 예가 개념을 잘 설명 할 수 있습니다.

예 1.2.1 "찾기 최대 두 손가락 알고리즘"

1) 사양 : 입력 인스턴스는 요소 목록 L (1..n)로 구성됩니다. 출력은 L (i)이 최대 값을 갖도록 인덱스 i로 구성됩니다. 이 값이 같은 항목이 여러 개 있으면 그 중 하나가 반환됩니다.

2) 기본 단계 : 두 손가락 방법을 결정합니다. 오른쪽 손가락이 목록 아래로 내려갑니다.

3) 진행 측정 : 진행 측정은 오른손 손가락의 목록을 따라 얼마나 멀리 있는지입니다.

4) 루프 불변 : 루프 불변은 왼쪽 손가락이 지금까지 오른손 손가락에서 가장 큰 항목 중 하나를 가리킴을 나타냅니다.

5) 주요 단계 : 반복 할 때마다 목록에서 한 손가락 아래로 오른쪽 손가락을 움직입니다. 오른쪽 손가락이 왼쪽 손가락의 항목보다 큰 항목을 가리키면 왼쪽 손가락을 오른쪽 손가락과 같이 움직입니다.

6) 진행 : 오른쪽 손가락이 한 항목 씩 이동하기 때문에 진행합니다.

7) 루프 불변 값 유지 : 루프 불변 값이 다음과 같이 유지되었음을 알고 있습니다. 각 단계에서 새 왼쪽 손가락 요소는 최대 (오래된 왼쪽 손가락 요소, 새 요소)입니다. 루프 불변에 의해, 이것은 Max (Max (짧은리스트), 새로운 요소)입니다. 수학적으로 이것은 최대 (더 긴 목록)입니다.

8) 루프 불변 값 설정 : 처음에는 두 손가락을 첫 번째 요소로 가리켜 서 루프 불변 값을 설정합니다.

9) 종료 조건 : 오른쪽 손가락이 목록 탐색을 마치면 완료됩니다.

10) 끝 : 결국, 우리는 문제가 다음과 같이 해결된다는 것을 알고 있습니다. 종료 조건에 따라 오른쪽 손가락에 모든 항목이 표시되었습니다. 루프 불변으로, 왼쪽 손가락이 최대 값을 가리 킵니다. 이 항목을 반환하십시오.

11) 종료 및 실행 시간 : 필요한 시간은 목록 길이의 일정한 시간입니다.

12) 특수 사례 : 동일한 값을 가진 여러 항목이 있거나 n = 0 또는 n = 1 인 경우 어떻게되는지 확인하십시오.

13) 코딩 및 구현 세부 사항 : ...

14) 정식 증명 : 알고리즘의 정확성은 위의 단계에서 따릅니다.


나는이 답변이 불변의 직관적 인 요점에 실제로 "손가락을 넣는다"고 생각한다. :)
scanny

6

루프 불변 값은 반복이 시작될 때마다 그리고 루프가 종료 될 때 참이어야하는 변수 간의 중요한 관계를 나타내는 어설 션으로 간주 될 때 반복 알고리즘 설계에 도움이 될 수 있습니다. 이것이 유지된다면, 계산은 효과로가는 길에 있습니다. false이면 알고리즘이 실패한 것입니다.


5

이 경우 불변은 모든 루프 반복의 특정 지점에서 참이어야하는 조건을 의미합니다.

계약 프로그래밍에서 불변은 공용 메소드가 호출되기 전후에 (계약에 의해) 충족되어야하는 조건입니다.


4

불변의 의미는 결코 변하지 않습니다

여기서 루프 불변은 "루프에서 변수에 발생하는 변화 (증가 또는 감소)가 루프 조건을 변경하지 않고, 즉 조건이 충족되고있다"는 것을 의미하며, 루프 불변 개념이왔다


2

루프 불변 속성은 루프 실행의 모든 ​​단계 (즉, 루프, while 루프 등)를 유지하는 조건입니다.

이것은 루프 불변 증명에 필수적이며, 여기서는 실행의 모든 ​​단계에서이 루프 불변 속성이 유지되는 경우 알고리즘이 올바르게 실행됨을 보여줄 수 있습니다.

알고리즘이 정확하려면 루프 불변 값이 다음을 유지해야합니다.

초기화 (시작)

유지 보수 (각 단계 후)

종료 (완료시)

이것은 많은 것들을 평가하는 데 사용되지만 가장 좋은 예는 가중 그래프 순회에 대한 욕심 많은 알고리즘입니다. 욕심 많은 알고리즘이 최적의 솔루션 (그래프를 가로 지르는 경로)을 산출하려면 가능한 가장 낮은 가중치 경로로 모든 노드를 연결해야합니다.

따라서 루프 불변 속성은 가져온 경로의 가중치가 가장 작습니다. 상기 시작 이 속성은 (는이 경우, 거짓없는) 사실, 그래서 우리는 어떤 가장자리를 추가하지 않았습니다. 에서 각 단계 , 우리는 그렇게 다시 우리는 가장 낮은 무게 경로를 복용하고, 가장 낮은 무게 에지 (욕심 단계)를 따르십시오. 에서 결국 우리의 재산 또한 사실이다, 그래서 우리는 가장 낮은 가중 경로를 발견했다.

알고리즘이이를 수행하지 않으면 최적이 아님을 증명할 수 있습니다.


1

루프로 일어나는 일을 추적하기는 어렵습니다. 목표 동작을 달성하지 않고 종료하거나 종료하지 않는 루프는 컴퓨터 프로그래밍에서 일반적인 문제입니다. 루프 불변량 도움말. 루프 불변은 프로그램에서 변수 간의 관계에 대한 공식적인 진술로, 루프가 실행되기 직전 (불변을 설정) 직전에 유지되며 루프를 통해 매번 루프를 통해 (불변을 유지하면서) 다시 참입니다. ). 다음은 코드에서 루프 불 변형을 사용하는 일반적인 패턴입니다.

... // 루프 불변 값은 여기서 참이어야
하고 while (TEST CONDITION) {
// 루프의 상단
...
// 루프의 밑변에서
// 루프 불변 식은 여기서 참이어야합니다
}
// 종료 + 루프 불변 식 = 목표
...
루프의 상단과 하단 사이에서 아마도 헤드가 루프의 목표에 도달하는 방향으로 만들어지고 있습니다. 이것은 불변을 방해 (거짓) 할 수 있습니다. 루프 불변의 포인트는 매번 루프 바디를 반복하기 전에 불변이 복원 될 것이라는 약속입니다. 여기에는 두 가지 장점이 있습니다.

복잡한 데이터 종속 방식으로 다음 단계로 작업을 진행하지 않습니다. 각 패스는 다른 모든 패스와 독립적으로 루프를 통과하며, 변하지 않는 패스는 패스를 함께 작업 전체에 묶는 역할을합니다. 루프가 작동하는 이유는 루프를 통과 할 때마다 루프 불변 값이 복원된다는 이유로 줄어 듭니다. 이것은 루프의 복잡한 전반적인 동작을 작은 간단한 단계로 나눕니다. 각 단계는 개별적으로 고려 될 수 있습니다. 루프의 테스트 조건은 변하지 않는 부분이 아닙니다. 이것이 루프를 종료시키는 원인입니다. 루프를 종료해야하는 이유와 루프가 종료 될 때 루프가 목표를 달성하는 이유는 두 가지를 별도로 고려하십시오. 루프를 통해 매번 터미네이션 조건을 만족시키기 위해 더 가까이 이동하면 루프가 종료됩니다. 이를 확인하는 것이 쉬운 경우가 많습니다. 예 : 고정 상한에 도달 할 때까지 카운터 변수를 1 씩 스테핑합니다. 때로는 해지에 대한 추론이 더 어려울 때가 있습니다.

루프 불변 값은 종료 조건에 도달하고 불변 값이 참일 때 목표에 도달 할 수 있도록 작성해야합니다.

불변 + 종료 => 목표
종료를 제외한 모든 목표 달성을 포착하는 단순하고 관련이있는 불변을 만드는 연습이 필요합니다. 루프 불변량을 표현하기 위해 수학 기호를 사용하는 것이 가장 좋지만, 지나치게 복잡한 상황으로 이어질 때 명확한 산문과 상식에 의존합니다.


1

댓글 권한이 없습니다.

언급 한대로 @Tomas Petricek

또한 더 약한 불변은 i> = 0 && i <10입니다 (이것이 연속 조건이므로!) "

루프 불변 값은 어떻습니까?

나는 지금까지 내가 이해하지 틀렸다 희망 [1] , 루프 불변, 그것은 이전과 각 반복 (유지 관리) 후 사실 일 것이다 루프 (초기화)의 시작 부분에 진실하고 이 또한 사실 후가 될 것입니다 루프 종료 (Termination) . 그러나 마지막 반복 후 i는 10이됩니다. 따라서 i> = 0 && i <10 조건은 false가되고 루프를 종료합니다. 루프 불변의 세 번째 속성 (Termination)을 위반합니다.

[1] http://www.win.tue.nl/~kbuchin/teaching/JBP030/notebooks/loop-invariants.html


루프가 실제로 그러한 조건에서 실행되지 않기 때문에 이것이 사실이라고 생각합니다.
muiiu

0

루프 불변은와 같은 수학 공식 (x=y+1)입니다. 이 예에서 xy루프에서 두 변수를 나타낸다. 코드의 실행에 걸쳐 그 변수의 변화하는 행동을 고려할 때 모든 가능한을 테스트하는 것은 거의 불가능 x하고 y가치와 그들이 어떤 버그를 생성되는지 확인합니다. x정수 라고합시다 . 정수는 메모리에서 32 비트 공간을 보유 할 수 있습니다. 그 수가 초과되면 버퍼 오버 플로우가 발생합니다. 따라서 코드를 실행하는 동안 코드가 해당 공간을 초과하지 않도록해야합니다. 이를 위해서는 변수 간의 관계를 나타내는 일반 공식을 이해해야합니다. 결국, 우리는 단지 프로그램의 행동을 이해하려고 노력합니다.


0

간단히 말해서, 모든 루프 반복에서 참인 LOOP 조건입니다.

for(int i=0; i<10; i++)
{ }

이것에서 우리는 i의 상태를 말할 수 있습니다. i<10 and i>=0



-1

선형 검색 (책에서 주어진 운동에 따라)에서 주어진 배열에서 값 V를 찾아야합니다.

0 <= k <길이의 배열을 스캔하고 각 요소를 비교하는 것은 간단합니다. V가 발견되거나 스캔이 배열의 길이에 도달하면 루프를 종료하십시오.

위의 문제에 대한 나의 이해에 따라

루프 불변량 (초기화) : V는 k-1 반복에서 찾을 수 없습니다. 첫 번째 반복은 -1이므로 위치 -1에서 V를 찾을 수 없다고 말할 수 있습니다.

유지 관리 : 다음 반복에서 k-1에서 찾을 수없는 V는 참입니다.

종료 : k 위치 또는 k에서 찾은 V가 어레이의 길이에 도달하면 루프를 종료합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.