Chương trình này sẽ chấm dứt cho mọi số nguyên?


14

Trong phần Kiểm tra phần để chuẩn bị GATE, có một câu hỏi:

f(n):
     if n is even: f(n) = n/2
     else f(n) = f(f(n-1))

Tôi đã trả lời "Nó sẽ chấm dứt cho tất cả các số nguyên", bởi vì ngay cả đối với một số số nguyên âm, nó sẽ chấm dứt là Lỗi tràn ngăn xếp .

Nhưng bạn tôi không đồng ý rằng vì đây không phải là mã được triển khai và chỉ là mã giả, nên nó sẽ là đệ quy vô hạn trong trường hợp một số số nguyên âm.

Câu trả lời nào đúng và tại sao?


8
Nó không kết thúc với n = -1. Hầu hết các giới hạn lý thuyết được xem xét trong các trường hợp như vậy.
Sâu Joshi

9
Nếu tràn ngăn xếp được coi là chấm dứt, thì tất cả các chương trình sẽ chấm dứt và nó đánh bại mục đích của câu hỏi này ...
xuq01

10
@ xuq01 while (true);sẽ không chấm dứt cũng như, trên bất cứ điều gì hợp lý, gây ra tràn ngăn xếp.
TripeHound

3
@leftaroundabout Có lẽ tôi không nên sử dụng " trên bất cứ thứ gì hợp lý " bởi vì nó là một mức độ " hợp lý " hoàn toàn khác ... phát hiện và thực hiện đệ quy đuôi là tốt (hoặc thậm chí hợp lý ), nhưng không làm như vậy chỉ hơi " không hợp lý ". Bất cứ điều gì được thực hiện while(true);theo cách sử dụng bất kỳ ngăn xếp nào chắc chắn sẽ không hợp lý . Vấn đề là, trừ khi bạn cố tình đi ra ngoài để trở nên khó xử, while(true);sẽ không chấm dứt cũng không kích hoạt tràn ngăn xếp.
TripeHound

14
@ xuq01 Tôi không nghĩ rằng "sự hủy diệt của vũ trụ" được coi là một giải pháp cho vấn đề tạm dừng.
TripeHound

Câu trả lời:


49

Câu trả lời đúng là hàm này không chấm dứt đối với tất cả các số nguyên (cụ thể, nó không chấm dứt trên -1). Bạn của bạn đã đúng khi tuyên bố rằng đây là mã giả và mã giả không chấm dứt khi tràn ngăn xếp. Mã giả không được định nghĩa chính thức, nhưng ý tưởng là nó thực hiện những gì được nói trên hộp thiếc. Nếu mã không nói "chấm dứt với lỗi tràn ngăn xếp" thì không có lỗi tràn ngăn xếp.

Ngay cả khi đây là ngôn ngữ lập trình thực sự, câu trả lời đúng vẫn là "không chấm dứt", trừ khi việc sử dụng ngăn xếp là một phần của định nghĩa ngôn ngữ. Hầu hết các ngôn ngữ không chỉ định hành vi của các chương trình có thể tràn vào ngăn xếp, bởi vì thật khó để biết chính xác một chương trình sẽ sử dụng bao nhiêu ngăn xếp.

Nếu việc chạy mã trên trình thông dịch hoặc trình biên dịch thực tế gây ra lỗi tràn ngăn xếp, trong nhiều ngôn ngữ, đó là sự khác biệt giữa ngữ nghĩa chính thức của ngôn ngữ và việc triển khai. Người ta thường hiểu rằng việc triển khai một ngôn ngữ sẽ chỉ làm những gì có thể được thực hiện trên một máy tính cụ thể với bộ nhớ hữu hạn. Nếu chương trình bị chết do tràn ngăn xếp, bạn nên mua một máy tính lớn hơn, biên dịch lại hệ thống nếu cần để hỗ trợ tất cả bộ nhớ đó và thử lại. Nếu chương trình không kết thúc thì bạn có thể phải tiếp tục làm điều này mãi mãi.

Ngay cả thực tế là một chương trình sẽ hoặc không tràn ngăn xếp cũng không được xác định rõ, vì một số tối ưu hóa như tối ưu hóa cuộc gọi đuôighi nhớ có thể cho phép một chuỗi các lệnh gọi vô hạn trong không gian ngăn xếp liên kết không đổi. Một số đặc tả ngôn ngữ thậm chí bắt buộc các triển khai thực hiện tối ưu hóa cuộc gọi đuôi khi có thể (điều này là phổ biến trong các ngôn ngữ lập trình chức năng). Đối với chức năng này, f(-1)mở rộng đến f(f(-2)); cuộc gọi bên ngoài flà một cuộc gọi đuôi để nó không đẩy bất cứ thứ gì lên ngăn xếp, do đó chỉ f(-2)đi vào ngăn xếp và trả về -1, do đó, ngăn xếp trở lại trạng thái như lúc ban đầu. Do đó với f(-1)các vòng lặp tối ưu hóa cuộc gọi đuôi mãi mãi trong bộ nhớ không đổi.


3
Một ví dụ trong đó mã được dịch sang ngôn ngữ lập trình dẫn đến không bị tràn ngăn xếp là Haskell. Nó chỉ lặp đi lặp lại vô thời hạn:let f :: Int -> Int; f n = if even n then n `div` 2 else f (f (n - 1)) in f (-1)
JoL

5

Nếu chúng ta xem xét điều này theo ngôn ngữ C, việc triển khai có thể thay thế mã bằng mã tạo ra kết quả tương tự trong tất cả các trường hợp khi bản gốc không gọi hành vi không xác định. Vì vậy, nó có thể thay thế

f(n):
   if n is even: f(n) = n/2
   else f(n) = f(f(n-1))

với

f(n):
   if n is even: f(n) = n/2
   else f(n) = f((n-1) / 2)

Bây giờ việc thực hiện được phép áp dụng đệ quy đuôi:

f(n):
   while n is not even do n = (n-1) / 2
   f(n) = n/2

Và vòng lặp này mãi mãi khi và chỉ khi n = -1.


Tôi nghĩ, trong C, việc gọi đó f(-1)là hành vi không xác định (việc triển khai có thể giả định rằng mọi luồng đều chấm dứt hoặc thực hiện một thao tác nào khác trong danh sách ngắn các hoạt động mà hàm này không thực hiện), vì vậy trình biên dịch thực sự có thể làm bất cứ điều gì nó muốn trong đó trường hợp
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.