Bài 22. Kiếm thử và đánh giá chương trình trang 106 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức>
Trong các bài trước em đã học cách thiết kế thuật toán cho một số bài toán như bài toán tìm kiếm, bài toán sắp xếp và thiết lập chương trình thực hiện thuật toán đó. Một bài toán có nhiều thuật toán khác nhau và do đó có thể có nhiều chương trình khác nhau cùng giải quyết một bài toán. Hãy thảo luận và trả lời các câu hỏi sau: Làm thế nào để biết trong các thuật toán giải cùng một bài toán thì thuật toán nào là tốt nhất? Có những tiêu chí nào để đánh giá tính “tối ưu” của một thuật toán?
Khởi động
Trong các bài trước em đã học cách thiết kế thuật toán cho một số bài toán như bài toán tìm kiếm, bài toán sắp xếp và thiết lập chương trình thực hiện thuật toán đó. Một bài toán có nhiều thuật toán khác nhau và do đó có thể có nhiều chương trình khác nhau cùng giải quyết một bài toán. Hãy thảo luận và trả lời các câu hỏi sau:
Làm thế nào để biết trong các thuật toán giải cùng một bài toán thì thuật toán nào là tốt nhất?
Có những tiêu chí nào để đánh giá tính “tối ưu” của một thuật toán?
Phương pháp giải:
Dựa vào kiến thức trong bài kết hợp kiến thức thực tế của bản thân để trả lời câu hỏi.
Lời giải chi tiết:
Dựa vào hai yếu tố là thời gian thực hiện thuật toán (còn gọi là độ phức tạp thuật toán) và dung lượng bộ nhớ cần thiết để lưu trữ dữ liệu.
Thuật toán tối ưu là sử dụng ít thời gian, ít bộ nhớ, ít phép toán, giải bài toán trên máy tính thường được tiến hành qua 5 bước xác định bài toán, lựa chọn hoặc thiết kế thuật toán, viết chương trình, hiệu chỉnh và viết tài liệu.
? mục I HĐ1
Ở lớp 10, em đã học một số phương pháp kiểm thử chương trình. Em hãy thảo luận với các bạn về các phương pháp kiểm thử sau, nêu ý nghĩa của chúng trong việc đánh giá độ tin cậy và chứng minh tính đúng của chương trình:
1.Tạo các bộ dữ liệu kiểm thử (test) để kiểm tra dữ liệu đầu ra có chính xác hay không.
2.Thiết lập điểm dừng hoặc cho chương trình chạy theo từng lệnh để kiểm tra và tìm ra lỗi (bug) của chương trình.
3.Thực hiện in dữ liệu trung gian trong quá trình kiểm thử để tìm ra lỗi của chương trình (nếu có).
Phương pháp giải:
Vận dụng kiến thức mục 1 trang 106 SGK và kiến thức thực tế của bản thân để trả lời câu hỏi.
Lời giải chi tiết:
1.Tạo các bộ dữ liệu kiểm thử để kiểm tra dữ liệu đầu ra: Phương pháp này giúp đánh giá tính chính xác của dữ liệu đầu ra của chương trình. Bằng cách tạo ra các bộ dữ liệu kiểm thử đa dạng và phong phú, ta có thể đảm bảo rằng chương trình hoạt động đúng trên nhiều trường hợp khác nhau, từ đó đánh giá được độ tin cậy của chương trình. Nếu chương trình không đáp ứng được kết quả mong đợi trên các bộ dữ liệu kiểm thử, ta có thể suy ra rằng chương trình chưa hoạt động chính xác hoặc có thể chứa các lỗi còn chưa được phát hiện.
2.Thiết lập điểm dừng hoặc kiểm tra từng lệnh của chương trình: Phương pháp này giúp kiểm tra từng bước thực thi của chương trình, từ đó giúp tìm ra các lỗi hoặc bug của chương trình. Bằng cách dừng chương trình ở các điểm kiểm tra hoặc theo dõi từng lệnh, ta có thể kiểm tra giá trị của các biến, xác nhận tính đúng đắn của các phép tính, kiểm tra điều kiện của các câu lệnh rẽ nhánh, v.v. Nếu phát hiện lỗi trong quá trình này, ta có thể xác định nguyên nhân và sửa chữa chúng.
3.Thực hiện in dữ liệu trung gian trong quá trình kiểm thử: Phương pháp này giúp theo dõi dữ liệu giữa các bước trong quá trình kiểm thử. Bằng cách in ra dữ liệu trung gian, ta có thể xác nhận tính đúng đắn của các giá trị được sử dụng trong chương trình, theo dõi dòng dữ liệu từ đầu vào đến đầu ra của chương trình, từ đó giúp phát hiện và sửa chữa lỗi nếu có. Điều này giúp đảm bảo tính đúng đắn của kết quả của chương trình trong quá trình kiểm thử.
? mục I CH1
Giả sử em thiết lập chương trình giải bài toán nào đó. Em đã kiếm thử với 10 bộ dữ liệu và tất cả các kết quả đều đúng. Khi đó có thể kết luận chương trình đó đúng hay chưa?
Phương pháp giải:
Vận dụng kiến thức mục 1 trang 106 SGK và kiến thức thực tế của bản thân để trả lời câu hỏi.
Lời giải chi tiết:
Dựa trên việc kiểm thử với 10 bộ dữ liệu và tất cả các kết quả đều đúng, em có thể có một sự đánh giá tích cực về độ tin cậy của chương trình, nhưng không thể kết luận chắc chắn rằng chương trình đó đã hoàn toàn đúng.
Lý do là vì 10 bộ dữ liệu kiểm thử không đủ lớn và đa dạng để đảm bảo tính đúng đắn của chương trình trên mọi trường hợp có thể xảy ra trong thực tế. Có thể vẫn tồn tại các trường hợp đặc biệt hoặc dữ liệu đầu vào ngoại lệ mà chương trình chưa xử lý đúng, dẫn đến lỗi ở những bộ dữ liệu khác.
? mục I CH2
Giả sử một chương trình kiểm thử với 10 bộ dữ liệu cho kết quả 9 lần đúng, 1 lần sai. Chương trình đó là sai hay đúng?
Phương pháp giải:
Vận dụng kiến thức trong bài để trả lời câu hỏi.
Lời giải chi tiết:
Dựa trên kết quả của 10 bộ dữ liệu kiểm thử, với 9 lần đúng và 1 lần sai, không thể kết luận chương trình đó là đúng hoặc sai một cách chắc chắn. Kết quả này chỉ cho thấy chương trình có khả năng hoạt động chính xác trên hầu hết các trường hợp, nhưng vẫn có một trường hợp đặc biệt nào đó mà chương trình không xử lý đúng.
Việc phát hiện được một lỗi trong 1 lần kiểm thử không đồng nghĩa với việc chương trình đó là sai. Có thể có nhiều nguyên nhân dẫn đến kết quả sai trong lần kiểm thử đó, chẳng hạn như dữ liệu đầu vào đặc biệt, điều kiện biên, hay một vấn đề trong việc cấu hình môi trường kiểm thử.
Vì vậy, để đưa ra đánh giá chính xác về tính đúng của chương trình, cần phải tiếp tục kiểm thử với nhiều bộ dữ liệu kiểm thử khác nhau, đánh giá kết quả và phân tích sâu hơn về nguyên nhân của lỗi nếu có. Sau đó, cần tiến hành sửa chữa lỗi và thực hiện kiểm thử lại để đảm bảo tính đúng đắn của chương trình trước khi có thể kết luận chương trình là đúng hoặc sai.
? mục II HĐ2
Quan sát chương trình mô tả thuật toán sắp xếp chèn. Hãy thảo luận và đưa ra các lập luận để kiểm tra tính đúng của thuật toán sắp xếp chèn.
Phương pháp giải:
Vận dụng kiến thức mục 2 trang 107, 108 SGK để trả lời câu hỏi.
Lời giải chi tiết:
Tính đúng của thuật toán cần được chứng minh bằng lập luận toán học. Sử dụng các bộ dữ liệu kiểm thử có thể làm tăng độ tin cậy của chương trình nhưng chưa chứng minh được tính đúng của thuật toán.
? mục II CH1
Chương trình sau giải bài toán: Yêu cầu nhập số tự nhiên n và tính tổng 1 + 2+… +n. Chương trình trên có đúng không?
Phương pháp giải:
Vận dụng kiến thức mục 2 trang 107, 108 SGK và kiến thức thực tế của bản thân để trả lời câu hỏi.
Lời giải chi tiết:
Đúng
? mục II CH2
Chương trình sau giải bài toán đếm số các ước số thực sự của số tự nhiên n. Chương trình trên đúng hay sai.
Phương pháp giải:
Vận dụng kiến thức mục 2 trang 107, 108 SGK để trả lời câu hỏi.
Lời giải chi tiết:
Chương trình trên sai, vì thiếu ước bằng 1 của n.
? mục III HĐ3
Thảo luận về các tiêu chí đánh giá tính hiệu quả của thuật toán hay chương trình giải một bài toán.
1.Tiêu chí quan trọng nhất là thời gian chạy chương trình phải nhanh, không cần quan tâm đến không gian bộ nhớ sử dụng của chương trình.
2.Tiêu chí tiết kiệm bộ nhớ là quan trọng nhất, sau đó mới đến thời gian chạy chương trình.
3.Các tiêu chí 1 và 2 không quan trọng mà quan trọng là chương trình được viết một cách đơn giản, rõ ràng, dễ hiểu và áp dụng.
Phương pháp giải:
Vận dụng kiến thức mục 3 trang 109 SGK và kiến thức thực tế của bản thân để trả lời câu hỏi.
Lời giải chi tiết:
Các tiêu chí đánh giá tính hiệu quả của thuật toán hay chương trình giải một bài toán có thể khác nhau tùy vào mục đích và yêu cầu của dự án hoặc ứng dụng cụ thể. Dưới đây là một số thảo luận về các tiêu chí được đưa ra trong câu hỏi:
1. Tiêu chí thời gian chạy (runtime): Thời gian chạy của chương trình là một yếu tố quan trọng trong đánh giá tính hiệu quả của thuật toán hay chương trình. Nếu chương trình chạy nhanh, đáp ứng được yêu cầu về thời gian đối với ứng dụng cụ thể, thì đây là một tiêu chí quan trọng để đánh giá tính hiệu quả của chương trình.
2. Tiêu chí tiết kiệm bộ nhớ: Việc sử dụng bộ nhớ của chương trình cũng là một yếu tố quan trọng trong đánh giá tính hiệu quả của chương trình, đặc biệt là đối với các ứng dụng có yêu cầu về tài nguyên hạn chế. Nếu chương trình sử dụng ít bộ nhớ và đáp ứng được yêu cầu về tài nguyên, thì tiêu chí này cũng được coi là quan trọng.
3. Tiêu chí đơn giản, rõ ràng, dễ hiểu: Độ đơn giản, rõ ràng và dễ hiểu của chương trình cũng là một yếu tố quan trọng trong đánh giá tính hiệu quả của chương trình, đặc biệt là trong việc duy trì và phát triển sau này. Nếu chương trình được viết một cách đơn giản, rõ ràng và dễ hiểu, thì nó sẽ dễ dàng trong việc duy trì, nâng cấp, và áp dụng cho các tình huống khác nhau.
? mục III CH
Hai tiêu chỉ đánh giá độ phức tạp tính toán quan trọng nhất là gì?
Phương pháp giải:
Vận dụng kiến thức trong bài để trả lời câu hỏi.
Lời giải chi tiết:
Lời giải:
Hai tiêu chí đánh giá độ phức tạp tính toán quan trọng nhất là:
1. Thời gian thực thi (Runtime): Đây là thời gian mà chương trình hoặc thuật toán mất để thực hiện một nhiệm vụ hoặc tính toán. Thời gian thực thi là một tiêu chí quan trọng vì nó đo lường tốc độ hoạt động của chương trình, và đối với các ứng dụng yêu cầu xử lý dữ liệu lớn hoặc thực hiện tính toán phức tạp, thời gian thực thi càng nhanh thì chương trình càng hiệu quả.
2. Độ phức tạp không gian (Space complexity): Đây là lượng bộ nhớ mà chương trình hoặc thuật toán sử dụng trong quá trình thực hiện nhiệm vụ hoặc tính toán. Độ phức tạp không gian cũng là một tiêu chí quan trọng vì nó đo lường khả năng sử dụng tài nguyên bộ nhớ của chương trình, và đối với các ứng dụng có yêu cầu về tài nguyên hạn chế, độ phức tạp không gian càng thấp thì chương trình càng hiệu quả.
Luyện tập 1
Hãy xây dựng các bộ dữ liệu kiểm thử đề tìm lỗi cho chương trình tính n! với n là một số nguyên dương nhập từ bàn phím.
n=int(input(“nhập số n:”))
if n>0:
giaithua=1
for i in range(1,n+1):
giaithua=giaithua*i
print(n,”giai thừa bằng:”,giaithua)
Phương pháp giải:
Dựa vào kiến thức trong bài kết hợp kiến thức thực tế của bản thân để trả lời câu hỏi.
Lời giải chi tiết:
Dưới đây là một số bộ dữ liệu kiểm thử đề tìm lỗi cho chương trình tính n!:
Số nguyên dương: n = 5 Kết quả mong đợi: 5! = 120
Số nguyên âm: n = -3 Kết quả mong đợi: Lỗi - Số nguyên dương được yêu cầu
Số 0: n = 0 Kết quả mong đợi: Lỗi - Số nguyên dương được yêu cầu
Số nguyên lớn: n = 10 Kết quả mong đợi: 10! = 3628800
Số chẵn: n = 6 Kết quả mong đợi: 6! = 720
Số lẻ: n = 7 Kết quả mong đợi: 7! = 5040
Số nguyên tối đa: n = 12 Kết quả mong đợi: 12! = 479001600
Số nguyên tối thiểu: n = 1 Kết quả mong đợi: 1! = 1
Số nguyên dương lớn nhất: n = 999 Kết quả mong đợi: Kết quả chưa đúng do số quá lớn vượt quá giới hạn của kiểu dữ liệu int
Số nhập không phải số nguyên: n = "abc" Kết quả mong đợi: Lỗi - Số nguyên dương được yêu cầu
Những bộ dữ liệu này giúp kiểm thử chương trình với các trường hợp đặc biệt và tiềm ẩn lỗi, như số âm, số 0, số nguyên tối đa, số nhập không phải số nguyên, giúp đảm bảo tính đúng đắn và hoạt động ổn định của chương trình tính n!.
Luyện tập 2
Xét hàm mô tả thuật toán tính tổng các số chẵn của một dãy số cho trước.
def tongchan(A):
s=0
for i in range(len(A)):
if A[i]%2==0:
s=s+A[i]
return s
Tìm hai bộ dữ liệu đầu vào có cùng kích thước của thuật toán trên nhưng có thời gian chạy khác nhau.
Phương pháp giải:
Vận dụng kiến thức trong bài kết hợp kiến thức thực tế của bản thân để trả lời câu hỏi.
Lời giải chi tiết:
Hai bộ dữ liệu đầu vào có cùng kích thước của thuật toán trên nhưng có thời gian chạy khác nhau có thể là:
- Bộ dữ liệu 1: A = [2, 4, 6, 8, 10] # Có 5 phần tử Kết quả mong đợi: Tổng các số chẵn là 30
- Bộ dữ liệu 2: A = [1, 3, 5, 7, 9] # Có 5 phần tử Kết quả mong đợi: Tổng các số chẵn là 0
Trong trường hợp này, cả hai bộ dữ liệu đều có cùng kích thước là 5 phần tử, nhưng thời gian chạy của thuật toán sẽ khác nhau vì số lượng số chẵn trong dãy số khác nhau. Bộ dữ liệu 1 chứa toàn số chẵn nên thời gian chạy của thuật toán sẽ lớn hơn bộ dữ liệu 2 chỉ chứa các số lẻ.
Vận dụng 1
Cho dãy các số A = (3, 1, 0, 10, 13, 16, 9, 7, 5, 11].
a) Viết chương trình mô tả thuật toán tìm kiếm phần tử C = 9 của dãy trên. Tính thời gian chính xác thực hiện công việc tìm kiếm này.
b) Giả sử dãy A ở trên đã được sắp xếp theo thứ tự tăng dần: A= [0,1,3,5,7,9,10,11,13, 16]. Viết chương trình tìm kiếm nhị phân để tìm kiếm phân tử C = 9, đo thời gian thực hiện thuật toán. So sánh với kết quả 1ìm kiếm ở câu a.
Phương pháp giải:
Vận dụng kiến thức trong bài kết hợp kiến thức thực tế của bản thân để trả lời câu hỏi.
Lời giải chi tiết:
a)
import time
def linear_search(arr, x):
"""
Tìm kiếm tuyến tính trong dãy arr để tìm giá trị x.
Trả về vị trí của x trong dãy nếu x được tìm thấy, -1 nếu không tìm thấy.
"""
n = len(arr)
for i in range(n):
if arr[i] == x:
return i
return -1
# Dãy số A
A = [3, 1, 0, 10, 13, 16, 9, 7, 5, 11]
# Phần tử cần tìm kiếm
C = 9
# Bắt đầu đo thời gian
start_time = time.perf_counter()
# Tìm kiếm phần tử C trong dãy A
result = linear_search(A, C)
# Kết thúc đo thời gian
end_time = time.perf_counter()
if result != -1:
print(f"Phần tử {C} được tìm thấy tại vị trí {result} trong dãy A.")
else:
print(f"Phần tử {C} không có trong dãy A.")
print(f"Thời gian thực hiện thuật toán: {end_time - start_time} giây.")
b)
import time
def binary_search(arr, x):
"""
Tìm kiếm nhị phân trong dãy arr để tìm giá trị x.
Trả về vị trí của x trong dãy nếu x được tìm thấy, -1 nếu không tìm thấy.
"""
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == x:
return mid
elif arr[mid] < x:
left = mid + 1
else:
right = mid - 1
return -1
# Dãy số A đã được sắp xếp
A = [0, 1, 3, 5, 7, 9, 10, 11, 13, 16]
# Phần tử cần tìm kiếm
C = 9
# Bắt đầu đo thời gian
start_time = time.perf_counter()
# Tìm kiếm phần tử C trong dãy A bằng thuật toán tìm kiếm nhị phân
result = binary_search(A, C)
# Kết thúc đo thời gian
end_time = time.perf_counter()
if result != -1:
print(f"Phần tử {C} được tìm thấy tại vị trí {result} trong dãy A.")
else:
print(f"Phần tử {C} không có trong dãy A.")
print(f"Thời gian thực hiện thuật toán: {end_time - start_time} giây.")
-Thời gian thực hiện ở câu a là 8.99999,thời gian thực hiện ở câu b là 6,49999 giây.
Vận dụng 2
Viết ba chương trình mô phỏng các thuật toán sắp xếp chèn, sắp xếp chọn và sắp xếp nổi bọt mà em đã biết. Cho biết thời gian thực tế thực hiện các chương trình trên với bộ dữ liệu đầu vào là dãy A = {3, 1, 0, 10, 13, 16, 9,7, 5, T1]
Phương pháp giải:
Vận dụng kiến thức trong bài để trả lời câu hỏi.
Lời giải chi tiết:
*Thuật toán sắp xếp chèn (Insertion Sort):
import time
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
# Dãy số nguyên đầu vào
A = [3, 1, 0, 10, 13, 16, 9, 7, 5, 1]
# In dãy số nguyên trước khi sắp xếp
print("Dãy số nguyên trước khi sắp xếp:", A)
# Bắt đầu đo thời gian thực hiện thuật toán
start_time = time.time()
# Gọi hàm sắp xếp chèn
insertion_sort(A)
# Kết thúc đo thời gian thực hiện thuật toán
end_time = time.time()
# In dãy số nguyên sau khi sắp xếp
print("Dãy số nguyên sau khi sắp xếp:", A)
# In thời gian thực hiện thuật toán
print("Thời gian thực hiện thuật toán: {:.6f} giây".format(end_time - start_time))
Thời gian thực hiện là 0 giây
*Thuật toán sắp xếp chọn:
import time
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
# Dãy số nguyên đầu vào
A = [3, 1, 0, 10, 13, 16, 9, 7, 5, 1]
# In dãy số nguyên trước khi sắp xếp
print("Dãy số nguyên trước khi sắp xếp:", A)
# Bắt đầu đo thời gian thực hiện thuật toán
start_time = time.time()
# Gọi hàm sắp xếp chọn
selection_sort(A)
# Kết thúc đo thời gian thực hiện thuật toán
end_time = time.time()
# In dãy số nguyên sau khi sắp xếp
print("Dãy số nguyên sau khi sắp xếp:", A)
# In thời gian thực hiện thuật toán
print("Thời gian thực hiện thuật toán: {:.6f} giây".format(end_time - start_time))
Thời gian thực hiện là: 0 giây
*Thuật toán sắp xếp nổi bọt:
import time
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1):
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
# Dãy số nguyên đầu vào
A = [3, 1, 0, 10, 13, 16, 9, 7, 5, 1]
# In dãy số nguyên trước khi sắp xếp
print("Dãy số nguyên trước khi sắp xếp:", A)
# Bắt đầu đo thời gian thực hiện thuật toán
start_time = time.time()
# Gọi hàm sắp xếp nổi bọt
bubble_sort(A)
# Kết thúc đo thời gian thực hiện thuật toán
end_time = time.time()
# In dãy số nguyên sau khi sắp xếp
print("Dãy số nguyên sau khi sắp xếp:", A)
# In thời gian thực hiện thuật toán
print("Thời gian thực hiện thuật toán: {:.6f} giây".format(end_time - start_time))
Thời gian thực hiện là: 0 giây
- Bài 24. Đánh giá độ phức tạp thời gian thuật toán trang 111 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 25. Thực hành xác định độ phức tạp thời gian thuật toán trang 115 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 26. Phương pháp làm mịn dần trong thiết kế chương trình trang 118 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 27. Thực hành thiết kế chương trình theo phương pháp làm mịn dần trang 123 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 28. Thiết kế chương trình theo mô đun trang 127 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
>> Xem thêm
Các bài khác cùng chuyên mục
- Bài 31. Thực hành thiết lập thư viên chương trình trang 143 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 30. Thiết lập thư viện cho chương trình trang 137 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 29. Thực hành thiết kế chương trình theo mô đun trang 132 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 28. Thiết kế chương trình theo mô đun trang 127 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 27. Thực hành thiết kế chương trình theo phương pháp làm mịn dần trang 123 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 31. Thực hành thiết lập thư viên chương trình trang 143 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 30. Thiết lập thư viện cho chương trình trang 137 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 29. Thực hành thiết kế chương trình theo mô đun trang 132 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 28. Thiết kế chương trình theo mô đun trang 127 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức
- Bài 27. Thực hành thiết kế chương trình theo phương pháp làm mịn dần trang 123 SGK Tin học 11 Định hướng khoa học máy tính Kết nối tri thức