选择排序(Selection Sort)
1.什么是选择排序?
- 选择排序算法是一种简单的排序算法,它将待排序的序列分为两个部分:已排序和未排序。其中,已排序的部分是在算法的执行过程中逐渐增加的,而未排序的部分则逐渐减少。选择排序的核心思想是在未排序部分中找到最小(或最大)元素,然后将其放到已排序的末尾。由此,选择排序的时间复杂度为 O ( n 2 ) 。 O(n^2)。 O(n2)。
选择排序 | 解析 |
---|---|
外循环 | 依次将内循环找到的最小值放到已排序的位置,执行n-1次,取决于元素的数量 |
内循环 | 找到未排序中的最小值,执行n-i-1次,取决于未排序元素的数量 |
2.代码实现
for (i = 0; i < n-1; i++) {
int min = i;//记录最小值的下标
for (j = i+1; j < n; j++) {
if (nums[j] < nums[min]) {
min = j;
}
}
int temp = nums[i];
nums[i] = nums[min];
nums[min] = temp;
}
冒泡排序(Bubble Sort)
1.什么是冒泡排序?
- 冒泡排序是一种简单的排序算法,它基于元素比较并交换。它通过迭代数组,对相邻元素进行比较,如果它们的顺序不正确(即是否递增或递减),就交换它们的位置。在每次迭代中,最大或最小的元素“冒泡”到数组的末端(内循环)。这个过程一直重复进行(外循环),直到排序完成。时间复杂度为 O ( n 2 ) O(n^2) O(n2)
冒泡排序 | 解析 |
---|---|
外循环 | 重复执行内循环操作,每次执行至少有相邻的元素交换顺序,使本次内循环最值移到数组最右端,至少执行n-1次,取决于元素的数量 |
内循环 | 内层循环从未排序元素迭代,如果相邻元素不是指定顺序,则交换它们。随着每次迭代,最大的元素会“冒泡”到数组的末端。执行n-i-1次,取决于未排序元素的数量 |
2.代码实现
int i, j;
for (i = 0; i < n-1; i++) {
for (j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
插入排序(Insertion Sort)
1.什么是插入排序?
- 插入排序的思想是将待排序元素逐个插入已排序序列的合适位置,从而构成一个新的有序序列。该算法是一种简单的排序算法,虽然在最坏情况下的时间复杂度为 O ( n 2 ) O(n^2) O(n2),但在插入元素的数量少或者待排序序列基本有序的情况下,其时间复杂度会近似为0(n)。插入排序是稳定的,也就是说对于值相同的元素,排序前后它们的相对位置不会发生改变。
插入排序 | 解析 |
---|---|
外循环 | 重复执行内循环操作,每次执行将未排序的一个元素按顺序放置已排序序列中,至少执行n-1次,即第一个元素可事先视为已排序,取决于元素的数量 |
内循环 | 内层循环从未排序的元素中选中元素,依次与已排序的序列的元素比较,若符合顺序(递增或递减),则交换它们。若外循环已执行i次,则本次内循环至多执行i次,至少执行1次 |
2.代码实现
int i, j, key;
for (i = 1; i < n; i++) {
key = arr[i];
j = i - 1;
//将元素逐个移动,直到找到合适的位置
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j -= 1;
}
//插入指定位置
arr[j + 1] = key;
}
希尔排序
什么是希尔排序?
-
希尔排序是插入排序的一种优化版本,它的主要思想是将待排序的数组分成若干个子序列进行插入排序,然后再对整个序列进行插入排序。实现过程中,需要预先设定一个增量序列(间隔),不同的增量序列会对排序效果产生不一样的影响。
-
具体实现时,可以使用一个外层循环来控制增量序列的选择,一个内层循环来对每组子序列进行插入排序。每次循环,我们将数组中的数据按照指定增量分组,然后对每组数据进行插入排序,直至将所有数据排序完毕。
-
为什么不直接对待排序数组进行插入排序?,当元素基数较少时,快速排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2),但当元素较多时,其时间复杂度接近 O ( n 2 3 ) O(n^{2\over3}) O(n32),所以希尔排序是一个不稳定的排序方法,下面是个例子可做参考:
对长度10000的数组排序
插入排序 | 希尔排序 | |
---|---|---|
比较次数 | 25267717 | 295559 |
交换次数 | 25257717 | 165559 |
所以对于较多元素的数组排序,相比于插入排序,我们可以选择希尔排序。
- 解析
希尔排序 | 解析 |
---|---|
外循环 | 重复定义间隔gap,每次间隔为上次的 1 2 1\over2 21 |
内循环 | 内层循环从下标gap开始进行插入排序,每次比较与对应组内元素比较,如选定元素下标为i,则将其与下标i-gap的元素比较。 |
代码实现
for (gap = n / 2; gap > 0; gap /= 2) {
for (i = gap; i < n; i++) {
key = arr[i];
j = i - gap;
while (j >= 0 && arr[j] > key) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = key;
}
}
快速排序(Quick Sort)
1.什么是快速排序?
-
快速排序是一种高效的排序算法,它的基本思想是通过分治的策略将原问题分解成若干子问题,再将子问题的解进行合并以得到原问题的解。其时间复杂度为 O ( n l o g n ) O(n_{log}n) O(nlogn)~ O ( n 2 ) O(n^2) O(n2)。
-
分治思想:
-
递归思想:
每个子问题在将解归并前,都将进行快速排序,调用快速排序的函数的动作一致,因此可改变给定条件递归函数。 -
快速排序步骤:
- 选择基数
在快速排序中,我们首先要选择一个基数。一般选择待排序数组中的第一个元素,也可以按照一定的规则选择。选择主元的目的是将原数组分成两部分,一部分比基数小,另一部分比基数大。 - 划分数组(核心)
选择主元后,将数组划分成两个子序列。这时需要使用两个指针,一个指向待排序数组的起始位置,另一个指向数组的结束位置。从前往后扫描数组,如果某个元素比主元小,就将其交换到数组的左侧;否则,就将该元素交换到数组的右侧。这样,我们就得到了两个子序列:一个是比主元小的,一个是比主元大的。 - 递归调用
接下来,将分别对两个子序列进行递归调用。也就是按照快速排序的方法对子序列进行排序,直到子序列的长度为1时终止递归(递归终止条件)。
2.代码实现
void quick_sort(int arr[], int left, int right) {
if (left >= right) {
// 如果序列长度小于等于1,则无需排序
return;
}
int i = left; // 定义左右指针
int j = right;
int pivot = arr[left]; // 定义基数,选择左端点元素
while (i < j) {
// 划分数组
while (i < j && arr[j] >= pivot) {
j--;
}
arr[i] = arr[j];//交换
while (i < j && arr[i] <= pivot) {
i++;
}
arr[j] = arr[i];
}
arr[i] = pivot; // 将基数放在坑位
quick_sort(arr, left, i - 1); // 递归排序左边子序列
quick_sort(arr, i + 1, right); // 递归排序右边子序列
}