Tải bản đầy đủ (.pdf) (40 trang)

Sáng tạo trong thuật toán và lập trình với ngôn ngữ Pascal và C# Tập 1 - Chương 2 doc

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 (969.07 KB, 40 trang )

Sáng tạo trong Thuật toán và Lập trình Tập I

26



CHƢƠNG 2
SINH DỮ LIỆU VÀO VÀ RA







Hầu hết các bài toán tin đều đòi hỏi dữ liệu vào và ra. Người ta thường dùng ba
phương thức sinh và nạp dữ liệu sau đây:
1. Nạp dữ liệu trực tiếp từ bàn phím. Phương thức này được dùng khi dữ liệu
không nhiều.
2. Sinh dữ liệu nhờ hàm random (xem chương 1). Phương thức này nhanh chóng và
tiện lợi, nếu khéo tổ chức có thể sinh ngẫu nhiên được các dữ liệu đáp ứng được một số
điều kiện định trước.
3. Đọc dữ liệu từ một tệp, thường là tệp văn bản. Phương thức này khá tiện lợi khi
phải chuẩn bị trước những tệp dữ liệu phức tạp.
Kết quả thực hiện chương trình cũng thường được thông báo trực tiếp trên màn
hình hoặc ghi vào một tệp văn bản.
Bài 2.1. Sinh ngẫu nhiên theo khoảng
Sinh ngẫu nhiên cho mảng nguyên a n phần tử trong khoảng -M M; M > 0.
Đặc tả
Ta viết thủ tục tổng quát Gen(n,d,c) - sinh ngẫu nhiên n số nguyên trong
khoảng từ d đến c (d < c) (xem bài giải 1.4). Để giải bài 2.1 ta chỉ cần gọi


Gen(n,-M,M).
Để ý rằng random(c–d+1) biến thiên trong khoảng từ 0 đến c-d do đó
d+random(c–d+1) sẽ biến thiên trong khoảng từ d đến d+c-d = c.
(*
sinh ngau nhien n so nguyen trong khoang
d den c va ghi vao mang a
*)
Procedure Gen(n,d,c: integer);
var i,len: integer;
begin
randomize;
len := c-d+1;
for i:=1 to n do a[i]:= d+random(len);
end;
Sáng tạo trong Thuật toán và Lập trình Tập I

27



(* Pascal *)
(*
Sinh ngau nhien cho mang nguyen a
n phan tu trong khoang -M M; M > 0.
*)
program RGen;
uses crt;
const MN = 1000;
var a: array[1 MN] of integer;
(*

sinh ngau nhien n so nguyen trong khoang
d den c va ghi vao mang a
*)
Procedure Gen(n,d,c: integer); tự viết
procedure Xem(n: integer); Hiển thị mảng a, tự viết
procedure Test;
var n: integer;
begin
n := 20;
{ sinh ngau nhien 20 so trong khoang -8 8 }
Gen(n,-8,8);
Xem(n);
readln;
end;
BEGIN
Test;
END.
// C#
using System;
using System.Collections.Generic;
using System.Text;
namespace SangTao1
{
/*
* Sinh ngau nhien n so
* trong khoang d c
* */
class RGen
{
static void Main(string[] args)

{
Print(Gen(20, -8, 8));
Console.ReadLine();
}
static public int[] Gen(int n, int d, int c)
{
Random r = new Random();
int len = c-d+1;
int [] a = new int[n];
for (int i = 0; i < n; ++i)
a[i] = d + r.Next(len);
Sáng tạo trong Thuật toán và Lập trình Tập I

28



return a;
}
static public void Print(int [] a)
{
Console.WriteLine();
foreach (int x in a)
Console.Write(x + " ");
Console.WriteLine();
}
} // RGen
} // SangTao1
Bài 2.2. Sinh ngẫu nhiên tăng
Sinh ngẫu nhiên n phần tử được sắp không giảm cho mảng nguyên a.

Thuật toán
1. Sinh ngẫu nhiên phần tử đầu tiên: a[1] := random(n);
2. Từ phần tử thứ hai trở đi, trị được sinh bằng trị của phần tử sát trước nó cộng
thêm một đại lượng ngẫu nhiên:
(i = 2 n): a[i] := a[i - 1] + random(n), do đó a[i] >= a[i - 1].
(* Pascal *)
(*
Sinh ngau nhien cho mang nguyen a
n phan tu sap khong giam
*)
program IncGen;
uses crt;
const MN = 1000;
var a: array [1 MN] of integer;
(*
Sinh ngau nhien day tang gom n phan tu
*)
procedure Gen(n: integer);
var i: integer;
begin
randomize;
a[1]:= random(5); {khoi tao phan tu dau tien }
for i:= 2 to n do a[i]:= a[i-1]+random(10);
end;
procedure Xem(n: integer); tự viết
procedure Test;
var n: integer;
begin
n := 200; { test voi 200 phan tu }
Gen(n); Xem(n); readln;

end;
BEGIN
Test;
END.
// C#
Sáng tạo trong Thuật toán và Lập trình Tập I

29



using System;
using System.Collections.Generic;
using System.Text;
namespace SangTao1
{
/*
* Sinh ngau nhien n so
* tao thanh day khong giam
* */
class IncGen
{
static void Main(string[] args)
{
Print(Gen(200));
Console.ReadLine();
}
static public int[] Gen(int n)
{
Random r = new Random();

int [] a = new int[n];
a[0] = r.Next(5);
for (int i = 1; i < n; ++i)
a[i] = a[i-1] + r.Next(10);
return a;
}
static public void Print(int [] a) tự viết
} // IncGen
} // SangTao1
Bài 2.3. Sinh hoán vị ngẫu nhiên
Sinh ngẫu nhiên cho mảng nguyên a một hoán vị của 1 n.
Đặc tả
Xuất phát từ hoán vị đơn vị a = (1, 2, , n) ta đổi chỗ a[1] với một phần tử tuỳ ý (được
chọn ngẫu nhiên) a[j] sẽ được một hoán vị. Ta có thể thực hiện việc đổi chỗ nhiều lần.
(* Pascal *)
(*
Sinh ngau nhien cho mang nguyen a
mot hoan vi cua 1 n
*)
program GenPer;
const MN = 1000; { so luong toi da }
Esc = #27; { dau thoat }
BL = #32; { dau cach }
var a: array[1 MN] of integer;
(*
Sinh du lieu
*)
procedure Gen(n: integer);
var i,j,x: integer;
begin

{ Khoi tao hoan vi don vi }
Sáng tạo trong Thuật toán và Lập trình Tập I

30



for i:= 1 to n do a[i]:= i;
for i:= 1 to n do
begin
j := random(n)+1;
x := a[1]; a[1] := a[j]; a[j] := x;
end;
end;
procedure Xem(n: integer); tự viết
procedure Test;
var n: integer;
begin
randomize;
repeat {chon ngau nhien kich thuoc n = 10 39}
n := random(30)+10; Gen(n); Xem(n);
until ReadKey = Esc; { Nhan ESC de thoat }
end;
BEGIN
Test;
END.
// C#
using System;
using System.Collections.Generic;
using System.Text;

namespace SangTao1
{
/*
* Sinh ngau nhien hoan vi
* 1 n
* */
class GenPer
{
static void Main(string[] args)
{
Print(Gen(20));
Console.ReadLine();
}
static public int[] Gen(int n)
{
Random r = new Random();
int[] a = new int[n];
for (int i = 0; i < n; ++i)
a[i] = i+1;
for (int i = 0; i < n; ++i)
{
int j = r.Next(n);
int t = a[0];
a[0] = a[j]; a[j] = t;
}
return a;
}
static public void Print(int [] a) tự viết
Sáng tạo trong Thuật toán và Lập trình Tập I


31



} // IncGen
} // SangTao1
Bài 2.4. Sinh ngẫu nhiên đều
Sinh ngẫu nhiên n phần tử cho mảng nguyên a thoả điều kiện n phần tử tạo
thành k đoạn liên tiếp có tổng các phần tử trong mỗi đoạn bằng nhau và bằng
giá trị t cho trước.
Thuật toán
1. Chọn số lượng các phần tử trong mỗi đoạn là random(n div k) + 1, khi
đó số lượng các phần tử được phát sinh ngẫu nhiên sẽ không vượt quá
k*(n div k) ≤ n
Sau đó ta sẽ chỉnh sao cho số lượng các phần tử đúng bằng n.
2. Giả sử a[d c] là đoạn thứ j cần được sinh ngẫu nhiên sao cho
a[d] + a[d + 1] + + a[c] = t
Ta sinh đoạn này như sau:
2.1. Gán tr := t; { tr - giá trị còn lại của tổng }.
2.2. Gán trị ngẫu nhiên 0 tr-1 cho các phần tử a[d (c - 1)]
(i = d c ): a[i] := random(tr)
2.3. Đồng thời chỉnh giá trị còn lại của tr:
tr := tr - a[i]
Ta có:
a[d] < t
a[d+1] < t - a[d]
a[d+2] < t - a[d+1] - a[d]

a[c - 1] < t - a[d] - a[d + 1] - - a[c - 2]
Chuyển vế các phần tử a[*] trong biểu thức cuối cùng, ta thu được

a[d] + a[d + 1] + + a[c – 1] < t
2.4. Ta đặt giá trị còn lại của tổng riêng vào phần tử cuối đoạn: a[c] :=
tr sẽ thu được a[d] + a[d + 1] + + a[c] = t.
(* Pascal *)
(*
Sinh ngau nhien cho mang nguyen a
n phan tu tao thanh k doan lien tiep
co tong bang nhau
*)
program KGen;
uses crt;
const MN = 1000; {kich thuoc toi da cua mang a}
Esc = #27; {dau thoat}
BL = #32; {dau cách}
var a: array[1 MN] of integer;
(*
Sinh du lieu
*)
procedure Gen(n,k,t: integer);
var i,j,p,tr,s: integer;
Sáng tạo trong Thuật toán và Lập trình Tập I

32



begin
if (k < 1) or (k > n) then exit;
s := n div k;{s - so toi da phan tu trong moi doan}
i := 0; {chi dan lien tuc cho cac phan tu moi sinh}

for j := 1 to k do {sinh doan thu j}
begin
tr := t;
for p := 1 to random(s) do
{ random(s)+1 = so phan tu trong 1 doan }
begin
inc(i);
a[i] := random(tr);
tr := tr – a[i]; {gia tri con lai cua tong}
end;
inc(i); {i phan tu cuoi cung cua doan j}
a[i] := tr;
end;
{bu 0 cho cac phan tu con lai}
for i := i+1 to n do a[i] := 0;
end;
procedure Xem(n: integer); Hiển thị mảng a, tự viết
procedure Test;
var n,k: integer;
begin
randomize;
repeat
n := random(30) + 1;
k := random(8) + 1;
t := random(30)+10;
writeln('n = ',n,' k = ',k,' t = ',t);
Gen(n,k,t); Xem(n);
until ReadKey = Esc;
end;
BEGIN

Test;
END.
// C#
using System;
using System.Collections.Generic;
using System.Text;
using System;
namespace SangTao1
{
class KGen
{
static void Main(string[] args)
{
Random r = new Random();
int n, k, t;
do
{
n = r.Next(30) + 1;
Sáng tạo trong Thuật toán và Lập trình Tập I

33



t = r.Next(30) + 1;
k = r.Next(8) + 1;
Console.WriteLine("\n n = " + n +
" k = " + k + " t = " + t);
Print(Gen(n, k, t));
Console.Write("\n Bam RETURN de tiep tuc: ");

} while (Console.ReadLine() == "");
}
// sinh n phan tu chia thanh k doan,
// moi doan co tong t
static public int[] Gen(int n, int k, int t)
{
if (k < 1 || k > n) return new int[0];
Random r = new Random();
int[] a = new int[n];
int s = n / k; // so phan tu trong 1 doan
int i = 0;
for (int j = 0; j < k; ++j)
{ // sinh doan thu j
int tr = t;
int endp = r.Next(s);
for (int p = 0; p < endp; ++p,++i)
{ a[i] = r.Next(tr); tr -= a[i]; }
a[i++] = tr;
}
// dien 0 cho du n phan tu
for (; i < n; ++i) a[i] = 0;
return a;
}
static public void Print(int[] a) tự viết
} // KGen
} // SangTao1
Bài 2.5. Sinh ngẫu nhiên tỉ lệ
Sinh ngẫu nhiên cho mảng nguyên a có n phần tử tạo thành hai đoạn liên tiếp
có tổng các phần tử trong một đoạn gấp k lần tổng các phần tử của đoạn kia.
Thuật toán

1. Sinh ngẫu nhiên tổng t1 := random(n) + 1.
2. Tính t2 := k*t1.
3. Gieo đồng xu bằng cách gọi random(2) để xác định tổng nào trong số t1
và t2 được chọn trước.
4. Sinh random(n div 2)+1 phần tử cho đoạn thứ nhất sao cho tổng các
phần tử của đoạn này bằng t1 (xem bài 2.4).
5. Sinh nốt các phần tử cho đoạn thứ hai sao cho tổng các phần tử của đoạn này
bằng t2.
(* Pascal *)
program K2gen;
uses crt;
const MN = 1000;
Sáng tạo trong Thuật toán và Lập trình Tập I

34



var a: array[1 MN] of integer;
(*
Sinh du lieu
*)
procedure Gen(n,k:integer);
var i,j,t1,t2:integer;
begin
if (k < 1) OR (k > n) then exit;
t1 := random(n) + 1;
{tong mot doan; tong doan con lai = k*t1 }
{chon ngau nhien doan co tong lon dat truoc hay sau
}

if random(2)= 0 then t2 := k*t1
else
begin
t2 := t1; t1 := k*t2;
end;
i := 0; {sinh doan thu nhat}
for j := 1 to random(n div 2) do
begin
inc(i); a[i] := random(t1);
t1 := t1 – a[i];
end;
inc(i); a[i] := t1;
while i < n do {sinh doan thu hai }
begin
inc(i); a[i]:= random(t2);
t2 := t2 – a[i];
end;
a[n] := a[n] + t2;
end;
procedure Xem(n: integer); tự viết
procedure Test;
var n,k: integer;
begin
randomize;
repeat
n := random(30) + 1;
k := random(8) + 1;
write(' n = ',n,' k = ',k);
Gen(n,k); Xem(n);
until ReadKey = #27;

end;
BEGIN
Test;
Sáng tạo trong Thuật toán và Lập trình Tập I

35



END.
// C#
using System;
using System.Collections.Generic;
using System.Text;
namespace SangTao1
{
class K2Gen
{
static void Main(string[] args)
{
Random r = new Random();
int n, k;
do
{
n = r.Next(30) + 2;
k = r.Next(8) + 1;
Console.WriteLine("\n n = " + n +
" k = " + k);
int [] a = new int [n];
int n1 = Gen(a,n,k);

Print(a);
Test(a, n1, k);
Console.Write("\n Bam RETURN " +
" de tiep tuc: ");
} while (Console.ReadLine() == "");
}
// Kiem tra ket qua
static void Test(int[] a, int n1, int k)
{
int t1 = 0;
for (int i = 0; i < n1; ++i)
t1 += a[i];
Console.WriteLine("\n Doan thu nhat: " +
"sum(a[0 " + (n1 - 1) + "]) = " + t1);
int t2 = 0;
for (int i = n1; i < a.Length; ++i)
t2 += a[i];
Console.WriteLine("\n Doan thu hai: " +
"sum(a["+n1+" "+(a.Length - 1)+"]) = "+t2);
if ((t1 == k * t2) || (t2 == k * t1))
Console.WriteLine("\n DUNG");
else Console.WriteLine("\n SAI");
}

Sáng tạo trong Thuật toán và Lập trình Tập I

36




static public int Gen(int [] a, int n, int k)
{
Random r = new Random();
int i = 0; // phan tu thu i trong a
// n1 - so phan tu trong doan 1
int n1 = r.Next(n / 2) + 1;
int t1 = 0; // tong doan 1
// sinh doan thu 1
for (; i < n1; ++i) //
{
a[i] = r.Next(10); t1 += a[i];
}
int t2 = k* t1;
int tt = t1;
// xac dinh ngau nhien
// 0. t2 gap k lan t1, hoac
// 1. t1 gap k lan t2
if (r.Next(2)==1)
{ // t1 gap k lan t2
t1 = t2; t2 = tt; a[i-1] += (t1-t2);
}
// sinh doan 2
for (; i < n; ++i) //
{
a[i] = r.Next(t2); t2 -= a[i];
}
a[n-1] += t2;
return n1;
}


static public void Print(int[] a)
{
Console.WriteLine();
foreach (int x in a)
Console.Write(x + " ");
Console.WriteLine();
}
} // K2Gen
} // SangTao1
Bài 2.6. Sinh ngẫu nhiên tệp tăng
Sinh ngẫu nhiên n số tự nhiên sắp tăng và ghi vào một tệp văn bản có tên cho
trước.
Thuật toán
Bạn đọc xem trực tiếp chương trình và giải thích cách làm.
Sáng tạo trong Thuật toán và Lập trình Tập I

37



(* Pascal *)
(*
Sinh ngau nhien n so tu nhien sap tang
va ghi vao tep van ban co ten cho truoc
*)
program FincGen;
uses crt;
const BL = #32; { dau cach }
(*
Sinh du lieu

*)
procedure Gen(fn: string; n: integer);
var f: text; i: integer; x: longint;
begin
assign(f,fn); {fn: file name (ten tep)}
rewrite(f); randomize;
x := 0;
for i:= 1 to n do
begin
x := x+random(10); write(f,x,BL);
{ moi dong trong file chua 20 so }
if i mod 20 = 0 then writeln(f);
end;
if i mod 20 <> 0 then writeln(f);
close(f);
end;
procedure Test;
begin
Gen('DATA.INP',200);
write('Ket'); readln;
end;
BEGIN
Test;
END.
// C#
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace SangTaoT1

{
class FincGen
{
static void Main(string[] args)
{
string fn = "Data.txt";
GenToFile(fn, 81);
Show(fn); Console.ReadLine();
}
static public void GenToFile(string fn, int n)
Sáng tạo trong Thuật toán và Lập trình Tập I

38



{
StreamWriter f = File.CreateText(fn);
Random r = new Random();
int x = r.Next(10);
for (int i = 0; i < n; ++i)
{
f.Write(x + " ");
// Moi dong 20 so
if (i % 20 == 19) f.WriteLine();
x += r.Next(10);
}
if (n % 20 != 19) f.WriteLine();
f.Close();
}

static public void Show(string fn)
{
Console.WriteLine(File.ReadAllText(fn));
}
} // FincGen
} // SangTao1
Bài 2.7. Sinh ngẫu nhiên tệp cấp số cộng
Sinh ngẫu nhiên một cấp số cộng có n số hạng và ghi vào một tệp văn bản có
tên cho trước.
Thuật toán
1. Sinh ngẫu nhiên số hạng thứ nhất a[1] và công sai d.
2. Sinh các phần tử a[i], i = 2 n
for i:=2 to n do a[i]:= a[i–1]+ d;
3. Ghi file
Độ phức tạp: n.
(* Pascal *)
program FCapCong;
uses crt;
const BL = #32;
procedure Gen(fn: string; n: integer);
var f: text; i,d: integer; x: longint;
begin
assign(f,fn); rewrite(f);
randomize;
d := random(n div 4)+1; {cong sai }
x := random(20); write(f,x,BL);
for i:= 2 to n do
begin { mỗi dòng ghi 20 số }
x:= x + d; write(f,x,BL);
if i mod 20 = 0 then writeln(f);

end;
if i mod 20 <> 0 then writeln(f);
close(f);
end;
BEGIN
Sáng tạo trong Thuật toán và Lập trình Tập I

39



Gen('DATA.INP',200); write('Ket'); readln;
END.
// C#
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace SangTao1
{
class FCapCong
{
static void Main(string[] args)
{
string fn = "Data.txt";
GenToFile(fn, 81);
Show(fn); Console.ReadLine();
}
static public void GenToFile(string fn, int n)
{

StreamWriter f = File.CreateText(fn);
Random r = new Random();
int s = r.Next(n), d = r.Next(10)+1;
for (int i = 0; i < n; ++i)
{
f.Write(s + " ");
if (i % 20 == 19) f.WriteLine();
s += d;
}
if (n % 20 != 19) f.WriteLine();
f.Close();
}
static public void Show(string fn)
{
Console.WriteLine(File.ReadAllText(fn));
}
} // FcapCong
} // SangTao1
Bài 2.8. Sinh ngẫu nhiên mảng đối xứng
Sinh ngẫu nhiên các giá trị để ghi vào một mảng hai chiều a[1 n, 1 n] sao cho
các phần tử đối xứng nhau qua đường chéo chính, tức là
a[i, j] = a[j, i], 1 ≤ i, j ≤ N.
Thuật toán
1. Sinh ngẫu nhiên các phần tử trên đường chéo chính a[i,i],i=1 n.
2. Sinh ngẫu nhiên các phần tử nằm phía trên đường chéo chính a[i,j],
i=1 n, j=i+1 n rồi lấy đối xứng: a[j,i]:= a[i,j].
Độ phức tạp: n
2
.
(* Pascal *)

Sáng tạo trong Thuật toán và Lập trình Tập I

40



program GenMatSym;
uses crt;
const MN = 100;
var a = array[1 MN,1 MN] of integer;
procedure Gen(n: integer);
var i, j: integer;
begin
randomize;
for I := 1 to n do
begin
a[i,i] := random(n);
for j := i+1 to n do
begin
a[i,j]:=random(n); a[j,i]:=a[i,j];
end;
end;
end;
procedure Xem(n: integer);
var i, j: integer;
begin
writeln;
for i:= 1 to n do
begin
writeln;

for j:= 1 to n do write(a[i,j]:4);
end;
end;
BEGIN
Gen(20); Xem(20); readln;
END.
// C#
using System;
using System.Collections.Generic;
using System.Text;
namespace SangTao1
{
class GenMatSym
{
static void Main(string[] args)
{
int n = 20;
int[,] a = Gen(n);
Print(a, n);
Console.WriteLine("\n Fini ");
Console.ReadLine();
}
static public int [,] Gen(int n)
{
int[,] a = new int[n, n];
Random r = new Random();
for (int i = 0; i < n; ++i)
Sáng tạo trong Thuật toán và Lập trình Tập I

41




{
a[i, i] = r.Next(100);
for (int j=i+1; j<n; ++j)
{ a[i, j] = r.Next(100);
a[j, i] = a[i, j];
}
}
return a;
}
// hiển thị mảng a[0 (n-1]
static public void Print(int [,] a,int n) tự
viết
} // GenMatSym
} // SangTao1
Bài 2.9. Số độ cao h
Độ cao của một số tự nhiên là tổng các chữ số của số đó. Sinh toàn bộ các số
tự nhiên có tối đa ba chữ số và có độ cao h cho trước. Ghi kết quả vào một tệp
văn bản có tên cho trước.
Thuật toán
Bài toán này có cách phát biểu khác và tổng quát như sau: có n cốc nước dung tích
9 thìa mỗi cốc. Cho một bình đựng h thìa nước. Hãy xác định mọi phương án chia
nước vào các cốc.
Ta xét lời giải với n = 3. Ta có h = 0 27.
1. Các số cần tìm y có dạng y = abc, a + b + c = h và a biến thiên từ mina đến maxa,
trong đó mina là lượng nước ít nhất trong cốc đầu tiên a, maxa là lượng nước lớn nhất trong
cốc a. Nếu đổ đầy hai cốc b và c, mỗi cốc 9 thìa nước thì lượng nước còn lại sẽ là tối thiểu
cho cốc a. Ngược lại, nếu tổng cộng chỉ có h < 9 thìa nước thì lượng nước tối đa trong cốc a

phải là h. Ta có
if h  18 then mina := 0 else mina := h-18;
if h  9 then maxa := 9 else maxa := h;
2. Với mỗi a = mina maxa ta tính
2.1. bc = h-a (biến bc chứa tổng các chữ số b và c).
2.2. Giải bài toán nhỏ với n = 2.
if bc  9 then minb := 0 else minb := bc-9;
if bc  9 then maxb := 9 else maxb := bc;
2.3. Với mỗi b = minb maxb ta tính
y = 100*a + 10*b + (bc – b).
Ghi số này vào tệp.
(* Pascal *)
(*-=
Sinh cac so khong qua 3 chu so
co do cao h va ghi vao tep fn
*)
program HGen;
uses crt;
Sáng tạo trong Thuật toán và Lập trình Tập I

42



function Gen(fn:string;h:integer): integer;
var f: text;
a,b,bc,mina,maxa,minb,maxb: integer;
x,y,d: integer;
begin {tong 3 chu so toi da la 27, toi thieu la 0 }
if (h < 0) OR (h > 27) then exit;

assign(f,fn); rewrite(f);
d:= 0; {dem so luong cac so do cao h}
if h <= 18 then mina := 0 else mina := h-18;
if h >= 9 then maxa := 9 else maxa := h;
for a := mina to maxa do
begin
x := 100*a;
bc := h-a;
if bc <= 9 then minb := 0 else minb := bc-9;
if bc >= 9 then maxb := 9 else maxb := bc;
for b := minb to maxb do
begin
y := x + 10*b + (bc - b);
write(f,y:4);
inc(d); { Ghi moi dong 10 so }
if d mod 10 = 0 then writeln(f);
end;
end;
close(f);
Gen := d;
end;
procedure Test;
var n: integer;
begin
n := Gen('HEIGHT.NUM',10);
write('Tong cong ‟,n,' so');
readln;
end;
BEGIN
Test;

END.
// C#
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace SangTao1
{
class HGen
{
static void Main(string[] args)
{
string fn = "HGen.txt";
int h = 10;
Console.WriteLine("\n File " + fn);
Sáng tạo trong Thuật toán và Lập trình Tập I

43



Console.WriteLine(" H = " + h);
int d = Gen(fn,10);
Test(fn,d);
Console.WriteLine("\n Fini ");
Console.ReadLine();
}
static public int Gen(string fn, int h)
{
int a, b, mina, maxa, minb, maxb,

bc, x, y, d = 0;
StreamWriter f = File.CreateText(fn);
mina = (h <= 18) ? 0 : h-18;
maxa = (h <= 9) ? h : 9;
for (a = mina; a <= maxa; ++a)
{
x = 100*a; bc = h - a;
minb = (bc <= 9) ? 0 : bc-9;
maxb = (bc >= 9) ? 9 : bc;
for (b = minb; b <= maxb; ++b)
{
++d; y=x+10*b+(bc-b); f.Write(y+" ");
if (d % 10 == 0) f.WriteLine();
}
}
f.Close();
return d;
}
// Doc lai file de kiem tra
static public void Test(string fn,int d)
{
Console.WriteLine("\n Tong cong " +
d + " so");
Console.WriteLine(File.ReadAllText(fn));
}
} // HGen
} // SangTao1
Chú ý
1. Có thể giải bài toán trên bằng phương pháp vét cạn dùng ba vòng for như sau:
for a := 0 to 9 do

for b := 0 to 9 do
for c := 0 to 9 do
if a+b+c = h then
write(f,a,b,c,#32);
2. Phương pháp vét cạn đòi hỏi 10*10*10 = 1000 lần duyệt trong khi với h = 10 chỉ
có 63 số thoả mãn điều kiện của đầu bài. Dùng phương pháp sinh ta nhận được đúng 63
số cần tìm, không phải duyệt thừa số nào.
Bài 2.10. Tệp các hoán vị
Sáng tạo trong Thuật toán và Lập trình Tập I

44



Với mỗi số n cho trước trong khoảng 1 9, ghi vào một tệp văn bản có tên cho
trước toàn bộ các hoán vị của 1 n. Hoán vị được sắp xếp tăng theo thứ tự từ
điển, thí dụ 21345 < 21354.
Thuật toán
1. Khởi tạo và ghi hoán vị nhỏ nhất là hoán vị đơn vị s [1 n] =
(1,2, ,n).
2. Giả sử ta đã ghi được hoán vị s[1.n] vào tệp. Hoán vị tiếp theo được tạo từ s
thông qua hàm Next như sau:
2.1 Tìm điểm gãy: Tìm ngược từ s[n] trở về trước đến vị trí i đầu tiên thoả
điều kiện s[i] < s[i + 1].
- Nếu không tìm được i tức là s là hoán vị lớn nhất, s[1 n] = (n,n-
1, ,1). Đặt trị false cho hàm Next và dừng thuật toán. Next =
false có nghĩa là không tồn tại hoán vị sát sau hoán vị s hay s là hoán vị
lớn nhất.
- Nếu tìm được: thực hiện bước 2.2.
2.2 Tìm điểm vượt: Tìm ngược từ s[n] trở về trước đến vị trí j đầu tiên thoả

điều kiện s[j] > s[i].
2.3. Đổi chỗ s[i] với s[j].
2.4. Lật: Đảo lại trật tự của dãy s[i + 1 n] ta sẽ thu được hoán vị đứng
sát sau hoán vị s.
3. Đặt trị true cho hàm Next. Next = true có nghĩa là tìm được hoán vị sát
sau hoán vị s.
Chú ý
Khi khởi trị hoán vị đơn vị ta sử dụng phần tử s[0] = 0 làm lính canh. Nhờ vậy, khi
duyệt ngược để tìm điểm gãy ta không phải kiểm tra giới hạn mảng. Thay vì viết
i := n-1;
while (i > 0) and (s[i] > s[i+1]) do i:= i-1;
ta chỉ cần viết
i := n-1;
while (s[i] > s[i+1]) do i := i-1;
Hàm Next được mô tả như sau:
function Next(n: integer): Boolean;
var i, j, t: integer;
begin
Next := false; i := n-1;
while (s[i] > s[i+1]) do i:= i-1;
if i = 0 then exit;
{ s[i] < s[i+1], i là điểm gãy }
j := n; { Tìm điểm vượt a[j] > a[i] }
while (s[j] < s[i]) do j := j-1;
{ Đổi chỗ s[i] , s[j] }
t:= s[i]; s[i]:= s[j]; s[j]:= t;
{ Lật s[i+1 n] } i:= i+1; j:= n;
while i < j do
begin
t:= s[i];s[i]:= s[j]; s[j]:= t;

i:= i+1; j:= j-1;
Sáng tạo trong Thuật toán và Lập trình Tập I

45



end;
Next:= true;
end;
Thí dụ, với n = 8, giả sử ta đã ghi được hoán vị s = 74286531, khi đó hoán vị sát
sau s sẽ được xây dựng như sau:










S
7
4
2
8
6
5
3

1
Tìm điểm gãy: i = 3, vì s[3] < s[4]
7
4
2
8
6
5
3
1
Tìm điểm vượt: j = 7, vì s[7] > s[3]
7
4
2
8
6
5
3
1
Đổi chỗ điểm gãy và điểm vượt: s[3]  s[7]
7
4
3
8
6
5
2
1
Lật đoạn s[4 8]
7

4
3
1
2
5
6
8
Quy trình hoạt động của hàm Next
74286531  74312568
(* Pascal *)
program GenAllPer;
{$B-}
uses crt;
const MN = 9; {max n} BL = #32; {dau cach}
var s: array[0 MN] of integer;
function Next(n: integer): Boolean; tự viết
procedure Gen(n: integer);
const
fn = 'HoanVi.dat'; {ten tep ket qua}
var
d: longint; {dem so luong hoan vi}
i: integer;
f: text; {tep ket qua}
begin
if (n < 1) or (n > MN) then exit;
assign(f,fn); rewrite(f);
d := 0; {dem so hoan vi}
{Sinh hoán vị đơn vị. Đặt lính canh s[0] = 0}
for i := 0 to n do s[i]:= i;
repeat

d := d+1; {Ghi hoan vi thu d, s vao tep}
for i:= 1 to n do write(f, s[i], BL);
writeln(f);
until not (next(n));
writeln(f,' Tong cong ',d, ' hoan vi');
close(f);
end;
BEGIN
Gen(5); write('fini'); readln;
END.
// C#
using System;
using System.Collections.Generic;
using System.Text;
Sáng tạo trong Thuật toán và Lập trình Tập I

46



using System.IO;
namespace SangTao1
{
class GenAllPer
{
static void Main(string[] args)
{
string fn = "HoanVi.txt";
int d = Gen(fn,5);
Test(fn,d); // Xem kết quả

Console.WriteLine("\n Fini ");
Console.ReadLine();
}
// Sinh các hoán vị, ghi file fn
static public int Gen(string fn, int n)
{
if (n < 1 | n > 9) return 0;
int d = 0; // dem so hoan vi d = n!
StreamWriter f = File.CreateText(fn);
int[] a = new int[n + 1];
for (int i=0; i <= n; ++i) a[i]=i;
do { // Ghi file
for (int i=1; i <= n; ++i)
f.Write(a[i] + " ");
f.WriteLine(); ++d;
} while (Next(a));
f.Close();
return d;
}
static bool Next(int[] a)//Hoán vị tiếp
{
int i, j, t, n = a.Length-1;
for (i=n-1; a[i] > a[i+1]; i) ;
if (i == 0) return false;
for (j = n; a[j] < a[i]; j) ;
t = a[i]; a[i] = a[j]; a[j] = t;
for (++i, j = n; i < j; ++i, j)
{ t = a[i]; a[i] = a[j]; a[j] = t; }
return true;
}

static public void Test(string fn, int d)
{
Console.WriteLine("\n Tong cong "
+ d + " so");
Console.WriteLine(File.ReadAllText(fn));
}
} // GenAllPer
} // SangTao1
Bài 2.11. Đọc dữ liệu từ tệp vào mảng biết hai kích thước
Đọc dữ liệu kiểu nguyên từ một tệp văn bản vào một mảng hai chiều.
Tệp có cấu trúc như sau:
- Hai số đầu tiên n, m là kích thước của mảng gồm n dòng và m cột.
Sáng tạo trong Thuật toán và Lập trình Tập I

47



- Tiếp đến là các dữ liệu ghi liên tiếp nhau theo từng dòng của mảng.
- Các số cách nhau ít nhất một dấu cách.
Thí dụ:
2 3 -1 4 5 3 7 1
cho biết mảng có n = 2 dòng và m = 3 cột với dữ liệu như sau:
-1
4
5
3
7
1
Đặc tả

Ta viết hàm Doc cho giá trị true nếu đọc được dữ liệu. Chú ý rằng dữ liệu vào là
đúng do đó không cần kiểm tra tính đúng đắn của chúng. Như vậy Doc sẽ cho giá trị
false trong trường hợp không mở được file, do ghi sai đường dẫn hoặc file
không được tạo lập từ trước.
Chỉ thị {$I-} yêu cầu hệ thống chỉ ghi nhận chứ không bắt các lỗi vào/ra, tức là
không dừng sự thực hiện chương trình. Biến hệ thống IORESULT sẽ ghi nhận số hiệu
lỗi. Nếu IORESULT=0 thì thao tác vào ra không sinh lỗi, ngược lại, nếu IORESULT ≠
0 tức là đã có lỗi.
Chỉ thị {$I+} yêu cầu hệ thống bắt mọi lỗi vào/ra. Như vậy, dòng lệnh
{$I-} reset(f); {$I+}
sẽ được hiểu như sau:
Thoạt tiên ta yêu cầu hệ thống bỏ chế độ bắt lỗi vào/ra {$I-}. Sau đó thực hiện
lệnh mở tệp để đọc reset(f).Tiếp đến đặt lại chế độ bắt lỗi {$I+}.
(* Pascal *)
uses crt;
const MN = 100;
var a: array[1 MN,1 MN] of integer;
m,n: integer;
Function Doc(fn: string): Boolean;
var
f: text;
i, j: integer;
begin
Doc := false; assign(f,fn);
{$I-} reset(f); {$I+}
if IORESULT <> 0 then exit; {không mở được file}
read(f,n,m);{doc kich thuoc n va m cua mang }
for i := 1 to n do
for j:= 1 to m do
read(f,a[i, j]);

close(f);
Doc := true;
end;
procedure Xem(n,m: integer); Hiển thị mảng 2 chiều,
tự viết
BEGIN
if Doc('DATA.INP') then Xem(n,m)
else write('Khong mo duoc tep ');
readln;
Sáng tạo trong Thuật toán và Lập trình Tập I

48



END.
// C#
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace sabgTao1
{
class DocMang2Chieu
{
static void Main(string[] args)
{
string fn = "Data.inp";
int n = 0, m = 0;
int [,] a = Doc(fn, ref n, ref m);

if (a != null)
{
PrintInput(fn);
Print(a, n, m);
}
else
Console.WriteLine("\n " +
" Khong mo duoc file " +fn);
Console.WriteLine("\n Fini ");
Console.ReadLine();
}

static public int[,] Doc(string fn,
ref int n, ref int m)
{
if (!File.Exists(fn)) return null;
// Cac dau ngan
char[] cc = new char[]
{ ' ', '\n', '\t', '\r' };
// Mo tep ten fn doc, tỏch, dong tep
string[] ss = (File.ReadAllText(fn)).
Split(cc,
StringSplitOptions.RemoveEmptyEntries);
// Chuyển sang mảng 1 chiều int [] c
int [] c = Array.ConvertAll(ss,
new Converter<string,int>(int.Parse));
n = c[0]; m = c[1];
int[,] a = new int[n, m];
int k = 2;
for (int i = 0; i < n; ++i)

for (int j = 0; j < m; ++j)
a[i,j] = c[k++];
return a;
}
static void Print(int[,] a, int n, int m)
Hiển thị mảng 2 chiều a, tự viết
Sáng tạo trong Thuật toán và Lập trình Tập I

49



static public void PrintInput(string fn)
Đọc lại file fn, tự viết
} // DocMang2Chieu
} // sangTao1
Giải thích
Trong các máy tính hiện đại, bộ nhớ trong RAM đủ lớn để có thể chứa toàn bộ dữ
liệu trong hầu hết các file input vì thế với môi trường C# .NET bạn nên đọc một lần
dữ liệu từ các file này. Hàm Doc cho ra mảng nguyên hai chiều. Nếu file không tồn tại,
hàm cho ra giá trị null. Bạn cần chuẩn bị trước file input với tên Data.inp và ghi vào thư
mục BIN\DEBUG trong Project hiện hành. Nếu ghi file vào thư mục khác thì trong
tham biến fn phải ghi chi tiết đường dẫn, thí dụ “D:\\MyDIR\\Data.inp”. Khi
viết đường dẫn, thay vì viết dấu “\” ta phải viết hai dấu đó, tức là “\\” vì bản thân
dấu “\” trong đóng vai trò báo hiệu kí tự đứng sát sau nó là kí tự điều khiển, thí dụ,
“\n” biểu thị dấu xuống dòng. Bạn cũng có thể viết dấu đổi mức @ cạnh đường dẫn
để chỉ thị rằng bạn muốn dùng một dấu “\” thay vì hai dấu, thí dụ,
@“D:\MyDIR\Data.inp”
Lệnh File.ReadAllText(fn) mở file với đường dẫn fn đọc toàn bộ dữ liệu
một lần vào một biến string sau đó tự động đóng file.

Lệnh Split(cc,StringSplitOptions.RemoveEmptyEntries)
tách các đơn vị trong biến string để ghi vào biến string[] ss đồng thời bỏ đi các
dấu trắng mô tả trong biến cc, bao gồm dấu cách „ „, dấu xuống dòng „\n‟, dấu tab
„\t‟ và dấu kết RETURN „\r‟, cuối cùng loại bỏ các đơn vị rỗng, tức là các string
không chứa kí tự nào (Length = 0).
Lệnh int[] c = Array.ConvertAll(ss,
New Converter<string,int>(int.Parse));
chuyển các string trong ss sang dạng số nguyên và ghi vào mảng nguyên (một chiều)
c. Đến đây toàn bộ dữ liệu trong file input fn đã được đọc và ghi vào mảng nguyên c. Các
mảng trong C# được đánh chỉ dẫn từ 0 đến Length-1. Theo điều kiện của đầu bài c[0]
chứa giá trị n, c[1] chứa giá trị m, từ c[2] trở đi chứa lần lượt các giá trị trên các dòng
của mảng hai chiều.
Bài 2.12. Đọc dữ liệu từ tệp vào mảng biết một kích thước
Đọc dữ liệu kiểu nguyên từ một tệp văn bản vào một mảng hai chiều a[n,m] cho
biết một kích thước m (số cột).
Tệp có cấu trúc như sau:
- Số đầu tiên ghi số lượng cột m của mảng tức là số phần tử trên một dòng.
- Tiếp đến là các dữ liệu ghi liên tiếp nhau theo từng dòng của mảng.
- Các số cách nhau ít nhất một dấu cách.
Thí dụ:
3 -1 4 5 3 7 1
sẽ được bố trí vào mảng n = 3 dòng, m = 3 cột như sau:
-1
4
5
3
7
1
Thuật toán
Sáng tạo trong Thuật toán và Lập trình Tập I


50



1. Mở tệp.
2. Đọc giá trị đầu tiên vào biến m: số lượng cột của ma trận.
3. Mỗi lần đọc xong một dòng ta tăng con đếm dòng (n) thêm 1.
Chú ý
Do có thể gặp dòng trống nên ta cần sử dụng hàm SeekEof. Hàm SeekEof
duyệt tiếp từ vị trí hiện thời của con trỏ tệp, bỏ qua các dấu trắng (gồm dấu cách, dấu
kết thúc dòng, dấu đầu dòng, dấu nhảy TAB), nếu gặp dấu hết tệp thì cho giá trị true,
ngược lại, nếu sau khi đã bỏ qua các dấu trắng mà chưa gặp dấu hết tệp thì cho giá trị
false.
(* Pascal *)
uses crt;
const MN = 100;
var a: array[1 MN,1 MN] of integer;
m,n: integer;
Function Doc(fn: string): Boolean;
var f: text; j: integer;
begin
Doc := FALSE; assign(f,fn);
{$I-} reset(f); {$I+}
if IORESULT <> 0 then exit;
read(f,m); {m: so luong cot}
n := 0; {n: so luong dong}
while NOT SeekEof(f) do
begin
inc(n);

for j := 1 to m do read(f,a[n,j]);
end;
close(f);
Doc := TRUE;
end;
procedure Xem(n,m: integer); tự viết
BEGIN
if Doc('DATA.INP') then Xem(n,m)
else write('Khong mo duoc tep ');
readln;
END.
Chú ý
Cần chuẩn bị trước dữ liệu và ghi trong tệp văn bản DATA.INP, thí dụ:

DATA.INP
3 -1 4 5 3 7 1
// C#
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

×