मैं सीएलआरएस द्वारा "इंट्रोडक्शन टू एलगोरिदम" पढ़ रहा हूं। अध्याय 2 में, लेखक "लूप इनवेरिएंट्स" का उल्लेख करते हैं। लूप आक्रमणकारी क्या है?
मैं सीएलआरएस द्वारा "इंट्रोडक्शन टू एलगोरिदम" पढ़ रहा हूं। अध्याय 2 में, लेखक "लूप इनवेरिएंट्स" का उल्लेख करते हैं। लूप आक्रमणकारी क्या है?
जवाबों:
सरल शब्दों में, एक लूप इनवेरियंट कुछ विधेय (स्थिति) है जो लूप के प्रत्येक पुनरावृत्ति के लिए होता है। उदाहरण के लिए, आइए एक साधारण for
लूप देखें जो इस तरह दिखता है:
int j = 9;
for(int i=0; i<10; i++)
j--;
इस उदाहरण में यह सच है (प्रत्येक पुनरावृत्ति के लिए) i + j == 9
। एक कमजोर आक्रमणकारी जो सत्य भी है
i >= 0 && i <= 10
।
मुझे यह बहुत सरल परिभाषा पसंद है: ( स्रोत )
एक लूप इनवेरिएंट एक शर्त है [प्रोग्राम चर के बीच] जो लूप के प्रत्येक पुनरावृत्ति के तुरंत पहले और तुरंत बाद जरूरी है। (ध्यान दें कि यह एक सत्य या मिथ्यात्व के बारे में कुछ भी नहीं कहता है कि एक पुनरावृत्ति के माध्यम से।)
अपने आप में, एक लूप अपरिवर्तनीय बहुत कुछ नहीं करता है। हालांकि, एक उपयुक्त अशुभ संकेत दिया, यह एक एल्गोरिथ्म की शुद्धता साबित करने में मदद करने के लिए इस्तेमाल किया जा सकता है। सीएलआरएस में सरल उदाहरण संभवतः छंटाई के साथ करना है। उदाहरण के लिए, अपने लूप को अपरिवर्तित करें, जैसे कि लूप की शुरुआत में, i
इस सरणी की पहली प्रविष्टियां क्रमबद्ध हैं। यदि आप यह साबित कर सकते हैं कि यह वास्तव में एक लूप इनवेरिएंट है (अर्थात यह प्रत्येक लूप पुनरावृत्ति के पहले और बाद में है), तो आप इसका उपयोग सॉर्टिंग एल्गोरिथ्म की शुद्धता को साबित करने के लिए कर सकते हैं: लूप की समाप्ति पर, लूप इनवेरिएंट अभी भी संतुष्ट है , और काउंटर i
सरणी की लंबाई है। इसलिए, पहली i
प्रविष्टियों को क्रमबद्ध किया जाता है, जिसका अर्थ है कि संपूर्ण सरणी क्रमबद्ध है।
एक और भी सरल उदाहरण: लूप्स इनवेरिएंट्स, सुधार और प्रोग्राम व्युत्पत्ति ।
जिस तरह से मैं एक लूप अपरिवर्तनीय समझता हूं, वह कार्यक्रमों के बारे में व्यवस्थित, औपचारिक उपकरण के रूप में है। हम एक ऐसा बयान देते हैं जिसे हम सच साबित करने पर ध्यान केंद्रित करते हैं, और हम इसे लूप इनवेरिएंट कहते हैं। यह हमारे तर्क को व्यवस्थित करता है। जबकि हम बस कुछ एल्गोरिथ्म की शुद्धता के बारे में अनौपचारिक रूप से बहस कर सकते हैं, एक लूप इनवेरिएंट का उपयोग हमें बहुत सावधानी से सोचने के लिए मजबूर करता है और यह सुनिश्चित करता है कि हमारा तर्क एयरटाइट है।
एक बात यह है कि बहुत से लोगों को तुरंत नहीं पता है जब छोरों और आक्रमणकारियों से निपटते हैं। वे लूप इनवेरिएंट और लूप सशर्त (लूप की समाप्ति को नियंत्रित करने वाली स्थिति) के बीच भ्रमित हो जाते हैं।
जैसा कि लोग बताते हैं, लूप इनवेरियन सही होना चाहिए
(हालांकि यह लूप के शरीर के दौरान अस्थायी रूप से गलत हो सकता है)। दूसरी ओर लूप की स्थिति लूप समाप्त होने के बाद झूठी होनी चाहिए, अन्यथा लूप कभी समाप्त नहीं होगा।
इस प्रकार लूप इनवेरिएंट और लूप सशर्त अलग-अलग स्थिति होनी चाहिए।
द्विआधारी खोज के लिए एक जटिल लूप इनवेरिएंट का एक अच्छा उदाहरण है।
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
झूठा होना चाहिए। हालाँकि, परिणामस्वरूप समग्र तार्किक कथन अभी भी शून्य अर्थ में सत्य है। (तर्क में कथन यदि (असत्य) है तो (कुछ) हमेशा सत्य है)
अब लूप सशर्त के बारे में मैंने क्या कहा जो जरूरी है कि लूप समाप्त होने पर गलत है? ऐसा लगता है कि जब तत्व सरणी में पाया जाता है तो लूप की स्थिति तब सही होती है जब लूप समाप्त हो जाता है !? यह वास्तव में नहीं है, क्योंकि निहित लूप सशर्त वास्तव में है, while ( A[mid] != a && start <= end )
लेकिन हम वास्तविक परीक्षण को छोटा करते हैं क्योंकि पहला भाग निहित है। यह शर्त लूप के बाद स्पष्ट रूप से झूठी है कि लूप कैसे समाप्त होता है।
a
यह मौजूद है A
। अनौपचारिक रूप से यह होगा, "यदि कुंजी a
सरणी में मौजूद है, तो यह बीच start
और end
समावेशी होना चाहिए "। तब यह अनुसरण करता है कि यदि A[start..end]
खाली है, तो वह a
ए
पिछले उत्तरों ने एक लूप इनवेरिएंट को बहुत अच्छे तरीके से परिभाषित किया है।
इसके बाद सीएलआरएस के लेखकों ने इंसर्शन सॉर्ट की शुद्धता साबित करने के लिए लूप इनवेरिएंट का उपयोग किया ।
प्रविष्टि सॉर्ट एल्गोरिथ्म (जैसा कि पुस्तक में दिया गया है):
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.2.1 "द फाइंड-मैक्स टू-फिंगर एल्गोरिथम"
1) विनिर्देशों: एक इनपुट उदाहरण में तत्वों की एक सूची L (1..n) होती है। आउटपुट में एक इंडेक्स i होता है, जिसमें L (i) का अधिकतम मूल्य होता है। यदि इसी मान के साथ कई प्रविष्टियाँ हैं, तो उनमें से किसी एक को लौटा दिया जाता है।
2) बेसिक स्टेप्स: आप टू-फिंगर मेथड पर निर्णय लेते हैं। आपकी दाहिनी उंगली नीचे की ओर जाती है।
3) प्रगति का माप: प्रगति का माप आपकी दाहिनी उंगली की सूची से कितनी दूर है।
4) लूप इनवेरिएंट : लूप इनवेरिएंट बताता है कि आपकी बायीं ऊँगली एक दायीं उँगली से अब तक हुई सबसे बड़ी प्रविष्टियों में से एक है।
5) मुख्य चरण: प्रत्येक पुनरावृत्ति, आप सूची में एक प्रविष्टि के नीचे अपनी दाहिनी उंगली ले जाते हैं। यदि आपकी दाहिनी उंगली अब एक ऐसी प्रविष्टि की ओर इशारा कर रही है जो बड़ी है तो बाईं उंगली का प्रवेश, तो अपनी बाईं उंगली को अपनी दाहिनी उंगली से ले जाएं।
6) प्रगति करें: आप प्रगति करते हैं क्योंकि आपकी दाहिनी उंगली एक प्रवेश करती है।
7) लूप इनवेरिएंट बनाए रखें: आप जानते हैं कि लूप इनवेरिएंट को निम्न प्रकार से बनाए रखा गया है। प्रत्येक चरण के लिए, नई बाईं उंगली तत्व मैक्स (पुरानी बाईं उंगली तत्व, नया तत्व) है। लूप इनवेरिएंट द्वारा, यह मैक्स (अधिकतम (छोटी सूची), नया तत्व) है। मैथ- मैटिक, यह मैक्स (लंबी सूची) है।
8) लूप इनवेरिएंट की स्थापना : आप शुरू में लूप इनवेरिएंट को बिंदु से स्थापित करते हैं- दोनों अंगुलियों को पहले तत्व पर।
9) बाहर निकलने की स्थिति: आप तब करते हैं जब आपकी दाहिनी उंगली ने सूची को पीछे छोड़ दिया है।
10) अंत: अंत में, हम जानते हैं कि समस्या हल हो गई है। एग्जिट कोंड- tion द्वारा, आपकी दाहिनी उंगली ने सभी प्रविष्टियों का सामना किया है। लूप इनवेरिएंट द्वारा, आपकी बायीं ऊँगली इनमें से अधिकतम पर इंगित करती है। इस प्रविष्टि को वापस करें।
11) समाप्ति और रनिंग टाइम: आवश्यक समय सूची की लंबाई के कुछ स्थिर समय है।
12) विशेष मामले: चेक करें कि क्या होता है जब एक ही मान के साथ कई प्रविष्टियाँ हों या जब n = 0 या n = 1 हो।
13) कोडिंग और कार्यान्वयन विवरण: ...
14) औपचारिक प्रमाण: एल्गोरिथ्म की शुद्धता उपरोक्त चरणों से होती है।
यह ध्यान दिया जाना चाहिए कि एक लूप इनवेरिएंट पुनरावृत्त एल्गोरिदम के डिजाइन में मदद कर सकता है, जब एक मुखर माना जाता है जो चर के बीच महत्वपूर्ण रिश्तों को व्यक्त करता है जो हर पुनरावृत्ति की शुरुआत में सच होना चाहिए और जब लूप समाप्त हो जाता है। यदि यह माना जाता है, तो गणना प्रभावशीलता की राह पर है। यदि गलत है, तो एल्गोरिथ्म विफल हो गया है।
इस मामले में अपरिवर्तनीय का अर्थ है एक शर्त जो प्रत्येक लूप पुनरावृत्ति में एक निश्चित बिंदु पर सही होनी चाहिए।
कॉन्ट्रैक्ट प्रोग्रामिंग में, एक अपरिवर्तनीय स्थिति एक शर्त है जिसे किसी भी सार्वजनिक विधि से पहले और बाद में (अनुबंध द्वारा) सही होना चाहिए।
लूप इनवेरिएंट प्रॉपर्टी एक ऐसी स्थिति है जो लूप्स निष्पादन के हर चरण के लिए होती है (यानी लूप के लिए, जबकि लूप आदि)।
यह लूप इन्वारिएंट प्रूफ के लिए आवश्यक है, जहां कोई यह दिखाने में सक्षम है कि एक एल्गोरिथ्म सही ढंग से निष्पादित होता है यदि इसके निष्पादन के हर चरण में यह लूप इनवेरियेंट प्रॉपर्टी रखता है।
एल्गोरिथ्म सही होने के लिए, लूप इनवेरिएंट को अपने पास रखना चाहिए:
प्रारंभ (शुरुआत)
रखरखाव (प्रत्येक चरण के बाद)
समाप्ति (जब यह समाप्त हो जाए)
इसका उपयोग चीजों के एक समूह का मूल्यांकन करने के लिए किया जाता है, लेकिन सबसे अच्छा उदाहरण भारित ग्राफ ट्रैवर्सल के लिए लालची एल्गोरिदम है। एक लालची एल्गोरिथ्म के लिए एक इष्टतम समाधान (ग्राफ़ के पार एक पथ) प्राप्त करने के लिए, इसे संभव सबसे कम वजन वाले मार्ग में सभी नोड्स तक पहुंचना होगा।
इस प्रकार, लूप इनवेरिएंट प्रॉपर्टी का अर्थ है कि लिए गए पथ में कम से कम वजन है। पर शुरुआत हम नहीं जोड़े हैं किनारों, तो यह संपत्ति सच है (यह इस मामले में, झूठी नहीं है)। पर हर कदम , हम सबसे कम वजन बढ़त (लालची कदम) का पालन करें, तो फिर हम सबसे कम वजन पथ ले जा रहे हैं। पर अंत में, हम तो हमारे संपत्ति भी सच है, सबसे कम भारित पथ मिल गया है।
यदि एक एल्गोरिथ्म ऐसा नहीं करता है, तो हम यह साबित कर सकते हैं कि यह इष्टतम नहीं है।
लूप के साथ क्या हो रहा है, इसका ट्रैक रखना कठिन है। लूप जो अपने लक्ष्य व्यवहार को प्राप्त किए बिना समाप्त या समाप्त नहीं करते हैं, कंप्यूटर प्रोग्रामिंग में एक आम समस्या है। लूप आक्रमणकारी मदद करते हैं। एक लूप इनवेरिएंट आपके प्रोग्राम में वैरिएबल के बीच संबंध के बारे में एक औपचारिक कथन है जो लूप के चलने से ठीक पहले होता है (इनवेरिएंट को स्थापित करना) और लूप के नीचे हर बार सच होता है, लूप के माध्यम से हर बार (इनवेरिएंट को बनाए रखना) )। आपके कोड में लूप इनवेरिएंट के उपयोग का सामान्य पैटर्न यहां दिया गया है:
... // लूप इनवेरिएंट यहाँ सही होना चाहिए
(टेस्ट कॉन्स्टिट्यूशन) {
// लूप के ऊपर
...
// लूप के नीचे
// लूप इनवेरेंट यहाँ सही होना चाहिए
}
// टर्मिनेशन + लूप इनवेरिएंट = लक्ष्य
...
लूप के ऊपर और नीचे के बीच, हेडवे संभवतः लूप के लक्ष्य तक पहुंचने के लिए बनाया जा रहा है। यह अतिक्रमणकर्ता को परेशान कर सकता है। लूप इनवेरिएंट्स का मुद्दा यह वादा है कि प्रत्येक बार लूप बॉडी को दोहराने से पहले इनवेरिएंट को बहाल किया जाएगा। इसके दो फायदे हैं:
जटिल, डेटा निर्भर तरीकों से अगले पास के लिए काम आगे नहीं बढ़ाया जाता है। प्रत्येक लूप के माध्यम से सभी अन्य से स्वतंत्र रूप से गुजरता है, अपरिवर्तनीय के साथ पासिंग को एक पूरे काम में एक साथ बांधने की सेवा करता है। यह तर्क देते हुए कि आपका पाश काम करता है यह तर्क देने के लिए कम हो जाता है कि लूप इनवेरिएंट को लूप के माध्यम से प्रत्येक पास के साथ बहाल किया जाता है। यह लूप के जटिल समग्र व्यवहार को छोटे सरल चरणों में तोड़ता है, प्रत्येक को अलग से माना जा सकता है। लूप की परीक्षण स्थिति अपरिवर्तनीय का हिस्सा नहीं है। यह वह है जो लूप को समाप्त करता है। आप अलग से दो चीजों पर विचार करते हैं: लूप को कभी भी समाप्त क्यों करना चाहिए, और लूप समाप्त होने पर अपने लक्ष्य को क्यों प्राप्त करता है। लूप समाप्त हो जाएगा यदि लूप के माध्यम से हर बार आप समाप्ति की स्थिति को संतुष्ट करने के लिए करीब चले जाते हैं। अक्सर यह आश्वासन देना आसान होता है: जैसे जब तक यह एक निश्चित ऊपरी सीमा तक नहीं पहुंचता, तब तक एक काउंटर चर को चरणबद्ध करना। कभी-कभी समाप्ति के पीछे तर्क अधिक कठिन होता है।
लूप इनवेरिएंट बनाया जाना चाहिए ताकि जब समाप्ति की स्थिति प्राप्त हो, और अयोग्य सच हो, तो लक्ष्य पूरा हो गया है:
invariant + समाप्ति => लक्ष्य
यह invariants बनाने के लिए अभ्यास लेता है जो सरल हैं और संबंधित हैं जो समाप्ति को छोड़कर लक्ष्य प्राप्ति के सभी पर कब्जा करते हैं। लूप आक्रमणकारियों को व्यक्त करने के लिए गणितीय प्रतीकों का उपयोग करना सबसे अच्छा है, लेकिन जब यह अधिक जटिल परिस्थितियों की ओर जाता है, तो हम स्पष्ट गद्य और सामान्य ज्ञान पर भरोसा करते हैं।
क्षमा करें, मेरे पास टिप्पणी की अनुमति नहीं है।
@ टॉमस पेट्रिसक जैसा कि आपने बताया
एक कमजोर आक्रमणकारी जो सच भी है कि i> = 0 && i <10 (क्योंकि यह निरंतरता की स्थिति है!)
यह कैसे एक पाश अपरिवर्तनीय है?
मुझे उम्मीद है कि मैं गलत नहीं हूं, जहां तक मैं समझता हूं [1] , लूप अपरिवर्तनीय लूप की शुरुआत में सही होगा (प्रारंभ), यह प्रत्येक पुनरावृत्ति (रखरखाव) से पहले और बाद में सच होगा और यह बाद में भी सच होगा लूप की समाप्ति (समाप्ति) । लेकिन अंतिम पुनरावृत्ति के बाद मैं 10. हो जाता है, इसलिए, i> = 0 && i <10 गलत हो जाता है और लूप को समाप्त कर देता है। यह लूप इनवेरिएंट की तीसरी संपत्ति (समाप्ति) का उल्लंघन करता है।
[१] http://www.win.tue.nl/~kbuchin/teaching/JBP030/notebooks/loop-invariants.html
पाश अपरिवर्तनीय एक गणितीय सूत्र है जैसे कि (x=y+1)
। उस उदाहरण में, x
और y
एक लूप में दो चर का प्रतिनिधित्व करते हैं। कोड के निष्पादन के दौरान वे चर के बदलते व्यवहार को देखते हुए, यह करने के लिए सभी संभव परीक्षण करने के लिए लगभग असंभव है x
और y
मूल्यों और देखें कि क्या वे किसी भी बग का उत्पादन। कहते हैं x
कि एक पूर्णांक है। इंटेगर मेमोरी में 32 बिट स्पेस रख सकता है। उस नंबर से अधिक है, बफर अतिप्रवाह होता है। इसलिए हमें यह सुनिश्चित करने की आवश्यकता है कि कोड के निष्पादन के दौरान, यह कभी भी उस स्थान से अधिक न हो। उसके लिए, हमें एक सामान्य सूत्र को समझने की आवश्यकता है जो चर के बीच के संबंध को दर्शाता है। आखिरकार, हम सिर्फ कार्यक्रम के व्यवहार को समझने की कोशिश करते हैं।
लूप इन्वारिएंट एक जोर है जो लूप निष्पादन से पहले और बाद में सच है।
रैखिक खोज में (पुस्तक में दिए गए अभ्यास के अनुसार), हमें दिए गए सरणी में मान V खोजने की आवश्यकता है।
सरणी को 0 <= k <लंबाई से स्कैन करना और प्रत्येक तत्व की तुलना करना सरल है। यदि V पाया जाता है, या यदि स्कैनिंग सरणी की लंबाई तक पहुंचता है, तो बस लूप को समाप्त करें।
उपरोक्त समस्या में मेरी समझ के अनुसार-
लूप आक्रमणकारियों (प्रारंभिक): V k - 1 पुनरावृत्ति में नहीं पाया जाता है। बहुत पहले पुनरावृत्ति, यह -1 होगा इसलिए हम कह सकते हैं कि वी -1 स्थिति में नहीं मिला है
अनुरक्षण: अगले पुनरावृत्ति में, V k-1 में नहीं पाया गया है
टर्मिनेशन: यदि वी को k स्थिति में या k सरणी की लंबाई तक पहुँचता है, तो लूप को समाप्त करें।