Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 18 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
18
Dung lượng
350 KB
Nội dung
CÁC THUẬT TOÁN SẮPXẾP 1. Giới thiệu bài toán Ta sẽ xem xét các thuật toán để sắpxếp một tập các bản ghi theo giá trị của một trường nào đó. Thứ tự sắpxếp là một quy luật đã được định nghĩa rõ ràng: thường là thứ tự tăng dần (hay giảm dần) đối với dãy số, thứ tự từ điển đối với các chữ, . Bài toán đặt ra ở đây là sắpxếp đối với một tập gồm n bản ghi r 1 , r 2 , ., r n . Tuy nhiên không phải toàn bộ các trường dữ liệu trong bản ghi đều được xét đến trong quá trình sắpxếp mà chỉ một hoặc vài trường nào đó thôi. Trường như vậy gọi là trường khoá. Sắpxếp sẽ được tiến hành dựa vào giá trị của khoá này. Do khoá có vai trò đặc biệt như vậy nên sau này khi trình bày các thuật toán hay trong các ví dụ minh hoạ, ta sẽ coi nó như đại diện cho bản ghi và để đơn giản hơn ta chỉ nói đến khoá thôi. Thực ra phép đổi chỗ được tác động lên các bản ghi và ở đây ta cũng chỉ nói đến phép đổi chỗ với các khoá. Giá trị khoá có thể là số hay chữ và thứ tự sắpxếp cũng được quy định tương ứng với khoá. Ở đây để minh hoạ cho các thuật toán sắpxếp ta sẽ coi giá trị khoá là số và thứ tự sắpxếp là tăng dần. Bây giờ bài toán sắpxếp được đặt ra đơn giản nhưng vẫn không làm mất tính tổng quát như sau: cho một dãy các khoá a 1 , a 2 , ., a n là các số nguyên (tương ứng với các bản ghi r 1 , r 2 , ., r n ). Hãy sắpxếp các khoá trên theo thứ tự tăng dần. 2. Các thuật toán sắpxếp đơn giản a. Sắpxếp kiểu lựa chọn (Selection sort) Một trong những phương pháp đơn giản nhất để sắpxếp là dựa trên phép lựa chọn: đầu tiên tìm phần tử nhỏ nhất trong dãy và hoán vị nó với phần tử trong vị trí đầu tiên. Sau đó tìm phần tử nhỏ nhất kế tiếp và hoán vị nó với phần tử trong vị trí thứ hai và tiếp tục theo phương pháp này cho đến khi toàn bộ dãy được sắp xếp. Cách sắpxếp mà ta vẫn thường sử dụng trước đây chính là phương pháp này. Để tìm phần tử nhỏ nhất thứ i (i = 1, 2, ., n-1) ta so sánh nó với các phần tử thứ j đứng sau nó (j = i+1, ., n), vì các phần tử đứng trước đã được đứng đúng vị trí. Nếu phần tử thứ j nào nhỏ hơn phần tử thứ i thì ta đổi chỗ 2 phần tử này với nhau. Như vậy phần tử thứ i luôn giữ lại phần tử nhỏ nhất, vì vậy sau một lượt duyệt đối với j thì phần tử nhỏ nhất thứ i sẽ nằm ở vị trí thứ i. Thủ tục sắpxếp kiểu lựa chọn được cài đặt như sau: procedure selection; var i, j, t : integer; begin for i := 1 to n do for j := i+1 to n do if a[i] > a[j] then begin t := a[i]; a[i] := a[j]; a[j] := t; end; end; b. Sắpxếp kiểu thêm dần (Insertion sort) Nguyên tắc sắpxếp ở đây dựa theo kinh nghiệm của những người chơi bài. Khi có i-1 lá bài đã được sắpxếp ở trên tay, nay rút thêm lá bài thứ i nữa thì sắpxếp lại như thế nào? Có thể so sánh lá bài mới rút lần lượt với các lá bài đã có ở trên tay để tìm ra “chỗ” thích hợp và “chèn” nó vào chỗ đó. 1 Dựa vào nguyên tắc trên ta có thể xây dựng thuật toán sắpxếp như sau: lúc đầu dãy coi như chỉ có một khoá là a 1 đã được sắp xếp. Xét thêm a 2 , so sánh nó với a 1 để xác định chỗ chèn nó vào, sau đó ta có một dãy gồm hai khoá đã được sắp xếp. Cứ tiếp tục như vậy đối với a 3 , a 4 , . Cuối cùng sau khi xét xong a n thì dãy khoá đã được sắpxếp hoàn toàn. Hình ảnh của thuật toán sắpxếp kiểu thêm dần với dãy khóa 42, 23, 74, 11, 65, 58 được minh hoạ qua bảng sau: Lượt 1 2 3 4 5 6 Khoá đưa vào 42 23 74 11 65 58 1 42 23 23 11 11 11 2 42 42 23 23 23 3 74 42 42 42 4 74 65 58 5 74 65 6 74 Thủ tục sắpxếp kiểu thêm dần được cài đặt như sau: procedure insertion; var i, j, t : integer; begin for i := 2 to n do begin t := a[i]; j := i - 1; while (j >= 1) and (a[j] > t) do begin a[j+1] := a[j]; j := j - 1; end; a[j+1] := t; end; end; c. Sắpxếp nổi bọt (Bubble sort) Ý tưởng của thuật toán này như sau: dãy các khoá sẽ được duyệt từ đáy lên đỉnh. Dọc đường nếu gặp hai khoá kế cận ngược thứ tự thì đổi chỗ chúng cho nhau. Như vậy trong lượt đầu tiên khoá có giá trị nhỏ nhất sẽ chuyển dần lên đỉnh. Đến lượt thứ hai, khoá có giá trị nhỏ thứ hai sẽ được chuyển đến vị trí thứ hai, . Nếu hình dung khoá được đặt thẳng đứng thì sau từng lượt sắpxếp các giá trị khoá nhỏ sẽ “nổi” dần lên giống như các bọt nước nổi lên trong nồi nước đang sôi. Vì vậy thuật toán này được gọi với tên khá đặc trưng là sắpxếp kiểu nổi bọt. Quá trình sắpxếp của thuật toán nổi bọt có thể minh hoạ qua bảng sau: i a i Lượt 1 2 3 4 5 1 42 11 11 11 11 11 2 23 42 23 23 23 23 3 74 23 42 42 42 42 4 11 74 58 58 58 58 5 65 58 74 65 65 65 2 6 58 65 65 74 74 74 Thủ tục sắpxếp nổi bọt được cài đặt như sau: procedure bubble; var i, j, t : integer; begin for i := 1 to n-1 do for j := n downto i+1 do if a[j] < a[j-1] then begin t := a[j]; a[j] := a[j-1]; a[j-1] := t; end; end; d. Phân tích tính hiệu quả của các thuật toán sắpxếp đơn giản Cả ba thuật toán đều có độ phức tạp thời gian cỡ O(n 2 ) và độ phức tạp bộ nhớ là O(n). Vì vậy, ta sẽ lấy thuật toán sắpxếp kiểu lựa chọn đại diện cho các thuật toán sắpxếp đơn giản để thực hiện với các bộ test sau trên máy tính PentiumIV 3.0Ghz. Để thuật tiện cho quá trình test với dữ liệu lớn, ta có chút thay đổi một chút khi cài đặt là thay tất cả các biến kiểu interger thành các biến kiểu longint. Sau đây là bảng kết quả khi thực hiện thuật toán: TT n Mô tả Thời gian (giây) 1 10 Kích thước nhỏ (tạo bằng tay) 0.00 2 100 Kích thước nhỏ (tạo ngẫu nhiên) 0.00 3 1.000 Kích thước nhỏ (tạo ngẫu nhiên) 0.00 4 10.000 Kích thước trung bình (tạo ngẫu nhiên) 0.61 5 15.000 Kích thước trung bình (tạo ngẫu nhiên) 1.32 6 50.000 Kích thước trung bình (tạo ngẫu nhiên) 14.21 7 100.000 Kích thước lớn (tạo có chủ định: nửa đầu giảm, nửa sau tăng) 57.97 8 500.000 Kích thước lớn (tạo ngẫu nhiên) 1426.07 9 1.000.000 Kích thước lớn (tạo ngẫu nhiên) Quá lâu 10 1.000.000 Kích thước lớn (tạo có chủ định: nửa đầu tăng, nửa sau giảm) Quá lâu Nhìn vào bảng kết quả trên, ta thấy rằng các thuật toán cơ bản chỉ áp dụng với n ≤ 10.000. Tuy nhiên với n lớn thì chi phí về thời gian thực hiện của cả ba thuật toán trên là một chi phí cao so với một số thuật toán mà ta sẽ xét sau đây. 3. Thuật toán sắpxếp nhanh (Quick sort) Sắpxếp nhanh Quick sort là thuật toán được A.R. Hoare phát minh vào năm 1960. Quick sort là phương pháp sắpxếp phổ biến vì việc cài đặt nó không khó khăn. Ý tưởng của thuật toán như sau: chọn một khoá ngẫu nhiên nào đó làm “chốt”, thường là phần tử nằm giữa dãy. Mọi phần tử nhỏ hơn “khoá chốt” phải được xếp vào vị trí ở trước “chốt” (đầu dãy), mọi phần tử lớn hơn khoá “chốt” phải được xếp sau “chốt” (cuối dãy). Muốn vậy các phần tử trong dãy phải được so sánh với khoá chốt và sẽ đổi vị trí cho nhau, nếu nó lớn hơn chốt mà lại nằm trước chốt hoặc nhỏ hơn chốt mà lại nằm sau chốt. 3 Khi thực hiện việc đổi chỗ xong thì dãy khoá được chia làm hai đoạn: một đoạn gồm các khoá nhỏ hơn chốt, một đoạn gồm các khoá lớn hơn hoặc bằng chốt. ở các bước tiếp theo ta cũng áp dụng kỹ thuật trên cho các phân đoạn. Quá trình xử lý một phân đoạn dừng lại khi ta gặp một phân đoạn chỉ gồm một phần tử. Việc sắpxếp sẽ kết thúc khi phân đoạn cuối cùng đã được xử lý xong. Thuật toán Quick sort được mô tả như sau: Bước 1: • Chọn khoá đứng giữa dãy làm khoá chốt x := a[(l+r) div 2]; (l, r là 2 biến chỉ số đầu, cuối của một phân đoạn) • Dùng 2 biến chỉ số i, j để phát hiện ra hai khoá cần đổi chỗ. Khởi tạo giá trị ban đầu cho hai biến này: i := l; j := r; Bước 2: • Duyệt từ trái sang phải để tìm chỉ số i sao cho a[i] >= x. • Duyệt từ phải sang trái để tìm chỉ số j sao cho a[j] <= x. • Nếu i <= j thì: Đổi chỗ a[i] và a[j]. i := i + 1. j := j - 1. Lặp lại bước 2 cho đến khi i > j. Bước 3: • Nếu l < j thì lặp lại bước 1, 2 cho phân đoạn a[l], ., a[j]. • Nếu i < r thì lặp lại bước 1, 2 cho phân đoạn a[i], ., a[r]. Hình ảnh sau đây minh họa diễn biến trong lượt đầu của thuật toán với dãy số: 42, 23, 65, 74, 11, 58. Khóa chốt x = 65. i↓ j↓ 42 23 65 74 11 58 ↑_____________↑ i↓ j↓ 42 23 58 74 11 65 ↑___↑ j↓ i↓ 42 23 58 11 74 65 Dãy khoá được chia làm hai phân đoạn: (42, 23, 58 11) và (74, 65). Công việc tiếp theo lại tiến hành lần lượt trên hai phân đoạn mới này. Cài đặt: procedure qSort(l, r : integer); var i, j, x, y : integer; begin i := l; j := r; x := a[(l+r) div 2]; repeat while a[i] < x do i := i + 1; while x < a[j] do j := j – 1; if i <= j then begin 4 y := a[i]; a[i] := a[j]; a[j] := y; i := i + 1; j := j – 1; end; until i > j; if l < j then qSort(l, j); if i < r then qSort(i, r); end; Khi đó trong thân của chương trình chính ta chỉ gọi qSort(1, n); Bây giờ ta sẽ đánh giá độ phức tạp thời gian của thuật toán Quick sort. Trường hợp tốt nhất của Quick sort xảy ra khi dãy khoá luôn được chia đôi thì độ phức tạp tính toán của thuật toán là O(n.log 2 n). Như vậy khi n khá lớn thì Quick sort tỏ ra hiệu lực hơn các thuật toán đơn giản mà ta đã xét. Trường hợp xấu nhất của Quick sort xảy ra khi nửa đầu của dãy khoá đã có thứ tự sắpxếpvà nửa dãy còn lại có thứ tự ngược lại hoặc nửa đầu có thứ tự ngược với thứ tự cần sắpxếpvà nửa sau đã được sắpxếp thì độ phức tạp của thuật toán là O(n 2 ). Trường hợp này Quick sort không hơn gì các thuật toán đơn giản đã nêu. Một trong những yếu điểm của Quick sort là tính đệ quy. Sau đây là kết quả thực hiện thuật toán Quick sort với các bộ dữ liệu đã dùng cho các thuật toán sắpxếp đơn giản để tiện theo dõi và so sánh: TT n Mô tả Thời gian (giây) Đơn giản Phân đoạn 1 10 Kích thước nhỏ (tạo bằng tay) 0.00 0.00 2 100 Kích thước nhỏ (tạo ngẫu nhiên) 0.00 0.00 3 1.000 Kích thước nhỏ (tạo ngẫu nhiên) 0.00 0.00 4 10.000 Kích thước trung bình (tạo ngẫu nhiên) 0.61 0.01 5 15.000 Kích thước trung bình (tạo ngẫu nhiên) 1.32 0.02 6 50.000 Kích thước trung bình (tạo ngẫu nhiên) 14.21 0.05 7 100.000 Kích thước lớn (tạo có chủ định: nửa đầu giảm, nửa sau tăng – trường hợp xấu của Quick sort) 27.42 6.25 8 500.000 Kích thước lớn (tạo ngẫu nhiên) 1426.07 0.57 9 1.000.000 Kích thước lớn (tạo ngẫu nhiên) Quá lâu 1.11 10 1.000.000 Kích thước lớn (tạo có chủ định: nửa đầu tăng, nửa sau giảm – trường hợp xấu của Quick sort) Quá lâu Quá lâu 4. Thuật toán sắpxếp kiểu vun đống (Heap sort) Sắpxếp kiểu vun đống được chia làm 2 giai đoạn: • Giai đoạn đầu người ta coi dãy khoá cần sắp như là cấu trúc của một cây nhị phân hoàn chỉnh: nếu i chỉ vị trí nút con thì (i div 2) chỉ vị trí nút cha, còn nếu j chỉ vị trí nút cha thì 2.j và 2.j + 1 chỉ nút con. Sau đó cây nhị phân biểu diễn dãy khoá này được biến đổi để trở thành một đống. ở đây đống là một cây nhị phân hoàn chỉnh mà mỗi nút được gán cho một giá trị khoá sao cho khoá của nút cha bao giờ cũng lớn hơn khoá của nút con nó. Do đó khoá của nút gốc của đống chính là khoá lớn nhất (khoá trội) so với mọi khoá trên cây. Giai đoạn này gọi là giai đoạn tạo đống. • Giai đoạn thứ hai là sắp xếp. Ta thực hiện lặp các việc sau cho đến khi dãy khoá được sắp: Đưa khoá trội về vị trí thực của nó bằng cách đổi chỗ với khoá hiện đang ở vị trí đó. Sau đó “vun lại thành đống” đối với các khoá còn lại (sau khi đã loại khoá trội ra ngoài). 5 Điểm mấu chốt của thuật toán là việc tạo đống hay vun đống. Mặt khác ta nhận thấy rằng một nút lá có thể coi là một cây con đã thoả mãn tính chất của đống rồi. Như vậy việc tạo đống hay vun đống có thể được tiến hành theo kiểu từ đáy lên (bottom-up). Khi đó bài toán này sẽ được quy về một phép xử lý chung sau đây: chuyển đổi thành đống cho một cây mà cây con trái và cây con phải đã là đống rồi. Thủ tục adjust(i, n : integer) sau đây sẽ chỉnh sửa một cây nhị phân với gốc i để nó thành đống. Giả sử cây con trái và cây con phải của i, tức là cây với gốc 2.i và 2.i + 1 đã thỏa mãn điều kiện của đống. Không có nút nào ứng với chỉ số lớn hơn n cả. procedure adjust(i, n : integer); var t : integer; begin repeat i := i * 2; if i > n then break; if (i < n) and (a[i] < a[i+1]) then i := i + 1; { tìm nút con lớn hơn } if a[i div 2] >= a[i] then break; t := a[i div 2]; a[i div 2] := a[i]; a[i] := t; until false; end; Khi đó thuật toán sắpxếp kiểu vun đống được cài đặt như sau: procedure heapSort; var i, t : integer; begin { 1. Tạo đống } for i := (n div 2) downto 1 do adjust(i, n); { 2. Sắpxếp } for i := n downto 2 do begin { đưa khoá trội về vị trí thực của nó } t := a[1]; a[1] := a[i]; a[i] := t; { vun các khoá còn lại thành đống } adjust(1, i-1); end; end; Ví dụ sau đây minh họa các bước của thuật toán vun đống với dãy khoá 42, 23, 74, 11, 65, 58. Bước 1. Tạo đống 6 i = 3 42 23 11 65 74 58 42 23 11 65 74 58 i = 1 74 65 11 23 58 42 42 65 11 23 74 58 i = 2 Bước 2. Sắpxếp Đối với thuật toán sắpxếp kiểu vun đống, người ta đã chứng minh được trong tất cả các trường hợp chi phí về thời gian đều là O(n.log 2 n). Đây là một chi phí tốt hơn so với các phương pháp sắpxếp đơn giản. Dưới đây là bảng thực hiện thuật toán sắpxếp kiểu vun đống: TT n Mô tả Thời gian (giây) Đơn giản Phân đoạn Vun đống 1 6 Kích thước nhỏ (tạo bằng tay) 0.00 0.00 0.00 2 100 Kích thước nhỏ (tạo ngẫu nhiên) 0.00 0.00 0.00 3 1.000 Kích thước nhỏ (tạo ngẫu nhiên) 0.00 0.00 0.00 4 10.000 Kích thước trung bình (tạo ngẫu nhiên) 0.61 0.01 0.00 5 15.000 Kích thước trung bình (tạo ngẫu nhiên) 1.32 0.02 0.02 6 50.000 Kích thước trung bình (tạo ngẫu nhiên) 14.21 0.05 0.07 7 100.000 Kích thước lớn (tạo có chủ định: nửa đầu giảm, nửa sau tăng – trường hợp xấu của Quick sort) 27.42 6.25 0.11 8 500.000 Kích thước lớn (tạo ngẫu nhiên) 1426.07 0.57 0.70 9 1.000.000 Kích thước lớn (tạo ngẫu nhiên) Quá lâu 1.11 1.45 10 1.000.000 Kích thước lớn (tạo có chủ định: nửa đầu tăng, nửa sau giảm – trường hợp xấu của Quick sort) Quá lâu Quá lâu 1.09 7 65 42 11 23 58 74 i = 6 58 42 11 65 23 74 i = 5 42 11 58 65 23 74 i = 4 23 11 58 65 42 74 i = 3 11 23 58 65 42 74 i = 2 5. Kết luận Qua những thuật toán nêu trên ta thấy rõ cùng một mục đích sắpxếp như nhau mà có nhiều phương pháp và kỹ thuật giải quyết khác nhau. Cấu trúc dữ liệu được chọn để hình dung đối tượng của sắpxếp đã ảnh hưởng tới các thuật toán xử lý. Các thuật toán sắpxếp đơn giản đã thể hiện kỹ thuật cơ sở của sắpxếp (dựa vào phép so sánh giá trị khoá) và có thời gian thực hiện cỡ O(n 2 ). Vì vậy các phương pháp sắpxếp đơn giản chỉ áp dụng khi n nhỏ (n ≤ 10.000) . Các thuật toán cải tiến như Quick sort, Heap sort đã đạt được khoảng thời gian thực hiện nhỏ hơn, cỡ O(n.log 2 n) nên thường được sử dụng khi n lớn (n > 10.000). Thông thường, nếu các dãy khóa có thứ tự ngẫu nhiên thì người ta dùng thuật toán Quick sort, vì việc cài đặt nó đơn giản. Trong trường hợp dãy khóa vốn có thứ tự sắpxếp hoặc có thứ tự ngược lại với thứ tự sắpxếp thì Quick sort lại không tốt hơn các thuật toán sắpxếp đơn giản, vì vậy trong tình huống này người ta lại dùng Heap sort. Việc khẳng định một phương pháp nào trong các kỹ thuật sắpxếp nói trên luôn luôn tốt hơn mọi kỹ thuật khác là không nên. Việc chọn một thuật toán sắpxếp thích hợp thường tuỳ thuộc vào từng yêu cầu, từng điều kiện cụ thể. Bài tập cho thuật toán Bài 1. Stamps Mọi người đều ghét Raymond. Anh ta là nhà sưu tầm tem lớn nhất trên thế giới và vì lẽ đó anh ta thường chế diễu tất cả các nhà sưu tầm tem khác. May thay, mọi người đều yêu Lucy và cô ta có một kế hoạch. Cô ấy bí mật hỏi bạn bè cho cô ấy mượn tem, để cô ấy có thể làm cho Raymond xấu hổ bằng cách chứng tỏ bộ sưu tầm tem của mình còn lớn hơn bộ sưu tầm tem của anh ta. Raymond rất tin tưởng vào sự tốt nhất của bộ tem của mình, cho nên anh ta luôn nói cho mọi người biết anh ta sẽ trưng bày bao nhiêu con tem. Lucy biết mình có bao nhiêu con tem và cô ta biết rằng mình cần bao nhiêu con tem nữa. Cô ấy cũng biết có bao nhiêu người bạn sẽ cho cô mượn tem và mỗi người sẽ cho mượn bao nhiêu. Nhưng cô ấy muốn mượn từ một số người bạn ít nhất có thể. Bạn hãy viết chương trình, tính giúp cô ấy cần mượn tem từ ít nhất bao nhiêu người bạn. Dữ liệu: Dòng đầu tiên của file vào chứa hai số nguyên s và n ngăn cách nhau bởi một dấu cách, ở đó s là số con tem tối thiểu mà Lucy cần mượn (1 ≤ s ≤ 1.000.000) và n là số người bạn sẽ cho Lucy mượn tem (1 ≤ n ≤ 10.000). Dòng thứ hai chứa n số nguyên là số tem mà mỗi người bạn sẽ cho Lucy mượn (các số nằm trong phạm vi từ 1 đến 10.000). Kết quả: Ghi ra file ra một số duy nhất là số người bạn ít nhất mà Lucy cần mượn tem. Trong trường hợp Lucy không mượn được số tem tối thiểu mình cần thì ghi ra file ra dòng thông báo “impossible”. Ví dụ: stamps.in stamps.out 100 6 13 17 42 9 23 57 3 99 6 13 17 42 9 23 57 2 1000 3 314 159 265 impossible 8 Baif 2. Mixing Milk Vì việc đóng gói sữa là một nghề kinh doanh có lợi nhuận thấp, cho nên việc mua sữa tươi về đóng gói ở mức giá thấp nhất có thể là rất quan trọng. Công ty Merry Milk Makers có một số nông dân chuyên cung cấp sữa và mỗi người có một giá bán khác nhau cho công ty. Tuy nhiên vì mỗi con bò chỉ có thể cho một lượng sữa nhất định mỗi ngày nên những người nông dân đó cũng chỉ có một lượng sữa nhất định để bán mỗi ngày. Mỗi ngày, công ty có thể mua một số nguyên lít sữa từ mỗi người nông dân, ít hơn hoặc bằng giới hạn sữa của mỗi người nông dân đó. Cho trước nhu cầu mua sữa mỗi ngày của công ty, giá mỗi lít sữa và số lít sữa có sẵn của mỗi người nông dân. Hãy tính số tiền ít nhất mà công ty cần để mua lượng sữa yêu cầu. Giả thiết rằng tổng số sữa của các người nông dân đủ để đáp ứng lượng sữa của công ty. Dữ liệu: Dòng đầu tiên của file vào chứa hai số nguyên N và M ngăn cách nhau bởi một dấu cách. N (0 ≤ N ≤ 2.000.000) là số lít sữa mà công ty cần mỗi ngày, M (0 ≤ M ≤ 5000) là số người nông dân cung cấp sữa cho công ty. Dòng thứ i trong số M dòng tiếp theo, chứa hai số nguyên P i và A i ngăn cách nhau bởi một dấu cách. P i (0 ≤ P i ≤ 1000) là giá một lít sữa của bác nông dân i, A i (0 ≤ A i ≤ 2.000.000) là số lít sữa tối đa mà bác nông dân có thể bán cho công ty mỗi ngày. Kết quả: File ra gồm một dòng chứa đúng một số nguyên là số tiền nhỏ nhất mà công ty Merry Milk Makers có thể mua sữa mỗi ngày. Ví dụ: milk.in milk.out 100 5 5 20 9 40 3 10 8 80 6 30 630 Bài 3. Directory of telephone numbers Các nhà doanh nghiệp muốn có số điện thoại dễ nhớ. Một cách làm số điện thoại dễ nhớ là có thể đánh vần nó từ một từ hoặc một cụm từ dễ nhớ. Ví dụ, bạn có thể gọi đến trường Đại học Waterloo bằng cách quay cụm từ dễ nhớ TUT-GLOP. Đôi khi chỉ một phần của số điện thoại được sử dụng để đánh vần một từ. Khi bạn quay trở lại khách sạn vào buổi tối, bạn có thể gọi bánh pizza từ quán Gino bằng cách quay số 310-GINO. Một cách khác để làm số điện thoại dễ nhớ là nhóm các chữ số theo một cách dễ nhớ. Bạn có thể gọi bánh pizza từ quầy bán bánh pizza bằng cách gọi số “ba mười” 3-10- 10-10. Dạng chuẩn của số điện thoại là 7 chữ số thập phân và có dấu gạch nối giữa chữ số thứ ba và thứ tư (ví dụ 888-1200). Bàn phím của điện thoại cung cấp sơ đồ giữa các chữ cái đến các chữ số như sau: A, B và C tương ứng với 2 D, E và F tương ứng với 3 G, H và I tương ứng với 4 9 J, K và L tương ứng với 5 M, N và O tương ứng với 6 P, R và S tương ứng với 7 T, U và V tương ứng với 8 W, X và Y tương ứng với 9 Không có sơ đồ cho Q và Z. Các dấu gạch nối không được quay và nó được thêm hoặc bỏ đi khi cần thiết. Dạng chuẩn của TUT-GLOP là 888-4567, dạng chuẩn của 310-GINO là 310-4466 và dạng chuẩn của 3-10-10-10 là 310-1010. Hai số điện thoại là giống nhau nếu chúng có cùng một dạng chuẩn (chúng gọi cùng một số). Công ty của bạn cần biên soạn một danh bạ điện thoại của các doanh nghiệp địa phương. Một phần việc quan trọng là bạn muốn kiểm tra xem có hai (hoặc nhiều hơn) các doanh nghiệp có cùng số điện thoại. Dữ liệu: Dòng đầu tiên của file vào chứa số nguyên dương n (n ≤ 10.000) là số điện thoại trong danh bạ. n dòng tiếp theo, mỗi dòng chứa một số điện thoại trong danh bạ. Mỗi số điện thoại là một xâu ký tự gồm các chữ số thập phân, các chữ cái la tinh in hoa (trừ Q và Z) và dấu gạch nối. Có đúng 7 ký tự trong xâu là các chữ số và các chữ cái. Kết quả: Mỗi dòng của file ra chứa thông tin về một số điện thoại xuất hiện nhiều hơn một lần trong bất kỳ dạng nào, bao gồm số điện thoại ở dạng chuẩn, tiếp theo một dấu cách và tiếp theo là số lần số diện thoại xuất hiện trong danh bạ. Các dòng của file ra cần sắpxếp theo thứ tự từ điển của số điện thoại. Nếu không có số điện thoại nào trùng nhau thì ghi ra dòng chữ: “No duplicates.” Ví dụ: phone.in phone.out 12 4873279 ITS-EASY 888-4567 3-10-10-10 888-GLOP TUT-GLOP 967-11-11 310-GINO F101010 888-1200 -4-8-7-3-2-7-9- 487-3279 310-1010 2 487-3279 4 888-4567 3 CÁC THUẬT TOÁN TÌMKIẾM 1. Giới thiệu bài toán Việc tìmkiếm là thao tác nền móng cho rất nhiều tác vụ tính toán. Tìmkiếm có nghĩa là tìm một hay nhiều mẩu tin từ một số lượng lớn thông tin đã được lưu trữ. Thông thường thông tin được chia thành các mẩu tin (bản ghi), mỗi mẩu tin có một khoá (key) dùng cho việc tìm kiếm. Mục đích của việc tìmkiếm là tìm tất cả các mẩu tin mà khoá của chúng đồng nhất với một khoá đã cho trước. Sau khi một mẩu tin đã tìm thấy, thông tin bên trong nó sẽ cung cấp cho một quá trình xử lý nào đó. 10 [...]... được cài đặt như sau: program dong_goi_san_pham; const FI = 'zxy.in'; FO = 'zxy.out'; var n, k : integer; a : array[1 15000] of integer; m : longint; procedure doc; var i : integer; f : text; begin assign(f, FI); reset(f); readln(f, n, k); for i := 1 to n do readln(f, a[i]); close(f); end; function ok : boolean; var s, t, i : longint; begin s := 1; t := 0; for i := 1 to n do begin if t + a[i] > m then... if t + a[i] > m then begin s := s + 1; t := 0; 13 end; t := t + a[i]; end; ok := (s ai thì tìmkiếm lại được làm với ai+1, , ar Quá trình cứ tiếp tục khi tìm thấy khoá mong muốn hoặc bảng khoá là trở nên rỗng (không tìm thấy) function binarySearch(x : integer) : integer; var l, r, c : integer; begin l := 1; r := n; repeat c := (l+r) div 2; if x < a[c] then r := c-1 else l := c+1; until (a[c] = x) or (l > r); if x = a[c] then binarySearch := c else binarySearch := n+1;... M0 không thỏa mãn thì tất cả các giá trị M < M0 cũng đều không thỏa mãn Vì vậy, để tìm giá trị nhỏ nhất của M ta áp dụng chiến lược tìmkiếm nhị phân Thuật toán 2 được cài đặt như sau: procedure xu_ly; var l, r, i : longint; begin l := 0; r := 0; for i := 1 to n do begin if l < a[i] then l := a[i]; r := r + a[i]; end; repeat m := (l+r) div 2; if ok then r := m else l := m + 1; until l = r; m := l; end; . 'zxy.in'; FO = 'zxy.out'; var n, k : integer; a : array[1 15000] of integer; m : longint; procedure doc; var i : integer; f : text; begin assign(f,. i. Thủ tục sắp xếp kiểu lựa chọn được cài đặt như sau: procedure selection; var i, j, t : integer; begin for i := 1 to n do for j := i+1 to n do if a[i]