冒泡排序
简介
冒泡排序(Bubble sort)是一种简单的排序算法。由于在算法的执行过程中,较小的元素像是气泡般慢慢「浮」到数列的顶端,故叫做冒泡排序。
它的工作原理是每次检查相邻两个元素,如果前面的元素与后面的元素满足给定的排序条件,就将相邻两个元素交换。当没有相邻的元素需要交换时,排序就完成了。
经过 \(i\) 次扫描后,数列的末尾 \(i\) 项必然是最大的 \(i\) 项,因此冒泡排序最多需要扫描 \(n-1\) 遍数组就能完成排序。
代码实现
def bubble_sort(a, n):
flag = True
while flag:
flag = False
for i in range(1, n):
if a[i] > a[i + 1]:
flag = True
a[i], a[i + 1] = a[i + 1], a[i]
选择排序
简介
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每次找出第 i 小的元素(也就是 A_{i..n} 中最小的元素),然后将这个元素与数组第 i 个位置上的元素交换。
由于 swap(交换两个元素)操作的存在,选择排序是一种不稳定的排序算法。选择排序的最优时间复杂度、平均时间复杂度和最坏时间复杂度均为 O(n^2)。
代码实现
def selection_sort(a, n):
for i in range(1, n):
ith = i
for j in range(i + 1, n + 1):
if a[j] < a[ith]:
ith = j
a[i], a[ith] = a[ith], a[i]
插入排序
简介
插入排序(Insertion sort)是一种简单直观的排序算法。它的工作原理为将待排列元素划分为「已排序」和「未排序」两部分,每次从「未排序的」元素中选择一个插入到「已排序的」元素中的正确位置。
一个与插入排序相同的操作是打扑克牌时,从牌桌上抓一张牌,按牌面大小插到手牌后,再抓下一张牌。
插入排序是一种稳定的排序算法。
代码实现
def insertion_sort(arr, n):
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 = j - 1
arr[j + 1] = key
折半插入排序
简介
插入排序还可以通过二分算法优化性能,在排序元素数量较多时优化的效果比较明显。
代码实现
void insertion_sort(int arr[], int len) {
if (len < 2) return;
for (int i = 1; i != len; ++i) {
int key = arr[i];
auto index = upper_bound(arr, arr + i, key) - arr;
// 使用 memmove 移动元素,比使用 for 循环速度更快,时间复杂度仍为 O(n)
memmove(arr + index + 1, arr + index, (i - index) * sizeof(int));
arr[index] = key;
}
}
快速排序
简介
快速排序(Quick sort),又称分区交换排序(partition-exchange sort),简称「快排」,是一种被广泛运用的排序算法。
快速排序的工作原理是通过 分治 的方式来将一个数组排序。
快速排序分为三个过程:
-
将数列划分为两部分(要求保证相对大小关系);
-
递归到两个子序列中分别进行快速排序;
-
不用合并,因为此时数列已经完全有序。
和归并排序不同,第一步并不是直接分成前后两个序列,而是在分的过程中要保证相对大小关系。具体来说,第一步要是要把数列分成两个部分,然后保证前一个子数列中的数都小于后一个子数列中的数。为了保证平均时间复杂度,一般是随机选择一个数 m 来当做两个子数列的分界。
之后,维护一前一后两个指针 p 和 q,依次考虑当前的数是否放在了应该放的位置(前还是后)。如果当前的数没放对,比如说如果后面的指针 q 遇到了一个比 m 小的数,那么可以交换 p 和 q 位置上的数,再把 p 向后移一位。当前的数的位置全放对后,再移动指针继续处理,直到两个指针相遇。
其实,快速排序没有指定应如何具体实现第一步,不论是选择 m 的过程还是划分的过程,都有不止一种实现方法。
第三步中的序列已经分别有序且第一个序列中的数都小于第二个数,所以直接拼接起来就好了。
代码实现
def quick_sort(alist, first, last):
if first >= last:
return
mid_value = alist[first]
low = first
high = last
while low < high:
while low < high and alist[high] >= mid_value:
high -= 1
alist[low] = alist[high]
while low < high and alist[low] < mid_value:
low += 1
alist[high] = alist[low]
alist[low] = mid_value
quick_sort(alist, first, low - 1)
quick_sort(alist, low + 1, last)
归并排序
简介
归并排序(merge sort)是高效的基于比较的稳定排序算法。归并排序基于分治思想将数组分段排序后合并,时间复杂度在最优、最坏与平均情况下均为 \(\Theta (n \log n)\),空间复杂度为 \(\Theta (n)\)。
归并排序可以只使用 \(\Theta (1)\) 的辅助空间,但为便捷通常使用与原数组等长的辅助数组。
归并排序最核心的部分是合并(merge)过程:
-
将两个有序的数组 \(a[i]\) 和 \(b[j]\) 合并为一个有序数组 \(c[k]\)
-
从左往右枚举 \(a[i]\) 和 \(b[j]\),找出最小的值并放入数组 \(c[k]\);
-
重复上述过程直到 \(a[i]\) 和 \(b[j]\) 有一个为空时,将另一个数组剩下的元素放入 \(c[k]\)。
-
为保证排序的稳定性,前段首元素小于或等于后段首元素时(\(a[i] <= b[j]\))而非小于时(\(a[i] < b[j]\))就要作为最小值放入 \(c[k]\)。
代码实现
def merge_sort(a, ll, rr):
if rr - ll <= 1:
return
# 分解
mid = (rr + ll) // 2
merge_sort(a, ll, mid)
merge_sort(a, mid, rr)
# 合并
a[ll:rr] = merge(a[ll:mid], a[mid:rr])
def merge(a, b):
i, j = 0, 0
c = []
while(i < len(a) and j < len(b)):
# <!> 先判断 b[j] < a[i],保证稳定性
if(b[j] < a[i]):
c.append(b[j])
j += 1
else:
c.append(a[i])
i += 1
# 此时一个数组已空,另一个数组非空,将非空的数组并入 c 中
c.extend(a[i:])
c.extend(b[j:])
return c
堆排序
简介
堆排序(Heap sort)是指利用 二叉堆 这种数据结构所设计的一种排序算法。堆排序的适用数据结构为数组。
过程:
-
首先建立大顶堆,然后将堆顶的元素取出,作为最大值,与数组尾部的元素交换,并维持残余堆的性质;
-
之后将堆顶的元素取出,作为次大值,与数组倒数第二位元素交换,并维持残余堆的性质;
-
以此类推,在第 n-1 次操作后,整个数组就完成了排序。
代码实现
def sift_down(arr, start, end):
# 计算父结点和子结点的下标
parent = int(start)
child = int(parent * 2 + 1)
while child <= end: # 子结点下标在范围内才做比较
# 先比较两个子结点大小,选择最大的
if child + 1 <= end and arr[child] < arr[child + 1]:
child += 1
# 如果父结点比子结点大,代表调整完毕,直接跳出函数
if arr[parent] >= arr[child]:
return
else: # 否则交换父子内容,子结点再和孙结点比较
arr[parent], arr[child] = arr[child], arr[parent]
parent = child
child = int(parent * 2 + 1)
def heap_sort(arr, len):
# 从最后一个节点的父节点开始 sift down 以完成堆化 (heapify)
i = (len - 1 - 1) / 2
while(i >= 0):
sift_down(arr, i, len - 1)
i -= 1
# 先将第一个元素和已经排好的元素前一位做交换,再重新调整(刚调整的元素之前的元素),直到排序完毕
i = len - 1
while(i > 0):
arr[0], arr[i] = arr[i], arr[0]
sift_down(arr, 0, i - 1)
i -= 1
桶排序
简介
桶排序(Bucket sort)是排序算法的一种,适用于待排序数据值域较大但分布比较均匀的情况。
过程:
-
设置一个定量的数组当作空桶;
-
遍历序列,并将元素一个个放到对应的桶中;
-
对每个不是空的桶进行排序;
-
从不是空的桶里把元素再放回原来的序列中。
代码实现
N = 100010
w = n = 0
a = [0] * N
bucket = [[] for i in range(N)]
def insertion_sort(A):
for i in range(1, len(A)):
key = A[i]
j = i - 1
while j >= 0 and A[j] > key:
A[j + 1] = A[j]
j -= 1
A[j + 1] = key
def bucket_sort():
bucket_size = int(w / n + 1)
for i in range(0, n):
bucket[i].clear()
for i in range(1, n + 1):
bucket[int(a[i] / bucket_size)].append(a[i])
p = 0
for i in range(0, n):
insertion_sort(bucket[i])
for j in range(0, len(bucket[i])):
a[p] = bucket[i][j]
p += 1
希尔排序
简介
希尔排序(Shell sort),也称为缩小增量排序法,是 插入排序 的一种改进版本。希尔排序以它的发明者希尔(英语:Donald Shell)命名。
过程
-
将待排序序列分为若干子序列(每个子序列的元素在原始数组中间距相同);
-
对这些子序列进行插入排序;
-
减小每个子序列中元素之间的间距,重复上述过程直至间距减少为 1。
代码实现
def shell_sort(array, length):
h = 1
while h < length / 3:
h = int(3 * h + 1)
while h >= 1:
for i in range(h, length):
j = i
while j >= h and array[j] < array[j - h]:
array[j], array[j - h] = array[j - h], array[j]
j -= h
h = int(h / 3)
参考: