一、冒泡排序
1.简介
冒泡排序是计算机科学中最简单的排序算法之一,它重复地遍历要排序的元素,比较每对相邻项,如果它们的顺序错误就将它们交换过来。经过一轮的遍历,最大(或最小)的元素就会“冒泡”到顶端(或底端),然后在下一轮中继续这个过程。这个过程持续进行直到所有的元素都排好序为止。
2.代码
int bubble_sort(int* data, int length) {
// 外层循环控制轮数,每轮确定一个最大元素的位置
for (int i = 0; i < length; ++i) {
// 内层循环遍历未排序部分的相邻元素,进行比较和交换
for (int j = 0; j < length - i - 1; ++j) {
// 如果相邻元素顺序错误,则进行交换
if (data[j] > data[j + 1]) {
// 交换相邻元素的值
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
return 0;
}
3.其他分析
平均时间复杂度:O(n*n) 最好情况:O(n) 最坏情况:O(n*n)
空间复杂度:O(1) 排序方式:内排序 稳定性:稳定
二、选择排序
1.简介
选择排序是一种简单直观的排序算法,它的基本思想是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
2.代码
// 定义选择排序函数,参数为待排序数组和数组长度
int selection_sort(int* data, int length) {
// 外层循环遍历数组,每次确定一个最小值的位置
for (int i = 0; i < length - 1; ++i) {
// 假设当前位置为最小值的位置
int minIndex = i;
// 内层循环遍历未排序部分,查找最小值的位置
for (int j = i + 1; j < length; ++j) {
// 如果当前元素比最小值还小,则更新最小值的位置
if (data[j] < data[minIndex]) {
minIndex = j;
}
}
// 将当前最小值与当前轮次第一个位置的元素交换
int temp = data[i];
data[i] = data[minIndex];
data[minIndex] = temp;
}
return 0; // 返回排序完成标志
}
3.其他分析
平均时间复杂度:O(n*n) 最好情况:O(n*n) 最坏情况:O(n*n)
空间复杂度:O(1) 排序方式:内排序 稳定性:不稳定
三、插入排序
1.简介
插入排序的核心思想是逐步构建有序序列。在遍历未排序部分的元素时,将每个元素插入到已排序序列的合适位置,从而逐步扩大已排序序列的范围,最终使整个序列有序。
2.代码
int insertion_sort(int* data, int length) {
// 从第二个元素开始遍历,因为第一个元素默认为已排序的序列
for (int i = 1; i < length; ++i) {
// 将当前元素保存为 key
int key = data[i];
// 从已排序的序列末尾开始向前查找要插入的位置
int j = i - 1;
// 在已排序的序列中寻找插入位置,并将比 key 大的元素后移
while (j >= 0 && data[j] > key) {
data[j + 1] = data[j]; // 后移元素
--j; // 向前查找
}
// 将 key 插入到合适的位置
data[j + 1] = key;
}
return 0;
}
3.其他分析
平均时间复杂度:O(n*n) 最好情况:O(n) 最坏情况:O(n*n)
空间复杂度:O(1) 排序方式:内排序 稳定性:稳定
四、希尔排序
1.简介
希尔排序是一种插入排序的改进版本,它的基本思想是将数组分成若干个子序列,对每个子序列进行插入排序,然后逐步缩小子序列的间隔,直至间隔为1,最后进行一次完整的插入排序。
2.代码
int shell_sort(int* data, int length) {
// 遍历增量序列,初始增量为数组长度的一半,逐步缩小增量直至为1
for (int gap = length / 2; gap > 0; gap /= 2) {
// 对每个子序列进行插入排序
for (int i = gap; i < length; ++i) {
int temp = data[i]; // 保存当前值,以便后续插入
int j = i; // 初始化插入位置为当前位置
// 插入排序,将大于temp的元素向后移动
while (j >= gap && data[j - gap] > temp) {
data[j] = data[j - gap]; // 向后移动元素
j -= gap; // 向前移动插入位置
}
data[j] = temp; // 插入temp到合适位置
}
}
return 0; // 排序完成
}
3.其他分析
平均时间复杂度:O(n*1.3) 最好情况:O(n) 最坏情况:O(n*n)
空间复杂度:O(1) 排序方式:内排序 稳定性:不稳定
五、归并排序
1.简介
归并排序是一种分治算法,其基本思想是将原始数组分成若干个较小的子数组,分别对子数组进行排序,然后再将排好序的子数组合并成一个整体有序的数组。具体步骤如下:
(1)分解:将原始数组分成两个较小的子数组,直到子数组的大小为1。
(2)排序:对每个子数组进行排序。
(3)合并:将两个有序的子数组合并成一个整体有序的数组。
2.代码
// 归并排序算法,对数组data的[left, right]范围进行排序
int merge_sort(int* data, int left, int right) {
// 当左边界小于右边界时,进行递归排序
if (left < right) {
// 计算中间位置,使用这种方式计算防止整数过大时溢出
int middle = left + (right - left) / 2;
// 递归排序左半部分
merge_sort(data, left, middle);
// 递归排序右半部分
merge_sort(data, middle + 1, right);
// 合并左右两部分有序数组
merge_arr(data, left, middle, right);
}
return 0;
}
// 合并两个有序数组
void merge_arr(int* data, int left, int middle, int right) {
// 计算左右两个数组的长度
int left_arr_length = middle - left + 1;
int right_arr_length = right - middle;
// 创建临时数组存放左右两个有序数组
int left_arr[left_arr_length];
int right_arr[right_arr_length];
// 将左边界到中间位置的元素复制到左边数组
for (int i = 0; i < left_arr_length; ++i) {
left_arr[i] = data[left + i];
}
// 将中间位置+1到右边界的元素复制到右边数组
for (int i = 0; i < right_arr_length; ++i) {
right_arr[i] = data[middle + i + 1];
}
// 合并两个有序数组
int i = 0, j = 0, k = left;
while (i < left_arr_length && j < right_arr_length) {
// 比较左右两个数组当前位置的元素,将较小的元素放入原数组
if (left_arr[i] <= right_arr[j]) {
data[k] = left_arr[i];
i++;
} else {
data[k] = right_arr[j];
j++;
}
k++;
}
// 将剩余的元素依次放入原数组
while (i < left_arr_length) {
data[k] = left_arr[i];
i++;
k++;
}
while (j < right_arr_length) {
data[k] = right_arr[j];
j++;
k++;
}
}
3.其他分析
平均时间复杂度:O(nlogn) 最好情况:O(nlogn) 最坏情况:O(nlogn)
空间复杂度:O(n) 排序方式:外排序 稳定性:稳定
六、快速排序
1.简介
快速排序(Quick Sort)是一种常见的高效排序算法,基于分治思想。其思想是选择一个基准元素,然后将数组分成两部分,左边部分的元素都小于基准元素,右边部分的元素都大于基准元素,然后递归地对左右两部分进行快速排序。
2.代码
// 交换两个元素的值
void swap(int* first, int* second) {
int temp = *first;
*first = *second;
*second = temp;
}
// 快速排序递归函数
int quick_sort(int* data, int left, int right) {
if (left < right) {
// 找到基准元素的位置
int pivot = findPivot(data, left, right);
// 对左右两部分进行快速排序
quick_sort(data, left, pivot - 1);
quick_sort(data, pivot + 1, right);
}
return 0;
}
// 找到基准元素的位置,并将数组划分为左右两部分
int findPivot(int* data, int left, int right) {
// 选择最后一个元素作为基准元素
int pivot = data[right];
// 定义左右指针
int i = left - 1; // 左指针
// 将小于基准元素的放在左边,大于等于基准元素的放在右边
for (int j = left; j < right; ++j) {
if (data[j] < pivot) {
// 如果当前元素小于基准元素,交换位置
++i;
swap(&data[i], &data[j]);
}
}
// 将基准元素放在正确的位置
swap(&data[i + 1], &data[right]);
// 返回基准元素的位置
return i + 1;
}
3.其他分析
平均时间复杂度:O(nlogn) 最好情况:O(nlogn) 最坏情况:O(n*n)
空间复杂度:O(logn) 排序方式:内排序 稳定性:不稳定
七、堆排序
1.简介
堆排序是一种高效的排序算法,其基本思想是利用堆这种数据结构进行排序。堆是一种特殊的完全二叉树,满足堆的性质:对于每个节点 i,父节点的值大于等于(或小于等于)其子节点的值。堆排序的基本步骤如下:
(1)将待排序序列构建成一个大顶堆(或小顶堆)。
(2)将堆顶元素(最大值或最小值)与堆末尾元素进行交换,然后重新调整堆,使其满足堆的性质。
(3)重复步骤 2,直到堆中只剩下一个元素,此时序列已经有序。
2.代码
void heap_sort(int *data, int length) {
// 构建最大堆
for (int i = length / 2 - 1; i >= 0; --i) {
heapify(data, length, i);
}
// 从最后一个节点开始,逐个将最大元素移到末尾,然后重新调整堆
for (int i = length - 1; i > 0; --i) {
// 将堆顶元素(最大元素)与当前未排序部分的末尾元素交换
heap_swap(&data[0], &data[i]);
// 对剩余部分重新进行堆化,保持最大堆性质
heapify(data, i, 0);
}
}
void heapify(int *data, int length, int currentIndex) {
int largest = currentIndex; // 当前节点索引
int left_node = 2 * currentIndex + 1; // 左子节点索引
int right_node = 2 * currentIndex + 2; // 右子节点索引
// 如果左子节点存在且大于当前节点,则更新最大值索引
if (left_node < length && data[left_node] > data[largest]) {
largest = left_node;
}
// 如果右子节点存在且大于当前节点,则更新最大值索引
if (right_node < length && data[right_node] > data[largest]) {
largest = right_node;
}
// 如果最大值索引不等于当前节点索引,说明需要调整堆
if (largest != currentIndex) {
// 将当前节点与最大值节点交换
heap_swap(&data[currentIndex], &data[largest]);
// 对以最大值节点为根的子树继续进行堆化
heapify(data, length, largest);
}
}
void heap_swap(int* first, int* second) {
int temp = *first;
*first = *second;
*second = temp;
}
3.其他分析
平均时间复杂度:O(nlogn) 最好情况:O(nlogn) 最坏情况:O(nlogn)
空间复杂度:O(1) 排序方式:内排序 稳定性:不稳定
八、计数排序
1.简介
计数排序(Counting Sort)是一种非比较排序算法,适用于排序一定范围内的整数。其基本思想是通过统计每个元素出现的次数,然后根据元素的值和出现次数重新构建有序序列。
2.代码
void counting_sort(int* data, int length) {
// 找到数组中的最大值和最小值
int max_val = data[length - 1];
int min_val = data[length - 1];
for (int i = 0; i < length; ++i) {
if (data[i] > max_val) {
max_val = data[i];
}
if (data[i] < min_val) {
min_val = data[i];
}
}
// 计算计数数组的大小
int count_size = max_val - min_val + 1;
// 初始化计数数组为0
int count[count_size];
for (int i = 0; i < count_size; ++i) {
count[i] = 0;
}
// 统计每个元素出现的次数
for (int i = 0; i < length; ++i) {
int index = data[i] - min_val;
count[index] = count[index] + 1;
}
// 将计数数组中的元素依次填充回原数组
int index = 0;
for (int i = 0; i < count_size; ++i) {
for (int j = 0; j < count[i]; ++j) {
data[index++] = i + min_val;
}
}
}
3.其他分析
平均时间复杂度:O(n+k) 最好情况:O(n+k) 最坏情况:O(n+k)
空间复杂度:O(k) 排序方式:外排序 稳定性:稳定
九、桶排序
1.简介
桶排序(Bucket Sort)是一种排序算法,它通过将数据分到有限数量的桶中,然后对每个桶中的数据进行排序,最后按照顺序将所有的桶中的数据合并起来。桶排序适用于待排序数据分布比较均匀的情况下。
2.代码
void bucket_sort(int* data, int length) {
// 找到数组中的最大值和最小值
int max_val = data[length - 1];
int min_val = data[length - 1];
for (int i = 0; i < length; ++i) {
if (data[i] < min_val) {
min_val = data[i];
}
if (data[i] > max_val) {
max_val = data[i];
}
}
// 计算桶的大小
int bucket_size = (max_val - min_val) / length + 1;
// 创建桶,初始化为-1
int bucket[bucket_size][length];
for (int i = 0; i < bucket_size; ++i) {
for (int j = 0; j < length; ++j) {
bucket[i][j] = -1;
}
}
// 将元素分配到桶中
for (int i = 0; i < length; ++i) {
int bucket_index = (data[i] - min_val) / length;
int j = 0;
// 在桶中找到合适的位置存放元素
while (bucket[bucket_index][j] != -1) {
++j;
}
bucket[bucket_index][j] = data[i];
}
// 对每个桶中的元素进行排序(这里使用冒泡排序)
for (int i = 0; i < bucket_size; ++i) {
for (int j = 0; j < length; ++j) {
if (bucket[i][j] == -1) {
break;
}
// 冒泡排序
for (int k = 0; k < length - 1; ++k) {
if ((bucket[i][k] > bucket[i][k + 1]) && (bucket[i][k + 1] != -1)) {
int temp = bucket[i][k];
bucket[i][k] = bucket[i][k + 1];
bucket[i][k + 1] = temp;
}
}
}
}
// 将桶中的元素依次放回原数组中
int index = 0;
for (int i = 0; i < bucket_size; ++i) {
for (int j = 0; j < length; ++j) {
if (bucket[i][j] == -1) {
break;
}
data[index++] = bucket[i][j];
}
}
}
3.其他分析
平均时间复杂度:O(n+k) 最好情况:O(n+k) 最坏情况:O(n*n)
空间复杂度:O(n + k) 排序方式:外排序 稳定性:稳定
十、基数排序
1.简介
数排序是一种非比较性的排序算法,它根据元素的位数来对元素进行排序。基数排序的思想是从最低位开始,依次对各个位上的元素进行排序,直到最高位。每次排序过程都是稳定的,即在同一位上相同元素的相对顺序不会改变。基数排序可以应用于整数或字符串等数据类型。
2.代码
// 获取数字 num 的第 digit 位数字
int get_digit(int num, int digit) {
return (num / (int)(pow(10, digit - 1))) % 10;
}
// 基数排序函数
void radix_sort(int* data, int length) {
int bucket_size = 10;
int bucket[10][length];
// 初始化桶
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < length; ++j) {
bucket[i][j] = -1;
}
}
// 获取数组中的最大值
int max_val = data[length - 1];
for (int i = 0; i < length; ++i) {
if (data[i] > max_val) {
max_val = data[i];
}
}
// 获取最大值的位数
int max_digit = 0;
while (max_val > 0) {
max_val /= 10;
max_digit++;
}
// 按位进行排序
for (int digit = 1; digit <= max_digit; ++digit) {
// 分配过程:将数字放入对应的桶中
for (int i = 0; i < length; ++i) {
int bucket_index = get_digit(data[i], digit);
int j = 0;
int wait_insert = data[i];
while (bucket[bucket_index][j] != -1) {
if (wait_insert < bucket[bucket_index][j]) {
// 插入操作
int temp = bucket[bucket_index][j];
bucket[bucket_index][j] = wait_insert;
wait_insert = temp;
}
j++;
}
// 将待插入的元素放入桶中
bucket[bucket_index][j] = wait_insert;
}
// 收集过程:将桶中的数字按顺序放回原数组中
int index = 0;
for (int i = 0; i < bucket_size; ++i) {
for (int j = 0; j < length; ++j) {
if (bucket[i][j] == -1) {
break;
}
data[index++] = bucket[i][j];
}
}
// 清空桶
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < length; ++j) {
bucket[i][j] = -1;
}
}
}
}
3.其他分析
平均时间复杂度:O(n*k) 最好情况:O(n*k) 最坏情况:O(n*k)
空间复杂度:O(n + k) 排序方式:外排序 稳定性:稳定