Tải bản đầy đủ (.doc) (12 trang)

Tiểu luận môn Lý thuyết tính toán Giới thiệu máy Turing

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (168.79 KB, 12 trang )

1
Tiểu luận môn Lý thuyết tính toán
Giới thiệu máy Turing
Trong chương này chúng tôi thay đổi khá nhiều về phương pháp truyền đạt. Cho
đến nay, chúng tôi đã quan tâm đến các lớp ngôn ngữ đơn giản và phương pháp mà
chúng có thể được sử dụng cho các vấn đề tương đối hạn chế, chẳng hạn như phân tích
các giao thức, tìm kiếm văn bản, hoặc phân tích chương trình. Bây giờ, chúng ta sẽ bắt
đầu tìm kiếm các câu hỏi cho những ngôn ngữ có thể được định nghĩa bởi bất kỳ thiết
bị tính toán nào. Câu hỏi này tương tự như câu hỏi về những gì máy tính có thể làm, kể
từ khi công nhận các chuỗi trong ngôn ngữ là phương pháp chính thức của bất kỳ vấn
đề cụ thể nào, và việc giải quyết một vấn đề là một thay thế hợp lý cho những gì mà
máy tính làm.
Chúng ta bắt đầu với một lý lẽ không thân thiện, bằng cách sử dụng một kiến
thức giả định của lập trình C, để thấy rằng có những vấn đề cụ thể mà chúng ta không
thể giải quyết đựơc bằng máy tính. Những vấn đề này được gọi là "không giải được."
Sau đây, chúng tôi giới thiệu một hình thức đáng nể cho máy tính, được gọi là máy
Turing. Trong khi một máy Turing trông không giống một máy tính, và không được
hiệu quả vô cùng thì một số công ty khởi đầu vẫn quyết định sản xuất và bán chúng;
trong một thời gian dài máy Turing được công nhận là mô hình chính xác so với bất kỳ
thiết bị tính toán vật lý nào có khả năng làm việc.
Trong chương 9, chúng tôi sử dụng máy Turing để phát triển một lý thuyết về bài
toán "không giải đuợc", điều này có nghĩa là không có máy tính nào có thể giải đựơc
các bài toán này. Chúng tôi chỉ ra rằng một số bài toán có thể dễ dàng để biểu diễn là
không giải được trên thực tế. Một ví dụ đựơc đưa ra ở đây là khi đưa ra một văn phạm
được cho là mơ hồ thì chúng ta sẽ thấy nhiều bài toán cần giải quyết.
8.1. Những bài toán mà máy tính không thể giải quyết
Mục đích của phần này là cung cấp vô hình thức trong việc giới thiệu ngôn ngữ
lập trình C cơ bản để kiểm chứng một bài toán cụ thể mà máy tính không thể giải
quyết. Các vấn đề cụ thể đựơc đưa ra thảo luận ở đây là tại sao từ đầu tiên mà chương
trình C in ra là từ hello và world. Mặc dù chúng ta có thể cho rằng việc mô phỏng của
chương trình sẽ cho phép chúng ta chỉ ra chương trình đang làm gì, chúng ta phải xác


thực nội dung với các chương trình và điều này phải mất một thời gian dài trước khi
xuất bất kỳ thông tin nào ra. Với bài toán này – không biết khi nào, bao giờ, việc gì đó
sẽ xảy ra – là nguyên nhân căn bản của sự bất lực của chúng tôi khi cho biết chương
trình làm gì. Tuy nhiên, vấn đề ở đây là không có chương trình nào có thể đảm nhiệm
nhiệm vụ khá khó khăn này, và chúng ta cần phải phát triển một số máy hình thức.
Trong phần này, chúng tôi đưa ra những bằng chứng xác thực liên quan đến vấn dề
này.
2
8.1.1. Chương trình In "Hello, World"
Trong hình 8.1 là chương trình C đầu tiên các sinh viên gặp khi đọc cuốn sách
Kernighan và Ritchie. Chúng ta dễ dàng để nhận thấy rằng chương trình này in hello,
world và kết thúc . Chương trình này quá dễ hiểu để trở thành một bài thực hành thông
dụng để giới thiệu các ngôn ngữ bằng cách làm thế nào để viết một chương trình in
hello, world trong những ngôn ngữ đó.
Main ()
{
Printf ("hello, world\ n")
}
Hình 8.1: Chương trình hello-world của Ritchie và Kernighan
Tuy nhiên, có những chương trình khác cũng in hello, world, nhưng thực tế là
những chương trình đó chưa được rõ ràng . Hình 8.2 cho thấy chương trình khác có thể
in hello, world. Phải nhập đầu vào n, và tìm số nguyên dương để giải phương trình
x
n
+y
n
=z
n
. Nếu tìm thấy, in hello, world. Nếu nó không bao giờ tìm thấy các số nguyên
x, y, z thỏa mãn phương trình, sau đó nó tiếp tục tìm kiếm mãi mãi, và không bao giờ

in hello, world.
Để hiểu những gì chương trình này làm, trước tiên phải tiến chú ý đến exp là hàm
hỗ trợ tính toán lũy thừa. Chương trình chính cần phải tìm kiếm thông qua ba tham số
(x, y, z) trong một bậc như vậy cuối cùng chúng ta chắc chắn nhận được mọi bộ ba số
nguyên dương. Để tổ chức tìm kiếm, chúng ta sử dụng một biến thứ tư, total, bắt đầu
tại 3 và trong vòng lặp while, được tăng lên một đơn vị tại một thời điểm, cuối cùng
cũng đạt đến bất kỳ số nguyên hữu hạn nào đó. Bên trong vòng lặp while, chúng ta
phân chia total cho ba số nguyên dương x, y, z, bằng cách đầu tiên cho phép x nằm
trong khoảng từ 1 đến total-2, và trong vòng lặp này có vòng lặp for sẽ cho phép y
trong giới hạn từ 1 đến một số nhỏ hơn x mà không được lấy từ total. Cái còn lại, phải
giữa 1 và total-2, được đưa cho z.
Trong vòng lặp trong cùng, ba tham số (x, y, z) là để kiểm tra phương trình
x
n
+y
n
=z
n
. Nếu bằng, chương trình in hello, world, và nếu không bằng, không in gì cả.
int exp(int i, n)
/*tính i mũ n */
{
int ans, j;
3
ans=1;
for (j=1; j<=n; j++) ans *=i;
return(ans)
}
main()
{

int n, total, x, y, z;
scanf(“%d”, &n);
total=3;
while (1) {
for (x=1; x<=total-2; x++)
for (y=1; y<=total-x-1;y++) {
z=total-x-y;
if (exp(x,n)+exp(y,n)==exp(z,n))
}
total++;
}
}
Hình 8.2: Định lý Fermat cuối cùng được thể hiện như một chương trình hello-world
Nếu giá trị của n mà chương trình đọc là 2, thì cuối cùng chương trình sẽ tìm
thấy sự kết hợp các số nguyên ví dụ như total = 12, x = 3, y = 4 và z = 5, do x
n
+y
n
=z
n
.
Như vậy, cho đầu vào là 2, chương trình in hello, world.
Tuy nhiên, đối với bất kỳ số nguyên n> 2, chương trình sẽ không bao giờ tìm
thấy một bộ ba các số nguyên dương để thoả mãn phương trình x
n
+y
n
=z
n
, và do đó sẽ

không in hello, world. Thật thú vị, cho đến một vài năm trước đây, người ta không biết
chương trình này sẽ không in được hello, world đối với một số nguyên lớn n. Những
tuyên bố cho rằng điều đó sẽ không xảy ra, tức là không có giải pháp số nguyên của
phương trình: x
n
+y
n
=z
n
nếu n> 2, giải pháp này đã được Fermat thực hiện cách đây
300 năm, nhưng không chứng minh được cho đến gần đây. Tuyên bố này thường được
gọi là "định lý cuối cùng của Fermat" (phương trình toán học của Fermat với nghiệm
có giới hạn).
4
Bài toán hello-world được định nghĩa như sau: hãy xác định có hay không một
chương trình C mà khi cho đầu vào, in hello, word như 12 ký tự đầu tiên mà nó in ra.
Trong những phần sau, chúng ta thường sử dụng, như một phương pháp tốc ký, việc
trình bày một chương trình in hello, world có nghĩa là in hello, word như 12 ký tự đầu
tiên mà nó in ra.
Tại sao tồn tại bài toán không giải được
Trong khi thật khó để chứng minh đó là một bài toán đặc trưng, chẳng hạn như
"bài toán hello-world" đã phân tích ở đây, là không giải được, khá dễ dàng để hiểu tại
sao hầu như tất cả các bài toán không giải được bởi bất kỳ hệ điều hành nào thì đều
liên quan đến lập trình. Cần nhớ rằng một "bài toán" là thành phần của một chuỗi
trong một ngôn ngữ. Số lượng các ngôn ngữ khác nhau có nhiều hơn môt ký tự trên
các bảng chữ cái là không đếm được. Điều này có nghĩa là không có cách nào để gán
các số nguyên cho các ngôn ngữ vì mọi ngôn ngữ có một số nguyên, và mỗi số nguyên
thì được gán cho một ngôn ngữ.
Trên những chương trình khác, các chuỗi hữu hạn trong một bảng chữ cái hữu
hạn (thường là một tập hợp con của bảng chữ cái ASCCII) là có thể đếm được. Điều

này có nghĩa, chúng ta có thể sắp xếp chúng theo chiều dài, và đối với các chương
trình có chiều dài bằng nhau thì sắp xếp theo thứ tự từ điển. Vì vậy, chúng ta có thể
viết đến chương trình đầu tiên, chương trình thứ hai, và đến chương trình thứ i cho bất
kỳ số nguyên i nào.
Kết quả, chúng ta biết số chương trình ít hơn số bài toán. Nếu chúng ta chọn một
ngôn ngữ một cách ngẫu nhiên, gần như chắc chắn nó sẽ là một bài toán không giải
được. Lý do duy nhất mà hầu hết các bài toán được xem là không giải được là chúng ta
hiếm khi quan tâm đến các bài toán ngẫu nhiên. Thay vào đó, chúng ta có xu hướng
xem xét những bài toán khá đơn giản, có cấu trúc rõ ràng, và thực sự thường là giải
được. Tuy nhiên, ngay cả trong những bài toán chúng ta quan tâm và có thể có cách
trình bày rõ ràng và ngắn gọn thì chúng ta cũng sẽ tìm thấy nhiều bài toán không giải
được, bài toán hello-world là một trường hợp trong số đó.
Dường như các nhà toán học phải mất 300 năm để giải quyết một câu hỏi duy
nhất về một chương trình 22 dòng, sau đó vấn đề được quan tâm là chỉ ra có hay
không một chương trình, cho dữ liệu vào, in ra hello, word quả thật là điều khó khăn.
Trong thực tế, bất kỳ những bài toán mà các nhà toán học đã không thể giải được đều
được đưa về thành một câu hỏi "chương trình này, với đầu vào này, in được hello,
world không?”. Vì vậy, người ta sẽ rất chú ý nếu chúng ta có thể viết một chương trình
có thể kiểm tra bất kỳ chương trình P và đầu vào I cho chương trình P, và chỉ ra có hay
không có P, chạy với đầu vào I, sẽ in hello, world. Chúng ta sẽ chứng minh rằng
không tồn tại một chương trình như vậy.
5
8.1.2. Máy thử “Hello, word”
Chứng minh về việc không thể thực hiện các kiểm tra hello-world là cách
chứng minh bằng phương pháp phản chứng. Đó là, chúng ta giả sử có một chương
trình, gọi đó là H, được xem như đầu vào là một chương trình P và đầu vào I, và cho
dù có hay không P và đầu vào I thì vẫn in ra hello, world. Hình 8.3 là một diễn tả của
những gì H làm. Thực tế, chỉ có đầu ra của H mới in ra ba ký tự là “Yes” hoặc in ra hai
ký tự là “No”. Đầu ra thường in ra hoặc làm một việc khác.
Nếu một bài toán có thuật toán giống như chương trình H, mà thường nói một

cách chính xác dù có hay không trường hợp bài toán có câu trả lời là "Yes" hoặc "No",
thì sau đó bài toán được xem là giải được. Nếu không thì bài toán không giải được.

Mục tiêu của chúng ta là để chứng minh rằng chương trình H không tồn tại, tức là, các
vấn đề “hello –world” không thể giải quyết được.
Để chứng minh sự trình bày bằng phản chứng, chúng ta sẽ tạo một vài thay đổi
cho chương trình H, cuối cùng xây dựng một chương trình liên quan được gọi là
chương trình “H
2
”, đây là một chương trình cho chúng ta thấy nó không tồn tại. Vì
việc thay đổi H là một biến đổi đơn giản do đó nó có thể được thực hiện bởi bất kỳ
chương trình C nào, sự trình bày đáng ngờ duy nhất là sự tồn tại của chương trình H,
vì vậy đó là giả định mà chúng ta phủ nhận.
Chúng ta sẽ thảo luận đơn giản hơn, chúng ta sẽ làm giả định về một chương
trình C. Những giả định làm cho chức năng của H dễ dàng hơn, không khó khăn, vì
vậy nếu chúng ta có thể hiển thị thử nghiệm "hello - world " cho các chương trình hạn
chế này không tồn tại, sau đó chắc chắn không có thử nghiệm nào có thể làm việc cho
một lớp rộng lớn hơn của chương trình. Giả định của chúng ta là:
1. Tất cả đầu ra đều dựa trên ký tự, chẳng hạn, chúng ta không sử dụng một gói
phần mềm đồ họa hay cơ sở nào khác để tạo đầu ra không thuộc mẫu của ký tự.
2. Tất cả đầu ra là ký tự cơ sở được thực hiện bằng cách sử dụng “printf”, chứ
không phải là put-char () hoặc một hàm đầu ra dựa trên ký tự khác.
Hình 8.3. Chương trình giả thiết H là một máy dò hello – word
6
Bây giờ chúng ta giả định rằng chương trình H tồn tại. Sửa đổi đầu tiên là thay
đổi đầu ra no, đó là câu trả lời mà H thực hiện khi nó cho đầu vào chương trình P
không in được hello, word như đầu ra đầu tiên trong hồi đáp đầu vào I. Ngay sau khi H
in "n," chúng ta biết cuối cùng nó sẽ đi kèm theo sau là "o". Vì vậy, chúng ta có thể
sửa đổi bất kỳ sự trình bày printf nào trong H để in ra "n" thay vào đó là in hello,
word. Một việc trình bày printf nữa đó là in "o" nhưng không phải là "n" được bỏ qua.

Kết quả là, các chương trình mới, mà chúng ta gọi là H
1
, hoạt động như H, ngoại trừ
việc nó in hello, word một cách chính xác khi H in no. H
1
được đề xuất bởi hình 8.4
Chuyển đổi tiếp theo của chúng ta trên chương trình là một chút phức tạp hơn, đó
là cách nhìn cơ bản sâu sắc cho phép Alan Turing chứng minh kết quả không giải
được của mình về máy Turing. Từ khi chúng ta thực sự quan tâm đến các chương trình
mà làm cho các chương trình khác như là đầu vào và diễn đạt một số vấn đề về chúng,
chúng ta sẽ giới hạn H
1
vì vậy H
1
:
a) Giữ lại đầu vào P, không phải P và I.
b) Yêu cầu P làm gì nếu đầu vào là mã riêng của nó, tức là, H
1
sẽ làm gì trên dữ
liệu vào P như chương trình và P như dữ liệu vào I thì tốt?
Việc thay đổi mà chúng ta phải thực hiện trên H
1
để tạo ra chương trình H
2
đề xuất
ở hình 8.5 như sau:
1. H
2
đầu tiên đọc toàn bộ dữ liệu vào P và lưu trữ trong mảng A, đó là hàm
“malloc’s” cho ra kết quả.

2. H
2
có mô hình như H
1
, nhưng bất kỳ lúc nào H
1
cũng đọc được dữ liệu vào từ P
hoặc I, H
2
đọc từ bản copy lưu trữ trong mảng A. Để đánh dấu có bao nhiêu P và I thì
H
1
đọc, H
2
có thể duy trì hai con trỏ đánh dấu vị trí trong A.
Hình 8.4. H
1
hoạt động như H, nhưng máy nói Hello, Word thay vì No
Figure 8.5: Hi behaves like H\, but uses its input P as both P and I
Hình 8.5. H
2
hoạt động như H
1
, nhưng sử dụng đầu vào P của nó như cả P và I
Yes
Hello, world
P
7
Ngay bây giờ chúng ta có thể sẵn sàng đi chứng minh H
2

không tồn tại. Vì vậy,
H
1
không tồn tại, và tương tự, H không tồn tại. Trọng tâm của vấn đề đó là hình dung
H
2
làm gì khi xem chính bản thân nó như là dữ liệu vào. Trường hợp này được đề xuất
ở hình 8.6. Gọi lại H
2
đó, cho bất kỳ chương trình P nào như là đầu vào, H
2
tạo được
đầu ra Yes nếu P in ra hello, word khi H
2
tự cho chính bản thân nó như là đầu vào.
Ngoài ra, H
2
in ra hello, word nếu P tự cho chính bản thân nó như đầu vào, không in ra
hello, word như đầu ra đầu tiên của nó.
Giả sử H
2
được miêu tả bằng hình 8.6 tạo đầu ra Yes. Sau đó H
2
ở trong ô cho
rằng dữ liệu vào H
2
của nó chính là H
2
, nó cho chính bản thân nó như là đầu vào, in ra
hello, word như đầu ra đầu tiên của nó. Tuy nhiên chúng ta chỉ giả sử rằng đầu ra H

2
đầu tiên tạo ở vị trí này là Yes hơn là hello, word.
Vì vậy, xuất hiện ở hình 8.6 đầu ra của ô là hello, word, vì nó là hello, word hoặc
là một cái khác. Nhưng nếu H
2
tự cho nó như đầu vào, in ra hello, word đầu tiên, thì đầu ra của ô trong
hình 8.6 phải là Yes. Chúng ta giả sử H
2
tạo ra bất cứ đầu ra nào, chúng ta có thể bàn luận việc nó tạo ra đầu ra
khác.
Tình huống này là nghịch lý, và chúng ta quyết định rằng H
2
không tồn tại. Vì
chúng ta đã trái với giả định là H

tồn tại. Tức là chúng ta đã chứng minh được rằng
không có chương trình H nào cho dù có đưa ra hay không chương trình P với đầu vào
I, in ra hello, word như đầu ra đầu tiên của nó.
8.1.3. Biến đổi một bài toán thành một bài toán khác
Bây giờ, chúng tôi có bài toán - một chương trình có chạy được với đầu vào được
cho, in hello, word như cái đầu tiên mà nó in ra hay không? - mà chúng ta biết không
có chương trình máy tính nào có thể giải quyết được. Một vấn đề mà không thể được
giải quyết bằng máy tính được gọi là không giải được. Chúng tôi sẽ đưa ra định nghĩa
Figure 8.5: Hi behaves like H\, but uses its input P as both P and I
Hình 8.6. H
2
làm gì khi tự cho chính bản thân nó như đầu vào?
Yes
Hello, world
H

2
8
chính thức của "undecide" trong phần 9.3, nhưng cho đến thời điểm này, chúng ta hãy
cùng sử dụng thuật ngữ chính thức. Giả sử chúng ta muốn xác định có hay không một
số bài toán khác có thể được giải quyết bởi một máy tính. Chúng tôi có thể thử viết
chương trình để giải quyết nó, nhưng nếu chúng ta không thể tìm ra cách để làm như
vậy, thì sau đó chúng ta có thể thử một bằng chứng cho thấy không có chương trình
như vậy.
Có lẽ chúng ta có thể chứng minh bài toán mới không giải quyết được này bằng
một kỹ thuật tương tự như những gì chúng ta đã làm cho các bài toán hello-word: giả
sử có một chương trình để giải quyết nó và phát triển một chương trình nghịch lý mà
chương trình này phải làm hai điều trái ngược, giống như chương trình H
2
. Tuy nhiên,
một khi chúng ta có một bài toán mà chúng ta biết là không giải được, chúng ta không
còn phải chứng minh sự tồn tại của một tình huống nghịch lý. Điều đó quá đủ để cho
thấy rằng nếu chúng ta có thể giải quyết bài toán mới, sau đó chúng ta có thể sử dụng
giải pháp để giải quyết một bài toán mà chúng ta đã biết là không giải được. Chiến
lược này được đề xuất trong hình 8.7; kỹ thuật được gọi là biến đổi P
1
thành P
2
.
Giả sử chúng ta biết vấn đề P
1
không giải được, và P
2
là một bài toán mới mà
chúng ta muốn chứng minh càng không giải được càng tốt. Chúng ta giả sử rằng có
một chương trình miêu tả trong hình 8.7 bằng nhãn hình thoi "decide" (quyết định),

chương trình này in ra Yes hoặc No, tùy thuộc vào việc có hay không đầu vào của nó,
ví dụ như bài toán P
2
có hoặc không có trong ngôn ngữ của bài toán đó.
Để tạo ra một bằng chứng rằng bài toán P
2
là không giải được, chúng ta phải phát
minh ra một cấu trúc, đại diện bởi các ô vuông trong hình 8.7, có thể chuyển đổi
trường hợp của P1 thành trường hợp của P2 có câu trả lời tương tự. Đó là, bất kỳ chuỗi
trong ngôn ngữ P
1
được chuyển đổi sang một số chuỗi trong ngôn ngữ P
2
, và bất kỳ
chuỗi nào trên bảng chữ cái của P
1
mà không thuộc ngôn ngữ P
1
đều được chuyển đổi
thành một chuỗi thuộc ngôn ngữ P2. Một khi chúng ta có cấu trúc này, chúng ta có thể
giải quyết P
1
như sau:
1. Cho một ví dụ như P
1
, đó là, cho một chuỗi w có thể hoặc không thể thuộc ngôn
ngữ P
1
, áp dụng các thuật toán xây dựng một chuỗi x.
No

P
1
instance
Construct
P
2
instance
Yes
decide
Hình 8.7. Nếu chúng ta có thể giải quyết bài toán P
2
, thì chúng ta có thể sử dụng lời
giải của nó để giải quyết bài toán P
1
9
2. Kiểm tra có x trong P
2
không, và đưa ra câu trả lời tương tự cho w và P
1
.
Nếu w thuộc P
1
, sau đó x thuộc P
2
, vì vậy thuật toán này nói rằng Yes. Nếu w
không thuộc P
1
, sau đó x không thuộc P2, và thuật toán nói No. Dù bằng cách nào, nó
cũng nói đúng về w. Kể từ khi chúng ta giả định rằng không có thuật toán để quyết
định các thành viên của một chuỗi trong P

1
tồn tại, chúng ta có một bằng chứng bởi sự
mâu thuẫn rằng thuật toán quyết định giả thuyết cho P
2
không tồn tại, tức là, P2 không
giải được.
Ví dụ 8.1: cho phép chúng ta sử dụng phương pháp này để hiển thị câu hỏi "tạo
chương trình Q, cho đầu vào y, gọi hàm foo" là không giải được. Lưu ý rằng Q có thể
không có một hàm foo, trong trường hợp bài toán là dễ, nhưng các trường hợp khó
khăn là khi Q có một hàm foo nhưng có thể hoặc không thể đạt được một lời gọi đến
foo với đầu vào y. Từ đó chúng ta chỉ biết một bài toán không giải được, vai trò của P
1
trong hình 8.7 sẽ được thể hiện bởi bài toán hello-word. P
2
sẽ đề cập đến bài toán gọi
foo. Chúng ta giả sử có một chương trình giải quyết bài toán gọi foo. Công việc của
chúng ta là thiết kế một thuật toán chuyển đổi bài toán hello-word thành bài toán gọi
foo.
Đó là, cho chương trình Q và đầu y của nó, chúng ta phải xây dựng một chương
trình R và một đầu vào z như R vậy, với đầu vào z, gọi foo nếu và chỉ nếu Q với đầu
vào y in ra hello, word. Việc xây dựng không phải là khó.
Một máy tính có thể thực sự làm tất cả những điều đó hay không?
Nếu chúng ta kiểm tra một chương trình như hình 8.2, chúng ta có thể đặt câu hỏi
liệu nó có thực sự tìm kiếm phản ví dụ cho phương trình toán học của Fermat với
nghiệm có giới hạn . Cuối cùng, các số nguyên chỉ có độ dài 32 bit trong máy tính cổ
điển, và nếu các phản ví dụ nhỏ nhất liên quan đến các số nguyên trong hàng tỉ số, sẽ
có một lỗi tràn bộ nhớ trước khi giải pháp được tìm thấy. Trong thực tế, người ta có
thể tranh luận rằng một máy tính với bộ nhớ chính128 MB và một đĩa 30 GB, "chỉ" có
256
30128000000

trạng thái, và đó là một sự tự động hữu hạn.
Tuy nhiên, việc nghiên cứu các máy tính như là những automat hữu hạn (hoặc
nghiên cứu bộ não như là những automat hữu hạn, nó chính là nơi bắt nguồn ý tưởng
của FA), là không hữu ích. Số lượng của các trạng thái liên quan quá lớn, và giới hạn
quá khó hiểu đến nỗi bạn không rút ra được bất kỳ kết luận hữu ích nào. Trong thực tế,
có mọi lý do để tin rằng, nếu chúng ta muốn, chúng ta có thể mở rộng tập hợp các
trạng thái của một máy tính một cách tùy ý.
Ví dụ, chúng ta có thể miêu tả các số nguyên như danh sách liên kết của các chữ
số, chiều dài tùy ý. Nếu chúng ta gỡ bỏ bộ nhớ ra, chương trình có thể in một yêu cầu
cho người tháo dỡ đĩa của nó là lưu nó, và thay thế nó bằng một đĩa trống. Thời gian
trôi qua, các máy tính có thể in các yêu cầu để trao đổi càng nhiều giữa các đĩa thì
10
càng đáp ứng nhu cầu của máy tính. Chương trình này sẽ phức tạp hơn nhiều so với
hình 8.2, nhưng không vượt quá khả năng của chúng ta để lập trình. Thủ thuật tương
tự sẽ cho phép bất kỳ chương trình khác tránh giới hạn hữu hạn trên kích thước của bộ
nhớ hay kích thước của số nguyên hoặc những mục tin dữ liệu khác.
1. Nếu Q có một hàm được gọi là foo, đổi tên nó và gọi hàm đó. Rõ ràng các
chương trình Q
1
mới thực hiện chính xác cái mà Q thực hiện.
2. Thêm vào Q
1
một hàm foo. Hàm này hiện không làm gì cả, và không được gọi.
Chương trình kết quả là Q
2
.
3. Chỉnh sửa Q
2
để lưu12 ký tự đầu tiên mà nó in ra, lưu trữ chúng trong một mảng
A bao trùm toàn chương trình. Hãy để chương trình kết quả là Q

3
.
4. Sửa đổi Q
3
để bất cứ khi nào nó cũng thực hiện bản trình bày đầu ra, sau đó nó đi
kiểm tra trong mảng A để nhìn thấy bản đó nếu nó được viết 12 ký tự hoặc nhiều hơn,
và nếu vậy, có hay không hello, world là 12 ký tự đầu tiên. Trong trường hợp đó, gọi
hàm mới foo là hàm được thêm vào trong mục. Chương trình kết quả là R, và đầu vào
z giống như y.
Giả sử rằng Q với đầu vào y in hello, word như đầu ra đầu tiên của mình. Sau đó,
R được xây dựng sẽ gọi foo. Tuy nhiên, nếu Q với đầu vào y không in hello, word như
đầu ra đầu tiên của mình, thì sau đó R sẽ không bao giờ gọi foo. Nếu chúng ta có thể
quyết định có hay không R với đầu vào z gọi foo, sau đó chúng ta còn biết có hay
không Q với đầu vào y (lưu y = z) in hello, word. Vì chúng ta biết rằng không có thuật
toán quyết định bài toán hello-word tồn tại, và tất cả bốn bước của việc xây dựng R từ
Q có thể được đưa ra bởi một chương trình đã chỉnh sửa mã của các chương trình, giả
định của chúng tôi rằng có một bài kiểm tra calls- foo là sai. Không có chương trình
như vậy tồn tại, và bài toán calls-foo là không giải được.
Phương diện biến đổi là quan trọng
Là một sai lầm phổ biến để cố gắng chứng minh một bài toán không giải được P
2
bằng cách biến đổi P
2
thành một số bài toán P
1
không giải được đã được biết đến, tức
là, tuyên bố "nếu P
1
giải được, thì P2 giải được". Tuyên bố đó, mặc dù chắc chắn đúng,
nhưng là vô ích, kể từ khi giả thuyết của nó "P

1
giải được" là sai.
Cách duy nhất để chứng minh một bài toán P
2
mới là không giải được đó là biến
đổi một bài toán không giải được P
1
đã được biết thành P
2
. Bằng cách đó, chúng ta
chứng minh tuyên bố "nếu P
1
giải được, thì P2 giải được". Trái ngược với tuyên bố đó
là "nếu P
1
không giải được, thì P2 không giải được". Vì chúng ta biết rằng P
1
không
giải được, do đó chúng ta có thể suy ra rằng P2 không giải được.
8.1.4. Bài tập phần 8.1
11
Bài tập 8.1.1: Cho sự biến đổi từ bài toán hello-word thành mỗi bài toán dưới
đây. Sử dụng tính vô hình thức của phần này để môt tả một cách hợp lý sự biến đổi
của bài toán, và không quan tâm đến giới hạn thực như kích cỡ tệp hoặc kích cỡ bộ
nhớ lớn nhất mà máy tính thực chấp nhận.
a) Cho một bài toán và đầu vào, chương trình dừng lại cuối cùng không; tức là,
chương trình không lặp mãi mãi trên đầu vào đó không?
b) Cho một bài toán và đầu vào, chương trình có cho kết quả đầu ra nào không?
c) Cho hai bài toán và đầu vào, các chương trình có cho kết quả đầu ra giống
nhau cho đầu vào không?

Bài làm câu b:
Ta có bài toán hello, word là bài toán in ra 12 ký tự đầu tiên mà nó in ra. Đây là
bài toán làm quen giới thiệu về ngôn ngữ dành cho những đối tượng mới làm quen với
ngôn ngữ. Tuy nhiên nếu bài toán hello, word được biến đổi thành bài toán với môt
đầu vào thì có xác định có hay không một chương trình mà khi cho đầu vào thì in
hello, word như 12 ký tự đầu tiên mà nó in ra. Tức là chương trình cho ra kết quả đầu
ra nào không.
Các nhà toán học phải mất 300 năm để giải quyết câu hỏi đó. Trong thực tế, bất
kỳ những bài toán nào mà các nhà toán học không giải quyết được đều được đưa về
thành một câu hỏi “chương trình này, với đầu vào này, in được hello, word không”.
Nếu chương trình đó được kiểm tra xem xét dưới hai hình thức: Máy tính kiểm tra và
con người kiểm tra thì sẽ cho kết quả như sau:
+ Máy tính kiểm tra, tức là có một chương trình H kiểm tra xem bài toán có đầu
vào thì có cho ra kết quả nào không. Liệu chương trình H có tồn tại không. Theo hình
8.3 thì chương trình P với đầu vào I khi được qua máy kiểm tra H thì cho đầu ra Yes
hoặc No.
Ta giả sử chương trình H tồn tại, tất cả đầu ra đều dựa trên ký tự và là ký tự có
sở được thực hiện bằng lệnh printf. Biến đổi đầu tiên là thay đổi đầu ra No, đó là câu
trả lời mà H thực hiện khi nó cho đầu vào chương trình P không in được hello, word
như 12 ký tự đầu tiên trong hồi đáp đầu vào I. Ngay sau khi H in ra “n” thì chắc chắn
nó sẽ in ra “o”. Đối với bất kỳ lệnh printf nào trong H in ra “no” đều được biến đổi
thành in hello, word. Như vậy ta được chương trình mới H
1
, thay vì in “no”, nó in ra
hello, word.
Tiếp tục biến đổi H
1
thành H
2
trong đó H

2
chỉ có đầu vào là P (không có I). H
2

có mô hình như H
1
nhưng H
1
đọc dữ liệu từ P và I còn H
2
đọc dữ liệu từ bản copy
trong mảng A (toàn bộ dữ liệu của P được lưu trong A). Tiếp tục biến đổi mô hình H
2
,
H
2
xem chính bản thân nó như đầu vào và in ra hello, word như đầu ra đầu tiên. Giả sử
12
đầu ra đầu tiên ở vị trí này là Yes hơn là hello, word. Vì vậy, đầu ra là hello, word.
Nhưng nếu H
2
tự cho nó như đầu vào và in ra hello, word đầu tiên thì đầu ra sẽ là Yes.
Điều này là nghịch lý, do đó H
2
không tồn tại, H
1
không tồn tại và H không tồn tại.
Vậy không có chương trình nào đi kiểm tra được một bài toán có đầu vào thì
chương trình có cho kết quả không.
+ Con người kiểm tra: Giả sử P là bài toán hello, word đã được biến đổi thành

bài toán đầu vào. Vậy chương trình này cho kết quả nào không. Chúng ta xét bài toán
đó ứng với ba loại bài toán: mập mờ, không giải được, giải được:
i) Bài toán mập mờ: giải phương trình x
n
+ y
n
+ z
n
, với đầu vào n thì ta thấy
rằng bài toán sẽ giải được (tức là tìm được x, y, z) trong trường hợp n

2 và in được
hello, word, nhưng nếu n>2 thì bài toán không giải được (tức là x, y, z không bao giờ
được tìm thấy) và không in được helllo, word.
ii) Bài toán giải được: Giải phương trình x
n
= y, với đầu vào n thì chương trình
sẽ luôn tìm được x, y thỏa mãn chương trình trên và in được hello, word.
iii) Bài toán không giải được: Giải phương trình
1
0
n
x
=
, với đầu vào n thì
chương trình không bao giờ giải được. Vì 0.
n
x

1 với

,x n∀
. Vậy chương trình không
in được hello, word.
Tóm lại, bài toán hello, word khi biến đổi thành một bài toán với đầu vào thì ta
không thể biết chắc chắn là chương trình đó có cho ra kết quả nào hay không.

×