排序与搜索算法全解析
1. 排序与搜索的重要性
在数据处理中,排序是一项极为常见的任务。例如,员工列表常常需要按字母顺序显示,或者根据薪水进行排序。掌握多种排序方法以及比较它们性能的技术,不仅对排序算法本身的研究有帮助,还能用于分析其他算法。当元素列表排好序后,就能快速定位单个元素,比如可以使用二分搜索算法来实现快速查找。
2. 选择排序算法
选择排序是一种常见的排序算法,它通过重复查找未排序尾部区域的最小元素并将其移动到前面来对列表进行排序。以下是选择排序的详细步骤:
- 假设有列表
11 9 17 5 12
,首先要找到最小元素。这里最小元素是
5
,位于
values[3]
。为了将
5
移到列表开头,需要将
values[3]
与
values[0]
进行交换,得到
5 9 17 11 12
。此时,第一个元素已处于正确位置。
- 接着,在剩余的
values[1]...values[4]
中找到最小值。这里最小值是
9
,它已经在正确位置,所以无需操作,直接将已排序区域向右扩展一位。
- 重复上述过程,找到未排序区域的最小值并与该区域的第一个值交换。例如,未排序区域的最小值是
11
,将其与
17
交换,得到
5 9 11 17 12
。
- 继续操作,将
12
与
17
交换,最终得到排序好的列表
5 9 11 12 17
。
以下是实现选择排序的 Python 代码:
## Sorts a list, using selection sort.
# @param values the list to sort
#
def selectionSort(values) :
for i in range(len(values)) :
minPos = minimumPosition(values, i)
temp = values[minPos] # Swap the two elements
values[minPos] = values[i]
values[i] = temp
## Finds the smallest element in a tail range of the list.
# @param values the list to sort
# @param start the first position in values to compare
# @return the position of the smallest element in the
# range values[start]...values[len(values) - 1]
#
def minimumPosition(values, start) :
minPos = start
for i in range(start + 1, len(values)) :
if values[i] < values[minPos] :
minPos = i
return minPos
下面是一个使用选择排序对随机数列表进行排序的示例代码:
from random import randint
from selectionsort import selectionSort
n = 20
values = []
for i in range(n) :
values.append(randint(1, 100))
print(values)
selectionSort(values)
print(values)
运行结果示例:
[65, 46, 14, 52, 38, 2, 96, 39, 14, 33, 13, 4, 24, 99, 89, 77, 73, 87, 36, 81]
[2, 4, 13, 14, 14, 24, 33, 36, 38, 39, 46, 52, 65, 73, 77, 81, 87, 89, 96, 99]
3. 选择排序算法的性能分析
为了准确测量排序算法的性能,我们可以使用 Python 的
time
模块中的
time()
函数。以下是一个测量选择排序算法性能的示例代码:
from random import randint
from selectionsort import selectionSort
from time import time
# Prompt the user for the list size.
n = int(input("Enter list size: "))
# Construct random list.
values = []
for i in range(n) :
values.append(randint(1, 100))
startTime = time()
selectionSort(values)
endTime = time()
print("Elapsed time: %.3f seconds" % (endTime - startTime))
运行示例:
Enter list size: 10000
Elapsed time: 9.380 seconds
通过多次运行不同大小的列表,可以得到以下性能数据:
| n(列表大小) | 耗时(秒) |
| ---- | ---- |
| 10,000 | 9 |
| 20,000 | 38 |
| 30,000 | 85 |
| 40,000 | 147 |
| 50,000 | 228 |
| 60,000 | 332 |
从这些数据可以看出,当数据集合的大小翻倍时,排序所需的时间大约增加四倍。这表明选择排序的性能与列表大小的平方成正比。
4. 选择排序算法的复杂度分析
我们可以通过计算列表元素的访问次数来分析选择排序算法的复杂度。假设列表大小为
n
,首先需要访问
n
个元素来找到最小元素,然后进行两次访问来交换元素。接下来,每次找到最小元素所需访问的元素数量依次减少,直到最后一次访问两个元素。因此,总的访问次数可以通过以下公式计算:
[
\begin{align
}
&n + 2+(n - 1)+ 2+(n - 2)+ 2+\cdots+ 2\
=&n+(n - 1)+(n - 2)+\cdots+ 2+(n - 1)\times2\
=&\frac{n(n + 1)}{2}- 1+2n - 2\
=&\frac{1}{2}n^2+\frac{5}{2}n - 3
\end{align
}
]
当
n
很大时,低阶项
\frac{5}{2}n - 3
的影响可以忽略不计,因此总的访问次数可以近似为
\frac{1}{2}n^2
。在比较不同大小列表的访问次数时,常数系数
\frac{1}{2}
也可以忽略,所以我们说选择排序的访问次数是
O(n^2)
级别的。这意味着当列表大小增加时,排序所需的时间会以平方的速度增长。
5. 大 O 表示法及相关概念
大 O 表示法用于描述函数的增长速度。假设我们有函数
T(n)
和
f(n)
,如果对于所有大于某个阈值的
n
,都有
\frac{T(n)}{f(n)}\leq C
(其中
C
是一个常数),则可以表示为
T(n) = O(f(n))
。例如,对于多项式
T(n)=\frac{1}{2}n^2+\frac{5}{2}n - 3
,其最快增长项是
n^2
,所以
T(n) = O(n^2)
。
除了大 O 表示法,还有
Ω
(Omega)和
Θ
(Theta)表示法:
-
T(n) = Ω(f(n))
表示
T(n)
的增长速度至少和
f(n)
一样快,即对于所有大于某个阈值的
n
,有
\frac{T(n)}{f(n)}\geq C
。
-
T(n) = Θ(f(n))
表示
T(n)
和
f(n)
的增长速度相同,即
T(n) = O(f(n))
且
T(n) = Ω(f(n))
。
常见的大 O 表达式按增长速度从小到大排序如下:
| 大 O 表达式 | 名称 |
| ---- | ---- |
| O(1) | 常数 |
| O(log(n)) | 对数 |
| O(n) | 线性 |
| O(n log(n)) | 线性对数 |
| O(n^2) | 二次 |
| O(n^3) | 三次 |
| O(2^n) | 指数 |
| O(n!) | 阶乘 |
6. 插入排序算法
插入排序是另一种简单的排序算法。它假设列表的初始序列
values[0] values[1]...values[k]
已经排好序(算法开始时
k = 0
),然后将下一个列表元素
values[k + 1]
插入到正确的位置,不断扩大已排序序列,直到列表末尾。以下是插入排序的示例:
- 假设有列表
11 9 16 5 7
,初始长度为 1 的序列
11
已经排好序。将
9
插入到
11
前面,得到
9 11 16 5 7
。
- 接着,
16
不需要移动,序列仍为
9 11 16 5 7
。
- 再将
5
插入到序列开头,得到
5 9 11 16 7
。
- 最后将
7
插入到正确位置,排序完成,得到
5 7 9 11 16
。
以下是实现插入排序的 Python 代码:
## Sorts a list, using insertion sort.
# @param values the list to sort
#
def insertionSort(values) :
for i in range(1, len(values)) :
next = values[i]
# Move all larger elements up.
j = i
while j > 0 and values[j - 1] > next :
values[j] = values[j - 1]
j = j - 1
# Insert the element.
values[j] = next
综上所述,选择排序和插入排序都是简单的排序算法,但它们在处理大规模数据时性能可能不佳。在实际应用中,需要根据具体情况选择合适的排序算法。同时,大 O 表示法等概念对于分析算法的性能和复杂度非常重要。
7. 归并排序算法
归并排序是一种高效的排序算法,它采用分治法的思想。分治法的基本步骤是将一个大问题分解为多个小问题,分别解决这些小问题,然后将小问题的解合并起来得到大问题的解。归并排序的具体步骤如下:
-
分解
:将待排序的列表分成两个子列表,直到每个子列表只有一个元素或为空。
-
合并
:将两个已排序的子列表合并成一个新的已排序的列表。
下面是归并排序的 Python 代码实现:
## Sorts a list, using merge sort.
# @param values the list to sort
#
def mergeSort(values):
if len(values) <= 1:
return values
# Split the list in half
mid = len(values) // 2
left = values[:mid]
right = values[mid:]
# Recursively sort the two halves
left = mergeSort(left)
right = mergeSort(right)
# Merge the sorted halves
return merge(left, right)
## Merges two sorted lists into one sorted list.
# @param left the first sorted list
# @param right the second sorted list
# @return the merged sorted list
#
def merge(left, right):
result = []
i = 0
j = 0
# Compare elements from both lists and add the smaller one to the result
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i = i + 1
else:
result.append(right[j])
j = j + 1
# Add the remaining elements from the left list
while i < len(left):
result.append(left[i])
i = i + 1
# Add the remaining elements from the right list
while j < len(right):
result.append(right[j])
j = j + 1
return result
以下是使用归并排序的示例代码:
values = [11, 9, 17, 5, 12]
sorted_values = mergeSort(values)
print(sorted_values)
运行结果:
[5, 9, 11, 12, 17]
8. 归并排序算法的性能分析
归并排序的时间复杂度是 $O(n log(n))$。这是因为每次将列表分成两半需要 $O(log(n))$ 的时间,而合并两个已排序的子列表需要 $O(n)$ 的时间。因此,总的时间复杂度是 $O(n log(n))$。
归并排序的空间复杂度是 $O(n)$,因为在合并过程中需要额外的空间来存储临时结果。
9. 快速排序算法
快速排序也是一种基于分治法的排序算法。它的基本思想是选择一个基准元素,将列表分为两部分,使得左边部分的所有元素都小于等于基准元素,右边部分的所有元素都大于基准元素,然后分别对左右两部分进行排序。
以下是快速排序的 Python 代码实现:
## Sorts a list, using quicksort.
# @param values the list to sort
#
def quickSort(values):
if len(values) <= 1:
return values
# Choose the first element as the pivot
pivot = values[0]
left = []
right = []
# Partition the list
for value in values[1:]:
if value <= pivot:
left.append(value)
else:
right.append(value)
# Recursively sort the two partitions
return quickSort(left) + [pivot] + quickSort(right)
以下是使用快速排序的示例代码:
values = [11, 9, 17, 5, 12]
sorted_values = quickSort(values)
print(sorted_values)
运行结果:
[5, 9, 11, 12, 17]
10. 快速排序算法的性能分析
快速排序的平均时间复杂度是 $O(n log(n))$,但在最坏情况下(例如列表已经有序),时间复杂度会退化为 $O(n^2)$。快速排序的空间复杂度是 $O(log(n))$,主要用于递归调用栈的空间。
11. 搜索算法 - 二分搜索
二分搜索是一种高效的搜索算法,用于在已排序的列表中查找特定元素。它的基本思想是每次将搜索范围缩小一半,直到找到目标元素或确定目标元素不存在。
以下是二分搜索的 Python 代码实现:
## Searches for a value in a sorted list using binary search.
# @param values the sorted list
# @param target the value to search for
# @return the index of the target value if found, -1 otherwise
#
def binarySearch(values, target):
low = 0
high = len(values) - 1
while low <= high:
mid = (low + high) // 2
if values[mid] == target:
return mid
elif values[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
以下是使用二分搜索的示例代码:
values = [5, 9, 11, 12, 17]
target = 11
index = binarySearch(values, target)
print(index)
运行结果:
2
12. 二分搜索算法的性能分析
二分搜索的时间复杂度是 $O(log(n))$,因为每次搜索都将搜索范围缩小一半。这使得二分搜索在处理大规模数据时非常高效。
总结
本文介绍了多种排序和搜索算法,包括选择排序、插入排序、归并排序、快速排序和二分搜索。不同的算法有不同的性能特点,适用于不同的场景。以下是这些算法的性能对比表格:
| 算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 空间复杂度 |
| ---- | ---- | ---- | ---- |
| 选择排序 | $O(n^2)$ | $O(n^2)$ | $O(1)$ |
| 插入排序 | $O(n^2)$ | $O(n^2)$ | $O(1)$ |
| 归并排序 | $O(n log(n))$ | $O(n log(n))$ | $O(n)$ |
| 快速排序 | $O(n log(n))$ | $O(n^2)$ | $O(log(n))$ |
| 二分搜索 | $O(log(n))$ | $O(log(n))$ | $O(1)$ |
在实际应用中,我们可以根据数据的规模、数据的初始状态等因素选择合适的算法。例如,当数据规模较小时,选择排序和插入排序可能是简单有效的选择;而当数据规模较大时,归并排序和快速排序通常能提供更好的性能。二分搜索则适用于在已排序的数据中进行快速查找。
下面是一个简单的流程图,展示了如何根据数据规模选择排序算法:
graph TD;
A[数据规模小] --> B[选择排序/插入排序];
A[数据规模大] --> C{数据是否接近有序};
C -->|是| D[插入排序];
C -->|否| E{对稳定性有要求?};
E -->|是| F[归并排序];
E -->|否| G[快速排序];
通过对这些算法的学习和理解,我们可以更好地处理数据排序和搜索问题,提高程序的性能和效率。
排序与搜索算法详解
超级会员免费看

被折叠的 条评论
为什么被折叠?



