算法学习之路(一):排序算法
文章目录
写在文章之前
算法与数据结构是计算学科知识结构的核心与技术体系的基石,学好数据结构和算法对于程序设计能力的提高有着相当大的作用。在这一系列文章中,将会从最基础的部分开始,记录和展示我在学习数据结构和算法的过程中的收获和感悟,希望这些能够帮助更多的人学习算法,爱上算法。考虑到C/C++在算法竞赛中的普遍应用以及大多数学校以C/C++作为第一门语言,该系列文章的所有示例代码均采用C/C++。
排序算法
排序算法是所有算法中最基础也是最重要的部分之一,D.Knuth曾指出,四分之一以上的CPU时间都用于执行同一类型的计算:按照某种约定的次序,将给定的一组元素顺序排列。因此,我们不妨从排序算法开始我们的学习之路。
PS:以下算法示例均为非降序排序
1、选择排序(Selection Sort)
1.1 算法思路
选择排序是最简单的,也是最直接的算法。简单的说,就是每次从无序部分中选出最大的元素放在有序部分的末尾,直到无序部分为空。
1.2 算法思想
减而治之,每次处理一个数据,从而使待处理的数据规模单调递减,直至全部处理完毕。
1.3 算法效率
时间复杂度(平均):O(
n
2
n^2
n2)
时间复杂度(最坏):O(
n
2
n^2
n2)
时间复杂度(最好):O(
n
2
n^2
n2)
空间复杂度:O(1)
稳定性:不稳定
1.4 代码实现:
void SelectionSort(int a[], int length) {
for (int i = 0; i < length - 1; ++i) {
//每次循环后将第i小的元素放好
int tmpMin = i;
//记录第i个到第n-1个元素中,最小的元素的下标
for (int j = i + 1; j < length; ++j) {
if (a[j] < a[tmpMin])
tmpMin = j;
}
//下面将第i小的元素放在第i个位置上,并将原来第i个位置的元素挪到后面
int tmp = a[i];
a[i] = a[tmpMin];
a[tmpMin] = tmp;
}
}
2、插入排序(Insert Sort)
2.1 算法思路
插入排序多用于打扑克时对摸到的扑克牌进行排序,具体来说,就是每次从无序部分中抽出一个元素,并将其放到有序部分的适当位置,直至所有数据排序完毕。
2.2 算法思想
遍历,将所有数据遍历,放到适当位置。
2.3 算法效率
时间复杂度(平均):O(
n
2
n^2
n2)
时间复杂度(最坏):O(
n
2
n^2
n2)
时间复杂度(最好):O(
n
n
n)
空间复杂度:O(
1
1
1)
稳定性:稳定
2.4 代码实现:
void InsertSort(int a[], int length) {
for (int i = 0; i < length; ++i) {
//a[i]是最左边的无序元素,每次循环将a[i]放到合适位置
for (int j = 0; j < i; ++j) {
if (a[j] > a[i]) {
//要把a[i]放到位置j,原下标j到i-1的元素都往后移一位
int tmp = a[i];
for (int k = i; k > j; --k)
a[k] = a[k - 1];
a[j] = tmp;
break;
}
}
}
}
3、冒泡排序(Bubble Sort)
3.1 算法思路
使较大的“气泡”上浮,如果遇到更大的“气泡”,则令原“气泡”停止,换由更大的“气泡”上浮,使得每执行一轮都会使得最大的“气泡”完成上浮,待循环执行结束后,所有的“气泡”都位于适当位置。
3.2 算法思想
渐进优化,不断改善局部有序性从而最终实现整体的有序性。
3.3 算法效率
时间复杂度(平均):O(
n
2
n^2
n2)
时间复杂度(最坏):O(
n
2
n^2
n2)
时间复杂度(最好):O(
n
n
n)
空间复杂度:O(
1
1
1)
稳定性:稳定
3.4 代码实现:
void BubbleSort(int a[], int length) {
for (int i = 0; i < length - 1; ++i) {
for (int j = 0; j < length - 1 - i; ++j) {
//使大数上浮
if (a[j] > a[j + 1]) {
int tmp = a[j];
a[j] = a[j + 1];
a[j + 1] = tmp;
}
}
}
}
4、希尔排序(Shell Sort)
4.1 算法思路
希尔排序是直接插入排序的改进版,也称缩小增量排序。希尔排序采用跳跃分组的策略,根据设定的增量根据下标将数据分为多组,对每组数据分别进行插入排序,然后不断减少增量(减少组数)再次排序,直到增量为1(所有数据变回1组),排序结束。
4.2 算法思想
渐进优化,不断改善局部有序性从而最终实现整体的有序性。
4.3 算法效率
时间复杂度(平均):O(
n
1.3
n^{1.3}
n1.3)
时间复杂度(最坏):O(
n
2
n^2
n2)
时间复杂度(最好):O(
n
n
n)
空间复杂度:O(
1
1
1)
稳定性:不稳定
4.4 代码实现:
void ShellSort(int a[], int length) {
int increment = length;//初始化增量
int temp = 0;
do {
//每次减小增量,直到增量为1
increment = increment / 3 + 1;
for (int i = increment; i < length; ++i) {
//对每个划分进行插入排序
if (a[i - increment] > a[i]) {
temp = a[i];
int j = i - increment;
do {
//移动元素并寻找位置
a[j + increment] = a[j];
j -= increment;
} while (j >= 0 && a[j] > temp);
a[j + increment] = temp;//插入元素
}
}
} while (increment > 1);
}
5、归并排序(Merge Sort)
5.1 算法思路
将未排序的序列拆分为两个子序列,直到子序列有序,然后将两个有序的子序列合并为完全有序序列,直至完全合并。
5.2 算法思想
归并排序是分而治之思想的一个典型应用,先将序列拆分,使每个子序列有序,再使子序列段间有序,最终合并成为大的序列。
5.3 算法效率
时间复杂度(平均):O(
n
log
2
n
n\log_2n
nlog2n)
时间复杂度(最坏):O(
n
log
2
n
n\log_2n
nlog2n)
时间复杂度(最好):O(
n
log
2
n
n\log_2n
nlog2n)
空间复杂度:O(
n
n
n)
稳定性:稳定
5.4 代码实现:
void Merge(int a[], int length) {
int temp[length]; //辅助数组
int b = 0; //辅助数组的起始位置
int mid = length / 2; //mid将数组从中间划分,前一半有序,后一半有序
int first = 0, second = mid; //两个有序序列的起始位置
while (first < mid&&second < length) {
if (a[first] <= a[second]) //比较两个序列
temp[b++] = a[first++];
else
temp[b++] = a[second++];
}
while (first < mid) //将剩余子序列复制到辅助序列中
temp[b++] = a[first++];
while (second < length)
temp[b++] = a[second++];
for (int i = 0; i < length; ++i) //辅助序列复制到原序列
a[i] = temp[i];
}
void MergeSort(int a[], int length) {
if (length <= 1)
return;
if (length > 1) {
MergeSort(a, length / 2); //对前半部分进行归并排序
MergeSort(a + length / 2, length - length / 2); //对后半部分进行归并排序
Merge(a, length); //归并两部分
}
}
6、快速排序(Quick Sort)
6.1 算法思路
用基准点将全部数据划分为比基准数大和比基准数小的两部分,然后对于每部分分别递归排序,直至每部分小于一个元素结束递归,最终完成排序。
6.2 算法思想
分治,利用基准点将数据分成两部分,从而对于每一个部分而言,减少其规模,达到分而治之的效果。
6.3 算法效率
时间复杂度(平均):O(
n
log
2
n
n\log_2n
nlog2n)
时间复杂度(最坏):O(
n
2
n^2
n2)
时间复杂度(最好):O(
n
log
2
n
n\log_2n
nlog2n)
空间复杂度:O(
n
log
2
n
n\log_2n
nlog2n)
稳定性:不稳定
6.4 代码实现:
void QuickSort(int a[], int left, int right) {
int i = left, j = right;//设置左右指针
int pivot = a[left];//设置基准数
if (i >= j)//递归基
return;
while (i < j)
{
while (i<j&&a[j]>pivot)//从右找到比基准小的数
j--;
if (i < j)
{
a[i++] = a[j];//把找到的数放到基准数左侧然后令左指针右移
}
while (i < j&&a[i] <= pivot)//从左找到比基准大的数
i++;
if (i < j)
{
a[j--] = a[i];//把找到的数放到基准数右侧(放在上一步移动的数留下的“坑”中)
}
}
a[i] = pivot;//把基准数插到合适位置
//分别对左右两侧进行快速排序
QuickSort(a, left, i - 1);
QuickSort(a, i + 1, right);
}
挖坑待填:
7、堆排序(Heap Sort)
8、计数排序(Counting Sort)
9、桶排序(Bucket Sort)
10、基数排序(Radix Sort)