Assessed coursework 1 for Algorithms and Machine Learning (MATH20017), Autumn 2023
Introduction
This document contains the questions for Part 1 of your assessed coursework for the unit Algorithms and Machine Learning (MATH20017). The marks for this coursework will count 10% towards your final grade.
Please contact henry.reeve@bristol.ac.uk with any questions regarding this document. Whilst I am unable to provide solutions in advance of all work being handed in, I can provide clarification.
The contents of this document should not be distributed without permission.
There are 5 sections to this coursework and you are encouraged to attempt to complete all sections.
Handing in your coursework
How you present your coursework is important. You should complete your coursework using either Google Colab, a Jupyter notebook, or an Rmarkdown. Whichever approach you take you must submit both (1) the notebook itself (typically either a .ipynb file or a .rmd file) and (2) an HTML file in which all of the blocks of code have been run.
The suggested approach is as follows:
- Complete your notebook in Google Colab.
- Use Run -> Run all to run all code blocks.
- Check your notebook looks as expected.
- Use File -> Download -> Download as .ipynb
- Open a new instance of Google Colab (fourth from top).
- Click the Upload icon (the first icon from the left).
- Upload your previously downloaded .ipynb file.
- Click the Refresh icon (the second from the left).
- In your new colab instance write in the following piece of code. You should replace
your_python_notebook_file_path
with the path of your coursework notebook. This can be found by clicking on the three dots to the right of your coursework notebook’s icon and selecting Copy path, then pasting in the path.
!jupyter nbconvert --to HTML your_python_notebook_file_path
Run the command.
Again, click the Refresh icon (the second from the left).
An HTML version of your file should now appear. Click the three dots to the right of this file and select Download to download the HTML file.
Open the HTML file and carefully check that all questions and answers are visible and appear as expected. 14. Upload your .HTML and your
.ipynb
files to the submission point within Blackboard.
Important: Ensure that you use the correct format to submit your report as failure to do so can lead to a substantial loss of marks.
Section A (20 marks)
In this question you will implement two algorithms for searching for a specific value within an array.
Throughout this question you should use Python lists to represent arrays.
(Q1) Implement the linear array search algorithm in Python. That is, create a function called linear_search
in Python which takes as input a list of integers X and a target integer z. If z is contained within the list then your function should output an index at which the target appears. Otherwise your function should return −1.
To test your linear_search
function first copy the following code.
import numpy as np # load the numpy library
def search_function_test(search_function, random_seed=2023, array_size=30, max_int=100, num_tests=50):
np.random.seed(random_seed) # set the random seed
output = []
for i in range(num_tests):
X = sorted(list(set(np.random.randint(max_int, size=(array_size)))))
z = np.random.randint(max_int)
j = search_function(X, z)
if j != -1:
output.append(j)
return output
Next apply the test function search_function_test
to linear_search as follow:
print(search_function_test(linear_search))
(Q2) Implement the binary search algorithm in Python. You should create a function called binary_search
which takes as input an ascending sorted list of integers X and a target integer z. If z is contained within the list then your function should output an index at which the target appears. Otherwise your function should return −1. Use the following code to test your function.
print(search_function_test(binary_search))
Solution A
A.1 什么是线性搜索算法🔍
线性搜索算法(Linear Search Algorithm),也叫顺序搜索算法,是一种非常基础的搜索算法。其工作原理很简单:从列表的第一个元素开始,按顺序检查每一个元素,直到找到我们想要的元素为止。
以下是线性搜索算法的基本步骤:
- 从列表的第一个元素开始。
- 逐个比较每个元素与我们要找的目标元素。
- 如果当前元素与目标元素匹配,则返回当前元素的索引。
- 如果遍历了整个列表都没有找到目标元素,则返回 -1 或其他标志,表示目标元素不在列表中。
这是线性搜索算法的 Python 实现:
def linear_search(arr, x):
for i in range(len(arr)):
if arr[i] == x:
return i # 返回目标元素的索引
return -1 # 如果没有找到,则返回-1
线性搜索算法的时间复杂度是 ,其中 n 是列表的长度。这意味着在最坏的情况下,算法可能需要检查列表中的每一个元素。因此,对于非常大的列表,线性搜索可能不是最高效的搜索方法。但对于小列表或无法排序的数据,它是简单且实用的方法。
A.2 Q1 Solution
首先,我们来实现线性搜索算法:
def linear_search(X, z):
for i in range(len(X)):
if X[i] == z:
return i
return -1
然后,你可以使用上面给出的测试函数 search_function_test
来测试 linear_search
函数:
print(search_function_test(linear_search))
下面是使用 while 来实现的:
def linear_search(X, z):
i = 0
length = len(X)
while i < length:
if X[i] == z:
return i
i += 1
return -1
A.2 Q2 Solution
接下来,我们来实现二分搜索算法。注意,二分搜索只对已排序的列表有效,所以我们假设输入的列表是按升序排序的。
def binary_search(X, z):
left, right = 0, len(X) - 1
while left <= right:
mid = (left + right) // 2
if X[mid] == z:
return mid
elif X[mid] < z:
left = mid + 1
else:
right = mid - 1
return -1
你可以使用同样的 search_function_test
函数来测试你的 binary_search
函数:
print(search_function_test(binary_search))
注意
注意:这里的测试函数 search_function_test
使用了 numpy
来生成测试数据,而且为了确保测试的准确性,它生成的 X
列表是已排序的唯一整数列表,这对于二分搜索来说是很重要的。
Section B (20 marks)
The aim of this question is to improve your understanding of Bachmann–Landau
notation. To include your answers for this question you have two options:
- Include your answers in the Python notebook directly via Tex;
- Write your answers via pen and paper and include a photo within the final document.
In either case, please ensure that the answers are clearly readable.
(Q1) Suppose that is a function. Show that if and then .
(Q2) Suppose that , and are functions. Show that if and then .
(Q3) Suppose that is a strictly positive function. Show that there exists at least one function with the property that holds but does not hold.
(Q4) Suppose that we define a function recursively as follows: , and for , . Can you find a number with the property that ? Justify your claim.
B.1 Bachmann–Landau
Bachmann–Landau
记法(通常简称为大 O 记法,但实际上包括大 、小 、大 、小 和大 五种)是一种表示算法复杂度和函数增长率的记法。这里我们列举其中一些主要定义:
大 O 记法 () (上界):
如果存在常数 和 使得对于所有 ,都满足 ,那么我们说 .
大 记法 () (下界):
如果存在常数 和 使得对于所有 ,都满足 ,那么我们说 .
大 记法 () (紧确界):
如果 并且 ,那么我们说 .
小o记法 (o) (严格的上界):
如果对于所有常数 ,存在一个 使得对于所有 ,都满足 ,那么我们说 .
小 记法 () (严格的下界):
如果对于所有常数 ,存在一个 使得对于所有 ,都满足 ,那么我们说 .
这些记法主要被用于算法分析中,以描述算法的运行时间或空间使用量随着输入大小的变化。在您的问题中,我们主要关注的是大 记法。
B.2 足够大,其它项目影响小
提示
当 n 变得非常大时,最高次项(这里是 )的影响会远大于其他较低次的项。
考虑一个多项式:
其中 是常数,并且 。
为了证明当 变得非常大时,最高次项 的影响会远大于其他较低次的项,我们可以考虑下面的比值:
- 与次高项的比值:
当 趋向于无穷大时,这个比值也会趋向于无穷大(假设 )。
- 与第三高项的比值:
同样地,当 趋向于无穷大时,这个比值会变得更大。
......
以此类推,我们可以看到与更低次项的比值会随着 的增长而变得更大。
最后,对于常数项 :
当 趋向于无穷大时,这个比值显然是趋向于无穷大的。
从上面的分析我们可以看到,当 变得非常大时,最高次项 相对于所有其他项的影响都会变得非常大。这意味着,对于足够大的 ,整个函数 的行为主要由最高次项 决定。
这就是为什么在分析算法的复杂性时,我们通常只关心最高次项:它是确定函数增长速度的主要因素。
B.3 为什么某个形式选择
这个选择基于我们想证明的目标。
在这个情境下,我们想要证明 在大 O 意义上是 的上界。为了达到这一点,我们会查看 中的每一项,并尝试将它们都限制在与 相关的某个项的上界。
- 为什么我们选择形式为 的项呢?
这是因为我们要确定 是否与 有相同的增长率(或更小)。如果 的每一项都可以被形式为 的项所界定,并且这些界是有效的(即对于足够大的 值都成立),那么我们可以得出结论说 。
在这个特定的例子中:
- 已经是 的形式,所以不需要更改。
- 对于 ,当 时,我们知道 ,所以我们可以用 来界定它。
- 对于常数 5,因为它是一个常数,所以当 时,它肯定小于任何形式为 的项,其中 。
总结来说,选择形式为 的项是为了确保 的每一项都被 的某个倍数所界定,从而验证 。
- 那么最高项是 n 的时候,是不是就可以直接找 ?
如果一个函数的最高项是 ,当我们想要证明这个函数是 时,我们确实会尝试找一个形式为 的上界来界定每一项。
例如,考虑函数 :
- 第一项 :已经是 的形式,不需要更改。
- 第二项 :当 时,我们知道 ,所以我们可以用 来界定它。
- 对于常数 3,因为它是一个常数,当 时,它肯定小于形式为 的任何项,其中 。
将这三个上界相加,我们得到:
因此,我们可以说 ,其中 。
所以,当最高次项为 (n) 时,我们确实会找一个形式为 的上界。这个原理可以应用于任何最高次项。您的观察非常准确!
B.4 大 O 法证明
给定两个函数 和 ,当说 时,其数学定义是存在两个正的,常数 和 使得对于所有 ,满足 。这里的 和 不是唯一的,只要满足上述条件即可。
为了更好地理解,我提供一个简单的例子:
考虑函数 和函数 .
我们希望证明 ,即 是 的上界。
对于 ,我们有:
所以,我们可以取 和 来证明 。因此, .
B.5 大 记法 () (下界)
大 记法是用于描述算法的下界复杂性的一种表示方法。具体来说,它给出了算法运行时间的一个下限。如果我们说一个算法的运行时间为 ,那么这意味着无论多快,算法的运行时间不会低于 的某个常数倍,至少对于足够大的 是这样的。
形式上,假设有函数 和 ,如果存在某个常数 和某个值 ,使得对于所有 ,都有 ,则我们可以说 。
直观上,大 记法为我们提供了一个下限,告诉我们函数的增长速度至少有多快。在算法分析中,使用大 记法是为了确保算法不会比某个特定速度慢。
举个例子,假设我们有一个排序算法,其最坏情况下的时间复杂性为 。这意味着不管其他情况下这个算法有多快,其运行时间在最坏的情况下至少与 成正比。这对于很多经典的比较排序算法(如归并排序和堆排序)来说都是真实的,因为它们的最坏情况时间复杂性确实是 。
B.6 分析验证
让我们通过一个具体的例子来理解和分析大 记法。
例子:
假设我们有两个函数 和 。
我们想验证 是不是 。
按照大 的定义,如果存在两个正数 和 ,使得对于所有的 ,都有:
那么我们可以说 是 。
分析:
现在,我们尝试找到 和 的值:
对于 ,我们有:
将两边的 相减,得到:
我们可以轻易地观察到,对于所有的正数 ,这个不等式都是成立的。因为 、 和 1 都是正的,所以他们的和也是正的。
对于所有的正数 都是真的(因为 是大于 的,而 只会使其增大)。
因此,我们可以选择 和 。这样,对于所有 ,都有 。
结论:
根据上述分析,我们可以得出 是 。 是 ,其中 和 。
这样的验证和分析通常需要一些数学直觉和对函数的了解。上述方法为最常见的一种方法,但在复杂的情况下,可能需要更高级的数学工具和方法。
B.7 Solution
Q1
(Q1) Suppose that is a function. Show that if and then .
(Q1) 假设 是一个函数。证明如果 且 ,则 。
给定:
要证明:
首先,我们要理解一点关于对数的属性:对于任何正数 b, c, 和 x, 我们有
使用这个性质,我们可以表达 为 的形式:
因为给定 , 存在常数 和 使得对于所有 ,
现在,我们要证明 也是 . 使用我们之前的关系式,我们有:
因为 , 我们知道 , 所以 也是一个正的常数。我们可以令 , 则有:
这意味着 .
所以我们证明了,如果 并且 , 那么 .
让我们使用换底公式 来看几个具体的例子:
例子 1:
计算 使用自然对数作为新的底数。
我们知道 ,所以 。
使用换底公式,我们有:
使用计算器,我们可以得到 和
除以这两个数,我们得到:
例子 2:
计算 使用以 10 为底的对数作为新的底数。
我们知道 ,所以 。
使用换底公式,我们有:
使用计算器,我们得到 和
除以这两个数,我们得到:
例子 3:
计算 使用自然对数作为新的底数。
我们知道 ,所以 。
使用换底公式,我们得到:
使用计算器, 和
除以这两个数,我们得到:
(Q2) Suppose that , and are functions. Show that if and then .
题目分析:
题目给出了三个函数:, , 和 ,它们都是定义在自然数集 上的非负实值函数。已知:
我们需要证明
证明:
步骤 1:根据 的定义,存在一个正常数 和 ,使得当 时,有:
—— (式 1)
步骤 2:根据 的定义,存在一个正常数 和 ,使得当 时,有:
—— (式 2)
步骤 3:我们现在要结合式 1 和式 2 来得出 和 之间的关系。
对于任何 (我们取两个界限中的较大者),式 1 和式 2 都成立。那么我们有:
将这两个不等式相乘,得到:
由于 ,我们可以得到:
这正是我们想要证明的结果,即存在一个常数 和 ,使得当 时,有 。所以 。
结论:
根据以上证明,如果 和 , 那么 。
(Q3) Suppose that is a strictly positive function. Show that there exists at least one function with the property that holds but does not hold.
这个题目要求我们证明存在至少一个函数 ,使得 成立,但 不成立。要解决这个问题,我们可以构建这样一个函数 。
考虑 。我们将分两步证明 和 不是 。
1. 证明 :
我们需要找到常数 和 使得对于所有 ,都有 。
使用我们定义的 ,不等式变为:
考虑选择 ,那么对于所有的 我们都有:
这显然是成立的,因为任何正数 (在这里 是 )都小于 。
所以 成立。
2. 证明 不是 :
为了证明 不是 ,我们需要证明对于所有正数 和 ,存在某个 ,使得:
考虑 。那么我们需要证明对于足够大的 ,都有:
这对于所有 都是显然成立的,因为 的增长速度比 快得多。
因此 不是 。
结论:
我们证明了存在一个函数 使得 成立,但 不是 。所以题目的结论得到了证明。
(Q4) Suppose that we define a function recursively as follows: , and for , . Can you find a number with the property that ? Justify your claim.
我们将尝试找到一个特征方程来描述这个递归关系。特征方程是递归数列的一种常用工具,它通常可以帮助我们找到一个非递归形式的公式来描述数列。
考虑递归公式:
对应的特征方程是:
将其重新组织,得到:
这是一个二次方程,我们需要求解它以获得两个根。
从中我们可以得到两个解: 和 。
对于这种递归数列,通常的非递归形式是:
其中 和 是需要确定的常数。
使用给出的初始条件:
和
我们可以建立以下系统:
(将 带入上述公式)
(将 带入上述公式)
通过解这个系统,我们可以得到:
和
因此,我们的数列是:
当 n 趋向于无穷大时, 的部分将变得微不足道,而 的部分将主导整个数列的增长。因此,我们可以说 是 。
综上, ,所以 。
Section C (20 marks)
In this question you will gain some practice implementing sorting algorithms.
(Q1) Implement the selection sort algorithm in Python. Create a function called selection_sort
in Python which takes as input a list of distinct numbers X
and returns an array Y
of the same size as X
and containing the same elements, but occurring in ascending order so that where n
is the size of X
.
To test your selection_sort function first copy the following code.
import numpy as np # load the numpy library
def sorting_function_test(sorting_function, random_seed=2023, array_size=30, max_int=100, num_tests=50):
np.random.seed(random_seed) # set the random seed
output = []
num_tests_failed = 0
for i in range(num_tests):
X=list(set(np.random.randint(max_int, size=(array_size))))
if sorted(X) != sorting_function(X):
num_tests_failed+=1
if num_tests_failed==0:
print("Success! All tests passed.")
else:
print(num_tests_failed,"/",num_tests," failed.")
Next apply the test function sorting_function_test to selection_sort as follow:
sorting_function_test(selection_sort)
(Q2) Implement the merge sort algorithm in Python Create a function called merge_sort
in Python which takes as input a list of distinct numbers X
and returns an array Y
of the same size as X
and containing the same elements, but occurring in ascending order so that where n
is the size of X
.
Next apply the test function sorting_function_test
to merge_sort
as follow:
sorting_function_test(merge_sort)
在这个问题中,你将练习实现排序算法。
(Q1) 用 Python 实现选择排序算法。创建一个名为 selection_sort
的 Python 函数,该函数接受一个名为 X
的不同数字的列表作为输入,并返回一个大小与 X
相同的数组 Y
,该数组包含相同的元素,但以升序出现,使得 ,其中n
是X
的大小。
要测试你的selection_sort
函数,请首先复制以下代码。
import numpy as np # 加载numpy库
def sorting_function_test(sorting_function, random_seed=2023, array_size=30, max_int=100, num_tests=50):
np.random.seed(random_seed) # 设置随机种子
output = []
num_tests_failed = 0
for i in range(num_tests):
X=list(set(np.random.randint(max_int, size=(array_size))))
if sorted(X) != sorting_function(X):
num_tests_failed+=1
if num_tests_failed==0:
print("成功!所有测试均已通过。")
else:
print(num_tests_failed,"/",num_tests," 测试未通过。")
接下来,如下所示,将测试函数sorting_function_test
应用于selection_sort
:
sorting_function_test(selection_sort)
(Q2) 用Python实现归并排序算法。创建一个名为merge_sort
的Python函数,该函数接受一个名为X
的不同数字的列表作为输入,并返回一个大小与X
相同的数组Y
,该数组包含相同的元素,但以升序出现,使得 ,其中n
是X
的大小。
接下来,如下所示,将测试函数sorting_function_test
应用于merge_sort
:
sorting_function_test(merge_sort)
def selection_sort(X):
n = len(X) # 获取列表X的长度
for i in range(n): # 外层循环,对于列表中的每个元素
min_index = i # 假设当前索引处的元素是最小的
for j in range(i+1, n): # 内层循环,检查后面的元素
if X[j] < X[min_index]: # 如果找到一个更小的元素
min_index = j # 更新最小元素的索引
# 将当前元素与找到的最小元素交换位置
X[i], X[min_index] = X[min_index], X[i]
return X # 返回排序后的列表
def merge_sort(X):
if len(X) <= 1: # 如果列表的长度为1或0,直接返回
return X
mid = len(X) // 2 # 计算列表的中间索引
left = X[:mid] # 将列表分为左半部分
right = X[mid:] # 将列表分为右半部分
# 递归地对左右两部分进行排序
left = merge_sort(left)
right = merge_sort(right)
# 合并两个已排序的部分
return merge(left, right)
def merge(left, right):
result = [] # 初始化一个空列表来存放结果
i = j = 0 # 初始化左右两部分的索引
# 当两部分都还有元素时
while i < len(left) and j < len(right):
if left[i] < right[j]: # 如果左边的元素小于右边的
result.append(left[i]) # 将左边的元素添加到结果中
i += 1 # 更新左边的索引
else:
result.append(right[j]) # 否则将右边的元素添加到结果中
j += 1 # 更新右边的索引
# 如果左边还有剩余的元素,将它们添加到结果中
result.extend(left[i:])
# 如果右边还有剩余的元素,将它们添加到结果中
result.extend(right[j:])
return result # 返回合并后的列表
def selection_sort(lst):
sorted_list = []
while lst:
max_value = lst[0]
max_index = 0
for i in range(len(lst)):
if lst[i] > max_value:
max_value = lst[i]
max_index = i
sorted_list.insert(0,max_value) # insert 是插入元素到列表
lst.pop(max_index) # 移除每次最大那个下标的那个值
return sorted_list
lst = [1, 2, 3, 34, 444, 53, 0]
r = selection_sort(lst)
print(r)
Section D (20 marks)
Recall that given a numerical array of length , an array inversion within is an ordered pair with and . In the lectures we discussed the problem of counting the number of array inversions within an array. We introduced the following count_inv_and_sort
algorithm for counting the number of array inversions through a divide-and-conquer strategy.
Algorithm 1: count_inv_and_sort
Input: An array X
1 n = len(X)
2 if n<2 then
3 return (0, X)
4 else
5 (l_inv, Y) = count_inv_and_sort(X[0:⌊n/2⌋])
6 (r_inv, Z) = count_inv_and_sort(X[⌊n/2⌋:n])
7 (split_inv, W) = count_split_inv_and_merge(Y, Z)
8 return (l_inv + r_inv + split_inv, W)
1 n = len(X)
2 if n<2 then
3 return (0, X)
4 else
5 (l_inv, Y) = count_inv_and_sort(X[0:⌊n/2⌋])
6 (r_inv, Z) = count_inv_and_sort(X[⌊n/2⌋:n])
7 (split_inv, W) = count_split_inv_and_merge(Y, Z)
8 return (l_inv + r_inv + split_inv, W)
Notice that the count_inv_and_sort
algorithm calls the following count_split_inv_and_merge
function as a sub-routine.
Algorithm 2: count_split_inv_and_merge
Input: Sorted arrays X and Y
1 = len(X), = len(Y)
2 Z = , split_inv = 0, i = j = 0 // initialisation
3 for k = 0 to + − 1 do
4 if i < and (j = or X[i] ≤ Y[j]) then
5 Z[k] = X[i]
6 i = i + 1
7 else
8 Z[k] = Y [j]
9 split_inv = split_inv + − i // Add on the length of X[i:]
10 j = j + 1
11 return (split_inv, Z ).
1 $n_X$ = len(X), $n_Y$ = len(Y)
2 Z = $0_n$, split_inv = 0, i = j = 0 // initialisation
3 for k = 0 to $n_X$ + $n_Y$ − 1 do
4 if i < $n_X$ and (j = $n_Y$ or X[i] ≤ Y[j]) then
5 Z[k] = X[i]
6 i = i + 1
7 else
8 Z[k] = Y [j]
9 split_inv = split_inv + $n_X$ − i // Add on the length of X[i:$n_X$]
10 j = j + 1
11 return (split_inv, Z ).
Theorem (Counting array inversions): Suppose X is an array consisting of n distinct numbers. Then the count_inv_and_sort
algorithm returns a tuple (n_inv, ) where n_inv is the number of array inversions within the array X and is a sorted copy of the array X.
A useful step in proving this theorem is the following lemma.
Lemma (Counting split inversion): Suppose X is a sorted array containing distinct numbers and Y is a sorted array containing distinct numbers. Then the count_split_inv_and_merge
function will return a tuple (split_inv, Z) where Z is a sorted array containing the distinct elements within the arrays X and Y ,and split_inv
is the number of split inversions across X and Y . More precisely, split_inv
consists of the number of ordered pairs such that .
(Q1) Prove the that the count_inv_and_sort
algorithm solves the inversion counting problem. That is, prove Theorem (Counting array inversions) above.
Note: You may use the result on the count_split_inv_and_merge
algorithm, Lemma (Counting split inversion),without proof.
(Q2) In Python, implement a function called count_inv_and_sort
which takes as input a numerical array X (a Python list) with distinct elements and outputs a tuple containing both:
- The number of array inversions in X.
- A sorted array Y containing the same elements as X but occurring in ascending order.
Your function should have a worst-case run-time complexity of .
Evaluate your function with the following test cases:
Your code should print out five integers corresponding to the number of inversions in each array.
import numpy as np
np.random.seed(0) # set random seed
num_tests=5
for i in range(num_tests):
test_list=list(set(np.random.randint(1000, size=(100))))
np.random.shuffle(test_list)
print(count_inv_and_sort(test_list)[0])
(Optional extra) Prove the result on the count_split_inv_and_merge
algorithm i.e. prove Lemma (Counting split inversion).
Note: This optional extra will not count towards your grade. However, it is a useful challenge to attempt.
Solution D
Q1
证明:
我们的目标是证明 count_inv_and_sort
算法正确地计算了数组 X 中的倒置数,并返回了排序后的数组。我们将使用归纳法来进行证明。
基本情况:对于长度为 0 或 1 的数组 X,即当 n < 2
时,数组自然是有序的,并且没有倒置。因此,算法在此情况下返回的 0 倒置和原数组是正确的。
假设对于所有长度小于n
的数组,count_inv_and_sort
算法都能正确地计算倒置数并返回排序后的数组。
归纳步骤:考虑一个长度为 n
的数组X。
分治策略:算法首先将数组 X 分为两个子数组 Y 和Z,这两个子数组的长度均小于
n
。算法递归地在这两个子数组上应用count_inv_and_sort
。根据我们的归纳假设,我们知道对于这两个子数组,算法能够正确地计算它们的倒置数(分别为l_inv
和r_inv
)并返回排序后的子数组。计算跨子数组的倒置:然后,算法使用
count_split_inv_and_merge
函数来计算跨越子数组 Y 和 Z 的倒置数split_inv
。由于 Y 和 Z 都是排序后的,根据提供的引理,我们知道count_split_inv_and_merge
能够正确地计算这两个排序后的数组之间的倒置数,并将它们合并为一个排序后的数组。总倒置数的计算:最后,整个数组X的总倒置数为
l_inv + r_inv + split_inv
。算法返回这个值以及由count_split_inv_and_merge
返回的排序后的数组,该数组是 Y 和 Z 的合并结果。
因此,基于我们的归纳假设,我们证明了对于长度为 n
的数组,count_inv_and_sort
算法能够正确地计算倒置数并返回排序后的数组。
这样,利用归纳法,我们证明了算法 count_inv_and_sort
对任意长度的数组都是正确的。
Q2
def count_inv_and_sort(X):
n = len(X)
if n < 2:
return (0, X)
else:
l_inv, Y = count_inv_and_sort(X[0:n//2])
r_inv, Z = count_inv_and_sort(X[n//2:n])
split_inv, W = count_split_inv_and_merge(Y, Z)
return (l_inv + r_inv + split_inv, W)
def count_split_inv_and_merge(X, Y):
n_X, n_Y = len(X), len(Y)
Z = [0] * (n_X + n_Y)
split_inv, i, j = 0, 0, 0
for k in range(n_X + n_Y):
if i < n_X and (j == n_Y or X[i] <= Y[j]):
Z[k] = X[i]
i += 1
else:
Z[k] = Y[j]
split_inv += n_X - i
j += 1
return (split_inv, Z)
# 使用给定的测试代码测试上述函数
import numpy as np
np.random.seed(0) # set random seed
num_tests=5
for i in range(num_tests):
test_list=list(set(np.random.randint(1000, size=(100))))
np.random.shuffle(test_list)
print(count_inv_and_sort(test_list)[0])
def count_inv_and_sort(X):
# 获取数组的长度
n = len(X)
# 基础情况:如果数组长度小于2,则没有倒置
if n < 2:
return (0, X)
else:
# 递归地处理数组的左半部分并获取其倒置数量和排序后的子数组
l_inv, Y = count_inv_and_sort(X[0:n//2])
# 递归地处理数组的右半部分并获取其倒置数量和排序后的子数组
r_inv, Z = count_inv_and_sort(X[n//2:n])
# 计算跨越左右两个子数组的倒置数量,并合并两个子数组
split_inv, W = count_split_inv_and_merge(Y, Z)
# 返回总倒置数量和排序后的数组
return (l_inv + r_inv + split_inv, W)
def count_split_inv_and_merge(X, Y):
# 获取两个已排序子数组的长度
n_X, n_Y = len(X), len(Y)
# 初始化结果数组Z的大小和倒置计数器
Z = [0] * (n_X + n_Y)
split_inv, i, j = 0, 0, 0
# 遍历两个子数组并合并它们
for k in range(n_X + n_Y):
# 如果X中还有元素,并且Y中没有元素或X的当前元素小于等于Y的当前元素
if i < n_X and (j == n_Y or X[i] <= Y[j]):
Z[k] = X[i]
i += 1
else:
# 如果Y的元素小于X的当前元素,那么存在倒置
Z[k] = Y[j]
# 增加跨越两个子数组的倒置数量
split_inv += n_X - i
j += 1
# 返回跨越两个子数组的倒置数量和合并后的数组
return (split_inv, Z)
# 使用给定的测试代码测试上述函数
import numpy as np
np.random.seed(0) # 设置随机种子以确保结果可重复
num_tests=5
for i in range(num_tests):
# 生成一个包含100个随机整数的列表
test_list=list(set(np.random.randint(1000, size=(100))))
# 随机打乱该列表的顺序
np.random.shuffle(test_list)
# 打印计算得到的倒置数量
print(count_inv_and_sort(test_list)[0])
Section E (20 marks)
In this question we shall consider the challenge of computing the median within an array.
(Q1) In Python, implement a function called compute_median_value
which takes as input a numerical array X (a Python list) with distinct elements and outputs a single number such that if were a sorted copy of the array , then is the middle value within i.e. . This is often referred to as the (lower) median.
Note: Aim for an approach with minimal worst-case asymptotic time complexity as a function of .
To test your compute_median_value
function first copy the following code.
import numpy as np # load the numpy library
def median_function_test(test_median_function, random_seed=2023, array_size=30, max_int=100, num_tests=50):
np.random.seed(random_seed) # set the random seed
output = []
num_tests_failed = 0
for i in range(num_tests):
X = list(set(np.random.randint(max_int, size=(array_size))))
n=len(X)
X_tilde=sorted(X)
z_test=test_median_function(X)
z_true = X_tilde[(n-1)//2]
if z_true != z_test:
num_tests_failed+=1
if num_tests_failed == 0:
print("Success! All tests passed.")
else:
print(num_tests_failed,"/",num_tests," failed.")
Next apply the test function median_function_test
to compute_median_value
as follow:
median_function_test(compute_median_value)
def compute_median_value(X):
# partition函数用于对数组进行分区,并返回pivot的索引位置。
def partition(arr, low, high):
# 选择最右边的元素作为pivot。
pivot = arr[high]
# i是小于pivot的元素的索引。
i = low - 1
# 遍历数组,将小于或等于pivot的元素移到左侧。
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
# 将pivot移到其正确的位置。
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
# 快速选择算法,用于找到数组中的第k小的元素。
def quick_select(arr, low, high, k):
# 如果数组只有一个元素,则直接返回该元素。
if low == high:
return arr[low]
# 执行分区操作,并获取pivot的索引位置。
pi = partition(arr, low, high)
# 如果pivot的索引就是k,则返回该位置的元素。
if k == pi:
return arr[pi]
# 如果k小于pivot的索引,则在左侧子数组中继续查找。
elif k < pi:
return quick_select(arr, low, pi - 1, k)
# 如果k大于pivot的索引,则在右侧子数组中继续查找。
else:
return quick_select(arr, pi + 1, high, k)
# 计算k为中位数的索引值。
n = len(X)
k = (n - 1) // 2
# 使用快速选择算法找到中位数。
return quick_select(X, 0, n - 1, k)
# 以下是测试代码
import numpy as np
def median_function_test(test_median_function, random_seed=2023, array_size=30, max_int=100, num_tests=50):
# 设置随机种子,确保每次运行的结果都是一致的。
np.random.seed(random_seed)
output = []
num_tests_failed = 0
# 循环运行测试
for i in range(num_tests):
# 随机生成一个唯一元素的数组X。
X = list(set(np.random.randint(max_int, size=(array_size))))
n = len(X)
# 对X进行排序,获得正确的中位数。
X_tilde = sorted(X)
z_test = test_median_function(X)
z_true = X_tilde[(n - 1) // 2]
# 如果测试结果和正确的中位数不一致,则增加失败的测试计数。
if z_true != z_test:
num_tests_failed += 1
# 输出测试结果。
if num_tests_failed == 0:
print("Success! All tests passed.")
else:
print(num_tests_failed, "/", num_tests, " failed.")
# 测试 compute_median_value 函数
median_function_test(compute_median_value)
公众号:AI悦创【二维码】
AI悦创·编程一对一
AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发、Web、Linux」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh
C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh
方法一:QQ
方法二:微信:Jiabcdefh
- 0
- 0
- 0
- 0
- 0
- 0