1. Trang chủ
  2. » Luận Văn - Báo Cáo

Ứng dụng lập trình hàm giải và suy luận một số bài toán rời rạc luận văn thạc sỹ ngành công nghệ thông tin

88 1 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Ứng dụng Lập Trình Hàm Giải và Suy Luận Một Số Bài Toán Rời Rạc
Tác giả Võ Đức Toàn
Người hướng dẫn TS. Trần Văn Dũng
Trường học Trường Đại học Giao Thông Vận Tải
Chuyên ngành Kỹ Thuật
Thể loại Luận văn thạc sỹ
Năm xuất bản 2017
Thành phố TP Hồ Chí Minh
Định dạng
Số trang 88
Dung lượng 2,18 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Các ràng buộc này phản ánh một tính chất là nếu hàm được áp dụng cho một đối số nào đó, thì kiểu của đối số đó cần phải bằng kiểu miền xác định của hàm số.. Các ví dụ này thể hiện các kh

Trang 1

BỘ GIÁO DỤC VÀ ĐÀO TẠO TRƯỜNG ĐẠI HỌC GIAO THÔNG VẬN TẢI

VÕ ĐỨC TOÀN

ỨNG DỤNG LẬP TRÌNH HÀM GIẢI VÀ SUY LUẬN MỘT SỐ

BÀI TOÁN RỜI RẠC

LUẬN VĂN THẠC SĨ KỸ THUẬT

TP Hồ Chí Minh – 2017

Trang 2

BỘ GIÁO DỤC VÀ ĐÀO TẠO TRƯỜNG ĐẠI HỌC GIAO THÔNG VẬN TẢI

VÕ ĐỨC TOÀN

ỨNG DỤNG LẬP TRÌNH HÀM GIẢI VÀ SUY LUẬN MỘT SỐ

BÀI TOÁN RỜI RẠC

NGÀNH CÔNG NGHỆ THÔNG TIN

Trang 3

LỜI CAM ĐOAN

Tôi xin cam đoan:

Những kết quả nghiên cứu trình bày trong luận văn này là hoàn toàn trung thực, của tôi không vi phạm bất cứ điều gì trong luật sở hữu trí tuệ và pháp luật Việt Nam Trong quá trình làm luận văn tôi có tham khảo các tài liệu có liên quan và đã ghi rõ nguồn tài liệu tham khảo đó Những kiến thức tôi trình bày trong luận văn này chưa được trình bày hoàn chỉnh trong bất cứ tài liệu nào

TÁC GIẢ LUẬN VĂN

Võ Đức Toàn

Trang 4

LỜI CẢM ƠN

Đầu tiên, tôi xin gửi lời cảm ơn chân thành sâu sắc đến các thầy cô giáo trong trường Đại học Giao Thông Vận Tải nói chung và các thầy cô trong khoa CNTT nói riêng đã tận tình dạy bảo, truyền đạt lại những kiến thức và kinh nghiệm quý báu trong suốt thời gian qua

Tôi xin trân trọng gửi lời cảm ơn tới thầy TS Trần Văn Dũng, người đã nhiệt tình giúp đỡ, trực tiếp chỉ bảo, hướng dẫn tôi trong suốt quá trình thực hiện luận văn cao học Trong quá trình làm việc với thầy, tôi đã tiếp thu thêm được nhiều kiến thức

bổ ích, những kinh nghiệm trong việc thực hiện luận văn cùng thái độ làm việc nghiêm túc, hiệu quả Đây là những kinh nghiệm cần thiết, quý báu giúp tôi áp dụng vào thực tiễn sau này khi làm việc

Sau cùng, cho phép tôi cảm ơn các bạn bè, gia đình đã giúp đỡ, ủng hộ tôi rất nhiều trong toàn bộ quá trình học tập cũng như nghiên cứu hoàn thành luận văn này

TP HCM, ngày tháng năm Học viên thực hiện

Võ Đức Toàn

Trang 5

MỤC LỤC

LỜI CAM ĐOAN i

LỜI CẢM ƠN ii

1 Đặt vấn đề 1

2 Đối tượng và phạm vi nghiên cứu 1

3 Hướng nghiên cứu của đề tài 2

4 Những nội dung nghiên cứu chính 2

TỔNG QUAN NGÔN NGỮ LẬP TRÌNH HÀM ML 3

Chương 1 1.1 Giới thiệu chung 3

Mở đầu 3

1.1.1 Các kiểu cơ bản 4

1.1.2 Danh sách (List) 6

1.1.3 Các kiểu hàm số và biểu thức 7

1.1.4 7

1.1.5 Hàm số chuẩn và operator overloading 8

1.1.6 Chuỗi hàm chuẩn (String) 9

1.1.7 Các hàm chuẩn trên danh sách 9

1.1.8 1.2 Suy luận kiểu 10

Suy luận kiểu 11

1.2.1 Thuật toán suy luận kiểu 12

1.2.2 Định nghĩa hàm đa hình 12

1.2.3 Hàm đệ quy 14

1.2.4 Hàm nhiều mệnh đề 16

1.2.5 1.3 Đệ quy 17

Định nghĩa đệ quy 17

1.3.1 Thành phần của hàm đệ quy 17 1.3.2

Trang 6

Một số loại đệ quy 171.3.3.

1.4 Hàm bậc cao 17

171.4.1

Dãy vô hạn 191.4.2

Bất biến vòng lặp 262.1.2

Ví dụ lớn 282.1.3

Ví dụ nhỏ 292.1.4

Tổng hợp 402.3.2

2.4 Xét lại đệ quy 42

Phạm vi 422.4.1

Quan hệ truy hồi (Recurrence relations) 462.4.2

Trang 7

Tim ước chung lớn nhất gcd 50

3.1.1 Tính giai thừa Factorial 51

3.1.2 Tính hàm Fibonachi 51

3.1.3 Tính lũy thừa: 52

3.1.4 3.2 Bài toán tìm kiếm 53

Tìm kiếm tuần tự 53

3.2.1 Tìm kiếm nhị phân: 56

3.2.2 3.3 Một số bài toán trên danh sách 59

Tìm phần tử max/min 59

3.3.1 Tính tổng, tích các phần tử của dãy 61

3.3.2 Ghép hai dãy 63

3.3.3 Đảo ngược một danh sách: 64

3.3.4 3.4 Bài toán sắp xếp 65

Sắp xếp chèn 65

3.4.1 Sắp xếp chọn 69

3.4.2 Quick sort 70

3.4.3 3.5 Một số bài toán khác: 72

Tập hợp: 72

3.5.1 Quan hệ: 73

3.5.2 Tính bắc cầu: 74

3.5.3 Tìm bao đóng bắc cầu: 76

3.5.4 Tóm tắt chương 3: 78

Kết luận và hướng phát triển 79

5 Phương pháp nghiên cứu 79

6 Ý nghĩa khoa học của đề tài 79

7 Tài liệu tham khảo 79

Trang 8

Phụ lục các chương trình:

Chương trình 2.1: Hàm tính tổng chuổi số từ 1 đến n 24

Chương trình 2.2: Hàm tìm số dư của a chia b 26

Chương trình 2.3: Hàm tìm phần tử nhỏ nhất trong mảng 29

Chương trình 2.4: Hàm kiểm tra phần tử có trong danh sách 37

Chương trình 2.5: Hàm đếm số phần tử có trong danh sách 39

Chương trình 2.6: Hàm tính giai thừa của n dùng vòng lặp 43

Chương trình 2.7: Hàm tính giai thừa của n dùng vòng lặp 43

Chương trình 2.8: Hàm tính giai thừa của n dùng đệ quy 44

Chương Trình 3.1: Hàm tìm ước chung lớn nhất của hai số……… ……… 56

Chương Trình 3.2: Hàm tính giai thừa 51

Chương Trình 3.3: Hàm tính số hạng thứ n của dãy Fibonachi 51

Chương Trình 3.4: Hàm tính luỹ thừa an 53

Chương Trình 3.5: Hàm tìm kiếm phần tử trong danh sách 54

Chương Trình 3.6: Hàm tìm kiếm tuần tự trả về vị trí phần tử trong danh sách 54

Chương Trình 3.7: Hàm tìm kiếm tuần tự trên mọi kiểu trong danh sách 55

Chương Trình 3.8: Hàm tìm kiếm một nút trên cây 58

Chương Trình 3.9: Hàm tìm phần tử max trong danh sách 59

Chương Trình 3.10: Hàm tìm phần tử max mọi kiểu trong danh sách 60

Chương Trình 3.11: Hàm tìm phần tử max trong danh sách bằng cách truyền hàm so sanh 60

Chương Trình 3.12: Hàm tìm phần tử max kiểu chuổi trong danh sách bằng cách truyền hàm so sanh 61

Chương Trình 3.13: Hàm tính tổng các phần tử trong danh sách 61

Chương Trình 3.14: Hàm tính tích các phần tử trong danh sách 62

Chương Trình 3 15: Hàm tính tích các phần tử trong danh sách bằng truyền hàm 62

Chương Trình 3.16: Hàm nối hai danh sách 64

Chương Trình 3.17: Hàm đảo ngược danh sách 65

Chương Trình 3.18: Hàm chèn một phần tử vào danh sách có thứ tự 66

Chương Trình 3.19: Hàm sắp xếp chèn một danh sách 67

Chương Trình 3.20: Hàm sắp xếp chèn một danh sách mọi kiểu 68

Trang 9

Chương Trình 3.21: Hàm sắp xếp chọn một danh sách 70

Chương Trình 3.22: Hàm sắp xếp nhanh một danh sách 71

Chương Trình 3.23: Hàm kiểm tra một phần tử nằm trong danh sách 72

Chương Trình 3.24: Hàm loại bỏ phần tử trùng trong danh sách 73

Chương Trình 3.25: Hàm kiểm tra 1 cặp có nằm trong quan hệ 73

Chương Trình 3.26: Hàm kiểm tra tính bắt cầu 75

Chương Trình 3.27: Hàm tìm bao đóng bắt cầu 76

Chương Trình 3.28: Hàm ghép quan hệ với cặp không bắt cầu 77

Chương Trình 3.29: Hàm kiểm tra tính bắt cầu trước khi lấy bao đóng 77

Trang 10

LỜI NÓI ĐẦU

1 Đặt vấn đề

Trong thời đại công nghệ thông tin ngày nay, việc ứng dụng công nghệ thông tin vào cuộc sống, cũng như các ngành khoa học ngày càng trở nên quan trọng và cần thiết, các ứng dụng của phần mềm trên các ngôn ngữ lập trình vào khoa học kỹ thuật ngày càng đóng vai trò quan trọng Trước đây khi công nghệ thông tin chưa phát triển thì việc giải các bài toán cũng gặp nhiều khó khăn và việc suy luận các kết quả đạt được chưa có tính thuyết phục cao

Giờ đây khi máy tính được sử dụng rộng rãi, thì những yêu cầu đó có thể được giải quyết một cách đơn giản Chỉ cần một thuật toán và chỉ cần thay các giá trị khác nhau thì sẽ cho ra những bài toán khác nhau Việc tìm hiểu, học hỏi, ứng dụng và lập trình các phần mềm giải quyết vấn đề này ngày nay là quan trọng và cần thiết

Đề tài khóa luận “ỨNG DỤNG LẬP TRÌNH HÀM GIẢI VÀ SUY LUẬN MỘT SỐ BÀI TOÁN RỜI RẠC” là nghiên cứu khoa học ứng dụng công nghệ thông

tin cụ thể, dùng ngôn ngữ lập trình hàm ML vào giải và chứng minh một số bài toán rời rạc

Khóa luận chia làm 3 chương:

Chương 1 Tổng quan ngôn ngữ lập trình hàm ML Chương 2 Toán rời rạc và lập trình hàm ML Chương 3 Ứng dụng ML suy luận một số bài toán rời rạc Khóa luận này được thực hiện và hoàn thành tại Khoa Công Nghệ Thông Tin của Trường Đại Học Giao Thông Vận Tải với sự hướng dẫn của thầy TS Trần Văn Dũng

Mặc dù đã có nhiều cố gắng nhưng vì năng lực và thời gian còn hạn chế nên khóa luận không thể tránh khỏi những thiếu sót cả về nội dung và hình thức Vì vậy tôi rất mong lời chỉ bảo và sự góp ý của quý Thầy Cô

2 Đối tượng và phạm vi nghiên cứu

Đối tượng nghiên cứu dùng ngôn ngữ lập trình hàm, giải và suy luận một số bài toán rời rạc, sau khi hoàn chỉnh có thể tham khảo tài liệu Luận văn để giảng dạy một

số chủ đề Toán rời rạc và có chương trình giải và suy luận minh hoạ

Trang 11

3 Hướng nghiên cứu của đề tài

Thu thập, tìm hiểu và phân tích các tài liệu, xây dựng giải thuật và cài đặt giải

một số bài toán rời rạc điển hình

4 Những nội dung nghiên cứu chính

Luận văn được chia làm 3 chương, lời mở đầu, kết thúc và tài liệu tham khảo

Trang 12

TỔNG QUAN NGÔN NGỮ LẬP TRÌNH HÀM ML Chương 1.

SML cung cấp vài kiểu chuẩn

các cơ chế để xác định các kiểu mới nhưng chúng tôi sẽ không xem xét các kiểu đó ở đây

Khi trình bày các đối tượng, SML luôn hiển thị các kiểu đi cùng các giá trị:

<giá trị>: <kiểu>

Trang 13

Kiểu biểu thức với một kiểu cơ bản là định danh của kiểu

Đơn vị kiểu chỉ có một phần tử, đƣợc viết nhƣ ngoặc rỗng:

():unit

Giống void trong C, unit đƣợc sử dụng nhƣ kiểu kết quả cho hàm mà đƣợc thực hiện chỉ vì các tác động phụ Kiểu unit cũng đƣợc sử dụng nhƣ kiểu của đối số cho hàm số mà không có đối số Những lập trình viên C có thể sẽ bị lẫn bởi sự việc unit cho là có một phần tử, trong khi đó void có vẻ nhƣ không có phần tử nào Từ góc độ toán học, „một phần tử‟ là đúng đắn Cụ thể, nếu hàm đƣợc giả thiết trả về Tuy nhiên chúng ta không cần theo dõi hàm nhƣ vậy trả về giá trị nào, vì chỉ có một thứ mà nó có thể trả về Hệ thống kiểu ML dựa trên nhiều năm nghiên cứu kiểu; hầu hết các khái

niệm kiểu trong ML đã đƣợc xem xét rất cẩn thận [ 1, tr 22]

Trang 14

˗ ˜84

>84: int

Chú ý việc sử dụng ˜ là dấu âm

Nhiều biểu thức nguyên được theo cách thông thường, với hằng số và các phép toán số học chuẩn:

0, 1, 2,….-1, -2,…int

+, -, *, div: int*int → int

Phép toán div là phép toán nhị phân trung tố, được sử dụng như sau:

fun quotien(x, y) = x div y;

val quotien = fn: int*int → int

Bản thân định danh div không phải là biểu thức Tương tự, +, -, * cũng là các

phép toán nhị phân trung tố [ 1, tr 23]

1.1.2.4 Kiểu Real:

Kiểu của ML cho số dấu phảy động là số thực Để dễ dàng hiểu khi chúng ta suy luận kiểu, ML đòi hỏi phải có dấu chấm thập phân trong các hằng số thực:

1.0, 2.0, 3.14149, 4.44444,…real

Các phép toán số học +, - và * có thể áp dụng cho số nguyên hoặc số thực Ở

đây chúng ta có một vài biểu thức ví dụ và đầu ra kết quả của chương trình dịch [ 1 , tr 24]

{First_name = "Donald", Last_name = "Knuth"};

val it = First_name = "Donald", Last_name = "Knuth"}

:{ First_name: string, Last_name: string}

Biểu thức ở đây có hai thành phần, một được gọi là First_name và thành phần thứ hai được gọi là Last_name Kiểu của bản ghi cho ta biết kiểu của từng thành phần

Trang 15

Các thành phần của bản ghi đƣợc truy cập bởi hàm # giống nhƣ bộ, nhƣng đƣợc gọi tên dựa theo tên của thành phần thay vì vị trí Sau đây là ví dụ:

#First_name ({First_name = "Donald", Last_name = "Knuth"};

Cách khác chọn các thành phần của bộ và bản ghi là sánh mẫu mà đƣợc mô tả

phải bao gồm các phần tử cùng loại và kết thúc với danh sách trống

Danh sách đƣợc viết là chuỗi các phần tử riêng biệt bên trong [ và ]

Ví dụ 1.3:

[1, 4, 9, 16, 25]

[“ant”, “beetle”, “caterpillar”, “dragonfly”, “earwig”]

Có một danh sách rỗng ngụ ý ở cuối danh sách: [ ] là danh sách rỗng Danh sách rỗng đƣợc viết là nil trong ML

Kiểu biểu thức cho một danh sách phụ thuộc vào các loại phần tử

Ví dụ đầu tiên ở trên:

[1, 4, 9, 16, 25];

>

Ví dụ thứ 2 ở trên:

[ “ant”, “beetle”, “caterpillar”, “dragonfly”, “earwig”];

>[ “ant”, “beetle”, “caterpillar”, “dragonfly”, “earwig”]:

sách các xâu

[[1, 1], [2, 8], [3, 27], [4, 64], [5, 125]];

>[[1, 1], [2, 8], [3, 27], [4, 64], [5, 125]]: danh s danh sách của danh sách số nguyên

Sau đây là một số ví dụ danh sách các kiểu khác nhau:

Trang 16

val it=[fn, fn]: (int->int) list

Đối với danh sách ngắn, chương trình dịch in ra các phần tử của danh sách khi chỉ ra rằng biểu thức danh sách đã được tính toán Đối với danh sách dài, các phần tử cuối được thay thế bởi ba dấu chấm Như ví dụ danh sách cuối ở trên chỉ ra có thể viết danh sách các hàm

Nói chung, t list là kiểu của danh sách, nếu mọi phần tử của nó có kiểu t [ 1, tr 26]

Trang 17

Phủ định được viết là not, hội là andalso và tuyển là orelse

Chẳng hạn, sau đây là hàm mà xác định xem hai biến bool của nó có cùng giá trị bool không, tiếp theo là biểu thức mà gọi hàm đó

Ví dụ 1.5:

fun equiv(x, y)= (x andalso y) orelse ((not x) andalso(not y)); val equiv = fn: bool * bool->bool

equiv(true, false);

val it = false: bool

Hàm số chuẩn và operator overloading

1.1.6.

Ta nói các biến bool x và y có cùng giá trị bool, tức là chúng cùng true hoặc cùng false Biểu thức con thứ nhất (x andalso y) là true nếu cả hai x và y cùng true và biểu thức con thứ hai ((not x) andalso (not y)) là true nếu cả hai cùng false

Nguyên nhân tên dài andalso và orelse là để nhấn mạnh thứ tự tính toán Trong biểu thức (a andalso b) trong đó a và b là hai biểu thức; a được tính trước, nếu a là true, thì b được tính tiếp Ngược lại giá của biểu thức (a andalso b) là false mà không cần tính b Tương tự, b trong (a orelse b) chỉ được tính chỉ trong trường hợp a là false

Ví dụ 1.6:

true orelse false;

val it = true: bool

true andalso false;

val it = false: bool

Hệ thống SML có thể không thể biểu diễn các loại của các toán tử này nhưng chúng tỏ ra hiệu quả: fn: (bool* bool) -> bool vì vậy chúng đều có 2 đối số boolean, được xử lí như một: bool * bool tuple cho cú pháp trung tố, và trả về một kết quả Boolean

Trang 18

Chuỗi hàm chuẩn (String)

1.1.7.

Các xâu được viết như dãy các ký hiệu được bao quanh dấu ngoặc kép:

"William Jefferson Clinton": string

"Boris Yeltsin": string

Nối xâu được viết dạng ^, như vậy ta có:

"Chelsea"^""^"Clinton";

Val it=" Chelsea Clinton": string

Nối 2 chuỗi lại với nhau: -op ^ ;

>fn: (string * string) - > string

Ví dụ 1.7:

"happy" ^ "brithday!";

val it = "happy brithday!": string

Toán tử: size trả về kích cỡ của 1 chuỗi

Hàm chuẩn để chuyển chuỗi vào danh sách chuỗi được bàn luận dưới đây

Các hàm chuẩn trên danh sách

Trang 19

Tương tự, toán tử đuôi trả về đuôi với kiểu: ´a list từ 1 danh sách gõ tùy ý Vì vậy, tl sẽ là kiểu:

tl;

> fn: (´a list) -> (´a list)

Ví dụ 1.8:

tl [“alpha”, “beta”, “gamma”, “delta”, “epsilon”];

> [“alpha”, “beta”, “gamma”, “delta”, “epsilon”]: string list

Các toán tử xâu chuỗi các danh sách trung tố là: ::

Cho một đối tượng và một danh sách các đối tượng cùng loại, :: trả về một danh sách mới với đối tượng ở đầu và danh sách đối tượng ở đuôi Vì vậy, :: có kiểu:

1.2 Suy luận kiểu

Suy luận kiểu là quá trình xác định kiểu của biểu thức dựa trên các kiểu đã biết của các ký hiệu xuất hiện trong các biểu thức đó Sự khác nhau giữa suy luận kiểu và kiểm tra kiểu thời gian dịch là ở mức độ Thuật toán kiểm tra kiểu duyệt qua chương trình kiểm tra các kiểu khai báo bởi lập trình viên Trong suy luận kiểu, ý tưởng đưa

ra là một số thông tin không cần đặc tả, và một số dạng suy diễn logic sẽ được dùng để xác định kiểu của các biến được sử dụng Suy diễn kiểu được phát triển bới Robin Milner cho ngôn ngữ lập trình ML Các ý tưởng tương tự được phát triển độc lập bởi Curry và Hindley trong khi nghiên cứu tính toán lambda

Mặc dù các suy diễn kiểu thực tế đã được phát triển cho ML, nhưng suy diễn kiểu có thể được áp dụng cho rất nhiều ngôn ngữ lập trình Chẳng hạn, về mặt nguyên tắc suy diễn kiểu có thể áp dụng cho C hoặc cho các ngôn ngữ khác

Để cung cấp dạng kiểm tra kiểu thời gian dịch linh hoạt, suy diễn kiểu ML cũng

hỗ trợ đa hình Khi xem xét kỹ thuật toán suy diễn kiểu, thuật toán này sẽ nhận biết

Trang 20

mọi kiểu của các biến và xác định chúng bằng một kiểu chuyên biệt nào đó như int, bool hoặc string

Suy luận kiểu

val f1 = fn : int -> int

Hàm f1 cộng 2 cho đối số của nó Trong ML, 2 là hằng nguyên; số thực 2 cần phải viết dạng 2.0 Phép toán + được tải đè; nó có thể là phép cộng nguyên hoặc phép cộng thực Trong hàm này, tuy nhiên, phép cộng cần phải là cộng nguyên, vì 2 là số nguyên Do đó, đối số của hàm x cần phải là số nguyên Tập hợp tất cả các quan sát này, chúng ta nhận được f1 cần phải có kiểu int -> int

Ví dụ 1.11:

fun f2(g,h) = g(h(0));

val f2 = fn : ('a -> 'b) * (int -> 'a) -> 'b

Kiểu („a -> „b)*(int -> „a) -> „b được suy bởi chương trình dịch và được phân tích như ((„a -> „b)*(int -> „a)) -> „b

Thuật toán suy luận kiểu chỉ ra rằng, vì h được áp dụng cho biến nguyên, h cần phải là hàm biến nguyên vào một kiểu gì đó Thuật toán thể hiện kiểu gì đó bằng cách đưa ra biến kiểu mà được viết dạng „a Sau đó thuật toán suy luận kiểu kết luận rằng g cần phải là hàm mà lấy giá trị của h trả về (một cái gì đó kiểu „a) và trả về một cái gì

đó khác đi Vì g không bị ràng buộc trả về cùng kiểu với giá trị như h, thuật toán biểu diễn cái gì đó thứ hai bằng biến kiểu giá trị mới „b Đặt các kiểu của h và g cùng nhau, chúng ta có thể thấy đối số thứ nhất của f2 có kiểu („a -> „b) và đối thứ hai có kiểu (int -> „a) Hàm f2 lấy cặp hai hàm này như đối số và trả về giá trị như hàm g Do đó kiểu của hàm f2 sẽ là ((„a -> „b)*(int -> „a)) -> „b như đầu ra của chương trình dịch đã nêu

[2 , Tr 39 – 40]

Trang 21

Thuật toán suy luận kiểu

1.2.2.

Thuật toán suy luận kiểu ML sử dụng ba bước sau:

1 Gán kiểu cho biểu thức và mỗi biểu thức con Sử dụng biến kiểu đối với mọi biểu thức phức hợp hoặc biến Với các thao tác hoặc các hằng đã biết, như + hoặc

3, sử dụng kiểu mà được biết cho ký hiệu đó

2 Sinh ra tập các ràng buộc trên kiểu, sử dụng cây phân tích cú pháp của biểu thức đó Các ràng buộc này phản ánh một tính chất là nếu hàm được áp dụng cho một đối số nào đó, thì kiểu của đối số đó cần phải bằng kiểu miền xác định của hàm

số

3 Giải các ràng buộc này theo cách đồng nhất, mà là thuật toán dựa trên phép thế để giải hệ các phương trình Thuật toán suy luận kiểu được giải thích qua các

ví dụ sau Các ví dụ này thể hiện các khía cạnh sau:

giải thích thuật toán định nghĩa hàm đa hình

áp dụng hàm đa hình hàm đệ quy

hàm với nhiều mệnh đề suy luận kểu chỉ ra lỗi của chương trình [2 , Tr 40]

Định nghĩa hàm đa hình

1.2.3.

fun apply(f,x) = f(x);

val apply = fn : ('a -> 'b) * 'a -> 'b

Chỉ còn một cách duy nhất để có int -> int = u -> s là nếu u = s = int Xử lý như trước, ta thế int cho cả u và s trong phương trình còn lại Do đó ta có

Đây là ví dụ về hàm kiểu của nó bao gồm các biến kiểu Thuật toán suy luận kiểu bắt đầu từ việc gán kiểu cho mỗi biểu thức Để làm cho dễ hiểu nhất về thuật toán, viết hàm như biểu thức lambda (f,x).fx ánh xạ cặp (f,x) vào kết quả áp dụng hàm f cho x

Đây là cây cú pháp của (f,x).fx, trong đó đỉnh cặp được sử dụng ở bên trái để chỉ ra rằng đối (f,x) của hàm đó là một cặp gắn kết tới f và x

Trang 22

t = u -> s

Trang 23

Nói cách khác, kiểu của việc áp dụng f x có kiểu s, như vậy kiểu của hàm f bằng kiểu của đối số -> s Vì kiểu của đối số là u, nên cho ta ràng buộc t = u -> s

Ràng buộc thứ hai lấy từ khái quát lambda có dạng như sau:

r = t * u -> s

Thể hiện qua lời nói, thì kiểu của (f,x).fx cần phải bằng kiểu của đối số -> s, với s là kiểu của cây con thể hiện kết quả hàm Vì đối là cặp (f,x), nên kiểu của đối là t

* u

Các ràng buộc có thể được giải theo thứ tự Trước hết ta có t = u -> s, sau đó thế

u -> s cho t vào ràng buộc còn lại, ta nhận được

r = (u -> s) * u -> s

Đây là kiểu của hàm số đã cho Nếu ta viết lại (u -> s) * u -> s với „a và „b thay cho u và s, thì ta nhận được kiểu mà chương trình dịch đưa ra Vì ở đây có các biến kiểu trong kiểu của biểu thức, nên hàm có thể được sử dụng (u -> s) * u -> s cho nhiều kiểu của đối số Nó được minh họa trong ví dụ sau, mà sử dụng kiểu chúng ta vừa tính toán cho apply [2 , Tr 41 – 48]

fun sum(x) = x + sum(x-1);

val sum = fn : int -> int

Đây là đồ thị cú pháp của hàm, với biến kiểu gắn kết với mỗi đỉnh trừ đối với +,

- và 1, ở đây chúng ta bỏ qua các phép ghi đè và coi đây là các phép toán nguyên và hằng nguyên Vì chúng ta đang xác định kiểu của tổng, ta gắn biến kiểu với tổng và tiếp tục xử lý

Trang 24

Bắt đầu từ việc áp dụng phép +, - và xử ký từ dưới thấp phả, các ràng buộc gắn kết với hàm và trừu tượng lambda trong biểu thức này là

int (int int) = r t,

int (int int) = r u,

Trang 25

Ví dụ xét hàm bổ sung trên danh sách được định nghĩa như sau:

Như kiểu: „a list * „a list -> „a list chỉ ra, hàm append có thể được áp dụng cho một cặp danh sách mà cả hai danh sách đều có cùng kiểu các phần tử của danh sách

Vì vậy hàm append là hàm đa hình trên danh sách

Chúng ta bắt đầu suy luận kiểu cho append bằng thuật toán ba bước cho mệnh

đề thứ nhất của định nghĩa, sau đó lặp lại các bước này cho mệnh đề thứ hai Nó cho ta hai kiểu:

fun append(nil,l) = l

|append(x::xs,l) = x::append(xs,l);

val append = fn : 'a list * 'a list -> 'a list

Bằng trực quan, mệnh đề thứ nhất có kiểu „a list * „b -> „b, vì đối thứ nhất cần sánh với nil, nhưng đối thứ hai có thể là bất cứ cái gì Mệnh đề thứ hai có kiểu „a list

*b -> „a list, vì kết quả trả về là danh sách chứa phần tử thứ nhất của danh sách truyền vào như đối thứ nhất

Nếu ta buộc có ràng buộc:

'a list * 'b -> 'b = 'a list* 'b -> 'a list

thì „b = „a list, điều đó cho ta kiểu cuối cùng của append là

append : 'a list * 'a list -> 'a list [2, Tr 52]

Trang 26

Thành phần của hàm đệ quy

1.3.2.

Hàm đệ quy gồm 2 thành phần:

Phần cơ sở: Điều kiện để thoát khỏi đệ quy

Phần đệ quy: Thân hàm có chứa lời gọi đệ quy

là đối số và trả về như là kết quả của các hàm khác, và hàm có thể được lưu trữ và lấy

ra từ các cấu trúc dữ liệu như danh sách và cây Chúng ta sẽ thấy rằng tính bậc nhất của kiểu hàm là một đặc tính quan trọng có ý nghĩa trong ML

Các hàm mà lấy các hàm như các đối số hay trả về các hàm như kết quả được biết đến như là bậc cao hàm (hoặc, ít thường xuyên hơn, như phiếm hàm hoặc toán tử)

Trang 27

Hàm bậc cao thường phát sinh trong toán học Hàm bậc cao là những công cụ không quen thuộc đối với nhiều lập trình viên của các ngôn ngữ lập trình nổi tiếng nhất chỉ có

cơ chế thô sơ để hỗ trợ việc sử dụng chúng Ngược lại hàm bậc cao đóng một vai trò nổi bật trong ML, với một loạt các phép khai triển thú vị

Ví dụ, các toán tử vi phân là hàm bậc cao, khi đưa ra một hàm trên các trục số, sinh ra đạo hàm đầu tiên của nó như là một hàm trên trục số

f: (a,b) → R

x f‟(x)

Chúng ta cũng gặp phải phiếm hàm ánh xạ hàm thành số thực và số thực thành hàm

Một ví dụ mẫu được cung cấp bởi tích phân xác định xem như là một hàm của hàm dưới dấu tích phân của nó, và một ví dụ nữa là tích phân xác định của một hàm nhất định trên một khoảng (a, x), xem như là một hàm của a, mà sinh ra diện tích dưới đường cong từ a đến x là hàm của x

Trong khi dường như rất đơn giản, các nguyên tắc của phạm vi từ vựng là nguồn sức mạnh đáng kể Chúng ta sẽ chứng minh điều này thông qua một loạt các ví

dụ

Để hiểu rõ chúng ta hãy xem xét một số ví dụ về việc đi qua các hàm như các đối số và sinh ra các hàm như kết quả Ví dụ tiêu chuẩn về việc đi qua một hàm như là đối số là hàm, mà áp dụng một hàm cho trước cho mọi phần tử của một danh sách Nó

Các định nghĩa của hàm map‟ cho trước ở trên có một hàm và danh sách đối

số, sinh ra một danh sách mới như kết quả Thường thì nó xảy ra mà chúng ta muốn sắp xếp cùng một hàm trên vài danh sách khác nhau Điều bất tiện (và một chút không hiệu quả) là luôn luôn đi cùng hàm để map‟, với các danh sách đối số khác nhau mỗi lần Thay vào đó, chúng tôi muốn tạo ra một trường hợp của sắp xếp chuyên biệt cho

Trang 28

các hàm cho trước mà sau đó có thể được áp dụng cho nhiều danh sách khác nhau Điều này dẫn đến các định nghĩa về hàm map:

fun map f nil = nil

|map f(h :: t) = (f h) :: map(f t)

Các hàm map để xác định có kiểu („a ->‟b) ->‟a list -> „b list Nó cho một hàm của loại „a ->‟b như là đối số, và sinh ra một hàm khác của loại ‟a list -> „b list như là kết quả

Các đoạn từ map‟ đến được gọi là currying Chúng ta đã thay đổi một hàm hai đối số (đúng hơn, một hàm cho một đôi như là đối số) vào một hàm mà cho hai đối số liên tiếp, sinh ra sau khi một hàm đầu tiên mà cho đối số thứ hai là đại diện duy nhất của nó Đoạn này có thể được mã hoá như sau:

fun curry f xy = f(x,y)

Các loại curry là

Với một hàm hai đối số, curry trả về một hàm khác, khi được áp dụng cho đối

số đầu tiên, sinh ra một hàm mà, khi áp dụng vào đối số thứ hai, áp dụng các hàm hai đối số ban đầu với đối số đầu tiên và thứ hai, cho trước một cách riêng biệt

Chú ý map có thể luân phiên được định nghĩa bởi các phép liên kết

fun map f l = curry map’ f l

Các phép khai triển hoàn toàn là gắn kết trái, để phép định nghĩa này là tương đương với việc khai báo dài dòng hơn

fun map f l = ((curry map’) f)l

Dãy vô hạn

1.4.2.

Hàm bậc cao - những hàm có các hàm như là đối số hoặc trả lại các hàm như kết quả - là những công cụ mạnh mẽ cho xây dựng chương trình Một phép khai triển thú vị của hàm bậc cao là thực hiện dãy vô hạn của các giá trị như các hàm từ các số tự nhiên (các số nguyên không âm) vào các loại giá trị trên dãy Ta sẽ phát triển một phần nhỏ trong phép toán để tạo và thao tác với các dãy, tất cả trong số đó là hàm bậc cao

kể từ khi chúng cho các dãy (hàm) như các đối số và hoặc trả lại chúng như kết quả Một cách tự nhiên để xác định nhiều dãy là bằng phép quy nạp, hoặc tự tham chiếu Bởi vì các dãy là hàm, ta có thể sử dụng các định nghĩa hàm đệ quy để xác định các chuỗi như vậy

Trang 29

Chúng ta hãy bắt đầu bằng cách phát triển một cấu trúc dãy Dưới đây là một ký

(if n mod 2=0 then s1 else s2) (n div 2)

fun stretch k s n = s (n div k)

fun shrink k s n = s (n* k)

fun drop k s n =s (n+k)

fun shift s = drop 1 s

fun take 0_ = nil

|take n s = s 0:: take (n-1) (shift s)

fun loopback loop n = loop (loopback loop) n

end

1.5 Quy nạp và đệ quy

Qui nạp là nói lên: Nếu muốn chứng minh một giả thiết p, chúng ta sẽ chứng minh p đúng với một trường hợp cơ sở, dựa vào giả thiết qui nạp, ta sẽ chứng minh nếu p đúng với trường hợp N, thì p cũng đúng với trường hợp N‟ Việc biến đổi từ N→N‟ sẽ là bao nhiêu hàm toàn bộ tập các dữ liệu

Bản chất của lập trình hàm (functional programming) là toán học Nghĩa của từ hàm trong lập trình chính là hàm toán học

Phép quy nạp toán học được sử dụng để chứng minh đúng với tất cả các

số tự nhiên n Ta chứng minh rằng các trường hợp cơ bản sẽ cố định, cụ thể là đúng Sau đó ta chứng minh kéo theo với mọi k Điều này được biết

Trang 30

đến như là bước quy nạp Nếu ta có thể chứng minh cả hai, sau đó suy ra bằng phép quy nạp, rằng đúng với mọi n Các quy tắc quy nạp có thể được viết, về mặt hình thức hơn, như

| ( (0) ( k: ( )k (k 1))) n: ( )n

Các bước quy nạp thường trông như thể ta đang lẩn tránh việc chứng minh Tuy nhiên, ta có thể sử dụng các bước quy nạp để xây dựng một chứng minh rõ ràng, với bất kì n cho trước Giả sử, ví dụ ta muốn có một chứng minh rằng đúng Ta biết rằng đúng từ trường hợp cơ bản Sau đó ta có thể áp dụng các bước chứng minh quy nạp cho trường hợp khi k = 0 để có chứng minh rằng là đúng Áp dụng các bước quy nạp một lần nữa, lần này với k = 1, cho ta một bước quy nạp rằng được chứng minh là đúng Ta có thể lặp lại quá trình này cho tới khi ta có được chứng minh của Đây là phương pháp tổng quát: ta có thể mở rộng các bằng chứng quy nạp

để tạo ra một chứng minh không quy nạp của với bất kì n cho trước

Thông thường, để chứng minh , ta phải giả định rằng , đối với một

số m < n, nhưng không nhất thiết phải chính xác phải nhỏ hơn Một chút suy nghĩ nên thuyết phục rằng sự chứng minh của phép quy nạp vẫn hoạt động nếu ta thay thế các bước quy nạp bằng một chứng minh rằng là đúng theo giả định rằng là đúng với mọi m < k Thật vậy, nếu ta có thể chứng minh điều này, ta không cần chứng minh chi tiết cho trường hợp k = 0, tức là đưa ra một chứng minh của theo giả định rằng đúng với mọi m < 0 Vì không có số tự nhiên nhỏ hơn 0, "trường hợp quy nạp" đã bao gồm một chi tiết rằng là đúng Ta tham chiếu đến kiểu này của phép quy nạp toán học như một phép quy nạp đầy đủ hay hoàn toàn Về mặt hình thức hơn,

else fastpower(x*x, k div 2)*x

Ta thấy rằng với mọi số tự nhiên k,

Trang 31

y fastpower(y,k) = yk

Đó là đệ quy giải quyết vấn đề bằng cách chia k cho 2, hình thức đơn giản của quy nạp toán học không phải là có thể ứng dụng trực tiếp trong trường hợp này Tuy nhiên, với mọi k > 0, trong mọi trường hợp (k div 2) < k, vì vậy ta có thể sử dụng phép quy nạp hoàn toàn Mặc dù ta đang sử dụng phép quy nạp hoàn toàn, chúng ta xét các trường hợp k = 0, và k > 0 một cách riêng biệt, vì chúng được xử lí khác nhau trong định nghĩa hàm

Trong trường hợp k = 0, ta phải chứng minh Nhưng,

và trả về 1; vậy việc thay thế các đẳng thức sẽ thực hiện công việc chứng minh này

Bây giờ hãy xét trường hợp k > 0 Có 2 trường hợp phụ thuộc vào việc k chẵn, tức là k mod 2=0, hoặc lẻ

1 Giả sử k mod 2= 0 và k div 2 = j Vì vậy, k = 2j, và j < k Bây

giả thuyết quy nạp là đúng cho phép chúng ta nhận định rằng kết luận cũng là đúng,

đã được chứng minh; cụ thể trong trường hợp này,

Đưa ra bước sau cùng với một chút đại số, ta có

theo yêu cầu

2 Trường hợp k lẻ: k mod 2 = 1 và k div 2 = j Vì vậy, k=2j +1, và j < k Bây

< k, giả thuyết quy nạp là đúng cho phép chúng ta nhận định rằng kết luận cũng là đúng,

đã được chứng minh; cụ thể trong trường hợp này,

Đưa ra bước sau cùng với một chút đại số, ta có

Các hàm có hai tham số; tham số thứ hai luôn nhỏ đi theo đệ quy Điều này cho phép ta thực hiện một phép quy nạp dựa trên kích thước của tham số này Thuyết quy nạp bao gồm tất cả các giá trị của các đối số khác, điều này cho phép ta sử

Trang 32

dụng biến chúng thành giá trị cụ thể được dùng trong các lời gọi là trong công thức đệ

quy (trong ví dụ của x2) [4]

http://homepages.inf.ed.ac.uk/mfourman/teaching/mlCourse/notes/L09.pdf

Tóm tắt chương 1:

Qua chương 1 giúp ta hiểu cú pháp của ngôn ngữ lập trình hàm ML, tính chất quy nạp toán học và đệ quy của lập trình hàm Ngoài ra chúng ta còn làm quen với phong cách lập trình hàm thông qua việc định nghĩa hàm bộ phận và áp dụng chúng cho các bộ tham số tương ứng ML còn hỗ trợ kiểm tra kiểu tự động, định nghĩa và áp dụng các hàm có tham số kiểu và tiến tới kiểm chứng và chứng minh tự động Đồng thời nó hỗ trợ tính chất hàm bậc cao là truyền hàm và trả về hàm

Trang 33

SUY LUẬN MỘT SỐ BÀI TOÁN RỜI RẠC BẰNG Chương 2.

LẬP TRÌNH HÀM ML

Công cụ quan trọng nhất để có được chương trình là phải có đặc tả và kiểm chứng Một đặc tả là một mô tả về các hành vi của một đoạn mã, có nhiều hình thức đặc tả Một loại đặc tả mô tả “định dạng” về giá trị của một biểu hiện mà không nói bất cứ điều gì về giá trị bản thân Đặc tả thay vì mô tả các giá trị của một biểu thức, nó

mô tả hiệu ứng nó có thể tạo ra khi đánh giá Đầu vào - đầu ra là một hành vi Một đầu vào - đầu ra là một đặc tả toán học Thời gian và dung lượng bộ nhớ cần, số lượng các bước thực hiện hoặc kích cỡ của một cấu trúc dữ liệu Phép quy nạp toán học được sử dụng để chứng minh một tính chất của hàm số là đúng chẵn hạn đúng với tất cả các số tự nhiên n Ta chứng minh rằng các trường hợp cơ bản sẽ cố định, cụ thể là đúng Sau đó ta chứng minh kéo theo với mọi k Điều này được biết đến như là bước quy nạp Mà quy nạp toán học lại dựa trên đệ quy của hàm số

2.1 Tính đúng đắn của thuật toán

Tính đúng đắn:

2.1.1.

Chương trình tính tổng chuổi số từ 1 đến n

fun arithSum(N) = let

in (while !i <= N do (s := !s + !i;

i := !i + 1);

!s) end;

Chương trình 2.1: Hàm tính tổng chuổi số từ 1 đến n

Nhưng làm thế nào chúng ta biết nếu điều này đúng hoặc nó có ý nghĩa gì cho việc chương trình là đúng? Một cách trực quan, một chương trình là đúng nếu, cho một đầu vào nhất định, nó sẽ cho ra một đầu ra mong muốn, trong trường hợp này, nếu

Trang 34

đầu vào n là một số nguyên, sau đó nó sẽ tính và cho ra tổng như mong muốn Khi lập trình viên kiểm tra phần mềm, anh ta sẽ chạy một số đầu vào được lựa chọn và so sánh kết quả chương trình với kết quả mong đợi Rõ ràng phương pháp này chính xác dựa trên thử nghiệm; Nó là lý luận quy nạp phi toán học, và nó có thể tạo ra một mức độ đúng đắn của chương trình, nó không thể chứng minh chính xác tuyệt đối Quá trình cho kết quả mong muốn của chương trình có thể kiểm tra thuận tiện bằng tay Nhưng chúng ta không thể kiểm tra với mọi dữ liệu đầu vào Chúng ta xem xét khái niệm đúng đắn này một cách hình thức hơn

Sự đúng đắn của một chương trình được xác định bởi 2 mệnh đề: điều kiện đầu, cái mà chúng ta mong đợi là đúng trước khi chương trình được đánh giá và điều kiện sau, cái mà chúng ta mong đợi sẽ giữ lại sau đó Nếu điều kiện sau giữ lại bất cứ khi nào điều kiện trước đáp ứng Khi đó chúng ta nói rằng thuật toán là đúng đắn Cách tiếp cận này đặc biệt hữu ích vì nó có xu hướng rút gọn dần để áp dụng cho các phần nhỏ hơn của một thuật toán; Giả sử chúng ta có điều kiện đầu là một số nguyên không

âm a:

b := !a + 1

Chúng ta có thể nghĩ đến nhiều điều kiện sau hợp lý cho điều này bao gồm b là

số nguyên dương b > a và b – a = 1 Cái nào trong điều này có nghĩa, chúng là những điều mà chúng ta có thể chứng minh bằng toán học Các mệnh đề đưa về suy luận bởi các lệnh gán

Hơn nữa hậu điều kiện của một câu lệnh đơn là tiền điều kiện của câu lệnh sau đó; Tiền điều kiện của toàn bộ chương trình là tiền điều kiện của tuyên bố đầu tiên; và hậu điều kiện của tuyên bố cuối cùng là hậu điều kiện của toàn bộ chương trình Như vậy bằng cách kiểm tra từng câu lệnh lần lượt tác động như thế nào đến các mệnh đề trong điều kiện trước và sau, chúng ta có thể chứng minh một thuật toán là đúng Xét chương trình tính phần dư a mod b (chúng ta vẫn có thể sử dụng div)

fun remainder(a, b) = let

in

Trang 35

r end;

Chương trình 2.2: Hàm tìm số dư của a chia b

Đôi khi chúng ta sẽ xem xét các khai báo đơn giản như thiết lập các tiền điều kiện, vì chúng làm tất cả các việc tính toán trong ví dụ này, chúng ta sẽ xem xét từng câu lệnh một Tiền điều kiện cho toàn bộ chương trình là a, b Z+ Kết quả của chương trình sẽ là a mod b tương đương với cách khai báo hậu điều kiện là r = a mod

b Phân tích a div b là làm gì chúng ta dựa vào kết quả tiêu chuẩn từ lý thuyết số, một bằng chứng có thể được tìm thấy trong bất kỳ tài liệu toán học rời rạc

Định lí: Nếu n Z và d Z+, khi đó tồn tại duy nhất số nguyên q và r sao cho n

= d.q + r và 0 ≤ r < d

Định lí này là cơ sở cho ta định nghĩa phép chia nguyên và chia dư Số q trong định lí trên gọi là thương và a div b = q Và r gọi là phần dư và a mod b = r Chúng ta phân tích thuật toán:

val r = a – b Bằng lệnh gán r = a – b Bằng cách thay thế và theo biến đổi đại

số: r = a – (a – R) = R, do đó bằng định nghĩa của mod ta có: r = a mod b [ 3 , Tr 109 – 110]

Bất biến vòng lặp

2.1.2.

Ví dụ trước hầu như đơn giản Nó bỏ qua những điều làm cho thuật toán khó suy luận Những biến thay đổi giá trị, phân nhánh và lặp đi lặp lại, chúng ta sẽ khám phá làm thế nào để suy luận về sự biến đổi của các biến và đánh giá của các điều kiện Chúng ta xem xét các vòng lặp, cái mà có nhiều khó khăn hơn

Đơn giản một vòng lặp có thể được phân tích tính đúng đắn bằng cách gỡ bỏ

nó Nếu một vòng lặp đã được thực hiện 5 lần khi đó chúng ta có thể chứng minh tính

Trang 36

đúng đắn của chương trình sẽ cho ra kết quả, nếu chúng ta dán vào thân của vòng lặp được lặp lại 5 lần Vấn đề với cách tiếp cận đó hầu như luôn luôn là số lần lặp lại của vòng lặp chính nó phụ thuộc vào đầu vào Như chúng ta thấy trong ví dụ số học i ≤ N Lần lặp là việc thực hiện thân trong một vòng lặp và biểu thức Bool mà chúng ta kiểm tra xem có tiếp tục vòng lặp không được gọi là guard – canh cổng Chúng ta muốn chứng minh một mệnh đề đúng cho một số lần lặp tuỳ ý Số lần lặp chắc chắn là một

số nguyên Điều này đề xuất chứng minh qui nạp theo số lần lặp

Chúng ta cần một mệnh đề, mà đối số của nó là số N của lần lặp Chúng ta cần chứng tỏ nó đúng cho tất cả N >= 0 Điều này đòi hỏi rất nhiều sự linh hoạt, vì nó phải đúng cho lần 0, nó là tiền điều kiện cho toàn bộ vòng lặp và nó phải đúng cho N lần lặp đi lặp lại, nó là hậu điều kiện cho toàn bộ vòng lặp và nó phải đúng với mỗi giá trị

từ 0 đến N Nó là tiền điều kiện và hậu điều kiện cho mổi lần lặp Tất nhiên, nếu một câu lệnh hoặc phần câu lệnh mà có tiền điều kiện và hậu điều kiện giống hệt nhau, thì điều đó cho thấy mã không làm gì cả Đó không phải là những gì mà chúng ta thấy ở đây, tiền điều kiện và hậu điều kiện không giống nhau và được tham số hoá Chúng ta phát biểu mệnh đề này theo cách sao cho tham số ghi nhận được những gì mà vòng lặp thực hiện Các mệnh đề phải xác định những gì vòng lặp không thay đổi, đối với số lần lặp

Một bất biến vòng lặp là một mệnh đề, mà đối số của nó n là số lần lặp, được chọn sao cho:

I(0) là đúng trước khi vòng lặp bắt đầu Điều này cần được chứng minh như là trường hợp cơ sở trong chứng minh

I(n) kéo theo I(n+1), nếu tiền điều kiện đúng trước vòng lặp cho trước, nó vẫn đúng sau lần lặp đó, điều này cần được chứng minh như là trường hợp qui nạp trong chứng minh

Nếu vòng lặp kết thúc, thì I(N) là đúng (điều này suy ra từ hai khẳng định trên

và nguyên lý qui nạp toán học)

Vì vậy nếu vòng lặp kết thúc, thì I(N) suy ra hậu điều kiện của toàn bộ vòng lặp

Bốn điểm này tương ứng bốn bước trong chứng minh một vòng lặp là đúng đắn Chúng ta chứng minh được rằng vòng lặp dược khởi tạo như thiết lập bất biến vòng

Trang 37

lặp Một sự lặp lại duy trì bất biến vòng lặp, vòng lặp cuối sẽ kết thúc, điều kiện tiên tạo thành chứng minh qui nạp, cái mà chúng ta quan tâm Hai bước cuối hoàn tất

chứng minh tính đúng đắn của thuật toán [ 3 , Tr 110 – 111]

Ví dụ lớn

2.1.3.

Bây giờ chúng ta áp dụng cách này cho thuật toán đưa ra ở đầu chương, hậu điều kiện cho toàn bộ thuật toán là biến s sẽ bằng giá trị của chuổi Khi s là biến tham chiếu, nó sẽ thay đổi giá trị trong suốt quá trình chạy chương trình, cụ thể sau n lần lặp

Định lý: Cho mọi n W chương trình ArithSum

 Trường hợp quy nạp: Giả sử I(n‟) là đúng Đặt iold là giá trị của i sau n lần lặp và trước lần lặp thứ n‟ + 1 và inew là giá trị của i sau lần lặp n‟ +

1, tương tự như vậy đối với sold và snew Do I(n‟) bao gồm: iold = n‟ + 1

và sold =

' 1

n

k

k.Bằng phép gán và phép thế '

1 ' 1

n

new old old

k

tương tự: i new i old 1 ( ' 1) 1n Vì thế: I(n‟+1) đúng

Vì vậy theo qui nạp toán học I(n) đúng với mọi n W và do đó I(N) đúng Trước khi tiếp tục, chú ý đến vấn đề chọn biến của chúng ta, với N, n và n‟ là gì? N là đầu vào của ArithSum n là đối số của I(n), dùng để phân tích ArithSum, một điều mà ta cố gắng chứng minh là I(N) đúng (I là đúng cho n = N) n‟ là số nguyên tuỳ

ý như là I(n‟) được sử dụng bên trong chứng minh quy nạp I(n) cho tất cả n Các biến

Trang 38

rõ ràng là cần thiết trong chứng minh và trở nên đặc biệt khó khăn trong việc chứng minh sự đúng đắn của thuật toán khi bạn đang đưa ra các biến tương tự và chứng minh một tính chất của một đối tượng mà nó có biến Làm sai sẽ làm cho điều này vô nghĩa

Kết thúc Do I(N) đúng, sau khi lặp N lần, i = N + 1 > N, do đó điều kiện tiếp tục vòng lặp guard sẽ sai

Hơn thế nữa bởi I(N) đúng, sau N lần lặp,

fun findMin(array) = let

val min = ref (sub(array, 0));

val i = ref 1;

in

then min := sub(array, !i) else ();

Trang 39

Vì vậy vòng lặp bất biến của chúng ta cần phải nắm khái niệm nhỏ nhất Để làm điều này thuận tiện hơn chúng ta giới thiệu ký hiệu A[i j], cái mà đại diện cho tất cả các phần tử của tập hợp A trong khoảng từ i đến j

I(n) là sau n lần lặp, i = n + 1 và min là minimum của mảng A[0 i]

Bây giờ chúng ta chứng minh nó là bất biến vòng lặp

Giả sử: Mảng là một mảng nguyên:

 Trường hợp cơ sở/ Khởi tạo Sau khi 0 lần lặp, min = array[0] và i = 1 =

0 + 1 Theo mệnh đề mảng con (subarray), array[0] là phần tử duy nhất của mảng array[0 1] = array[0 i], và vì vậy min là minimum của array[0 i] bằng định nghĩa minimum, hơn nữa I(0) và tồn tại n‟ >= 0 như I(n‟)

 Trường hợp đệ quy: Giả sử I(n‟), tức là minold là giá trị của min sau khi n‟ lần lặp và trước n‟ + 1 lần lặp Tương tự như vậy định nghĩa iold và inew Vì I(n‟), nên minold là minimum của mảng array[0 iold] Bây giờ chúng ta có hai trường hợp:

Trường hợp 1: Giả sử array[iold] < minold Khi đó minnew = array[iold], giả

sử x array[0 (iold +1)] Nếu x = array[iold], thì minnew xnếu không thì

0 old

x array i và minnew array i[old] minold x, bước cuối cùng ta định nghĩa

minimum, trong cả hai trường hợp minnew xvà minnew là minimum của array[0 (iold + 1)]

Trường hợp 2: Giả sử array i old minoldthì minnew = minold Bây giờ

Trang 40

Cả hai trường hợp minnew là minimum của array[0 (iold + 1)]; inew = iold+ 1 = (n‟ + 1) + 1 do phép thế, cũng bằng cách thay thế minnew là minimum của

array[0 inew], vì thế I(n‟+ 1) và I(n) là bất biến cho vòng lặp này [ 3 , Tr 111 – 112]

có tính xây dựng và trên thực tế nó cho nhiều thông tin hơn là tính đúng đắn của mệnh

đề Chứng minh truyền thống của QRT không mang tính xây dựng

Tuy nhiên, định lí chính nó, nếu nghiên cứu một cách cẩn thận chúng ta biết nhiều về cách xây dựng thuật toán tìm ra tỉ số và phần dư gọi là thuật toán chia (Division Algorithm) Trong chương trước chúng ta chứng minh kết quả (cụ thể, về tính đúng đắn) về thuật toán Trong chương này, chúng ta sẽ suy thuật toán từ kết quả cuối theo quá trình mà bản chất là ngược lại với quá trình trong chương trước Xét những gì QRT nói về q và r, hai khẳng định có thể được coi là các hạn chế cần phải đáp ứng khi chúng ta cố gắng tìm các giá trị phù hợp

Ngày đăng: 31/05/2023, 10:41

TỪ KHÓA LIÊN QUAN

TRÍCH ĐOẠN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm